tessera-learn 0.0.1 → 0.0.2
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 +93 -75
- package/README.md +11 -0
- package/dist/plugin/index.js +79 -78
- package/dist/plugin/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/FillInTheBlank.svelte +19 -69
- package/src/components/LockedBanner.svelte +30 -0
- package/src/components/Matching.svelte +44 -80
- package/src/components/MultipleChoice.svelte +14 -43
- package/src/components/Quiz.svelte +69 -263
- package/src/components/ResultIcon.svelte +13 -0
- package/src/components/RetryButton.svelte +25 -0
- package/src/components/Sorting.svelte +33 -76
- package/src/components/util.ts +10 -0
- package/src/plugin/export.ts +39 -33
- package/src/plugin/manifest.ts +38 -12
- package/src/plugin/validation.ts +36 -69
- package/src/runtime/App.svelte +15 -20
- package/src/runtime/ErrorPage.svelte +1 -1
- package/src/runtime/adapters/retry.ts +48 -41
- package/src/runtime/adapters/scorm-base.ts +143 -0
- package/src/runtime/adapters/scorm12.ts +37 -117
- package/src/runtime/adapters/scorm2004.ts +34 -115
- package/src/runtime/hooks.svelte.ts +63 -29
- package/src/runtime/xapi/client.ts +2 -2
- package/src/runtime/xapi/publisher.ts +15 -6
- package/src/runtime/xapi/setup.ts +8 -15
- package/styles/layout.css +21 -10
- package/styles/theme.css +4 -0
package/dist/plugin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["__dirname"],"sources":["../../src/plugin/manifest.ts","../../src/runtime/xapi/agent-rules.ts","../../src/plugin/validation.ts","../../src/runtime/slugify.ts","../../src/plugin/export.ts","../../src/plugin/layout.ts","../../src/plugin/quiz.ts","../../src/plugin/index.ts"],"sourcesContent":["import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';\nimport { resolve, basename, extname } from 'node:path';\nimport JSON5 from 'json5';\nimport type { 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}\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// ---------- 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<string, { mtimeMs: number; content: string }>();\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\n/** Matches both Svelte 5 `<script module>` and legacy `<script context=\"module\">`. */\nexport const MODULE_SCRIPT_RE =\n /<script\\s+(?:context\\s*=\\s*[\"']module[\"']|module)[^>]*>([\\s\\S]*?)<\\/script>/;\n\n/** Matches `export const pageConfig =` (RHS is read separately). */\nexport const PAGE_CONFIG_EXPORT_RE = /export\\s+const\\s+pageConfig\\s*=\\s*/;\n\n/** Matches `export default ` (RHS is read separately). */\nconst DEFAULT_EXPORT_RE = /export\\s+default\\s*/;\n\n/**\n * Locate `export default { ... }` and return the object literal substring,\n * or null if no balanced object literal follows the `export default` keyword.\n * Used by both manifest extraction and project validation.\n */\nexport function extractDefaultExportObjectLiteral(source: string): string | null {\n const match = source.match(DEFAULT_EXPORT_RE);\n if (!match || match.index === undefined) return null;\n const startIndex = source.indexOf('{', match.index);\n if (startIndex < 0) return null;\n return extractObjectLiteral(source, startIndex);\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): { title?: string; pages?: string[] } {\n if (!existsSync(metaPath)) return {};\n\n const objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(metaPath));\n if (!objectStr) return {};\n\n try {\n return JSON5.parse(objectStr);\n } catch {\n return {};\n }\n}\n\n/**\n * Extract pageConfig from a .svelte file's module script block.\n */\nexport function extractPageConfig(filePath: string): { title?: string; quiz?: QuizConfig } {\n const content = readSourceFileCached(filePath);\n\n const moduleScriptMatch = content.match(MODULE_SCRIPT_RE);\n if (!moduleScriptMatch) return {};\n\n const scriptContent = moduleScriptMatch[1];\n\n const configMatch = scriptContent.match(PAGE_CONFIG_EXPORT_RE);\n if (!configMatch || configMatch.index === undefined) return {};\n\n const startIndex = scriptContent.indexOf('{', configMatch.index + configMatch[0].length);\n if (startIndex < 0) return {};\n const objectStr = extractObjectLiteral(scriptContent, startIndex);\n if (!objectStr) return {};\n\n try {\n return JSON5.parse(objectStr);\n } catch {\n throw new Error(\n `${filePath}: pageConfig must be a static object literal (no variables, function calls, or computed values)`\n );\n }\n}\n\n/**\n * Extract an object literal from source starting at the opening brace.\n * Tracks brace depth to find the matching closing brace.\n */\nexport function extractObjectLiteral(source: string, startIndex: number): string | null {\n if (source[startIndex] !== '{') return null;\n\n let depth = 0;\n let inString: string | null = null;\n let escaped = false;\n\n for (let i = startIndex; i < source.length; i++) {\n const char = source[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n\n if (inString) {\n if (char === inString) {\n inString = null;\n }\n continue;\n }\n\n if (char === '\"' || char === \"'\" || char === '`') {\n inString = char;\n continue;\n }\n\n // Skip single-line comments\n if (char === '/' && i + 1 < source.length && source[i + 1] === '/') {\n const newline = source.indexOf('\\n', i);\n i = newline === -1 ? source.length : newline;\n continue;\n }\n\n // Skip multi-line comments\n if (char === '/' && i + 1 < source.length && source[i + 1] === '*') {\n const end = source.indexOf('*/', i + 2);\n i = end === -1 ? source.length : end + 1;\n continue;\n }\n\n if (char === '{') depth++;\n if (char === '}') {\n depth--;\n if (depth === 0) {\n return source.slice(startIndex, i + 1);\n }\n }\n }\n\n return null;\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// ---------- Main ----------\n\n/**\n * Generate a course manifest by scanning the pages/ directory.\n */\nexport function generateManifest(pagesDir: string): Manifest {\n const sections: ManifestSection[] = [];\n const flatPages: ManifestPage[] = [];\n let pageIndex = 0;\n\n const sectionDirs = getSortedDirs(pagesDir);\n\n for (const sectionName of sectionDirs) {\n const sectionPath = resolve(pagesDir, sectionName);\n const sectionMeta = readMetaFile(resolve(sectionPath, '_meta.js'));\n const sectionSlug = deriveSlug(sectionName);\n\n const section: ManifestSection = {\n title: sectionMeta.title || titleCase(sectionSlug),\n slug: sectionSlug,\n lessons: [],\n };\n\n const lessonDirs = getSortedDirs(sectionPath);\n\n for (const lessonName of lessonDirs) {\n const lessonPath = resolve(sectionPath, lessonName);\n const lessonMeta = readMetaFile(resolve(lessonPath, '_meta.js'));\n const lessonSlug = deriveSlug(lessonName);\n\n const lesson: ManifestLesson = {\n title: lessonMeta.title || titleCase(lessonSlug),\n slug: lessonSlug,\n pages: [],\n };\n\n // Determine page order\n const allSvelteFiles = getSvelteFiles(lessonPath);\n const orderedFiles = orderPageFiles(allSvelteFiles, lessonMeta.pages);\n\n for (const fileName of orderedFiles) {\n const filePath = resolve(lessonPath, fileName);\n const pageSlug = deriveSlug(fileName, true);\n\n let pageConfig: { title?: string; quiz?: QuizConfig } = {};\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 relativePath = `/pages/${sectionName}/${lessonName}/${fileName}`;\n\n const page: ManifestPage = {\n index: pageIndex,\n title: pageConfig.title || titleCase(pageSlug),\n slug: pageSlug,\n importPath: relativePath,\n quiz: pageConfig.quiz || null,\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(allFiles: string[], pagesArray?: string[]): string[] {\n if (!pagesArray || pagesArray.length === 0) {\n return allFiles;\n }\n\n const listed = pagesArray.map(name => name.endsWith('.svelte') ? name : `${name}.svelte`);\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/**\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 (typeof a.mbox_sha1sum !== 'string' || !/^[0-9a-f]{40}$/i.test(a.mbox_sha1sum)) {\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 'auth must be a non-empty string';\n }\n if (/^basic\\s/i.test(auth)) {\n return \"auth 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 { existsSync, readdirSync, statSync } from 'node:fs';\nimport { resolve, relative } from 'node:path';\nimport JSON5 from 'json5';\nimport {\n extractObjectLiteral,\n extractDefaultExportObjectLiteral,\n readSourceFileCached,\n MODULE_SCRIPT_RE,\n PAGE_CONFIG_EXPORT_RE,\n} from './manifest.js';\nimport { validateAgent } from '../runtime/xapi/agent-rules.js';\n\n// ---------- Types ----------\n\nexport interface ValidationResult {\n errors: string[];\n warnings: string[];\n}\n\n// Known top-level config fields\nconst KNOWN_CONFIG_FIELDS = new Set([\n 'title',\n 'description',\n 'author',\n 'version',\n 'branding',\n 'navigation',\n 'completion',\n 'scoring',\n 'export',\n 'chrome',\n 'xapi',\n]);\n\nconst VALID_NAV_MODES = ['free', 'sequential'];\nconst VALID_COMPLETION_MODES = ['quiz', 'percentage'];\nconst VALID_EXPORT_STANDARDS = ['web', 'scorm12', 'scorm2004', 'cmi5'];\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 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(configPath, errors, warnings);\n\n // 3. Validate pages directory\n const pagesDir = resolve(projectRoot, 'pages');\n const assetsDir = resolve(projectRoot, 'assets');\n const pageResults = validatePages(pagesDir, assetsDir, projectRoot);\n errors.push(...pageResults.errors);\n warnings.push(...pageResults.warnings);\n\n // 4. Cross-cutting validations\n if (config) {\n crossValidate(config, pageResults, errors, warnings);\n }\n\n return { errors, warnings };\n}\n\n// ---------- Config Validation ----------\n\ninterface ParsedConfig {\n title?: string;\n navigation?: { mode?: string };\n completion?: { mode?: string; percentageThreshold?: number };\n scoring?: { passingScore?: number };\n export?: { standard?: string };\n [key: string]: unknown;\n}\n\nfunction parseConfig(\n configPath: string,\n errors: string[],\n warnings: string[]\n): ParsedConfig | null {\n const objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(configPath));\n if (!objectStr) {\n errors.push(\n 'course.config.js: could not parse — must use `export default { ... }` syntax'\n );\n return null;\n }\n\n let config: ParsedConfig;\n try {\n config = JSON5.parse(objectStr);\n } catch {\n errors.push(\n 'course.config.js: syntax error — must export a static object literal'\n );\n return null;\n }\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 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\" or \"percentage\", got \"${config.completion.mode}\"`\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// ---------- xAPI Config Validation ----------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nconst SHA1_RE = /^[0-9a-f]{40}$/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 = ['auth', 'actor', 'activityId', 'registration', 'actorAccountHomePage'];\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 if (!auth) {\n errors.push(`course.config.js: ${label}.auth must be a non-empty string`);\n } else if (/^basic\\s/i.test(auth)) {\n errors.push(\n `course.config.js: ${label}.auth must be the Basic credential value only, not the full header. Drop the 'Basic ' prefix.`\n );\n } else if (/^bearer\\s/i.test(auth)) {\n errors.push(\n `course.config.js: ${label}.auth: 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 } 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 = validateStaticAgent(actor);\n if (err) {\n const joined = err.startsWith('.')\n ? `${label}.actor${err}`\n : `${label}.actor ${err}`;\n errors.push(`course.config.js: ${joined}`);\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 ) {\n let isHttp = false;\n try {\n const u = new URL(activityId);\n isHttp = u.protocol === 'http:' || u.protocol === 'https:';\n } catch {\n isHttp = false;\n }\n if (!isHttp && aahp === undefined) {\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\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/**\n * Build-time alias for the shared `validateAgent` rules. Suffixes are already\n * prefix-friendly (no leading \"actor\"), so this is a straight pass-through —\n * kept named so the call sites in this file stay readable.\n */\nconst validateStaticAgent = validateAgent;\n\n// ---------- Pages Validation ----------\n\ninterface PagesValidationResult extends ValidationResult {\n totalPages: number;\n totalQuizzes: number;\n hasGradedQuiz: boolean;\n}\n\nfunction validatePages(\n pagesDir: string,\n assetsDir: string,\n projectRoot: string\n): PagesValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n let totalPages = 0;\n let totalQuizzes = 0;\n let hasGradedQuiz = false;\n\n if (!existsSync(pagesDir)) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n return { errors, warnings, totalPages: 0, totalQuizzes: 0, hasGradedQuiz: false };\n }\n\n const topLevelEntries = readdirSync(pagesDir);\n\n // Check for stray .svelte files at pages/ root\n for (const entry of topLevelEntries) {\n const fullPath = resolve(pagesDir, entry);\n if (entry.endsWith('.svelte') && statSync(fullPath).isFile()) {\n const relPath = relative(projectRoot, fullPath);\n warnings.push(\n `${relPath}: this file is outside the section/lesson structure and will be ignored`\n );\n }\n }\n\n // Get section directories\n const sectionDirs = topLevelEntries\n .filter((name) => {\n const full = resolve(pagesDir, name);\n return statSync(full).isDirectory() && !name.startsWith('.');\n })\n .sort();\n\n if (sectionDirs.length === 0) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n return { errors, warnings, totalPages: 0, totalQuizzes: 0, hasGradedQuiz: false };\n }\n\n for (const sectionName of sectionDirs) {\n const sectionPath = resolve(pagesDir, sectionName);\n const sectionRel = relative(projectRoot, sectionPath);\n\n // Validate section _meta.js\n const sectionMeta = validateMetaFile(\n resolve(sectionPath, '_meta.js'),\n sectionRel,\n errors\n );\n\n // Flat mode: .svelte files directly at section level are pages of an\n // implicit single lesson. Validate them just like lesson-level pages.\n const sectionEntries = readdirSync(sectionPath);\n const sectionSvelteFiles = sectionEntries\n .filter((name) => {\n const full = resolve(sectionPath, name);\n return name.endsWith('.svelte') && statSync(full).isFile();\n })\n .sort();\n\n if (sectionMeta?.pages) {\n for (const pageName of sectionMeta.pages) {\n const fileName = pageName.endsWith('.svelte')\n ? pageName\n : `${pageName}.svelte`;\n if (!sectionSvelteFiles.includes(fileName)) {\n const metaRel = relative(projectRoot, resolve(sectionPath, '_meta.js'));\n errors.push(\n `${metaRel}: pages array lists \"${pageName}\" but ${fileName} not found in this directory`\n );\n }\n }\n }\n\n for (const fileName of sectionSvelteFiles) {\n const filePath = resolve(sectionPath, fileName);\n const fileRel = relative(projectRoot, filePath);\n const content = readSourceFileCached(filePath);\n\n const pageConfig = validatePageConfig(content, fileRel, errors);\n totalPages++;\n\n if (pageConfig?.quiz) {\n totalQuizzes++;\n validateQuizConfig(pageConfig.quiz, fileRel, errors);\n if ((pageConfig.quiz as { graded?: unknown }).graded === true) {\n hasGradedQuiz = true;\n }\n }\n\n validateAssetRefs(content, fileRel, assetsDir, warnings);\n }\n\n // Get lesson directories\n const lessonDirs = sectionEntries\n .filter((name) => {\n const full = resolve(sectionPath, name);\n return statSync(full).isDirectory() && !name.startsWith('.');\n })\n .sort();\n\n for (const lessonName of lessonDirs) {\n const lessonPath = resolve(sectionPath, lessonName);\n const lessonRel = relative(projectRoot, lessonPath);\n\n // Validate lesson _meta.js\n const meta = validateMetaFile(\n resolve(lessonPath, '_meta.js'),\n lessonRel,\n errors\n );\n\n // Get .svelte files\n const svelteFiles = readdirSync(lessonPath)\n .filter((name) => name.endsWith('.svelte'))\n .sort();\n\n // Check pages array references\n if (meta?.pages) {\n for (const pageName of meta.pages) {\n const fileName = pageName.endsWith('.svelte')\n ? pageName\n : `${pageName}.svelte`;\n if (!svelteFiles.includes(fileName)) {\n const metaRel = relative(projectRoot, resolve(lessonPath, '_meta.js'));\n errors.push(\n `${metaRel}: pages array lists \"${pageName}\" but ${fileName} not found in this directory`\n );\n }\n }\n }\n\n // Check for unlisted .svelte files\n if (meta?.pages && meta.pages.length > 0) {\n const listedSet = new Set(\n meta.pages.map((p: string) =>\n p.endsWith('.svelte') ? p : `${p}.svelte`\n )\n );\n for (const file of svelteFiles) {\n if (!listedSet.has(file)) {\n const relPath = relative(projectRoot, resolve(lessonPath, file));\n warnings.push(\n `${relPath}: not listed in _meta.js pages array — will be appended at end`\n );\n }\n }\n }\n\n // Validate each .svelte file\n for (const fileName of svelteFiles) {\n const filePath = resolve(lessonPath, fileName);\n const fileRel = relative(projectRoot, filePath);\n const content = readSourceFileCached(filePath);\n\n const pageConfig = validatePageConfig(content, fileRel, errors);\n totalPages++;\n\n if (pageConfig?.quiz) {\n totalQuizzes++;\n\n // Validate quiz config\n validateQuizConfig(pageConfig.quiz, fileRel, errors);\n\n if ((pageConfig.quiz as { graded?: unknown }).graded === true) {\n hasGradedQuiz = true;\n }\n }\n\n // Check $assets references\n validateAssetRefs(content, fileRel, assetsDir, warnings);\n }\n }\n }\n\n if (totalPages === 0) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n }\n\n return { errors, warnings, totalPages, totalQuizzes, hasGradedQuiz };\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 objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(metaPath));\n\n if (!objectStr) {\n errors.push(`${metaRel}: syntax error — must export default { title: \"...\" }`);\n return null;\n }\n\n let meta: { title?: string; pages?: string[] };\n try {\n meta = JSON5.parse(objectStr);\n } catch {\n errors.push(`${metaRel}: syntax error — must export default { title: \"...\" }`);\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 } | null {\n const moduleScriptMatch = content.match(MODULE_SCRIPT_RE);\n if (!moduleScriptMatch) return null;\n\n const scriptContent = moduleScriptMatch[1];\n const exportMatch = scriptContent.match(PAGE_CONFIG_EXPORT_RE);\n if (!exportMatch || exportMatch.index === undefined) return null;\n\n // Now check if the RHS starts with `{` (a static object literal)\n const afterEquals = scriptContent\n .slice(exportMatch.index + exportMatch[0].length)\n .trimStart();\n\n if (!afterEquals.startsWith('{')) {\n // pageConfig is exported but assigned to something other than an object literal\n errors.push(\n `${fileRel}: pageConfig must be a static object literal (no variables, function calls, or computed values)`\n );\n return null;\n }\n\n // Find the opening brace in the original scriptContent\n const braceIndex = scriptContent.indexOf(\n '{',\n exportMatch.index + exportMatch[0].length\n );\n const objectStr = extractObjectLiteral(scriptContent, braceIndex);\n if (!objectStr) {\n errors.push(\n `${fileRel}: pageConfig must be a static object literal (no variables, function calls, or computed values)`\n );\n return null;\n }\n\n try {\n return JSON5.parse(objectStr);\n } catch {\n errors.push(\n `${fileRel}: pageConfig must be a static object literal (no variables, function calls, or computed values)`\n );\n return null;\n }\n}\n\n// ---------- Quiz Config Validation ----------\n\nfunction validateQuizConfig(quiz: unknown, fileRel: string, errors: string[]): void {\n if (!quiz || typeof quiz !== 'object') return;\n const cfg = quiz as { maxAttempts?: unknown; graded?: unknown };\n\n if (cfg.maxAttempts !== undefined) {\n const val = cfg.maxAttempts;\n if (val !== Infinity && (typeof val !== 'number' || val <= 0 || !Number.isFinite(val))) {\n errors.push(\n `${fileRel}: quiz.maxAttempts must be a positive number or Infinity, got ${String(val)}`\n );\n }\n }\n\n if (cfg.graded !== undefined && typeof cfg.graded !== 'boolean') {\n errors.push(\n `${fileRel}: quiz.graded must be a boolean, got ${typeof cfg.graded}`\n );\n }\n}\n\n// ---------- Asset Reference Validation ----------\n\nfunction validateAssetRefs(\n content: string,\n fileRel: string,\n assetsDir: string,\n warnings: string[]\n): void {\n // Match $assets/... references in src attributes, import statements, url() etc.\n const assetRefPattern = /\\$assets\\/([^\\s\"'`)]+)/g;\n let match: RegExpExecArray | null;\n\n while ((match = assetRefPattern.exec(content)) !== null) {\n const assetPath = match[1];\n const fullAssetPath = resolve(assetsDir, assetPath);\n if (!existsSync(fullAssetPath)) {\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 (config.completion?.mode === 'quiz' && !pageResults.hasGradedQuiz) {\n errors.push(\n 'completion.mode is \"quiz\" but no pages have quiz config with graded: true'\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","/**\n * Slugify a string for use as a URL-safe / filename-safe identifier.\n * \"My Course Title\" → \"my-course-title\"\n *\n * Shared by the runtime (`WebAdapter` localStorage key) and the build-time\n * exporter (`runExport` zip filename). Both want identical, deterministic\n * output so a course's storage key matches its package name.\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import { existsSync, readdirSync, statSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { ZipArchive } from 'archiver';\nimport { slugify } from '../runtime/slugify.js';\n\n// ---------- Types ----------\n\ninterface ExportConfig {\n title: string;\n description?: string;\n version?: string;\n scoring?: { passingScore?: number };\n completion?: { mode?: 'quiz' | 'percentage' };\n export?: { standard?: string };\n}\n\n// ---------- Helpers ----------\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Recursively collect all file paths relative to a directory.\n */\nfunction collectFiles(dir: string, base: string = ''): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir)) {\n const fullPath = resolve(dir, entry);\n const relPath = base ? `${base}/${entry}` : entry;\n if (statSync(fullPath).isDirectory()) {\n files.push(...collectFiles(fullPath, relPath));\n } else {\n files.push(relPath);\n }\n }\n return files;\n}\n\n/**\n * Derive a stable URN IRI from a seed string. cmi5 §13.1 / xs:anyURI\n * require course / AU ids to be IRIs — bare hex or UUID-shaped strings\n * (without correct version/variant bits) aren't conformant URNs and may\n * be rejected by strict LMS importers.\n *\n * Hash the seed so the id survives rebuilds, then format as\n * `urn:tessera:<kind>:<hex>`. The same seed always produces the same\n * IRI, so existing LRS records are not orphaned by re-export.\n */\nfunction stableUrn(kind: 'course' | 'au', seed: string): string {\n const h = createHash('sha256').update(seed).digest('hex');\n // 32 hex chars (128 bits of entropy) is plenty; trim to keep ids short.\n return `urn:tessera:${kind}:${h.slice(0, 32)}`;\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ---------- Manifest Generators ----------\n\nexport function generateSCORM12Manifest(\n config: ExportConfig,\n distDir: string\n): string {\n const title = escapeXml(config.title || 'Tessera Course');\n const files = collectFiles(distDir);\n const fileElements = files\n .map((f) => ` <file href=\"${escapeXml(f)}\" />`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<manifest identifier=\"tessera-course\" version=\"1.0\"\n xmlns=\"http://www.imsproject.org/xsd/imscp_rootv1p1p2\"\n xmlns:adlcp=\"http://www.adlnet.org/xsd/adlcp_rootv1p2\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>1.2</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:scormtype=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function generateSCORM2004Manifest(\n config: ExportConfig,\n distDir: string\n): string {\n const title = escapeXml(config.title || 'Tessera Course');\n const files = collectFiles(distDir);\n const fileElements = files\n .map((f) => ` <file href=\"${escapeXml(f)}\" />`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<manifest identifier=\"tessera-course\" version=\"1.0\"\n xmlns=\"http://www.imsglobal.org/xsd/imscp_v1p1\"\n xmlns:adlcp=\"http://www.adlnet.org/xsd/adlcp_v1p3\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>2004 4th Edition</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:scormType=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function generateCMI5Xml(config: ExportConfig): string {\n const title = escapeXml(config.title || 'Tessera Course');\n const description = escapeXml(config.description || '');\n // Derive stable IDs from the course title so they survive rebuilds without\n // orphaning existing learner records in the LRS.\n const courseId = stableUrn('course', `tessera-course:${config.title || ''}`);\n const auId = stableUrn('au', `tessera-au:${config.title || ''}`);\n const masteryScore = (config.scoring?.passingScore ?? 70) / 100;\n // cmi5 §13.1.4 — `moveOn` decides which verb(s) the LMS treats as\n // satisfying the AU. For graded courses (completion gated on a quiz)\n // a learner who completes without passing should NOT receive credit, so\n // the LMS needs both a Completed AND a Passed before satisfaction.\n // Percentage-mode courses don't surface pass/fail, so completion alone\n // is the right signal.\n const moveOn =\n config.completion?.mode === 'quiz' ? 'CompletedAndPassed' : 'Completed';\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<courseStructure xmlns=\"https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd\">\n <course id=\"${courseId}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </course>\n <au id=\"${auId}\" url=\"index.html\" moveOn=\"${moveOn}\" masteryScore=\"${masteryScore}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </au>\n</courseStructure>`;\n}\n\n// ---------- ZIP Packaging ----------\n\nexport async function createZip(\n distDir: string,\n outputPath: string\n): Promise<number> {\n return new Promise((res, reject) => {\n const output = createWriteStream(outputPath);\n const archive = new ZipArchive({ zlib: { level: 9 } });\n\n output.on('close', () => {\n res(archive.pointer());\n });\n output.on('error', reject);\n archive.on('error', reject);\n\n archive.pipe(output);\n archive.directory(distDir, false);\n archive.finalize();\n });\n}\n\n// ---------- Main Export ----------\n\n/**\n * Run the export process after Vite build completes.\n * Writes manifest XML into dist/, then packages into ZIP if needed.\n */\n/** Remove any previously built zips for this package to prevent accumulation. */\nfunction cleanOldZips(projectRoot: string, slug: string): void {\n try {\n for (const f of readdirSync(projectRoot)) {\n if (f.startsWith(`${slug}-`) && f.endsWith('.zip')) {\n try { unlinkSync(resolve(projectRoot, f)); } catch {}\n }\n }\n } catch {}\n}\n\nexport async function runExport(\n projectRoot: string,\n config: ExportConfig\n): Promise<void> {\n const distDir = resolve(projectRoot, 'dist');\n const standard = config.export?.standard || 'web';\n const slug = slugify(config.title || 'tessera-course') || 'tessera-course';\n const version = config.version || '1.0.0';\n const zipName = `${slug}-${version}.zip`;\n const zipPath = resolve(projectRoot, zipName);\n\n switch (standard) {\n case 'web': {\n // Compute dist size\n const files = collectFiles(distDir);\n let totalSize = 0;\n for (const f of files) {\n totalSize += statSync(resolve(distDir, f)).size;\n }\n console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);\n break;\n }\n\n case 'scorm12': {\n const manifest = generateSCORM12Manifest(config, distDir);\n writeFileSync(resolve(distDir, 'imsmanifest.xml'), manifest, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(\n `✓ SCORM 1.2 export: ${zipName} (${formatSize(zipSize)})`\n );\n break;\n }\n\n case 'scorm2004': {\n const manifest = generateSCORM2004Manifest(config, distDir);\n writeFileSync(resolve(distDir, 'imsmanifest.xml'), manifest, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(\n `✓ SCORM 2004 export: ${zipName} (${formatSize(zipSize)})`\n );\n break;\n }\n\n case 'cmi5': {\n const xml = generateCMI5Xml(config);\n writeFileSync(resolve(distDir, 'cmi5.xml'), xml, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(`✓ CMI5 export: ${zipName} (${formatSize(zipSize)})`);\n break;\n }\n }\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst VIRTUAL_LAYOUT_ID = 'virtual:tessera-layout';\nconst RESOLVED_LAYOUT_ID = '\\0' + VIRTUAL_LAYOUT_ID;\n\nexport function tesseraLayoutPlugin(): Plugin {\n let projectRoot: string;\n\n return {\n name: 'tessera:layout',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_LAYOUT_ID) return RESOLVED_LAYOUT_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_LAYOUT_ID) return null;\n const layoutPath = resolve(projectRoot, 'layout.svelte');\n if (existsSync(layoutPath)) {\n // Register the file with Vite so edits trigger HMR / build --watch\n // re-runs. Only add when the file actually exists — calling\n // addWatchFile on a non-existent path makes Vite's importAnalysis\n // try to resolve it as a real import.\n this.addWatchFile(layoutPath);\n const normalized = layoutPath.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n }\n return `export default null;`;\n },\n\n configureServer(server: ViteDevServer) {\n const layoutPath = resolve(projectRoot, 'layout.svelte');\n // Only react to add/unlink: those flip the virtual module's load() output\n // between `export default null` and `export { default } from '...'`. A\n // `change` event leaves that output identical and is handled by Svelte's\n // own HMR for the underlying file — full-reloading on every edit would\n // wipe in-page state for no reason.\n server.watcher.on('all', (event, filePath) => {\n if (filePath !== layoutPath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(RESOLVED_LAYOUT_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst VIRTUAL_QUIZ_ID = 'virtual:tessera-quiz';\nconst RESOLVED_QUIZ_ID = '\\0' + VIRTUAL_QUIZ_ID;\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Resolve the project's quiz shell.\n * `projectRoot/quiz.svelte` overrides the built-in `<Quiz>` if it exists,\n * otherwise the built-in is used. Mirrors `tesseraLayoutPlugin` (Phase 3A).\n */\nexport function tesseraQuizPlugin(): Plugin {\n let projectRoot: string;\n // Resolve the built-in Quiz.svelte once. The plugin lives in\n // `dist/plugin/quiz.js` after build and `src/plugin/quiz.ts` in source —\n // both layouts put `Quiz.svelte` two levels up under `src/components/`.\n const packageRoot = resolve(__dirname, '..', '..');\n const builtinQuiz = resolve(packageRoot, 'src', 'components', 'Quiz.svelte');\n\n return {\n name: 'tessera:quiz',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_QUIZ_ID) return RESOLVED_QUIZ_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_QUIZ_ID) return null;\n const userQuizPath = resolve(projectRoot, 'quiz.svelte');\n if (existsSync(userQuizPath)) {\n // Watch the user file so add/remove flips through HMR (see below).\n this.addWatchFile(userQuizPath);\n const normalized = userQuizPath.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n }\n const normalized = builtinQuiz.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n },\n\n configureServer(server: ViteDevServer) {\n const userQuizPath = resolve(projectRoot, 'quiz.svelte');\n // Only react to add/unlink — those flip the load() output between the\n // user quiz and the built-in. A `change` event leaves the resolved\n // module identical and is handled by Svelte's own HMR.\n server.watcher.on('all', (event, filePath) => {\n if (filePath !== userQuizPath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(RESOLVED_QUIZ_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { svelte } from '@sveltejs/vite-plugin-svelte';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\nimport { existsSync, readFileSync, readdirSync, statSync, writeFileSync, unlinkSync, cpSync, mkdirSync } from 'node:fs';\nimport { generateManifest, extractDefaultExportObjectLiteral } from './manifest.js';\nimport JSON5 from 'json5';\nimport type { Manifest } from './manifest.js';\nimport { validateProject } from './validation.js';\nimport { runExport } from './export.js';\nimport { tesseraLayoutPlugin } from './layout.js';\nimport { tesseraQuizPlugin } from './quiz.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Resolve the runtime directory where App.svelte lives\nfunction resolveRuntimeDir(): string {\n const packageRoot = resolve(__dirname, '..', '..');\n return resolve(packageRoot, 'src', 'runtime');\n}\n\n// Resolve the framework styles directory\nfunction resolveStylesDir(): string {\n const packageRoot = resolve(__dirname, '..', '..');\n return resolve(packageRoot, 'styles');\n}\n\nexport function tesseraPlugin() {\n return [\n svelte({\n compilerOptions: { css: 'injected' },\n }),\n tesseraValidationPlugin(),\n tesseraEntryPlugin(),\n tesseraConfigPlugin(),\n tesseraPagesPlugin(),\n tesseraManifestPlugin(),\n tesseraLayoutPlugin(),\n tesseraQuizPlugin(),\n tesseraExportPlugin(),\n ];\n}\n\n// ---------- Entry Plugin ----------\n\nconst VIRTUAL_ENTRY_ID = 'virtual:tessera-entry';\nconst RESOLVED_ENTRY_ID = '\\0' + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MAIN_ID = '/virtual:tessera-main';\nconst RESOLVED_MAIN_ID = '\\0virtual:tessera-main';\n\nfunction tesseraEntryPlugin(): Plugin {\n const runtimeDir = resolveRuntimeDir();\n const stylesDir = resolveStylesDir();\n const appSveltePath = resolve(runtimeDir, 'App.svelte');\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:entry',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n // For build mode: write index.html so Rollup can find it\n buildStart() {\n if (isBuild) {\n writeFileSync(resolve(projectRoot, 'index.html'), generateIndexHtml(), 'utf-8');\n }\n },\n\n // For build mode: clean up temporary index.html and copy assets\n closeBundle() {\n if (isBuild) {\n const htmlPath = resolve(projectRoot, 'index.html');\n if (existsSync(htmlPath)) {\n try { unlinkSync(htmlPath); } catch {}\n }\n\n // Copy assets/ directory to dist/assets/ so $assets/ references resolve\n const assetsDir = resolve(projectRoot, 'assets');\n const distAssetsDir = resolve(projectRoot, 'dist', 'assets');\n if (existsSync(assetsDir)) {\n mkdirSync(distAssetsDir, { recursive: true });\n cpSync(assetsDir, distAssetsDir, { recursive: true });\n }\n }\n },\n\n // Serve index.html for the dev server\n configureServer(server: ViteDevServer) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n if (req.url === '/' || req.url === '/index.html') {\n const html = generateIndexHtml();\n const transformed = await server.transformIndexHtml(req.url, html);\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(transformed);\n return;\n }\n next();\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;\n if (id === VIRTUAL_MAIN_ID || id === 'virtual:tessera-main') return RESOLVED_MAIN_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_ENTRY_ID || id === RESOLVED_MAIN_ID) {\n return generateEntryScript(appSveltePath, stylesDir, projectRoot);\n }\n return null;\n },\n };\n}\n\nfunction generateIndexHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tessera Course</title>\n</head>\n<body>\n <div id=\"tessera-root\"></div>\n <script type=\"module\" src=\"/virtual:tessera-main\"></script>\n</body>\n</html>`;\n}\n\nfunction generateEntryScript(appSveltePath: string, frameworkStylesDir: string, projectRoot: string): string {\n const normalizedPath = appSveltePath.replace(/\\\\/g, '/');\n\n // Framework CSS imports (theme → base → layout)\n const frameworkCssOrder = ['theme.css', 'base.css', 'layout.css'];\n const frameworkImports = frameworkCssOrder\n .map(file => resolve(frameworkStylesDir, file).replace(/\\\\/g, '/'))\n .filter(path => existsSync(path))\n .map(path => `import '${path}';`)\n .join('\\n');\n\n // User CSS imports from project's styles/ directory\n const userStylesDir = resolve(projectRoot, 'styles');\n let userImports = '';\n if (existsSync(userStylesDir)) {\n const userCssFiles = readdirSync(userStylesDir)\n .filter(f => f.endsWith('.css'))\n .sort();\n userImports = userCssFiles\n .map(f => resolve(userStylesDir, f).replace(/\\\\/g, '/'))\n .map(path => `import '${path}';`)\n .join('\\n');\n }\n\n return `// Framework styles\n${frameworkImports}\n// User styles\n${userImports}\n\nimport { mount } from 'svelte';\nimport App from '${normalizedPath}';\n\nmount(App, {\n target: document.getElementById('tessera-root'),\n});\n`;\n}\n\n// ---------- Config Plugin ----------\n\nconst VIRTUAL_CONFIG_ID = 'virtual:tessera-config';\nconst RESOLVED_CONFIG_ID = '\\0' + VIRTUAL_CONFIG_ID;\n\nfunction tesseraConfigPlugin(): Plugin {\n let projectRoot: string;\n\n return {\n name: 'tessera:config',\n enforce: 'pre',\n\n config(config) {\n const root = config.root || process.cwd();\n\n return {\n base: './',\n resolve: {\n alias: {\n '$assets': resolve(root, 'assets'),\n },\n },\n };\n },\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_CONFIG_ID) return RESOLVED_CONFIG_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_CONFIG_ID) {\n const configPath = resolve(projectRoot, 'course.config.js');\n let userConfig: Record<string, any> = {};\n\n if (existsSync(configPath)) {\n this.addWatchFile(configPath);\n const objectStr = extractDefaultExportObjectLiteral(readFileSync(configPath, 'utf-8'));\n if (objectStr) {\n try { userConfig = JSON5.parse(objectStr); } catch {}\n }\n }\n\n const merged = {\n title: userConfig.title || 'Untitled Course',\n ...userConfig,\n navigation: { mode: 'free', ...userConfig.navigation },\n completion: { mode: 'percentage', percentageThreshold: 100, ...userConfig.completion },\n scoring: { passingScore: 70, ...userConfig.scoring },\n export: { standard: 'web', ...userConfig.export },\n };\n\n return `export default ${JSON.stringify(merged)};`;\n }\n return null;\n },\n };\n}\n\n// ---------- Manifest Watch Helpers ----------\n\n/** Register all _meta.js and .svelte files under pagesDir as watch files for build mode. */\nfunction addWatchFiles(ctx: { addWatchFile(id: string): void }, dir: string): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n const full = resolve(dir, entry);\n if (statSync(full).isDirectory()) {\n addWatchFiles(ctx, full);\n } else if (entry.endsWith('.svelte') || entry === '_meta.js') {\n ctx.addWatchFile(full);\n }\n }\n}\n\n// ---------- Pages Plugin ----------\n\nconst VIRTUAL_PAGES_ID = 'virtual:tessera-pages';\nconst RESOLVED_PAGES_ID = '\\0' + VIRTUAL_PAGES_ID;\n\n/**\n * Provides a virtual module that exports an import.meta.glob map for all .svelte\n * pages. This runs in the user's project context so the glob resolves against their\n * pages/ directory, and Vite can statically analyze it for code splitting.\n */\nfunction tesseraPagesPlugin(): Plugin {\n return {\n name: 'tessera:pages',\n enforce: 'pre',\n\n resolveId(id) {\n if (id === VIRTUAL_PAGES_ID) return RESOLVED_PAGES_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_PAGES_ID) {\n return `export default import.meta.glob('/pages/**/*.svelte');`;\n }\n return null;\n },\n };\n}\n\n// ---------- Validation Plugin ----------\n\nfunction tesseraValidationPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:validation',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n // Run validation during dev (configResolved fires before server starts)\n if (!isBuild) {\n runValidation(projectRoot);\n }\n },\n\n buildStart() {\n // Run validation during build (buildStart fires once before bundling)\n if (isBuild) {\n runValidation(projectRoot);\n }\n },\n };\n}\n\nfunction runValidation(projectRoot: string): void {\n const { errors, warnings } = validateProject(projectRoot);\n\n for (const warning of warnings) {\n console.warn(`\\x1b[33m[tessera warning]\\x1b[0m ${warning}`);\n }\n\n if (errors.length > 0) {\n for (const error of errors) {\n console.error(`\\x1b[31m[tessera error]\\x1b[0m ${error}`);\n }\n throw new Error(\n `Tessera validation failed with ${errors.length} error(s). Fix the errors above to continue.`\n );\n }\n}\n\n// ---------- Export Plugin ----------\n\nfunction tesseraExportPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:export',\n enforce: 'post',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n async closeBundle() {\n if (!isBuild) return;\n\n const configPath = resolve(projectRoot, 'course.config.js');\n if (!existsSync(configPath)) {\n // Validation already required course.config.js — getting here means\n // the file vanished mid-build. Surface that loudly rather than\n // shipping a bundle with no LMS export silently.\n throw new Error(\n '[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.'\n );\n }\n\n const objectStr = extractDefaultExportObjectLiteral(readFileSync(configPath, 'utf-8'));\n if (!objectStr) {\n throw new Error(\n '[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.'\n );\n }\n\n let config: any;\n try {\n config = JSON5.parse(objectStr);\n } catch (err) {\n throw new Error(\n `[tessera:export] course.config.js: failed to parse export-default object literal — ${(err as Error).message}`\n );\n }\n\n await runExport(projectRoot, config);\n },\n };\n}\n\n// ---------- Manifest Plugin ----------\n\nconst VIRTUAL_MANIFEST_ID = 'virtual:tessera-manifest';\nconst RESOLVED_MANIFEST_ID = '\\0' + VIRTUAL_MANIFEST_ID;\n\nfunction tesseraManifestPlugin(): Plugin {\n let projectRoot: string;\n let pagesDir: string;\n let currentManifest: Manifest | null = null;\n let server: ViteDevServer | null = null;\n\n function buildManifest(): Manifest {\n currentManifest = generateManifest(pagesDir);\n return currentManifest;\n }\n\n return {\n name: 'tessera:manifest',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n pagesDir = resolve(projectRoot, 'pages');\n },\n\n configureServer(devServer: ViteDevServer) {\n server = devServer;\n\n // Watch the pages directory for changes\n devServer.watcher.on('all', (event, filePath) => {\n if (!filePath.startsWith(pagesDir)) return;\n\n // Rebuild manifest on relevant file changes\n const isRelevant =\n filePath.endsWith('.svelte') ||\n filePath.endsWith('_meta.js') ||\n event === 'addDir' ||\n event === 'unlinkDir';\n\n if (isRelevant) {\n currentManifest = null; // invalidate cache\n\n // Invalidate the virtual module to trigger HMR\n const mod = devServer.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);\n if (mod) {\n devServer.moduleGraph.invalidateModule(mod);\n devServer.ws.send({ type: 'full-reload' });\n }\n\n console.log(`[tessera] Manifest rebuilt (${event}: ${filePath.replace(projectRoot, '')})`);\n }\n });\n },\n\n buildStart() {\n buildManifest();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_MANIFEST_ID) {\n if (!currentManifest) {\n buildManifest();\n }\n\n // Register watch files so Vite's built-in watcher (used in build --watch)\n // knows to re-trigger when pages/ content changes.\n addWatchFiles(this, pagesDir);\n\n // Encode as base64 to prevent Vite's import analysis from\n // scanning .svelte importPath strings as module imports.\n // Replace Infinity with 1e9 since JSON.stringify drops it.\n const json = JSON.stringify(currentManifest, (_key, value) =>\n value === Infinity ? 1e9 : value\n );\n const b64 = Buffer.from(json).toString('base64');\n return `export default JSON.parse(atob(\"${b64}\"));`;\n }\n return null;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;AA4CA,MAAM,mCAAmB,IAAI,KAAmD;AAEhF,SAAgB,qBAAqB,UAA0B;CAC7D,MAAM,OAAO,SAAS,SAAS;CAC/B,MAAM,SAAS,iBAAiB,IAAI,SAAS;CAC7C,IAAI,UAAU,OAAO,YAAY,KAAK,SAAS,OAAO,OAAO;CAC7D,MAAM,UAAU,aAAa,UAAU,QAAQ;CAC/C,iBAAiB,IAAI,UAAU;EAAE,SAAS,KAAK;EAAS;EAAS,CAAC;CAClE,OAAO;;;AAMT,SAAgB,YAAY,MAAsB;CAChD,OAAO,KAAK,QAAQ,SAAS,GAAG;;;AAIlC,SAAgB,UAAU,MAAsB;CAC9C,OAAO,KACJ,MAAM,IAAI,CACV,KAAI,SAAQ,KAAK,OAAO,EAAE,CAAC,aAAa,GAAG,KAAK,MAAM,EAAE,CAAC,CACzD,KAAK,IAAI;;;AAId,SAAgB,WAAW,MAAc,SAAS,OAAe;CAC/D,IAAI,QACF,OAAO,SAAS,MAAM,QAAQ,KAAK,CAAC;CAEtC,OAAO,YAAY,KAAK;;;AAI1B,MAAa,mBACX;;AAGF,MAAa,wBAAwB;;AAGrC,MAAM,oBAAoB;;;;;;AAO1B,SAAgB,kCAAkC,QAA+B;CAC/E,MAAM,QAAQ,OAAO,MAAM,kBAAkB;CAC7C,IAAI,CAAC,SAAS,MAAM,UAAU,KAAA,GAAW,OAAO;CAChD,MAAM,aAAa,OAAO,QAAQ,KAAK,MAAM,MAAM;CACnD,IAAI,aAAa,GAAG,OAAO;CAC3B,OAAO,qBAAqB,QAAQ,WAAW;;;;;;;AAQjD,SAAgB,aAAa,UAAwD;CACnF,IAAI,CAAC,WAAW,SAAS,EAAE,OAAO,EAAE;CAEpC,MAAM,YAAY,kCAAkC,qBAAqB,SAAS,CAAC;CACnF,IAAI,CAAC,WAAW,OAAO,EAAE;CAEzB,IAAI;EACF,OAAO,MAAM,MAAM,UAAU;SACvB;EACN,OAAO,EAAE;;;;;;AAOb,SAAgB,kBAAkB,UAAyD;CAGzF,MAAM,oBAFU,qBAAqB,SAEJ,CAAC,MAAM,iBAAiB;CACzD,IAAI,CAAC,mBAAmB,OAAO,EAAE;CAEjC,MAAM,gBAAgB,kBAAkB;CAExC,MAAM,cAAc,cAAc,MAAM,sBAAsB;CAC9D,IAAI,CAAC,eAAe,YAAY,UAAU,KAAA,GAAW,OAAO,EAAE;CAE9D,MAAM,aAAa,cAAc,QAAQ,KAAK,YAAY,QAAQ,YAAY,GAAG,OAAO;CACxF,IAAI,aAAa,GAAG,OAAO,EAAE;CAC7B,MAAM,YAAY,qBAAqB,eAAe,WAAW;CACjE,IAAI,CAAC,WAAW,OAAO,EAAE;CAEzB,IAAI;EACF,OAAO,MAAM,MAAM,UAAU;SACvB;EACN,MAAM,IAAI,MACR,GAAG,SAAS,iGACb;;;;;;;AAQL,SAAgB,qBAAqB,QAAgB,YAAmC;CACtF,IAAI,OAAO,gBAAgB,KAAK,OAAO;CAEvC,IAAI,QAAQ;CACZ,IAAI,WAA0B;CAC9B,IAAI,UAAU;CAEd,KAAK,IAAI,IAAI,YAAY,IAAI,OAAO,QAAQ,KAAK;EAC/C,MAAM,OAAO,OAAO;EAEpB,IAAI,SAAS;GACX,UAAU;GACV;;EAGF,IAAI,SAAS,QAAQ,UAAU;GAC7B,UAAU;GACV;;EAGF,IAAI,UAAU;GACZ,IAAI,SAAS,UACX,WAAW;GAEb;;EAGF,IAAI,SAAS,QAAO,SAAS,OAAO,SAAS,KAAK;GAChD,WAAW;GACX;;EAIF,IAAI,SAAS,OAAO,IAAI,IAAI,OAAO,UAAU,OAAO,IAAI,OAAO,KAAK;GAClE,MAAM,UAAU,OAAO,QAAQ,MAAM,EAAE;GACvC,IAAI,YAAY,KAAK,OAAO,SAAS;GACrC;;EAIF,IAAI,SAAS,OAAO,IAAI,IAAI,OAAO,UAAU,OAAO,IAAI,OAAO,KAAK;GAClE,MAAM,MAAM,OAAO,QAAQ,MAAM,IAAI,EAAE;GACvC,IAAI,QAAQ,KAAK,OAAO,SAAS,MAAM;GACvC;;EAGF,IAAI,SAAS,KAAK;EAClB,IAAI,SAAS,KAAK;GAChB;GACA,IAAI,UAAU,GACZ,OAAO,OAAO,MAAM,YAAY,IAAI,EAAE;;;CAK5C,OAAO;;;;;AAMT,SAAS,cAAc,SAA2B;CAChD,IAAI,CAAC,WAAW,QAAQ,EAAE,OAAO,EAAE;CACnC,OAAO,YAAY,QAAQ,CACxB,QAAO,SAAQ;EAEd,OAAO,SADM,QAAQ,SAAS,KACV,CAAC,CAAC,aAAa,IAAI,CAAC,KAAK,WAAW,IAAI;GAC5D,CACD,MAAM;;;;;AAMX,SAAS,eAAe,SAA2B;CACjD,IAAI,CAAC,WAAW,QAAQ,EAAE,OAAO,EAAE;CACnC,OAAO,YAAY,QAAQ,CACxB,QAAO,SAAQ,KAAK,SAAS,UAAU,CAAC,CACxC,MAAM;;;;;AAQX,SAAgB,iBAAiB,UAA4B;CAC3D,MAAM,WAA8B,EAAE;CACtC,MAAM,YAA4B,EAAE;CACpC,IAAI,YAAY;CAEhB,MAAM,cAAc,cAAc,SAAS;CAE3C,KAAK,MAAM,eAAe,aAAa;EACrC,MAAM,cAAc,QAAQ,UAAU,YAAY;EAClD,MAAM,cAAc,aAAa,QAAQ,aAAa,WAAW,CAAC;EAClE,MAAM,cAAc,WAAW,YAAY;EAE3C,MAAM,UAA2B;GAC/B,OAAO,YAAY,SAAS,UAAU,YAAY;GAClD,MAAM;GACN,SAAS,EAAE;GACZ;EAED,MAAM,aAAa,cAAc,YAAY;EAE7C,KAAK,MAAM,cAAc,YAAY;GACnC,MAAM,aAAa,QAAQ,aAAa,WAAW;GACnD,MAAM,aAAa,aAAa,QAAQ,YAAY,WAAW,CAAC;GAChE,MAAM,aAAa,WAAW,WAAW;GAEzC,MAAM,SAAyB;IAC7B,OAAO,WAAW,SAAS,UAAU,WAAW;IAChD,MAAM;IACN,OAAO,EAAE;IACV;GAID,MAAM,eAAe,eADE,eAAe,WACY,EAAE,WAAW,MAAM;GAErE,KAAK,MAAM,YAAY,cAAc;IACnC,MAAM,WAAW,QAAQ,YAAY,SAAS;IAC9C,MAAM,WAAW,WAAW,UAAU,KAAK;IAE3C,IAAI,aAAoD,EAAE;IAC1D,IAAI;KACF,aAAa,kBAAkB,SAAS;aACjC,GAAG;KAGV,QAAQ,KAAK,qBAAsB,EAAY,UAAU;;IAG3D,MAAM,eAAe,UAAU,YAAY,GAAG,WAAW,GAAG;IAE5D,MAAM,OAAqB;KACzB,OAAO;KACP,OAAO,WAAW,SAAS,UAAU,SAAS;KAC9C,MAAM;KACN,YAAY;KACZ,MAAM,WAAW,QAAQ;KAC1B;IAED,OAAO,MAAM,KAAK,KAAK;IACvB,UAAU,KAAK,KAAK;IACpB;;GAGF,QAAQ,QAAQ,KAAK,OAAO;;EAG9B,SAAS,KAAK,QAAQ;;CAGxB,OAAO;EACL;EACA,OAAO;EACP,YAAY,UAAU;EACvB;;;;;AAMH,SAAgB,eAAe,UAAoB,YAAiC;CAClF,IAAI,CAAC,cAAc,WAAW,WAAW,GACvC,OAAO;CAGT,MAAM,SAAS,WAAW,KAAI,SAAQ,KAAK,SAAS,UAAU,GAAG,OAAO,GAAG,KAAK,SAAS;CACzF,MAAM,YAAY,IAAI,IAAI,OAAO;CACjC,MAAM,WAAW,SAAS,QAAO,MAAK,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,MAAM;CAK/D,OAAO,CAAC,GAFY,OAAO,QAAO,MAAK,SAAS,SAAS,EAAE,CAErC,EAAE,GAAG,SAAS;;;;;;;;;;;;;;;;;;;;ACvTtC,SAAgB,cAAc,OAA+B;CAC3D,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;CAET,MAAM,IAAI;CACV,IAAI,MAAM,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,SAAS,GAC/C,OAAO;CAET,IAAI,QAAQ;CACZ,IAAI,EAAE,SAAS,KAAA,GAAW;CAC1B,IAAI,EAAE,iBAAiB,KAAA,GAAW;CAClC,IAAI,EAAE,WAAW,KAAA,GAAW;CAC5B,IAAI,EAAE,YAAY,KAAA,GAAW;CAC7B,IAAI,UAAU,GACZ,OAAO;CAET,IAAI,QAAQ,GACV,OAAO;CAET,IAAI,EAAE,SAAS,KAAA;MACT,OAAO,EAAE,SAAS,YAAY,CAAC,EAAE,KAAK,WAAW,UAAU,EAC7D,OAAO;;CAGX,IAAI,EAAE,iBAAiB,KAAA;MACjB,OAAO,EAAE,iBAAiB,YAAY,CAAC,kBAAkB,KAAK,EAAE,aAAa,EAC/E,OAAO;;CAGX,IAAI,EAAE,WAAW,KAAA,GAAW;EAC1B,IAAI,OAAO,EAAE,WAAW,YAAY,CAAC,EAAE,QACrC,OAAO;EAET,IAAI;GACF,IAAI,IAAI,EAAE,OAAO;UACX;GACN,OAAO;;;CAGX,IAAI,EAAE,YAAY,KAAA,GAAW;EAC3B,MAAM,MAAM,EAAE;EACd,IAAI,CAAC,OAAO,OAAO,QAAQ,UACzB,OAAO;EAET,IAAI,OAAO,IAAI,aAAa,YAAY,CAAC,IAAI,UAC3C,OAAO;EAET,IAAI;GACF,IAAI,IAAI,IAAI,SAAS;UACf;GACN,OAAO;;EAET,IAAI,OAAO,IAAI,SAAS,YAAY,CAAC,IAAI,MACvC,OAAO;;CAGX,OAAO;;;;ACrDT,MAAM,sBAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAEF,MAAM,kBAAkB,CAAC,QAAQ,aAAa;AAC9C,MAAM,yBAAyB,CAAC,QAAQ,aAAa;AACrD,MAAM,yBAAyB;CAAC;CAAO;CAAW;CAAa;CAAO;;;;;AAQtE,SAAgB,gBAAgB,aAAuC;CACrE,MAAM,SAAmB,EAAE;CAC3B,MAAM,WAAqB,EAAE;CAG7B,MAAM,aAAa,QAAQ,aAAa,mBAAmB;CAC3D,IAAI,CAAC,WAAW,WAAW,EAAE;EAC3B,OAAO,KAAK,6CAA6C;EACzD,OAAO;GAAE;GAAQ;GAAU;;CAI7B,MAAM,SAAS,YAAY,YAAY,QAAQ,SAAS;CAKxD,MAAM,cAAc,cAFH,QAAQ,aAAa,QAEI,EADxB,QAAQ,aAAa,SACc,EAAE,YAAY;CACnE,OAAO,KAAK,GAAG,YAAY,OAAO;CAClC,SAAS,KAAK,GAAG,YAAY,SAAS;CAGtC,IAAI,QACF,cAAc,QAAQ,aAAa,QAAQ,SAAS;CAGtD,OAAO;EAAE;EAAQ;EAAU;;AAc7B,SAAS,YACP,YACA,QACA,UACqB;CACrB,MAAM,YAAY,kCAAkC,qBAAqB,WAAW,CAAC;CACrF,IAAI,CAAC,WAAW;EACd,OAAO,KACL,+EACD;EACD,OAAO;;CAGT,IAAI;CACJ,IAAI;EACF,SAAS,MAAM,MAAM,UAAU;SACzB;EACN,OAAO,KACL,uEACD;EACD,OAAO;;CAIT,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,EACnC,IAAI,CAAC,oBAAoB,IAAI,IAAI,EAC/B,SAAS,KACP,oCAAoC,IAAI,qBACzC;CAKL,IAAI,OAAO,YAAY,SAAS,KAAA;MAC1B,CAAC,gBAAgB,SAAS,OAAO,WAAW,KAAK,EACnD,OAAO,KACL,4EAA4E,OAAO,WAAW,KAAK,GACpG;;CAKL,IAAI,OAAO,YAAY,SAAS,KAAA;MAC1B,CAAC,uBAAuB,SAAS,OAAO,WAAW,KAAK,EAC1D,OAAO,KACL,4EAA4E,OAAO,WAAW,KAAK,GACpG;;CAKL,IAAI,OAAO,QAAQ,aAAa,KAAA;MAC1B,CAAC,uBAAuB,SAAS,OAAO,OAAO,SAAS,EAC1D,OAAO,KACL,8FAA8F,OAAO,OAAO,SAAS,GACtH;;CAKL,IAAI,OAAO,SAAS,iBAAiB,KAAA,GAAW;EAC9C,MAAM,QAAQ,OAAO,QAAQ;EAC7B,IAAI,OAAO,UAAU,YAAY,QAAQ,KAAK,QAAQ,KACpD,OAAO,KACL,+DAA+D,QAChE;;CAKL,IAAI,OAAO,YAAY,wBAAwB,KAAA,GAAW;EACxD,MAAM,YAAY,OAAO,WAAW;EACpC,IAAI,OAAO,cAAc,YAAY,YAAY,KAAK,YAAY,KAChE,OAAO,KACL,yEAAyE,YAC1E;;CAKL,IAAI,OAAO,SAAS,KAAA,GAClB,mBACE,OAAO,MACP,OAAO,QAAQ,YAAY,OAC3B,QACA,SACD;CAGH,OAAO;;AAKT,MAAM,UAAU;AAGhB,SAAS,mBACP,KACA,UACA,QACA,UACM;CACN,IAAI,QAAQ,KAAA,KAAa,QAAQ,MAAM;CAIvC,MAAM,UAAqB,MAAM,QAAQ,IAAI,GAAG,MAAM,CAAC,IAAI;CAE3D,IAAI,MAAM,QAAQ,IAAI,EAAE;EACtB,IAAI,QAAQ,WAAW,GAAG;GACxB,OAAO,KACL,8EACD;GACD;;EASF,IANiB,QAAQ,QACtB,MACC,KACA,OAAO,MAAM,YACZ,EAA6B,aAAa,MAC9C,CAAC,SACa,GACb,OAAO,KACL,2HACD;EAGH,MAAM,uBAAO,IAAI,KAAqB;EACtC,KAAK,MAAM,KAAK,SACd,IAAI,KAAK,OAAO,MAAM,UAAU;GAC9B,MAAM,KAAM,EAA6B;GACzC,IAAI,OAAO,OAAO,YAAY,OAAO,OACnC,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,IAAI,KAAK,EAAE;;EAI3C,KAAK,MAAM,CAAC,IAAI,UAAU,MACxB,IAAI,QAAQ,GACV,SAAS,KACP,8BAA8B,MAAM,0BAA0B,GAAG,wHAElE;QAGA,IAAI,OAAO,QAAQ,UAAU;EAClC,OAAO,KACL,kEACD;EACD;;CAGF,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;EACvC,MAAM,QAAQ,QAAQ;EACtB,MAAM,QAAQ,MAAM,QAAQ,IAAI,GAAG,QAAQ,EAAE,KAAK;EAClD,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;GACvC,OAAO,KAAK,qBAAqB,MAAM,oBAAoB;GAC3D;;EAEF,wBACE,OACA,OACA,UACA,QACA,SACD;;;AAIL,SAAS,wBACP,OACA,OACA,UACA,QACA,UACM;CACN,MAAM,WAAW,MAAM;CACvB,IAAI,aAAa,KAAA,GAAW;EAC1B,OAAO,KAAK,qBAAqB,MAAM,uBAAuB;EAC9D;;CAEF,IAAI,OAAO,aAAa,UAAU;EAChC,OAAO,KAAK,qBAAqB,MAAM,4BAA4B;EACnE;;CAGF,IAAI,aAAa,OAAO;EAEtB,IAAI,aAAa,QACf,OAAO,KACL,qBAAqB,MAAM,+DAA+D,SAAS,4EAEpG;EAIH,KAAK,MAAM,KAAK;GADG;GAAQ;GAAS;GAAc;GAAgB;GACzC,EACvB,IAAI,MAAM,OAAO,KAAA,GACf,OAAO,KACL,qBAAqB,MAAM,GAAG,EAAE,wBAAwB,MAAM,4DAC/D;EAGL;;CAIF,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,IAAI,SAAS;SACjB;EACN,OAAO,KACL,qBAAqB,MAAM,kDAAkD,SAAS,GACvF;EACD;;CAEF,IAAI,IAAI,aAAa,WAAW,IAAI,aAAa,UAAU;EACzD,OAAO,KACL,qBAAqB,MAAM,2CAA2C,IAAI,SAAS,GACpF;EACD;;CAEF,IAAI,IAAI,aAAa,WAAW,QAAQ,IAAI,aAAa,cACvD,SAAS,KACP,qBAAqB,MAAM,0FAC5B;CAEH,IAAI,CAAC,SAAS,SAAS,IAAI,EACzB,SAAS,KACP,qBAAqB,MAAM,8KAE5B;CAIH,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GACX,OAAO,KAAK,qBAAqB,MAAM,mBAAmB;MACrD,IAAI,OAAO,SAAS,UACzB,IAAI,CAAC,MACH,OAAO,KAAK,qBAAqB,MAAM,kCAAkC;MACpE,IAAI,YAAY,KAAK,KAAK,EAC/B,OAAO,KACL,qBAAqB,MAAM,+FAC5B;MACI,IAAI,aAAa,KAAK,KAAK,EAChC,OAAO,KACL,qBAAqB,MAAM,2JAC5B;MAED,SAAS,KACP,qBAAqB,MAAM,uJAE5B;MAEE,IAAI,OAAO,SAAS,YACzB,OAAO,KACL,qBAAqB,MAAM,4CAA4C,OAAO,OAC/E;CAIH,MAAM,aAAa,MAAM;CACzB,IAAI,eAAe,KAAA,KAAa,eAAe,IAC7C,OAAO,KAAK,qBAAqB,MAAM,yBAAyB;MAC3D,IAAI,OAAO,eAAe,UAC/B,OAAO,KAAK,qBAAqB,MAAM,8BAA8B;MAErE,IAAI;EAEF,IAAI,IAAI,WAAW;SACb;EACN,OAAO,KACL,qBAAqB,MAAM,4CAA4C,WAAW,GACnF;;CAKL,MAAM,QAAQ,MAAM;CACpB,IAAI,UAAU,KAAA;MACR,aAAa,OACf,OAAO,KACL,qBAAqB,MAAM,2LAE5B;QAEE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM;EACtD,MAAM,MAAM,oBAAoB,MAAM;EACtC,IAAI,KAAK;GACP,MAAM,SAAS,IAAI,WAAW,IAAI,GAC9B,GAAG,MAAM,QAAQ,QACjB,GAAG,MAAM,SAAS;GACtB,OAAO,KAAK,qBAAqB,SAAS;;QAEvC,IAAI,OAAO,UAAU,YAC1B,OAAO,KACL,qBAAqB,MAAM,4CAA4C,OAAO,QAC/E;CAKH,MAAM,OAAO,MAAM;CACnB,IAAI,SAAS,KAAA,GAAW;EACtB,IAAI,OAAO,SAAS,UAClB,OAAO,KACL,qBAAqB,MAAM,wCAC5B;OAED,IAAI;GACF,IAAI,IAAI,KAAK;UACP;GACN,OAAO,KACL,qBAAqB,MAAM,+CAC5B;;EAGL,IAAI,UAAU,KAAA,GACZ,SAAS,KACP,qBAAqB,MAAM,wCAAwC,MAAM,gCAC1E;EAEH,IAAI,aAAa,UAAU,aAAa,OACtC,SAAS,KACP,qBAAqB,MAAM,6FAA6F,SAAS,IAClI;;CAML,IACE,UAAU,KAAA,MACT,aAAa,aAAa,aAAa,gBACxC,OAAO,eAAe,UACtB;EACA,IAAI,SAAS;EACb,IAAI;GACF,MAAM,IAAI,IAAI,IAAI,WAAW;GAC7B,SAAS,EAAE,aAAa,WAAW,EAAE,aAAa;UAC5C;GACN,SAAS;;EAEX,IAAI,CAAC,UAAU,SAAS,KAAA,GACtB,OAAO,KACL,qBAAqB,MAAM,gHACd,MAAM,mCACpB;;CAKL,MAAM,eAAe,MAAM;CAC3B,IAAI,iBAAiB,KAAA,GAAW;EAC9B,IAAI,OAAO,iBAAiB,YAAY,CAAC,QAAQ,KAAK,aAAa,EACjE,OAAO,KACL,qBAAqB,MAAM,wCAAwC,OAAO,aAAa,CAAC,GACzF;EAEH,IAAI,aAAa,QACf,SAAS,KACP,qBAAqB,MAAM,iEAAiE,SAAS,2DACtG;;;;;;;;AAUP,MAAM,sBAAsB;AAU5B,SAAS,cACP,UACA,WACA,aACuB;CACvB,MAAM,SAAmB,EAAE;CAC3B,MAAM,WAAqB,EAAE;CAC7B,IAAI,aAAa;CACjB,IAAI,eAAe;CACnB,IAAI,gBAAgB;CAEpB,IAAI,CAAC,WAAW,SAAS,EAAE;EACzB,OAAO,KACL,+EACD;EACD,OAAO;GAAE;GAAQ;GAAU,YAAY;GAAG,cAAc;GAAG,eAAe;GAAO;;CAGnF,MAAM,kBAAkB,YAAY,SAAS;CAG7C,KAAK,MAAM,SAAS,iBAAiB;EACnC,MAAM,WAAW,QAAQ,UAAU,MAAM;EACzC,IAAI,MAAM,SAAS,UAAU,IAAI,SAAS,SAAS,CAAC,QAAQ,EAAE;GAC5D,MAAM,UAAU,SAAS,aAAa,SAAS;GAC/C,SAAS,KACP,GAAG,QAAQ,yEACZ;;;CAKL,MAAM,cAAc,gBACjB,QAAQ,SAAS;EAEhB,OAAO,SADM,QAAQ,UAAU,KACX,CAAC,CAAC,aAAa,IAAI,CAAC,KAAK,WAAW,IAAI;GAC5D,CACD,MAAM;CAET,IAAI,YAAY,WAAW,GAAG;EAC5B,OAAO,KACL,+EACD;EACD,OAAO;GAAE;GAAQ;GAAU,YAAY;GAAG,cAAc;GAAG,eAAe;GAAO;;CAGnF,KAAK,MAAM,eAAe,aAAa;EACrC,MAAM,cAAc,QAAQ,UAAU,YAAY;EAClD,MAAM,aAAa,SAAS,aAAa,YAAY;EAGrD,MAAM,cAAc,iBAClB,QAAQ,aAAa,WAAW,EAChC,YACA,OACD;EAID,MAAM,iBAAiB,YAAY,YAAY;EAC/C,MAAM,qBAAqB,eACxB,QAAQ,SAAS;GAChB,MAAM,OAAO,QAAQ,aAAa,KAAK;GACvC,OAAO,KAAK,SAAS,UAAU,IAAI,SAAS,KAAK,CAAC,QAAQ;IAC1D,CACD,MAAM;EAET,IAAI,aAAa,OACf,KAAK,MAAM,YAAY,YAAY,OAAO;GACxC,MAAM,WAAW,SAAS,SAAS,UAAU,GACzC,WACA,GAAG,SAAS;GAChB,IAAI,CAAC,mBAAmB,SAAS,SAAS,EAAE;IAC1C,MAAM,UAAU,SAAS,aAAa,QAAQ,aAAa,WAAW,CAAC;IACvE,OAAO,KACL,GAAG,QAAQ,uBAAuB,SAAS,QAAQ,SAAS,8BAC7D;;;EAKP,KAAK,MAAM,YAAY,oBAAoB;GACzC,MAAM,WAAW,QAAQ,aAAa,SAAS;GAC/C,MAAM,UAAU,SAAS,aAAa,SAAS;GAC/C,MAAM,UAAU,qBAAqB,SAAS;GAE9C,MAAM,aAAa,mBAAmB,SAAS,SAAS,OAAO;GAC/D;GAEA,IAAI,YAAY,MAAM;IACpB;IACA,mBAAmB,WAAW,MAAM,SAAS,OAAO;IACpD,IAAK,WAAW,KAA8B,WAAW,MACvD,gBAAgB;;GAIpB,kBAAkB,SAAS,SAAS,WAAW,SAAS;;EAI1D,MAAM,aAAa,eAChB,QAAQ,SAAS;GAEhB,OAAO,SADM,QAAQ,aAAa,KACd,CAAC,CAAC,aAAa,IAAI,CAAC,KAAK,WAAW,IAAI;IAC5D,CACD,MAAM;EAET,KAAK,MAAM,cAAc,YAAY;GACnC,MAAM,aAAa,QAAQ,aAAa,WAAW;GACnD,MAAM,YAAY,SAAS,aAAa,WAAW;GAGnD,MAAM,OAAO,iBACX,QAAQ,YAAY,WAAW,EAC/B,WACA,OACD;GAGD,MAAM,cAAc,YAAY,WAAW,CACxC,QAAQ,SAAS,KAAK,SAAS,UAAU,CAAC,CAC1C,MAAM;GAGT,IAAI,MAAM,OACR,KAAK,MAAM,YAAY,KAAK,OAAO;IACjC,MAAM,WAAW,SAAS,SAAS,UAAU,GACzC,WACA,GAAG,SAAS;IAChB,IAAI,CAAC,YAAY,SAAS,SAAS,EAAE;KACnC,MAAM,UAAU,SAAS,aAAa,QAAQ,YAAY,WAAW,CAAC;KACtE,OAAO,KACL,GAAG,QAAQ,uBAAuB,SAAS,QAAQ,SAAS,8BAC7D;;;GAMP,IAAI,MAAM,SAAS,KAAK,MAAM,SAAS,GAAG;IACxC,MAAM,YAAY,IAAI,IACpB,KAAK,MAAM,KAAK,MACd,EAAE,SAAS,UAAU,GAAG,IAAI,GAAG,EAAE,SAClC,CACF;IACD,KAAK,MAAM,QAAQ,aACjB,IAAI,CAAC,UAAU,IAAI,KAAK,EAAE;KACxB,MAAM,UAAU,SAAS,aAAa,QAAQ,YAAY,KAAK,CAAC;KAChE,SAAS,KACP,GAAG,QAAQ,gEACZ;;;GAMP,KAAK,MAAM,YAAY,aAAa;IAClC,MAAM,WAAW,QAAQ,YAAY,SAAS;IAC9C,MAAM,UAAU,SAAS,aAAa,SAAS;IAC/C,MAAM,UAAU,qBAAqB,SAAS;IAE9C,MAAM,aAAa,mBAAmB,SAAS,SAAS,OAAO;IAC/D;IAEA,IAAI,YAAY,MAAM;KACpB;KAGA,mBAAmB,WAAW,MAAM,SAAS,OAAO;KAEpD,IAAK,WAAW,KAA8B,WAAW,MACvD,gBAAgB;;IAKpB,kBAAkB,SAAS,SAAS,WAAW,SAAS;;;;CAK9D,IAAI,eAAe,GACjB,OAAO,KACL,+EACD;CAGH,OAAO;EAAE;EAAQ;EAAU;EAAY;EAAc;EAAe;;AAKtE,SAAS,iBACP,UACA,WACA,QAC6C;CAC7C,IAAI,CAAC,WAAW,SAAS,EAAE,OAAO;CAElC,MAAM,UAAU,GAAG,UAAU;CAC7B,MAAM,YAAY,kCAAkC,qBAAqB,SAAS,CAAC;CAEnF,IAAI,CAAC,WAAW;EACd,OAAO,KAAK,GAAG,QAAQ,uDAAuD;EAC9E,OAAO;;CAGT,IAAI;CACJ,IAAI;EACF,OAAO,MAAM,MAAM,UAAU;SACvB;EACN,OAAO,KAAK,GAAG,QAAQ,uDAAuD;EAC9E,OAAO;;CAGT,IAAI,CAAC,KAAK,OACR,OAAO,KAAK,GAAG,QAAQ,kCAAkC;CAG3D,OAAO;;AAKT,SAAS,mBACP,SACA,SACA,QAC2C;CAC3C,MAAM,oBAAoB,QAAQ,MAAM,iBAAiB;CACzD,IAAI,CAAC,mBAAmB,OAAO;CAE/B,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,cAAc,cAAc,MAAM,sBAAsB;CAC9D,IAAI,CAAC,eAAe,YAAY,UAAU,KAAA,GAAW,OAAO;CAO5D,IAAI,CAJgB,cACjB,MAAM,YAAY,QAAQ,YAAY,GAAG,OAAO,CAChD,WAEa,CAAC,WAAW,IAAI,EAAE;EAEhC,OAAO,KACL,GAAG,QAAQ,iGACZ;EACD,OAAO;;CAQT,MAAM,YAAY,qBAAqB,eAJpB,cAAc,QAC/B,KACA,YAAY,QAAQ,YAAY,GAAG,OAE2B,CAAC;CACjE,IAAI,CAAC,WAAW;EACd,OAAO,KACL,GAAG,QAAQ,iGACZ;EACD,OAAO;;CAGT,IAAI;EACF,OAAO,MAAM,MAAM,UAAU;SACvB;EACN,OAAO,KACL,GAAG,QAAQ,iGACZ;EACD,OAAO;;;AAMX,SAAS,mBAAmB,MAAe,SAAiB,QAAwB;CAClF,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;CACvC,MAAM,MAAM;CAEZ,IAAI,IAAI,gBAAgB,KAAA,GAAW;EACjC,MAAM,MAAM,IAAI;EAChB,IAAI,QAAQ,aAAa,OAAO,QAAQ,YAAY,OAAO,KAAK,CAAC,OAAO,SAAS,IAAI,GACnF,OAAO,KACL,GAAG,QAAQ,gEAAgE,OAAO,IAAI,GACvF;;CAIL,IAAI,IAAI,WAAW,KAAA,KAAa,OAAO,IAAI,WAAW,WACpD,OAAO,KACL,GAAG,QAAQ,uCAAuC,OAAO,IAAI,SAC9D;;AAML,SAAS,kBACP,SACA,SACA,WACA,UACM;CAEN,MAAM,kBAAkB;CACxB,IAAI;CAEJ,QAAQ,QAAQ,gBAAgB,KAAK,QAAQ,MAAM,MAAM;EACvD,MAAM,YAAY,MAAM;EAExB,IAAI,CAAC,WADiB,QAAQ,WAAW,UACZ,CAAC,EAC5B,SAAS,KACP,GAAG,QAAQ,aAAa,UAAU,kCACnC;;;AAOP,SAAS,cACP,QACA,aACA,QACA,UACM;CAEN,IAAI,OAAO,YAAY,SAAS,UAAU,CAAC,YAAY,eACrD,OAAO,KACL,8EACD;CAIH,IAAI,OAAO,QAAQ,aAAa,WAAW;EAYzC,IAAI,eAAe;EACnB,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,YAAY,KAC1C,gBAAgB,OAAO,EAAE,CAAC,SAAS;EAErC,MAAM,WAAW;EACjB,MAAM,YAAY,YAAY,eAAe;EAC7C,MAAM,aAAa,YAAY,aAAa;EAC5C,MAAM,kBAAkB,YAAY,aAAa;EAEjD,MAAM,gBACJ,WACA,eACA,YACA,aACA,kBACA;EAEF,IAAI,gBAAgB,MAClB,SAAS,KACP,cAAc,YAAY,WAAW,cAAc,YAAY,aAAa,+CAA+C,cAAc,2JAC1I;;;;;;;;;;;;;AC1zBP,SAAgB,QAAQ,MAAsB;CAC5C,OAAO,KACJ,aAAa,CACb,MAAM,CACN,QAAQ,aAAa,GAAG,CACxB,QAAQ,WAAW,IAAI,CACvB,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG;;;;ACK1B,SAAS,UAAU,KAAqB;CACtC,OAAO,IACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,SAAS;;;;;AAM5B,SAAS,aAAa,KAAa,OAAe,IAAc;CAC9D,MAAM,QAAkB,EAAE;CAC1B,IAAI,CAAC,WAAW,IAAI,EAAE,OAAO;CAE7B,KAAK,MAAM,SAAS,YAAY,IAAI,EAAE;EACpC,MAAM,WAAW,QAAQ,KAAK,MAAM;EACpC,MAAM,UAAU,OAAO,GAAG,KAAK,GAAG,UAAU;EAC5C,IAAI,SAAS,SAAS,CAAC,aAAa,EAClC,MAAM,KAAK,GAAG,aAAa,UAAU,QAAQ,CAAC;OAE9C,MAAM,KAAK,QAAQ;;CAGvB,OAAO;;;;;;;;;;;;AAaT,SAAS,UAAU,MAAuB,MAAsB;CAG9D,OAAO,eAAe,KAAK,GAFjB,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAEpB,CAAC,MAAM,GAAG,GAAG;;AAG9C,SAAS,WAAW,OAAuB;CACzC,IAAI,QAAQ,MAAM,OAAO,GAAG,MAAM;CAClC,IAAI,QAAQ,OAAO,MAAM,OAAO,IAAI,QAAQ,MAAM,QAAQ,EAAE,CAAC;CAC7D,OAAO,IAAI,SAAS,OAAO,OAAO,QAAQ,EAAE,CAAC;;AAK/C,SAAgB,wBACd,QACA,SACQ;CACR,MAAM,QAAQ,UAAU,OAAO,SAAS,iBAAiB;CAMzD,OAAO;;;;;;;;;;eAUM,MAAM;;iBAEJ,MAAM;;;;;;EAjBP,aAAa,QACD,CACvB,KAAK,MAAM,qBAAqB,UAAU,EAAE,CAAC,MAAM,CACnD,KAAK,KAoBI,CAAC;;;;;AAMf,SAAgB,0BACd,QACA,SACQ;CACR,MAAM,QAAQ,UAAU,OAAO,SAAS,iBAAiB;CAMzD,OAAO;;;;;;;;;;eAUM,MAAM;;iBAEJ,MAAM;;;;;;EAjBP,aAAa,QACD,CACvB,KAAK,MAAM,qBAAqB,UAAU,EAAE,CAAC,MAAM,CACnD,KAAK,KAoBI,CAAC;;;;;AAMf,SAAgB,gBAAgB,QAA8B;CAC5D,MAAM,QAAQ,UAAU,OAAO,SAAS,iBAAiB;CACzD,MAAM,cAAc,UAAU,OAAO,eAAe,GAAG;CAGvD,MAAM,WAAW,UAAU,UAAU,kBAAkB,OAAO,SAAS,KAAK;CAC5E,MAAM,OAAO,UAAU,MAAM,cAAc,OAAO,SAAS,KAAK;CAChE,MAAM,gBAAgB,OAAO,SAAS,gBAAgB,MAAM;CAU5D,OAAO;;gBAEO,SAAS;sCACa,MAAM;4CACA,YAAY;;YAE5C,KAAK,6BARb,OAAO,YAAY,SAAS,SAAS,uBAAuB,YAQX,kBAAkB,aAAa;sCAC9C,MAAM;4CACA,YAAY;;;;AAOxD,eAAsB,UACpB,SACA,YACiB;CACjB,OAAO,IAAI,SAAS,KAAK,WAAW;EAClC,MAAM,SAAS,kBAAkB,WAAW;EAC5C,MAAM,UAAU,IAAI,WAAW,EAAE,MAAM,EAAE,OAAO,GAAG,EAAE,CAAC;EAEtD,OAAO,GAAG,eAAe;GACvB,IAAI,QAAQ,SAAS,CAAC;IACtB;EACF,OAAO,GAAG,SAAS,OAAO;EAC1B,QAAQ,GAAG,SAAS,OAAO;EAE3B,QAAQ,KAAK,OAAO;EACpB,QAAQ,UAAU,SAAS,MAAM;EACjC,QAAQ,UAAU;GAClB;;;;;;;AAUJ,SAAS,aAAa,aAAqB,MAAoB;CAC7D,IAAI;EACF,KAAK,MAAM,KAAK,YAAY,YAAY,EACtC,IAAI,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,EAAE,SAAS,OAAO,EAChD,IAAI;GAAE,WAAW,QAAQ,aAAa,EAAE,CAAC;UAAU;SAGjD;;AAGV,eAAsB,UACpB,aACA,QACe;CACf,MAAM,UAAU,QAAQ,aAAa,OAAO;CAC5C,MAAM,WAAW,OAAO,QAAQ,YAAY;CAC5C,MAAM,OAAO,QAAQ,OAAO,SAAS,iBAAiB,IAAI;CAE1D,MAAM,UAAU,GAAG,KAAK,GADR,OAAO,WAAW,QACC;CACnC,MAAM,UAAU,QAAQ,aAAa,QAAQ;CAE7C,QAAQ,UAAR;EACE,KAAK,OAAO;GAEV,MAAM,QAAQ,aAAa,QAAQ;GACnC,IAAI,YAAY;GAChB,KAAK,MAAM,KAAK,OACd,aAAa,SAAS,QAAQ,SAAS,EAAE,CAAC,CAAC;GAE7C,QAAQ,IAAI,wBAAwB,WAAW,UAAU,CAAC,GAAG;GAC7D;;EAGF,KAAK,WAAW;GACd,MAAM,WAAW,wBAAwB,QAAQ,QAAQ;GACzD,cAAc,QAAQ,SAAS,kBAAkB,EAAE,UAAU,QAAQ;GACrE,aAAa,aAAa,KAAK;GAC/B,MAAM,UAAU,MAAM,UAAU,SAAS,QAAQ;GACjD,QAAQ,IACN,uBAAuB,QAAQ,IAAI,WAAW,QAAQ,CAAC,GACxD;GACD;;EAGF,KAAK,aAAa;GAChB,MAAM,WAAW,0BAA0B,QAAQ,QAAQ;GAC3D,cAAc,QAAQ,SAAS,kBAAkB,EAAE,UAAU,QAAQ;GACrE,aAAa,aAAa,KAAK;GAC/B,MAAM,UAAU,MAAM,UAAU,SAAS,QAAQ;GACjD,QAAQ,IACN,wBAAwB,QAAQ,IAAI,WAAW,QAAQ,CAAC,GACzD;GACD;;EAGF,KAAK,QAAQ;GACX,MAAM,MAAM,gBAAgB,OAAO;GACnC,cAAc,QAAQ,SAAS,WAAW,EAAE,KAAK,QAAQ;GACzD,aAAa,aAAa,KAAK;GAC/B,MAAM,UAAU,MAAM,UAAU,SAAS,QAAQ;GACjD,QAAQ,IAAI,kBAAkB,QAAQ,IAAI,WAAW,QAAQ,CAAC,GAAG;GACjE;;;;;;AChQN,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB,OAAO;AAElC,SAAgB,sBAA8B;CAC5C,IAAI;CAEJ,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;;EAGvB,UAAU,IAAI;GACZ,IAAI,OAAO,mBAAmB,OAAO;GACrC,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,oBAAoB,OAAO;GACtC,MAAM,aAAa,QAAQ,aAAa,gBAAgB;GACxD,IAAI,WAAW,WAAW,EAAE;IAK1B,KAAK,aAAa,WAAW;IAE7B,OAAO,4BADY,WAAW,QAAQ,OAAO,IACA,CAAC;;GAEhD,OAAO;;EAGT,gBAAgB,QAAuB;GACrC,MAAM,aAAa,QAAQ,aAAa,gBAAgB;GAMxD,OAAO,QAAQ,GAAG,QAAQ,OAAO,aAAa;IAC5C,IAAI,aAAa,YAAY;IAC7B,IAAI,UAAU,SAAS,UAAU,UAAU;IAC3C,MAAM,MAAM,OAAO,YAAY,cAAc,mBAAmB;IAChE,IAAI,KAAK,OAAO,YAAY,iBAAiB,IAAI;IACjD,OAAO,GAAG,KAAK,EAAE,MAAM,eAAe,CAAC;KACvC;;EAEL;;;;AChDH,MAAM,kBAAkB;AACxB,MAAM,mBAAmB,OAAO;AAGhC,MAAMA,cAAY,QADC,cAAc,OAAO,KAAK,IACT,CAAC;;;;;;AAOrC,SAAgB,oBAA4B;CAC1C,IAAI;CAKJ,MAAM,cAAc,QADA,QAAQA,aAAW,MAAM,KACN,EAAE,OAAO,cAAc,cAAc;CAE5E,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;;EAGvB,UAAU,IAAI;GACZ,IAAI,OAAO,iBAAiB,OAAO;GACnC,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,kBAAkB,OAAO;GACpC,MAAM,eAAe,QAAQ,aAAa,cAAc;GACxD,IAAI,WAAW,aAAa,EAAE;IAE5B,KAAK,aAAa,aAAa;IAE/B,OAAO,4BADY,aAAa,QAAQ,OAAO,IACF,CAAC;;GAGhD,OAAO,4BADY,YAAY,QAAQ,OAAO,IACD,CAAC;;EAGhD,gBAAgB,QAAuB;GACrC,MAAM,eAAe,QAAQ,aAAa,cAAc;GAIxD,OAAO,QAAQ,GAAG,QAAQ,OAAO,aAAa;IAC5C,IAAI,aAAa,cAAc;IAC/B,IAAI,UAAU,SAAS,UAAU,UAAU;IAC3C,MAAM,MAAM,OAAO,YAAY,cAAc,iBAAiB;IAC9D,IAAI,KAAK,OAAO,YAAY,iBAAiB,IAAI;IACjD,OAAO,GAAG,KAAK,EAAE,MAAM,eAAe,CAAC;KACvC;;EAEL;;;;ACjDH,MAAM,YAAY,QADC,cAAc,OAAO,KAAK,IACT,CAAC;AAGrC,SAAS,oBAA4B;CAEnC,OAAO,QADa,QAAQ,WAAW,MAAM,KACnB,EAAE,OAAO,UAAU;;AAI/C,SAAS,mBAA2B;CAElC,OAAO,QADa,QAAQ,WAAW,MAAM,KACnB,EAAE,SAAS;;AAGvC,SAAgB,gBAAgB;CAC9B,OAAO;EACL,OAAO,EACL,iBAAiB,EAAE,KAAK,YAAY,EACrC,CAAC;EACF,yBAAyB;EACzB,oBAAoB;EACpB,qBAAqB;EACrB,oBAAoB;EACpB,uBAAuB;EACvB,qBAAqB;EACrB,mBAAmB;EACnB,qBAAqB;EACtB;;AAKH,MAAM,mBAAmB;AACzB,MAAM,oBAAoB,OAAO;AACjC,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAEzB,SAAS,qBAA6B;CACpC,MAAM,aAAa,mBAAmB;CACtC,MAAM,YAAY,kBAAkB;CACpC,MAAM,gBAAgB,QAAQ,YAAY,aAAa;CACvD,IAAI;CACJ,IAAI,UAAU;CAEd,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;GACrB,UAAU,OAAO,YAAY;;EAI/B,aAAa;GACX,IAAI,SACF,cAAc,QAAQ,aAAa,aAAa,EAAE,mBAAmB,EAAE,QAAQ;;EAKnF,cAAc;GACZ,IAAI,SAAS;IACX,MAAM,WAAW,QAAQ,aAAa,aAAa;IACnD,IAAI,WAAW,SAAS,EACtB,IAAI;KAAE,WAAW,SAAS;YAAU;IAItC,MAAM,YAAY,QAAQ,aAAa,SAAS;IAChD,MAAM,gBAAgB,QAAQ,aAAa,QAAQ,SAAS;IAC5D,IAAI,WAAW,UAAU,EAAE;KACzB,UAAU,eAAe,EAAE,WAAW,MAAM,CAAC;KAC7C,OAAO,WAAW,eAAe,EAAE,WAAW,MAAM,CAAC;;;;EAM3D,gBAAgB,QAAuB;GACrC,aAAa;IACX,OAAO,YAAY,IAAI,OAAO,KAAK,KAAK,SAAS;KAC/C,IAAI,IAAI,QAAQ,OAAO,IAAI,QAAQ,eAAe;MAChD,MAAM,OAAO,mBAAmB;MAChC,MAAM,cAAc,MAAM,OAAO,mBAAmB,IAAI,KAAK,KAAK;MAClE,IAAI,UAAU,gBAAgB,YAAY;MAC1C,IAAI,aAAa;MACjB,IAAI,IAAI,YAAY;MACpB;;KAEF,MAAM;MACN;;;EAIN,UAAU,IAAI;GACZ,IAAI,OAAO,kBAAkB,OAAO;GACpC,IAAI,OAAO,mBAAmB,OAAO,wBAAwB,OAAO;GACpE,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,qBAAqB,OAAO,kBACrC,OAAO,oBAAoB,eAAe,WAAW,YAAY;GAEnE,OAAO;;EAEV;;AAGH,SAAS,oBAA4B;CACnC,OAAO;;;;;;;;;;;;;AAcT,SAAS,oBAAoB,eAAuB,oBAA4B,aAA6B;CAC3G,MAAM,iBAAiB,cAAc,QAAQ,OAAO,IAAI;CAIxD,MAAM,mBAAmB;EADE;EAAa;EAAY;EACV,CACvC,KAAI,SAAQ,QAAQ,oBAAoB,KAAK,CAAC,QAAQ,OAAO,IAAI,CAAC,CAClE,QAAO,SAAQ,WAAW,KAAK,CAAC,CAChC,KAAI,SAAQ,WAAW,KAAK,IAAI,CAChC,KAAK,KAAK;CAGb,MAAM,gBAAgB,QAAQ,aAAa,SAAS;CACpD,IAAI,cAAc;CAClB,IAAI,WAAW,cAAc,EAI3B,cAHqB,YAAY,cAAc,CAC5C,QAAO,MAAK,EAAE,SAAS,OAAO,CAAC,CAC/B,MACuB,CACvB,KAAI,MAAK,QAAQ,eAAe,EAAE,CAAC,QAAQ,OAAO,IAAI,CAAC,CACvD,KAAI,SAAQ,WAAW,KAAK,IAAI,CAChC,KAAK,KAAK;CAGf,OAAO;EACP,iBAAiB;;EAEjB,YAAY;;;mBAGK,eAAe;;;;;;;AAUlC,MAAM,oBAAoB;AAC1B,MAAM,qBAAqB,OAAO;AAElC,SAAS,sBAA8B;CACrC,IAAI;CAEJ,OAAO;EACL,MAAM;EACN,SAAS;EAET,OAAO,QAAQ;GAGb,OAAO;IACL,MAAM;IACN,SAAS,EACP,OAAO,EACL,WAAW,QANJ,OAAO,QAAQ,QAAQ,KAAK,EAMV,SAAS,EACnC,EACF;IACF;;EAGH,eAAe,QAAwB;GACrC,cAAc,OAAO;;EAGvB,UAAU,IAAI;GACZ,IAAI,OAAO,mBAAmB,OAAO;GACrC,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,oBAAoB;IAC7B,MAAM,aAAa,QAAQ,aAAa,mBAAmB;IAC3D,IAAI,aAAkC,EAAE;IAExC,IAAI,WAAW,WAAW,EAAE;KAC1B,KAAK,aAAa,WAAW;KAC7B,MAAM,YAAY,kCAAkC,aAAa,YAAY,QAAQ,CAAC;KACtF,IAAI,WACF,IAAI;MAAE,aAAa,MAAM,MAAM,UAAU;aAAU;;IAIvD,MAAM,SAAS;KACb,OAAO,WAAW,SAAS;KAC3B,GAAG;KACH,YAAY;MAAE,MAAM;MAAQ,GAAG,WAAW;MAAY;KACtD,YAAY;MAAE,MAAM;MAAc,qBAAqB;MAAK,GAAG,WAAW;MAAY;KACtF,SAAS;MAAE,cAAc;MAAI,GAAG,WAAW;MAAS;KACpD,QAAQ;MAAE,UAAU;MAAO,GAAG,WAAW;MAAQ;KAClD;IAED,OAAO,kBAAkB,KAAK,UAAU,OAAO,CAAC;;GAElD,OAAO;;EAEV;;;AAMH,SAAS,cAAc,KAAyC,KAAmB;CACjF,IAAI,CAAC,WAAW,IAAI,EAAE;CACtB,KAAK,MAAM,SAAS,YAAY,IAAI,EAAE;EACpC,MAAM,OAAO,QAAQ,KAAK,MAAM;EAChC,IAAI,SAAS,KAAK,CAAC,aAAa,EAC9B,cAAc,KAAK,KAAK;OACnB,IAAI,MAAM,SAAS,UAAU,IAAI,UAAU,YAChD,IAAI,aAAa,KAAK;;;AAO5B,MAAM,mBAAmB;AACzB,MAAM,oBAAoB,OAAO;;;;;;AAOjC,SAAS,qBAA6B;CACpC,OAAO;EACL,MAAM;EACN,SAAS;EAET,UAAU,IAAI;GACZ,IAAI,OAAO,kBAAkB,OAAO;GACpC,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,mBACT,OAAO;GAET,OAAO;;EAEV;;AAKH,SAAS,0BAAkC;CACzC,IAAI;CACJ,IAAI,UAAU;CAEd,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;GACrB,UAAU,OAAO,YAAY;GAE7B,IAAI,CAAC,SACH,cAAc,YAAY;;EAI9B,aAAa;GAEX,IAAI,SACF,cAAc,YAAY;;EAG/B;;AAGH,SAAS,cAAc,aAA2B;CAChD,MAAM,EAAE,QAAQ,aAAa,gBAAgB,YAAY;CAEzD,KAAK,MAAM,WAAW,UACpB,QAAQ,KAAK,oCAAoC,UAAU;CAG7D,IAAI,OAAO,SAAS,GAAG;EACrB,KAAK,MAAM,SAAS,QAClB,QAAQ,MAAM,kCAAkC,QAAQ;EAE1D,MAAM,IAAI,MACR,kCAAkC,OAAO,OAAO,8CACjD;;;AAML,SAAS,sBAA8B;CACrC,IAAI;CACJ,IAAI,UAAU;CAEd,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;GACrB,UAAU,OAAO,YAAY;;EAG/B,MAAM,cAAc;GAClB,IAAI,CAAC,SAAS;GAEd,MAAM,aAAa,QAAQ,aAAa,mBAAmB;GAC3D,IAAI,CAAC,WAAW,WAAW,EAIzB,MAAM,IAAI,MACR,8GACD;GAGH,MAAM,YAAY,kCAAkC,aAAa,YAAY,QAAQ,CAAC;GACtF,IAAI,CAAC,WACH,MAAM,IAAI,MACR,kHACD;GAGH,IAAI;GACJ,IAAI;IACF,SAAS,MAAM,MAAM,UAAU;YACxB,KAAK;IACZ,MAAM,IAAI,MACR,sFAAuF,IAAc,UACtG;;GAGH,MAAM,UAAU,aAAa,OAAO;;EAEvC;;AAKH,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB,OAAO;AAEpC,SAAS,wBAAgC;CACvC,IAAI;CACJ,IAAI;CACJ,IAAI,kBAAmC;CAGvC,SAAS,gBAA0B;EACjC,kBAAkB,iBAAiB,SAAS;EAC5C,OAAO;;CAGT,OAAO;EACL,MAAM;EACN,SAAS;EAET,eAAe,QAAwB;GACrC,cAAc,OAAO;GACrB,WAAW,QAAQ,aAAa,QAAQ;;EAG1C,gBAAgB,WAA0B;GAIxC,UAAU,QAAQ,GAAG,QAAQ,OAAO,aAAa;IAC/C,IAAI,CAAC,SAAS,WAAW,SAAS,EAAE;IASpC,IALE,SAAS,SAAS,UAAU,IAC5B,SAAS,SAAS,WAAW,IAC7B,UAAU,YACV,UAAU,aAEI;KACd,kBAAkB;KAGlB,MAAM,MAAM,UAAU,YAAY,cAAc,qBAAqB;KACrE,IAAI,KAAK;MACP,UAAU,YAAY,iBAAiB,IAAI;MAC3C,UAAU,GAAG,KAAK,EAAE,MAAM,eAAe,CAAC;;KAG5C,QAAQ,IAAI,+BAA+B,MAAM,IAAI,SAAS,QAAQ,aAAa,GAAG,CAAC,GAAG;;KAE5F;;EAGJ,aAAa;GACX,eAAe;;EAGjB,UAAU,IAAI;GACZ,IAAI,OAAO,qBAAqB,OAAO;GACvC,OAAO;;EAGT,KAAK,IAAI;GACP,IAAI,OAAO,sBAAsB;IAC/B,IAAI,CAAC,iBACH,eAAe;IAKjB,cAAc,MAAM,SAAS;IAK7B,MAAM,OAAO,KAAK,UAAU,kBAAkB,MAAM,UAClD,UAAU,WAAW,MAAM,MAC5B;IAED,OAAO,mCADK,OAAO,KAAK,KAAK,CAAC,SAAS,SACM,CAAC;;GAEhD,OAAO;;EAEV"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["__dirname"],"sources":["../../src/plugin/manifest.ts","../../src/runtime/xapi/agent-rules.ts","../../src/plugin/validation.ts","../../src/runtime/slugify.ts","../../src/plugin/export.ts","../../src/plugin/layout.ts","../../src/plugin/quiz.ts","../../src/plugin/index.ts"],"sourcesContent":["import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';\nimport { resolve, basename, extname } from 'node:path';\nimport JSON5 from 'json5';\nimport type { 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}\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<string, { mtimeMs: number; content: string }>();\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\n/** Matches both Svelte 5 `<script module>` and legacy `<script context=\"module\">`. */\nexport const MODULE_SCRIPT_RE =\n /<script\\s+(?:context\\s*=\\s*[\"']module[\"']|module)[^>]*>([\\s\\S]*?)<\\/script>/;\n\n/** Matches `export const pageConfig =` (RHS is read separately). */\nexport const PAGE_CONFIG_EXPORT_RE = /export\\s+const\\s+pageConfig\\s*=\\s*/;\n\n/** Matches `export default ` (RHS is read separately). */\nconst DEFAULT_EXPORT_RE = /export\\s+default\\s*/;\n\n/**\n * Locate `export default { ... }` and return the object literal substring,\n * or null if no balanced object literal follows the `export default` keyword.\n * Used by both manifest extraction and project validation.\n */\nexport function extractDefaultExportObjectLiteral(source: string): string | null {\n const match = source.match(DEFAULT_EXPORT_RE);\n if (!match || match.index === undefined) return null;\n const startIndex = source.indexOf('{', match.index);\n if (startIndex < 0) return null;\n return extractObjectLiteral(source, startIndex);\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): { title?: string; pages?: string[] } {\n if (!existsSync(metaPath)) return {};\n\n const objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(metaPath));\n if (!objectStr) return {};\n\n try {\n return JSON5.parse(objectStr);\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 | { kind: 'ok'; value: { title?: string; quiz?: QuizConfig } }\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(content: string): PageConfigParseResult {\n const moduleScriptMatch = content.match(MODULE_SCRIPT_RE);\n if (!moduleScriptMatch) return { kind: 'none' };\n\n const scriptContent = moduleScriptMatch[1];\n\n const configMatch = scriptContent.match(PAGE_CONFIG_EXPORT_RE);\n if (!configMatch || configMatch.index === undefined) return { kind: 'none' };\n\n const afterExport = scriptContent\n .slice(configMatch.index + configMatch[0].length)\n .trimStart();\n // pageConfig assigned to something other than an object literal — flag as invalid.\n if (!afterExport.startsWith('{')) return { kind: 'invalid' };\n\n const startIndex = scriptContent.indexOf('{', configMatch.index + configMatch[0].length);\n if (startIndex < 0) return { kind: 'invalid' };\n const objectStr = extractObjectLiteral(scriptContent, startIndex);\n if (!objectStr) return { kind: 'invalid' };\n\n try {\n return { kind: 'ok', value: JSON5.parse(objectStr) };\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): { title?: string; quiz?: QuizConfig } {\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 * Extract an object literal from source starting at the opening brace.\n * Tracks brace depth to find the matching closing brace.\n */\nexport function extractObjectLiteral(source: string, startIndex: number): string | null {\n if (source[startIndex] !== '{') return null;\n\n let depth = 0;\n let inString: string | null = null;\n let escaped = false;\n\n for (let i = startIndex; i < source.length; i++) {\n const char = source[i];\n\n if (escaped) {\n escaped = false;\n continue;\n }\n\n if (char === '\\\\' && inString) {\n escaped = true;\n continue;\n }\n\n if (inString) {\n if (char === inString) {\n inString = null;\n }\n continue;\n }\n\n if (char === '\"' || char === \"'\" || char === '`') {\n inString = char;\n continue;\n }\n\n // Skip single-line comments\n if (char === '/' && i + 1 < source.length && source[i + 1] === '/') {\n const newline = source.indexOf('\\n', i);\n i = newline === -1 ? source.length : newline;\n continue;\n }\n\n // Skip multi-line comments\n if (char === '/' && i + 1 < source.length && source[i + 1] === '*') {\n const end = source.indexOf('*/', i + 2);\n i = end === -1 ? source.length : end + 1;\n continue;\n }\n\n if (char === '{') depth++;\n if (char === '}') {\n depth--;\n if (depth === 0) {\n return source.slice(startIndex, i + 1);\n }\n }\n }\n\n return null;\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// ---------- Main ----------\n\n/**\n * Generate a course manifest by scanning the pages/ directory.\n */\nexport function generateManifest(pagesDir: string): Manifest {\n const sections: ManifestSection[] = [];\n const flatPages: ManifestPage[] = [];\n let pageIndex = 0;\n\n const sectionDirs = getSortedDirs(pagesDir);\n\n for (const sectionName of sectionDirs) {\n const sectionPath = resolve(pagesDir, sectionName);\n const sectionMeta = readMetaFile(resolve(sectionPath, '_meta.js'));\n const sectionSlug = deriveSlug(sectionName);\n\n const section: ManifestSection = {\n title: sectionMeta.title || titleCase(sectionSlug),\n slug: sectionSlug,\n lessons: [],\n };\n\n const lessonDirs = getSortedDirs(sectionPath);\n\n for (const lessonName of lessonDirs) {\n const lessonPath = resolve(sectionPath, lessonName);\n const lessonMeta = readMetaFile(resolve(lessonPath, '_meta.js'));\n const lessonSlug = deriveSlug(lessonName);\n\n const lesson: ManifestLesson = {\n title: lessonMeta.title || titleCase(lessonSlug),\n slug: lessonSlug,\n pages: [],\n };\n\n // Determine page order\n const allSvelteFiles = getSvelteFiles(lessonPath);\n const orderedFiles = orderPageFiles(allSvelteFiles, lessonMeta.pages);\n\n for (const fileName of orderedFiles) {\n const filePath = resolve(lessonPath, fileName);\n const pageSlug = deriveSlug(fileName, true);\n\n let pageConfig: { title?: string; quiz?: QuizConfig } = {};\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 relativePath = `/pages/${sectionName}/${lessonName}/${fileName}`;\n\n const page: ManifestPage = {\n index: pageIndex,\n title: pageConfig.title || titleCase(pageSlug),\n slug: pageSlug,\n importPath: relativePath,\n quiz: pageConfig.quiz || null,\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(allFiles: string[], pagesArray?: string[]): 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/**\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 (typeof a.mbox_sha1sum !== 'string' || !/^[0-9a-f]{40}$/i.test(a.mbox_sha1sum)) {\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 'auth must be a non-empty string';\n }\n if (/^basic\\s/i.test(auth)) {\n return \"auth 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 { 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} from './manifest.js';\nimport { validateAgent } from '../runtime/xapi/agent-rules.js';\n\n// ---------- Types ----------\n\nexport interface ValidationResult {\n errors: string[];\n warnings: string[];\n}\n\n// Known top-level config fields\nconst KNOWN_CONFIG_FIELDS = new Set([\n 'title',\n 'description',\n 'author',\n 'version',\n 'branding',\n 'navigation',\n 'completion',\n 'scoring',\n 'export',\n 'chrome',\n 'xapi',\n]);\n\nconst VALID_NAV_MODES = ['free', 'sequential'];\nconst VALID_COMPLETION_MODES = ['quiz', 'percentage'];\nconst VALID_EXPORT_STANDARDS = ['web', 'scorm12', 'scorm2004', 'cmi5'];\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 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(configPath, errors, warnings);\n\n // 3. Validate pages directory\n const pagesDir = resolve(projectRoot, 'pages');\n const assetsDir = resolve(projectRoot, 'assets');\n const pageResults = validatePages(pagesDir, assetsDir, projectRoot);\n errors.push(...pageResults.errors);\n warnings.push(...pageResults.warnings);\n\n // 4. Cross-cutting validations\n if (config) {\n crossValidate(config, pageResults, errors, warnings);\n }\n\n return { errors, warnings };\n}\n\n// ---------- Config Validation ----------\n\ninterface ParsedConfig {\n title?: string;\n navigation?: { mode?: string };\n completion?: { mode?: string; percentageThreshold?: number };\n scoring?: { passingScore?: number };\n export?: { standard?: string };\n [key: string]: unknown;\n}\n\nfunction parseConfig(\n configPath: string,\n errors: string[],\n warnings: string[]\n): ParsedConfig | null {\n const objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(configPath));\n if (!objectStr) {\n errors.push(\n 'course.config.js: could not parse — must use `export default { ... }` syntax'\n );\n return null;\n }\n\n let config: ParsedConfig;\n try {\n config = JSON5.parse(objectStr);\n } catch {\n errors.push(\n 'course.config.js: syntax error — must export a static object literal'\n );\n return null;\n }\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 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\" or \"percentage\", got \"${config.completion.mode}\"`\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// ---------- xAPI Config Validation ----------\n\nconst UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\nconst SHA1_RE = /^[0-9a-f]{40}$/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 = ['auth', 'actor', 'activityId', 'registration', 'actorAccountHomePage'];\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 if (!auth) {\n errors.push(`course.config.js: ${label}.auth must be a non-empty string`);\n } else if (/^basic\\s/i.test(auth)) {\n errors.push(\n `course.config.js: ${label}.auth must be the Basic credential value only, not the full header. Drop the 'Basic ' prefix.`\n );\n } else if (/^bearer\\s/i.test(auth)) {\n errors.push(\n `course.config.js: ${label}.auth: 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 } 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 const joined = err.startsWith('.')\n ? `${label}.actor${err}`\n : `${label}.actor ${err}`;\n errors.push(`course.config.js: ${joined}`);\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 ) {\n let isHttp = false;\n try {\n const u = new URL(activityId);\n isHttp = u.protocol === 'http:' || u.protocol === 'https:';\n } catch {\n isHttp = false;\n }\n if (!isHttp && aahp === undefined) {\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\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 PagesValidationResult extends ValidationResult {\n totalPages: number;\n totalQuizzes: number;\n hasGradedQuiz: boolean;\n}\n\nfunction validatePages(\n pagesDir: string,\n assetsDir: string,\n projectRoot: string\n): PagesValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n let totalPages = 0;\n let totalQuizzes = 0;\n let hasGradedQuiz = false;\n // One existsSync per unique asset for the whole pass.\n const assetExistsCache = new Map<string, boolean>();\n\n if (!existsSync(pagesDir)) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n return { errors, warnings, totalPages: 0, totalQuizzes: 0, hasGradedQuiz: false };\n }\n\n const topLevelEntries = readdirSync(pagesDir);\n\n // Check for stray .svelte files at pages/ root\n for (const entry of topLevelEntries) {\n const fullPath = resolve(pagesDir, entry);\n if (entry.endsWith('.svelte') && statSync(fullPath).isFile()) {\n const relPath = relative(projectRoot, fullPath);\n warnings.push(\n `${relPath}: this file is outside the section/lesson structure and will be ignored`\n );\n }\n }\n\n // Get section directories\n const sectionDirs = topLevelEntries\n .filter((name) => {\n const full = resolve(pagesDir, name);\n return statSync(full).isDirectory() && !name.startsWith('.');\n })\n .sort();\n\n if (sectionDirs.length === 0) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n return { errors, warnings, totalPages: 0, totalQuizzes: 0, hasGradedQuiz: false };\n }\n\n for (const sectionName of sectionDirs) {\n const sectionPath = resolve(pagesDir, sectionName);\n const sectionRel = relative(projectRoot, sectionPath);\n\n // Validate section _meta.js\n const sectionMeta = validateMetaFile(\n resolve(sectionPath, '_meta.js'),\n sectionRel,\n errors\n );\n\n // Flat mode: .svelte files directly at section level are pages of an\n // implicit single lesson. Validate them just like lesson-level pages.\n const sectionEntries = readdirSync(sectionPath);\n const sectionSvelteFiles = sectionEntries\n .filter((name) => {\n const full = resolve(sectionPath, name);\n return name.endsWith('.svelte') && statSync(full).isFile();\n })\n .sort();\n\n if (sectionMeta?.pages) {\n for (const pageName of sectionMeta.pages) {\n const fileName = ensureSvelteSuffix(pageName);\n if (!sectionSvelteFiles.includes(fileName)) {\n const metaRel = relative(projectRoot, resolve(sectionPath, '_meta.js'));\n errors.push(\n `${metaRel}: pages array lists \"${pageName}\" but ${fileName} not found in this directory`\n );\n }\n }\n }\n\n for (const fileName of sectionSvelteFiles) {\n const filePath = resolve(sectionPath, fileName);\n const fileRel = relative(projectRoot, filePath);\n const content = readSourceFileCached(filePath);\n\n const pageConfig = validatePageConfig(content, fileRel, errors);\n totalPages++;\n\n if (pageConfig?.quiz) {\n totalQuizzes++;\n validateQuizConfig(pageConfig.quiz, fileRel, errors);\n if ((pageConfig.quiz as { graded?: unknown }).graded === true) {\n hasGradedQuiz = true;\n }\n }\n\n validateAssetRefs(content, fileRel, assetsDir, warnings, assetExistsCache);\n }\n\n // Get lesson directories\n const lessonDirs = sectionEntries\n .filter((name) => {\n const full = resolve(sectionPath, name);\n return statSync(full).isDirectory() && !name.startsWith('.');\n })\n .sort();\n\n for (const lessonName of lessonDirs) {\n const lessonPath = resolve(sectionPath, lessonName);\n const lessonRel = relative(projectRoot, lessonPath);\n\n // Validate lesson _meta.js\n const meta = validateMetaFile(\n resolve(lessonPath, '_meta.js'),\n lessonRel,\n errors\n );\n\n // Get .svelte files\n const svelteFiles = readdirSync(lessonPath)\n .filter((name) => name.endsWith('.svelte'))\n .sort();\n\n // Check pages array references\n if (meta?.pages) {\n for (const pageName of meta.pages) {\n const fileName = ensureSvelteSuffix(pageName);\n if (!svelteFiles.includes(fileName)) {\n const metaRel = relative(projectRoot, resolve(lessonPath, '_meta.js'));\n errors.push(\n `${metaRel}: pages array lists \"${pageName}\" but ${fileName} not found in this directory`\n );\n }\n }\n }\n\n // Check for unlisted .svelte files\n if (meta?.pages && meta.pages.length > 0) {\n const listedSet = new Set(meta.pages.map(ensureSvelteSuffix));\n for (const file of svelteFiles) {\n if (!listedSet.has(file)) {\n const relPath = relative(projectRoot, resolve(lessonPath, file));\n warnings.push(\n `${relPath}: not listed in _meta.js pages array — will be appended at end`\n );\n }\n }\n }\n\n // Validate each .svelte file\n for (const fileName of svelteFiles) {\n const filePath = resolve(lessonPath, fileName);\n const fileRel = relative(projectRoot, filePath);\n const content = readSourceFileCached(filePath);\n\n const pageConfig = validatePageConfig(content, fileRel, errors);\n totalPages++;\n\n if (pageConfig?.quiz) {\n totalQuizzes++;\n\n // Validate quiz config\n validateQuizConfig(pageConfig.quiz, fileRel, errors);\n\n if ((pageConfig.quiz as { graded?: unknown }).graded === true) {\n hasGradedQuiz = true;\n }\n }\n\n // Check $assets references\n validateAssetRefs(content, fileRel, assetsDir, warnings, assetExistsCache);\n }\n }\n }\n\n if (totalPages === 0) {\n errors.push(\n 'No pages found. Create at least one section with a lesson and page in pages/'\n );\n }\n\n return { errors, warnings, totalPages, totalQuizzes, hasGradedQuiz };\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 objectStr = extractDefaultExportObjectLiteral(readSourceFileCached(metaPath));\n\n if (!objectStr) {\n errors.push(`${metaRel}: syntax error — must export default { title: \"...\" }`);\n return null;\n }\n\n let meta: { title?: string; pages?: string[] };\n try {\n meta = JSON5.parse(objectStr);\n } catch {\n errors.push(`${metaRel}: syntax error — must export default { title: \"...\" }`);\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 } | 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\n// ---------- Quiz Config Validation ----------\n\nfunction validateQuizConfig(quiz: unknown, fileRel: string, errors: string[]): void {\n if (!quiz || typeof quiz !== 'object') return;\n const cfg = quiz as { maxAttempts?: unknown; graded?: unknown };\n\n if (cfg.maxAttempts !== undefined) {\n const val = cfg.maxAttempts;\n if (val !== Infinity && (typeof val !== 'number' || val <= 0 || !Number.isFinite(val))) {\n errors.push(\n `${fileRel}: quiz.maxAttempts must be a positive number or Infinity, got ${String(val)}`\n );\n }\n }\n\n if (cfg.graded !== undefined && typeof cfg.graded !== 'boolean') {\n errors.push(\n `${fileRel}: quiz.graded must be a boolean, got ${typeof cfg.graded}`\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]);\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 (config.completion?.mode === 'quiz' && !pageResults.hasGradedQuiz) {\n errors.push(\n 'completion.mode is \"quiz\" but no pages have quiz config with graded: true'\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","/**\n * Slugify a string for use as a URL-safe / filename-safe identifier.\n * \"My Course Title\" → \"my-course-title\"\n *\n * Shared by the runtime (`WebAdapter` localStorage key) and the build-time\n * exporter (`runExport` zip filename). Both want identical, deterministic\n * output so a course's storage key matches its package name.\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import { existsSync, readdirSync, statSync, writeFileSync, unlinkSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { ZipArchive } from 'archiver';\nimport { slugify } from '../runtime/slugify.js';\n\n// ---------- Types ----------\n\ninterface ExportConfig {\n title: string;\n description?: string;\n version?: string;\n scoring?: { passingScore?: number };\n completion?: { mode?: 'quiz' | 'percentage' };\n export?: { standard?: string };\n}\n\n// ---------- Helpers ----------\n\nfunction escapeXml(str: string): string {\n return str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''');\n}\n\n/**\n * Recursively collect all file paths relative to a directory.\n */\nfunction collectFiles(dir: string, base: string = ''): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir)) {\n const fullPath = resolve(dir, entry);\n const relPath = base ? `${base}/${entry}` : entry;\n if (statSync(fullPath).isDirectory()) {\n files.push(...collectFiles(fullPath, relPath));\n } else {\n files.push(relPath);\n }\n }\n return files;\n}\n\n/**\n * Derive a stable URN IRI from a seed string. cmi5 §13.1 / xs:anyURI\n * require course / AU ids to be IRIs — bare hex or UUID-shaped strings\n * (without correct version/variant bits) aren't conformant URNs and may\n * be rejected by strict LMS importers.\n *\n * Hash the seed so the id survives rebuilds, then format as\n * `urn:tessera:<kind>:<hex>`. The same seed always produces the same\n * IRI, so existing LRS records are not orphaned by re-export.\n */\nfunction stableUrn(kind: 'course' | 'au', seed: string): string {\n const h = createHash('sha256').update(seed).digest('hex');\n // 32 hex chars (128 bits of entropy) is plenty; trim to keep ids short.\n return `urn:tessera:${kind}:${h.slice(0, 32)}`;\n}\n\nfunction formatSize(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n// ---------- Manifest Generators ----------\n\n/** Per-version XML differences in imsmanifest.xml between SCORM 1.2 and 2004. */\ninterface ScormManifestDialect {\n rootNs: string;\n adlcpNs: string;\n schemaversion: string;\n /** Attribute name on <resource>: SCORM 1.2 uses lowercase, 2004 uses camelCase. */\n scormTypeAttr: 'scormtype' | 'scormType';\n}\n\nconst SCORM_DIALECTS: Record<'1.2' | '2004', ScormManifestDialect> = {\n '1.2': {\n rootNs: 'http://www.imsproject.org/xsd/imscp_rootv1p1p2',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_rootv1p2',\n schemaversion: '1.2',\n scormTypeAttr: 'scormtype',\n },\n '2004': {\n rootNs: 'http://www.imsglobal.org/xsd/imscp_v1p1',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_v1p3',\n schemaversion: '2004 4th Edition',\n scormTypeAttr: 'scormType',\n },\n};\n\nexport function generateScormManifest(\n version: '1.2' | '2004',\n config: ExportConfig,\n distDir: string\n): string {\n const dialect = SCORM_DIALECTS[version];\n const title = escapeXml(config.title || 'Tessera Course');\n const files = collectFiles(distDir);\n const fileElements = files\n .map((f) => ` <file href=\"${escapeXml(f)}\" />`)\n .join('\\n');\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<manifest identifier=\"tessera-course\" version=\"1.0\"\n xmlns=\"${dialect.rootNs}\"\n xmlns:adlcp=\"${dialect.adlcpNs}\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>${dialect.schemaversion}</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:${dialect.scormTypeAttr}=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function generateSCORM12Manifest(\n config: ExportConfig,\n distDir: string\n): string {\n return generateScormManifest('1.2', config, distDir);\n}\n\nexport function generateSCORM2004Manifest(\n config: ExportConfig,\n distDir: string\n): string {\n return generateScormManifest('2004', config, distDir);\n}\n\nexport function generateCMI5Xml(config: ExportConfig): string {\n const title = escapeXml(config.title || 'Tessera Course');\n const description = escapeXml(config.description || '');\n // Derive stable IDs from the course title so they survive rebuilds without\n // orphaning existing learner records in the LRS.\n const courseId = stableUrn('course', `tessera-course:${config.title || ''}`);\n const auId = stableUrn('au', `tessera-au:${config.title || ''}`);\n const masteryScore = (config.scoring?.passingScore ?? 70) / 100;\n // cmi5 §13.1.4 — `moveOn` decides which verb(s) the LMS treats as\n // satisfying the AU. For graded courses (completion gated on a quiz)\n // a learner who completes without passing should NOT receive credit, so\n // the LMS needs both a Completed AND a Passed before satisfaction.\n // Percentage-mode courses don't surface pass/fail, so completion alone\n // is the right signal.\n const moveOn =\n config.completion?.mode === 'quiz' ? 'CompletedAndPassed' : 'Completed';\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<courseStructure xmlns=\"https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd\">\n <course id=\"${courseId}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </course>\n <au id=\"${auId}\" url=\"index.html\" moveOn=\"${moveOn}\" masteryScore=\"${masteryScore}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </au>\n</courseStructure>`;\n}\n\n// ---------- ZIP Packaging ----------\n\nexport async function createZip(\n distDir: string,\n outputPath: string\n): Promise<number> {\n return new Promise((res, reject) => {\n const output = createWriteStream(outputPath);\n const archive = new ZipArchive({ zlib: { level: 9 } });\n\n output.on('close', () => {\n res(archive.pointer());\n });\n output.on('error', reject);\n archive.on('error', reject);\n\n archive.pipe(output);\n archive.directory(distDir, false);\n archive.finalize();\n });\n}\n\n// ---------- Main Export ----------\n\n/**\n * Run the export process after Vite build completes.\n * Writes manifest XML into dist/, then packages into ZIP if needed.\n */\n/** Remove any previously built zips for this package to prevent accumulation. */\nfunction cleanOldZips(projectRoot: string, slug: string): void {\n try {\n for (const f of readdirSync(projectRoot)) {\n if (f.startsWith(`${slug}-`) && f.endsWith('.zip')) {\n try { unlinkSync(resolve(projectRoot, f)); } catch {}\n }\n }\n } catch {}\n}\n\nexport async function runExport(\n projectRoot: string,\n config: ExportConfig\n): Promise<void> {\n const distDir = resolve(projectRoot, 'dist');\n const standard = config.export?.standard || 'web';\n const slug = slugify(config.title || 'tessera-course') || 'tessera-course';\n const version = config.version || '1.0.0';\n const zipName = `${slug}-${version}.zip`;\n const zipPath = resolve(projectRoot, zipName);\n\n switch (standard) {\n case 'web': {\n // Compute dist size\n const files = collectFiles(distDir);\n let totalSize = 0;\n for (const f of files) {\n totalSize += statSync(resolve(distDir, f)).size;\n }\n console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);\n break;\n }\n\n case 'scorm12': {\n const manifest = generateSCORM12Manifest(config, distDir);\n writeFileSync(resolve(distDir, 'imsmanifest.xml'), manifest, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(\n `✓ SCORM 1.2 export: ${zipName} (${formatSize(zipSize)})`\n );\n break;\n }\n\n case 'scorm2004': {\n const manifest = generateSCORM2004Manifest(config, distDir);\n writeFileSync(resolve(distDir, 'imsmanifest.xml'), manifest, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(\n `✓ SCORM 2004 export: ${zipName} (${formatSize(zipSize)})`\n );\n break;\n }\n\n case 'cmi5': {\n const xml = generateCMI5Xml(config);\n writeFileSync(resolve(distDir, 'cmi5.xml'), xml, 'utf-8');\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(`✓ CMI5 export: ${zipName} (${formatSize(zipSize)})`);\n break;\n }\n }\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nconst VIRTUAL_LAYOUT_ID = 'virtual:tessera-layout';\nconst RESOLVED_LAYOUT_ID = '\\0' + VIRTUAL_LAYOUT_ID;\n\nexport function tesseraLayoutPlugin(): Plugin {\n let projectRoot: string;\n\n return {\n name: 'tessera:layout',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_LAYOUT_ID) return RESOLVED_LAYOUT_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_LAYOUT_ID) return null;\n const layoutPath = resolve(projectRoot, 'layout.svelte');\n if (existsSync(layoutPath)) {\n // Register the file with Vite so edits trigger HMR / build --watch\n // re-runs. Only add when the file actually exists — calling\n // addWatchFile on a non-existent path makes Vite's importAnalysis\n // try to resolve it as a real import.\n this.addWatchFile(layoutPath);\n const normalized = layoutPath.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n }\n return `export default null;`;\n },\n\n configureServer(server: ViteDevServer) {\n const layoutPath = resolve(projectRoot, 'layout.svelte');\n // Only react to add/unlink: those flip the virtual module's load() output\n // between `export default null` and `export { default } from '...'`. A\n // `change` event leaves that output identical and is handled by Svelte's\n // own HMR for the underlying file — full-reloading on every edit would\n // wipe in-page state for no reason.\n server.watcher.on('all', (event, filePath) => {\n if (filePath !== layoutPath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(RESOLVED_LAYOUT_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst VIRTUAL_QUIZ_ID = 'virtual:tessera-quiz';\nconst RESOLVED_QUIZ_ID = '\\0' + VIRTUAL_QUIZ_ID;\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n/**\n * Resolve the project's quiz shell.\n * `projectRoot/quiz.svelte` overrides the built-in `<Quiz>` if it exists,\n * otherwise the built-in is used. Mirrors `tesseraLayoutPlugin` (Phase 3A).\n */\nexport function tesseraQuizPlugin(): Plugin {\n let projectRoot: string;\n // Resolve the built-in Quiz.svelte once. The plugin lives in\n // `dist/plugin/quiz.js` after build and `src/plugin/quiz.ts` in source —\n // both layouts put `Quiz.svelte` two levels up under `src/components/`.\n const packageRoot = resolve(__dirname, '..', '..');\n const builtinQuiz = resolve(packageRoot, 'src', 'components', 'Quiz.svelte');\n\n return {\n name: 'tessera:quiz',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_QUIZ_ID) return RESOLVED_QUIZ_ID;\n return null;\n },\n\n load(id) {\n if (id !== RESOLVED_QUIZ_ID) return null;\n const userQuizPath = resolve(projectRoot, 'quiz.svelte');\n if (existsSync(userQuizPath)) {\n // Watch the user file so add/remove flips through HMR (see below).\n this.addWatchFile(userQuizPath);\n const normalized = userQuizPath.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n }\n const normalized = builtinQuiz.replace(/\\\\/g, '/');\n return `export { default } from '${normalized}';`;\n },\n\n configureServer(server: ViteDevServer) {\n const userQuizPath = resolve(projectRoot, 'quiz.svelte');\n // Only react to add/unlink — those flip the load() output between the\n // user quiz and the built-in. A `change` event leaves the resolved\n // module identical and is handled by Svelte's own HMR.\n server.watcher.on('all', (event, filePath) => {\n if (filePath !== userQuizPath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(RESOLVED_QUIZ_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { svelte } from '@sveltejs/vite-plugin-svelte';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, resolve } from 'node:path';\nimport { existsSync, readFileSync, readdirSync, statSync, writeFileSync, unlinkSync, cpSync, mkdirSync } from 'node:fs';\nimport { generateManifest, extractDefaultExportObjectLiteral } from './manifest.js';\nimport JSON5 from 'json5';\nimport type { Manifest } from './manifest.js';\nimport { validateProject } from './validation.js';\nimport { runExport } from './export.js';\nimport { tesseraLayoutPlugin } from './layout.js';\nimport { tesseraQuizPlugin } from './quiz.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Resolve the runtime directory where App.svelte lives\nfunction resolveRuntimeDir(): string {\n const packageRoot = resolve(__dirname, '..', '..');\n return resolve(packageRoot, 'src', 'runtime');\n}\n\n// Resolve the framework styles directory\nfunction resolveStylesDir(): string {\n const packageRoot = resolve(__dirname, '..', '..');\n return resolve(packageRoot, 'styles');\n}\n\nexport function tesseraPlugin() {\n return [\n svelte({\n compilerOptions: { css: 'injected' },\n }),\n tesseraValidationPlugin(),\n tesseraEntryPlugin(),\n tesseraConfigPlugin(),\n tesseraPagesPlugin(),\n tesseraManifestPlugin(),\n tesseraLayoutPlugin(),\n tesseraQuizPlugin(),\n tesseraExportPlugin(),\n ];\n}\n\n// ---------- Entry Plugin ----------\n\nconst VIRTUAL_ENTRY_ID = 'virtual:tessera-entry';\nconst RESOLVED_ENTRY_ID = '\\0' + VIRTUAL_ENTRY_ID;\nconst VIRTUAL_MAIN_ID = '/virtual:tessera-main';\nconst RESOLVED_MAIN_ID = '\\0virtual:tessera-main';\n\nfunction tesseraEntryPlugin(): Plugin {\n const runtimeDir = resolveRuntimeDir();\n const stylesDir = resolveStylesDir();\n const appSveltePath = resolve(runtimeDir, 'App.svelte');\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:entry',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n // For build mode: write index.html so Rollup can find it\n buildStart() {\n if (isBuild) {\n writeFileSync(resolve(projectRoot, 'index.html'), generateIndexHtml(), 'utf-8');\n }\n },\n\n // For build mode: clean up temporary index.html and copy assets\n closeBundle() {\n if (isBuild) {\n const htmlPath = resolve(projectRoot, 'index.html');\n if (existsSync(htmlPath)) {\n try { unlinkSync(htmlPath); } catch {}\n }\n\n // Copy assets/ directory to dist/assets/ so $assets/ references resolve\n const assetsDir = resolve(projectRoot, 'assets');\n const distAssetsDir = resolve(projectRoot, 'dist', 'assets');\n if (existsSync(assetsDir)) {\n mkdirSync(distAssetsDir, { recursive: true });\n cpSync(assetsDir, distAssetsDir, { recursive: true });\n }\n }\n },\n\n // Serve index.html for the dev server\n configureServer(server: ViteDevServer) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n if (req.url === '/' || req.url === '/index.html') {\n const html = generateIndexHtml();\n const transformed = await server.transformIndexHtml(req.url, html);\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(transformed);\n return;\n }\n next();\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;\n if (id === VIRTUAL_MAIN_ID || id === 'virtual:tessera-main') return RESOLVED_MAIN_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_ENTRY_ID || id === RESOLVED_MAIN_ID) {\n return generateEntryScript(appSveltePath, stylesDir, projectRoot);\n }\n return null;\n },\n };\n}\n\nfunction generateIndexHtml(): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tessera Course</title>\n</head>\n<body>\n <div id=\"tessera-root\"></div>\n <script type=\"module\" src=\"/virtual:tessera-main\"></script>\n</body>\n</html>`;\n}\n\nfunction generateEntryScript(appSveltePath: string, frameworkStylesDir: string, projectRoot: string): string {\n const normalizedPath = appSveltePath.replace(/\\\\/g, '/');\n\n // Framework CSS imports (theme → base → layout)\n const frameworkCssOrder = ['theme.css', 'base.css', 'layout.css'];\n const frameworkImports = frameworkCssOrder\n .map(file => resolve(frameworkStylesDir, file).replace(/\\\\/g, '/'))\n .filter(path => existsSync(path))\n .map(path => `import '${path}';`)\n .join('\\n');\n\n // User CSS imports from project's styles/ directory\n const userStylesDir = resolve(projectRoot, 'styles');\n let userImports = '';\n if (existsSync(userStylesDir)) {\n const userCssFiles = readdirSync(userStylesDir)\n .filter(f => f.endsWith('.css'))\n .sort();\n userImports = userCssFiles\n .map(f => resolve(userStylesDir, f).replace(/\\\\/g, '/'))\n .map(path => `import '${path}';`)\n .join('\\n');\n }\n\n return `// Framework styles\n${frameworkImports}\n// User styles\n${userImports}\n\nimport { mount } from 'svelte';\nimport App from '${normalizedPath}';\n\nmount(App, {\n target: document.getElementById('tessera-root'),\n});\n`;\n}\n\n// ---------- Config Plugin ----------\n\nconst VIRTUAL_CONFIG_ID = 'virtual:tessera-config';\nconst RESOLVED_CONFIG_ID = '\\0' + VIRTUAL_CONFIG_ID;\n\nfunction tesseraConfigPlugin(): Plugin {\n let projectRoot: string;\n\n return {\n name: 'tessera:config',\n enforce: 'pre',\n\n config(config) {\n const root = config.root || process.cwd();\n\n return {\n base: './',\n resolve: {\n alias: {\n '$assets': resolve(root, 'assets'),\n },\n },\n };\n },\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n },\n\n resolveId(id) {\n if (id === VIRTUAL_CONFIG_ID) return RESOLVED_CONFIG_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_CONFIG_ID) {\n const configPath = resolve(projectRoot, 'course.config.js');\n let userConfig: Record<string, any> = {};\n\n if (existsSync(configPath)) {\n this.addWatchFile(configPath);\n const objectStr = extractDefaultExportObjectLiteral(readFileSync(configPath, 'utf-8'));\n if (objectStr) {\n try { userConfig = JSON5.parse(objectStr); } catch {}\n }\n }\n\n const merged = {\n title: userConfig.title || 'Untitled Course',\n ...userConfig,\n navigation: { mode: 'free', ...userConfig.navigation },\n completion: { mode: 'percentage', percentageThreshold: 100, ...userConfig.completion },\n scoring: { passingScore: 70, ...userConfig.scoring },\n export: { standard: 'web', ...userConfig.export },\n };\n\n return `export default ${JSON.stringify(merged)};`;\n }\n return null;\n },\n };\n}\n\n// ---------- Manifest Watch Helpers ----------\n\n/** Register all _meta.js and .svelte files under pagesDir as watch files for build mode. */\nfunction addWatchFiles(ctx: { addWatchFile(id: string): void }, dir: string): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n const full = resolve(dir, entry);\n if (statSync(full).isDirectory()) {\n addWatchFiles(ctx, full);\n } else if (entry.endsWith('.svelte') || entry === '_meta.js') {\n ctx.addWatchFile(full);\n }\n }\n}\n\n// ---------- Pages Plugin ----------\n\nconst VIRTUAL_PAGES_ID = 'virtual:tessera-pages';\nconst RESOLVED_PAGES_ID = '\\0' + VIRTUAL_PAGES_ID;\n\n/**\n * Provides a virtual module that exports an import.meta.glob map for all .svelte\n * pages. This runs in the user's project context so the glob resolves against their\n * pages/ directory, and Vite can statically analyze it for code splitting.\n */\nfunction tesseraPagesPlugin(): Plugin {\n return {\n name: 'tessera:pages',\n enforce: 'pre',\n\n resolveId(id) {\n if (id === VIRTUAL_PAGES_ID) return RESOLVED_PAGES_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_PAGES_ID) {\n return `export default import.meta.glob('/pages/**/*.svelte');`;\n }\n return null;\n },\n };\n}\n\n// ---------- Validation Plugin ----------\n\nfunction tesseraValidationPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:validation',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n // Run validation during dev (configResolved fires before server starts)\n if (!isBuild) {\n runValidation(projectRoot);\n }\n },\n\n buildStart() {\n // Run validation during build (buildStart fires once before bundling)\n if (isBuild) {\n runValidation(projectRoot);\n }\n },\n };\n}\n\nfunction runValidation(projectRoot: string): void {\n const { errors, warnings } = validateProject(projectRoot);\n\n for (const warning of warnings) {\n console.warn(`\\x1b[33m[tessera warning]\\x1b[0m ${warning}`);\n }\n\n if (errors.length > 0) {\n for (const error of errors) {\n console.error(`\\x1b[31m[tessera error]\\x1b[0m ${error}`);\n }\n throw new Error(\n `Tessera validation failed with ${errors.length} error(s). Fix the errors above to continue.`\n );\n }\n}\n\n// ---------- Export Plugin ----------\n\nfunction tesseraExportPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:export',\n enforce: 'post',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n async closeBundle() {\n if (!isBuild) return;\n\n const configPath = resolve(projectRoot, 'course.config.js');\n if (!existsSync(configPath)) {\n // Validation already required course.config.js — getting here means\n // the file vanished mid-build. Surface that loudly rather than\n // shipping a bundle with no LMS export silently.\n throw new Error(\n '[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.'\n );\n }\n\n const objectStr = extractDefaultExportObjectLiteral(readFileSync(configPath, 'utf-8'));\n if (!objectStr) {\n throw new Error(\n '[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.'\n );\n }\n\n let config: any;\n try {\n config = JSON5.parse(objectStr);\n } catch (err) {\n throw new Error(\n `[tessera:export] course.config.js: failed to parse export-default object literal — ${(err as Error).message}`\n );\n }\n\n await runExport(projectRoot, config);\n },\n };\n}\n\n// ---------- Manifest Plugin ----------\n\nconst VIRTUAL_MANIFEST_ID = 'virtual:tessera-manifest';\nconst RESOLVED_MANIFEST_ID = '\\0' + VIRTUAL_MANIFEST_ID;\n\nfunction tesseraManifestPlugin(): Plugin {\n let projectRoot: string;\n let pagesDir: string;\n let currentManifest: Manifest | null = null;\n let server: ViteDevServer | null = null;\n\n function buildManifest(): Manifest {\n currentManifest = generateManifest(pagesDir);\n return currentManifest;\n }\n\n return {\n name: 'tessera:manifest',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n pagesDir = resolve(projectRoot, 'pages');\n },\n\n configureServer(devServer: ViteDevServer) {\n server = devServer;\n\n // Watch the pages directory for changes\n devServer.watcher.on('all', (event, filePath) => {\n if (!filePath.startsWith(pagesDir)) return;\n\n // Rebuild manifest on relevant file changes\n const isRelevant =\n filePath.endsWith('.svelte') ||\n filePath.endsWith('_meta.js') ||\n event === 'addDir' ||\n event === 'unlinkDir';\n\n if (isRelevant) {\n currentManifest = null; // invalidate cache\n\n // Invalidate the virtual module to trigger HMR\n const mod = devServer.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);\n if (mod) {\n devServer.moduleGraph.invalidateModule(mod);\n devServer.ws.send({ type: 'full-reload' });\n }\n\n console.log(`[tessera] Manifest rebuilt (${event}: ${filePath.replace(projectRoot, '')})`);\n }\n });\n },\n\n buildStart() {\n buildManifest();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_MANIFEST_ID) {\n if (!currentManifest) {\n buildManifest();\n }\n\n // Register watch files so Vite's built-in watcher (used in build --watch)\n // knows to re-trigger when pages/ content changes.\n addWatchFiles(this, pagesDir);\n\n // Encode as base64 to prevent Vite's import analysis from\n // scanning .svelte importPath strings as module imports.\n // Replace Infinity with 1e9 since JSON.stringify drops it.\n const json = JSON.stringify(currentManifest, (_key, value) =>\n value === Infinity ? 1e9 : value\n );\n const b64 = Buffer.from(json).toString('base64');\n return `export default JSON.parse(atob(\"${b64}\"));`;\n }\n return null;\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tessera-learn",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"description": "LMS tracking runtime for interactive learning content. One adapter layer (SCORM 1.2, SCORM 2004 4th Edition, cmi5, static Web), your choice of components.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"svelte",
|