tessera-learn 0.2.0 → 0.2.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.
@@ -1,4 +1,4 @@
1
- import { resolveTesseraConfig } from "./inline-config-CroQ-_2Y.js";
1
+ import { resolveTesseraConfig } from "./inline-config-eHjv9XuA.js";
2
2
  //#region src/plugin/build-commands.ts
3
3
  async function runDev(projectRoot, workspaceRoot) {
4
4
  const vite = await import("vite");
@@ -24,4 +24,4 @@ async function runBuild(projectRoot, workspaceRoot) {
24
24
  //#endregion
25
25
  export { runBuild, runDev };
26
26
 
27
- //# sourceMappingURL=build-commands-C0OnV-Vg.js.map
27
+ //# sourceMappingURL=build-commands-D127jw0J.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-commands-C0OnV-Vg.js","names":[],"sources":["../src/plugin/build-commands.ts"],"sourcesContent":["import { resolveTesseraConfig } from './inline-config.js';\n\nexport async function runDev(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'serve',\n mode: 'development',\n });\n const server = await vite.createServer(config);\n await server.listen();\n server.printUrls();\n server.bindCLIShortcuts({ print: true });\n // Never resolve: the CLI wrapper would process.exit and kill the server.\n return new Promise<number>(() => {});\n}\n\nexport async function runBuild(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'build',\n mode: 'production',\n });\n await vite.build(config);\n return 0;\n}\n"],"mappings":";;AAEA,eAAsB,OACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,SAAS,MAAM,KAAK,aAAa,MAAM;CAC7C,MAAM,OAAO,OAAO;CACpB,OAAO,UAAU;CACjB,OAAO,iBAAiB,EAAE,OAAO,KAAK,CAAC;CAEvC,OAAO,IAAI,cAAsB,CAAC,CAAC;AACrC;AAEA,eAAsB,SACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,KAAK,MAAM,MAAM;CACvB,OAAO;AACT"}
1
+ {"version":3,"file":"build-commands-D127jw0J.js","names":[],"sources":["../src/plugin/build-commands.ts"],"sourcesContent":["import { resolveTesseraConfig } from './inline-config.js';\n\nexport async function runDev(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'serve',\n mode: 'development',\n });\n const server = await vite.createServer(config);\n await server.listen();\n server.printUrls();\n server.bindCLIShortcuts({ print: true });\n // Never resolve: the CLI wrapper would process.exit and kill the server.\n return new Promise<number>(() => {});\n}\n\nexport async function runBuild(\n projectRoot: string,\n workspaceRoot: string,\n): Promise<number> {\n const vite = await import('vite');\n const config = await resolveTesseraConfig(projectRoot, workspaceRoot, {\n command: 'build',\n mode: 'production',\n });\n await vite.build(config);\n return 0;\n}\n"],"mappings":";;AAEA,eAAsB,OACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,SAAS,MAAM,KAAK,aAAa,MAAM;CAC7C,MAAM,OAAO,OAAO;CACpB,OAAO,UAAU;CACjB,OAAO,iBAAiB,EAAE,OAAO,KAAK,CAAC;CAEvC,OAAO,IAAI,cAAsB,CAAC,CAAC;AACrC;AAEA,eAAsB,SACpB,aACA,eACiB;CACjB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,SAAS,MAAM,qBAAqB,aAAa,eAAe;EACpE,SAAS;EACT,MAAM;CACR,CAAC;CACD,MAAM,KAAK,MAAM,MAAM;CACvB,OAAO;AACT"}
@@ -1,4 +1,4 @@
1
- import { t as tesseraPlugin } from "./plugin-W_rk3Pit.js";
1
+ import { t as tesseraPlugin } from "./plugin--8H9xQIl.js";
2
2
  import { resolve } from "node:path";
3
3
  import { existsSync } from "node:fs";
4
4
  import { pathToFileURL } from "node:url";
@@ -28,4 +28,4 @@ async function resolveTesseraConfig(projectRoot, workspaceRoot, env) {
28
28
  //#endregion
29
29
  export { resolveTesseraConfig };
30
30
 
31
- //# sourceMappingURL=inline-config-CroQ-_2Y.js.map
31
+ //# sourceMappingURL=inline-config-eHjv9XuA.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"inline-config-CroQ-_2Y.js","names":[],"sources":["../src/plugin/inline-config.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ConfigEnv, InlineConfig } from 'vite';\nimport { tesseraPlugin } from './index.js';\n\n// Base Vite config for every Tessera command (dev, export, a11y build).\n// configFile:false disables Vite's own discovery — there is no vite.config.js —\n// and tesseraPlugin() supplies the Svelte compiler, so this is the full plugin set.\n//\n// $shared points at the workspace-level design system, which lives outside the\n// per-course Vite root, so it is wired here (where workspaceRoot is known) rather\n// than next to $assets in the plugin. server.fs.allow must list workspaceRoot or\n// the dev server's fs.strict gate refuses to serve $shared files.\nexport function buildInlineConfig(\n projectRoot: string,\n workspaceRoot: string,\n): InlineConfig {\n return {\n root: projectRoot,\n configFile: false,\n plugins: [tesseraPlugin()],\n resolve: { alias: { $shared: resolve(workspaceRoot, 'shared') } },\n server: { fs: { allow: [workspaceRoot] } },\n };\n}\n\n// Optional author-owned escape hatch, never scaffolded or reconciled. A *partial*\n// Vite config the caller merges on top of buildInlineConfig(), so tesseraPlugin()\n// stays wired in and the author only writes the delta.\nexport async function loadUserConfig(\n projectRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig | null> {\n const configPath = resolve(projectRoot, 'tessera.config.js');\n if (!existsSync(configPath)) return null;\n const mod = await import(pathToFileURL(configPath).href);\n const config = mod.default ?? mod;\n // mergeConfig throws on a function, so resolve Vite's callback form first.\n return (\n typeof config === 'function' ? await config(env) : config\n ) as InlineConfig;\n}\n\nexport async function resolveTesseraConfig(\n projectRoot: string,\n workspaceRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig> {\n const vite = await import('vite');\n const base = buildInlineConfig(projectRoot, workspaceRoot);\n const user = await loadUserConfig(projectRoot, env);\n return user ? vite.mergeConfig(base, user) : base;\n}\n"],"mappings":";;;;;AAcA,SAAgB,kBACd,aACA,eACc;CACd,OAAO;EACL,MAAM;EACN,YAAY;EACZ,SAAS,CAAC,cAAc,CAAC;EACzB,SAAS,EAAE,OAAO,EAAE,SAAS,QAAQ,eAAe,QAAQ,EAAE,EAAE;EAChE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE;CAC3C;AACF;AAKA,eAAsB,eACpB,aACA,KAC8B;CAC9B,MAAM,aAAa,QAAQ,aAAa,mBAAmB;CAC3D,IAAI,CAAC,WAAW,UAAU,GAAG,OAAO;CACpC,MAAM,MAAM,MAAM,OAAO,cAAc,UAAU,EAAE;CACnD,MAAM,SAAS,IAAI,WAAW;CAE9B,OACE,OAAO,WAAW,aAAa,MAAM,OAAO,GAAG,IAAI;AAEvD;AAEA,eAAsB,qBACpB,aACA,eACA,KACuB;CACvB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,OAAO,kBAAkB,aAAa,aAAa;CACzD,MAAM,OAAO,MAAM,eAAe,aAAa,GAAG;CAClD,OAAO,OAAO,KAAK,YAAY,MAAM,IAAI,IAAI;AAC/C"}
1
+ {"version":3,"file":"inline-config-eHjv9XuA.js","names":[],"sources":["../src/plugin/inline-config.ts"],"sourcesContent":["import { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { ConfigEnv, InlineConfig } from 'vite';\nimport { tesseraPlugin } from './index.js';\n\n// Base Vite config for every Tessera command (dev, export, a11y build).\n// configFile:false disables Vite's own discovery — there is no vite.config.js —\n// and tesseraPlugin() supplies the Svelte compiler, so this is the full plugin set.\n//\n// $shared points at the workspace-level design system, which lives outside the\n// per-course Vite root, so it is wired here (where workspaceRoot is known) rather\n// than next to $assets in the plugin. server.fs.allow must list workspaceRoot or\n// the dev server's fs.strict gate refuses to serve $shared files.\nexport function buildInlineConfig(\n projectRoot: string,\n workspaceRoot: string,\n): InlineConfig {\n return {\n root: projectRoot,\n configFile: false,\n plugins: [tesseraPlugin()],\n resolve: { alias: { $shared: resolve(workspaceRoot, 'shared') } },\n server: { fs: { allow: [workspaceRoot] } },\n };\n}\n\n// Optional author-owned escape hatch, never scaffolded or reconciled. A *partial*\n// Vite config the caller merges on top of buildInlineConfig(), so tesseraPlugin()\n// stays wired in and the author only writes the delta.\nexport async function loadUserConfig(\n projectRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig | null> {\n const configPath = resolve(projectRoot, 'tessera.config.js');\n if (!existsSync(configPath)) return null;\n const mod = await import(pathToFileURL(configPath).href);\n const config = mod.default ?? mod;\n // mergeConfig throws on a function, so resolve Vite's callback form first.\n return (\n typeof config === 'function' ? await config(env) : config\n ) as InlineConfig;\n}\n\nexport async function resolveTesseraConfig(\n projectRoot: string,\n workspaceRoot: string,\n env: ConfigEnv,\n): Promise<InlineConfig> {\n const vite = await import('vite');\n const base = buildInlineConfig(projectRoot, workspaceRoot);\n const user = await loadUserConfig(projectRoot, env);\n return user ? vite.mergeConfig(base, user) : base;\n}\n"],"mappings":";;;;;AAcA,SAAgB,kBACd,aACA,eACc;CACd,OAAO;EACL,MAAM;EACN,YAAY;EACZ,SAAS,CAAC,cAAc,CAAC;EACzB,SAAS,EAAE,OAAO,EAAE,SAAS,QAAQ,eAAe,QAAQ,EAAE,EAAE;EAChE,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,EAAE;CAC3C;AACF;AAKA,eAAsB,eACpB,aACA,KAC8B;CAC9B,MAAM,aAAa,QAAQ,aAAa,mBAAmB;CAC3D,IAAI,CAAC,WAAW,UAAU,GAAG,OAAO;CACpC,MAAM,MAAM,MAAM,OAAO,cAAc,UAAU,EAAE;CACnD,MAAM,SAAS,IAAI,WAAW;CAE9B,OACE,OAAO,WAAW,aAAa,MAAM,OAAO,GAAG,IAAI;AAEvD;AAEA,eAAsB,qBACpB,aACA,eACA,KACuB;CACvB,MAAM,OAAO,MAAM,OAAO;CAC1B,MAAM,OAAO,kBAAkB,aAAa,aAAa;CACzD,MAAM,OAAO,MAAM,eAAe,aAAa,GAAG;CAClD,OAAO,OAAO,KAAK,YAAY,MAAM,IAAI,IAAI;AAC/C"}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { c as validateProject, n as runAudit, r as resolvePackageRoot, s as reportValidationIssues } from "../audit-BA5o0ick.js";
2
+ import { c as validateProject, n as runAudit, r as resolvePackageRoot, s as reportValidationIssues } from "../audit-B9VHgVjk.js";
3
3
  import { basename, dirname, join, relative, resolve } from "node:path";
4
4
  import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
5
5
  //#region src/plugin/validate-cli.ts
@@ -91,20 +91,38 @@ function findWorkspaceRoot(cwd) {
91
91
  if (dirname(dir) === dir) return null;
92
92
  }
93
93
  }
94
- function listCourses(workspaceRoot) {
94
+ function scanCourses(workspaceRoot) {
95
95
  const coursesDir = join(workspaceRoot, "courses");
96
+ const courses = [];
97
+ const malformed = [];
96
98
  try {
97
- return readdirSync(coursesDir, { withFileTypes: true }).filter((e) => e.isDirectory() && isCourse(join(coursesDir, e.name))).map((e) => e.name).sort();
99
+ for (const e of readdirSync(coursesDir, { withFileTypes: true })) {
100
+ if (!e.isDirectory()) continue;
101
+ const dir = join(coursesDir, e.name);
102
+ if (isCourse(dir)) courses.push(e.name);
103
+ else if (isDir(join(dir, "pages"))) malformed.push(e.name);
104
+ }
98
105
  } catch {
99
- return [];
106
+ return {
107
+ courses,
108
+ malformed
109
+ };
100
110
  }
111
+ return {
112
+ courses: courses.sort(),
113
+ malformed: malformed.sort()
114
+ };
101
115
  }
102
116
  const NOT_A_WORKSPACE = "Not inside a Tessera workspace — no `courses/` directory was found at or above the current directory.";
117
+ function malformedHint(malformed) {
118
+ if (malformed.length === 0) return "";
119
+ return `\nSkipped (missing course.config.js):\n` + malformed.map((c) => ` courses/${c}`).join("\n");
120
+ }
103
121
  function listHint(workspaceRoot) {
104
- const courses = listCourses(workspaceRoot);
105
- if (courses.length === 0) return "\nNo courses found. Create one with `tessera new <name>`.";
122
+ const { courses, malformed } = scanCourses(workspaceRoot);
123
+ if (courses.length === 0) return "\nNo courses found. Create one with `tessera new <name>`." + malformedHint(malformed);
106
124
  return `\nAvailable courses:\n${courses.map((c) => ` ${c}`).join("\n")}
107
- Name one (\`tessera <command> <course>\`) or cd into its folder.`;
125
+ Name one (\`tessera <command> <course>\`) or cd into its folder.` + malformedHint(malformed);
108
126
  }
109
127
  function resolveCourse(cwd, name) {
110
128
  const here = resolve(cwd);
@@ -294,11 +312,11 @@ async function main(argv, cwd = process.cwd()) {
294
312
  const { courseRoot, workspaceRoot } = resolved;
295
313
  switch (sub) {
296
314
  case "dev": {
297
- const { runDev } = await import("../build-commands-C0OnV-Vg.js");
315
+ const { runDev } = await import("../build-commands-D127jw0J.js");
298
316
  return runDev(courseRoot, workspaceRoot);
299
317
  }
300
318
  case "export": {
301
- const { runBuild } = await import("../build-commands-C0OnV-Vg.js");
319
+ const { runBuild } = await import("../build-commands-D127jw0J.js");
302
320
  return runBuild(courseRoot, workspaceRoot);
303
321
  }
304
322
  case "validate": return runValidate(courseRoot);
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","names":[],"sources":["../../src/plugin/validate-cli.ts","../../src/plugin/a11y-cli.ts","../../src/plugin/project-name.ts","../../src/plugin/course-root.ts","../../src/plugin/template-copy.ts","../../src/plugin/new-cli.ts","../../src/plugin/duplicate-cli.ts","../../src/plugin/cli.ts"],"sourcesContent":["import { validateProject, reportValidationIssues } from './validation.js';\n\nexport function runValidate(projectRoot: string): number {\n const { errors, warnings } = validateProject(projectRoot);\n\n reportValidationIssues({ errors, warnings });\n\n if (errors.length > 0) {\n const summary =\n `Validation failed with ${errors.length} error(s)` +\n (warnings.length > 0 ? ` and ${warnings.length} warning(s)` : '') +\n '.';\n console.error(`\\n\\x1b[31m${summary}\\x1b[0m`);\n return 1;\n }\n\n if (warnings.length > 0) {\n console.log(\n `\\n\\x1b[33mValidation passed with ${warnings.length} warning(s).\\x1b[0m`,\n );\n } else {\n console.log(\n '\\x1b[32m[tessera]\\x1b[0m Validation passed — no issues found.',\n );\n }\n console.log(\n '\\x1b[2m[tessera] Static checks only. For a full runtime accessibility audit, run: pnpm exec tessera a11y\\x1b[0m',\n );\n return 0;\n}\n","import { runAudit, type AuditOptions, type ImpactLevel } from './a11y/audit.js';\n\nconst VALID_THRESHOLDS: ImpactLevel[] = [\n 'minor',\n 'moderate',\n 'serious',\n 'critical',\n];\n\nexport type ParsedA11yArgs =\n | { ok: true; args: AuditOptions }\n | { ok: false; error: string };\n\nexport function parseA11yArgs(argv: string[]): ParsedA11yArgs {\n let threshold: ImpactLevel | undefined;\n let rebuild = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--threshold') {\n const value = argv[++i] as ImpactLevel;\n if (!VALID_THRESHOLDS.includes(value)) {\n return {\n ok: false,\n error: `--threshold must be one of: ${VALID_THRESHOLDS.join(', ')}`,\n };\n }\n threshold = value;\n } else if (arg === '--build') {\n rebuild = true;\n } else {\n return { ok: false, error: `Unknown argument: ${arg}` };\n }\n }\n\n const args: AuditOptions = { rebuild };\n if (threshold !== undefined) args.threshold = threshold;\n return { ok: true, args };\n}\n\nexport async function runA11y(\n projectRoot: string,\n workspaceRoot: string,\n argv: string[],\n): Promise<number> {\n const parsed = parseA11yArgs(argv);\n if (!parsed.ok) {\n console.error(`[tessera a11y] ${parsed.error}`);\n return 1;\n }\n return runAudit(projectRoot, workspaceRoot, parsed.args);\n}\n","// Pure, dependency-free helpers shared by the `tessera new` subcommand and the\n// `create-tessera` scaffolder. Kept import-free so create-tessera can bundle it\n// at build time without dragging in the rest of the plugin (vite, svelte, …).\n\n// npm package name rules: 1-214 chars, lowercase, must start with [a-z0-9],\n// allowed chars [a-z0-9._-], no leading dot or underscore.\nexport function validateProjectName(\n name: string,\n label = 'Project name',\n): string | null {\n if (!name) return `${label} is required`;\n if (name.length > 214) return `${label} must be 214 characters or fewer`;\n if (name !== name.toLowerCase()) return `${label} must be lowercase`;\n if (!/^[a-z0-9]/.test(name)) {\n return `${label} must start with a letter or digit`;\n }\n if (!/^[a-z0-9._-]+$/.test(name)) {\n return `${label} may only contain lowercase letters, digits, \"-\", \"_\", and \".\"`;\n }\n return null;\n}\n\nexport function toTitleCase(slug: string): string {\n return slug\n .split(/[-_.\\s]+/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { validateProjectName } from './project-name.js';\n\nexport interface ResolvedCourse {\n ok: true;\n courseRoot: string;\n workspaceRoot: string;\n}\n\nexport type ResolveResult = ResolvedCourse | { ok: false; error: string };\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isCourse(dir: string): boolean {\n return existsSync(join(dir, 'course.config.js'));\n}\n\n// A workspace is the nearest ancestor holding a courses/ directory. The walk\n// includes the starting dir, so the workspace root resolves to itself.\nexport function findWorkspaceRoot(cwd: string): string | null {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n if (isDir(join(dir, 'courses'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n }\n}\n\nexport function listCourses(workspaceRoot: string): string[] {\n const coursesDir = join(workspaceRoot, 'courses');\n try {\n return readdirSync(coursesDir, { withFileTypes: true })\n .filter((e) => e.isDirectory() && isCourse(join(coursesDir, e.name)))\n .map((e) => e.name)\n .sort();\n } catch {\n return [];\n }\n}\n\nconst NOT_A_WORKSPACE =\n 'Not inside a Tessera workspace — no `courses/` directory was found at or above the current directory.';\n\nfunction listHint(workspaceRoot: string): string {\n const courses = listCourses(workspaceRoot);\n if (courses.length === 0) {\n return '\\nNo courses found. Create one with `tessera new <name>`.';\n }\n return (\n `\\nAvailable courses:\\n${courses.map((c) => ` ${c}`).join('\\n')}` +\n '\\nName one (`tessera <command> <course>`) or cd into its folder.'\n );\n}\n\n// A name argument always wins; otherwise the cwd must itself be a course. There\n// is deliberately no \"single course → use it implicitly\" rule, so a bare command\n// never changes meaning when a second course is added.\nexport function resolveCourse(cwd: string, name?: string): ResolveResult {\n const here = resolve(cwd);\n\n if (name) {\n const nameError = validateProjectName(name, 'the name');\n if (nameError) {\n return {\n ok: false,\n error: `Invalid course name \"${name}\" — ${nameError}.`,\n };\n }\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n const courseRoot = join(workspaceRoot, 'courses', name);\n if (!isCourse(courseRoot)) {\n return {\n ok: false,\n error: `Course \"${name}\" not found in courses/.${listHint(workspaceRoot)}`,\n };\n }\n return { ok: true, courseRoot, workspaceRoot };\n }\n\n if (isCourse(here)) {\n const workspaceRoot = findWorkspaceRoot(here) ?? dirname(dirname(here));\n return { ok: true, courseRoot: here, workspaceRoot };\n }\n\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n return {\n ok: false,\n error: `No course specified.${listHint(workspaceRoot)}`,\n };\n}\n","import {\n copyFileSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\n\n// npm's tarball packing strips/renames leading-dot files, so templates store\n// them prefixed and we restore the dot on copy. (create-vite convention.)\nconst RENAME: Record<string, string> = {\n _gitignore: '.gitignore',\n _gitkeep: '.gitkeep',\n};\n\n// Text files get token substitution; everything else is copied byte-for-byte.\nconst TEXT = /\\.(svelte|js|ts|json|css|md|html)$/;\n\nfunction applyTokens(s: string, tokens: Record<string, string>): string {\n return s.replace(/__([A-Z_]+)__/g, (m, key) =>\n key in tokens ? tokens[key] : m,\n );\n}\n\nexport function copyTemplate(\n srcDir: string,\n destDir: string,\n tokens: Record<string, string>,\n): void {\n mkdirSync(destDir, { recursive: true });\n for (const entry of readdirSync(srcDir, { withFileTypes: true })) {\n const src = join(srcDir, entry.name);\n const dest = join(destDir, RENAME[entry.name] ?? entry.name);\n if (entry.isDirectory()) {\n copyTemplate(src, dest, tokens);\n } else if (TEXT.test(entry.name)) {\n writeFileSync(dest, applyTokens(readFileSync(src, 'utf-8'), tokens));\n } else {\n copyFileSync(src, dest);\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { findWorkspaceRoot } from './course-root.js';\nimport { validateProjectName, toTitleCase } from './project-name.js';\nimport { copyTemplate } from './template-copy.js';\nimport { resolvePackageRoot } from './package-root.js';\n\n// `tessera new <name>` — stamp a new course into courses/<name> inside the\n// current workspace. No install step: the workspace already owns the deps.\nexport function runNew(name: string | undefined, cwd: string): number {\n if (name === '--help' || name === '-h') {\n console.log(\n 'Usage: tessera new <name>\\n\\n' +\n 'Scaffold a new course into courses/<name> inside the current workspace.',\n );\n return 0;\n }\n if (!name) {\n console.error('Usage: tessera new <name>');\n return 1;\n }\n\n const nameError = validateProjectName(name, 'Course name');\n if (nameError) {\n console.error(`[tessera new] ${nameError}`);\n return 1;\n }\n\n const workspaceRoot = findWorkspaceRoot(resolve(cwd));\n if (!workspaceRoot) {\n console.error(\n '[tessera new] Not inside a Tessera workspace — run this from a workspace (a directory with a `courses/` folder).',\n );\n return 1;\n }\n\n const courseDir = join(workspaceRoot, 'courses', name);\n if (existsSync(courseDir)) {\n console.error(`[tessera new] Course \"${name}\" already exists.`);\n return 1;\n }\n\n const templateDir = join(resolvePackageRoot(), 'templates', 'course');\n copyTemplate(templateDir, courseDir, {\n PROJECT_TITLE: toTitleCase(name),\n });\n\n const rel = relative(workspaceRoot, courseDir);\n console.log(`\\nCreated ${rel}.\\n\\nNext steps:\\n pnpm tessera dev ${name}\\n`);\n return 0;\n}\n","import { cpSync, existsSync } from 'node:fs';\nimport { basename, join, relative } from 'node:path';\nimport { resolveCourse } from './course-root.js';\nimport { validateProjectName } from './project-name.js';\n\nconst HELP =\n 'Usage: tessera duplicate <source> <new>\\n\\n' +\n 'Copy courses/<source>/ to courses/<new>/ within the current workspace.';\n\n// Generated/build artifacts that should never travel with a verbatim copy. The\n// a11y throwaway build and Vite's cache live under node_modules, so they're\n// already pruned by the node_modules skip; the rest are belt-and-suspenders.\nconst SKIP = new Set(['node_modules', 'dist', 'a11y-report.json', '.vite']);\n\nfunction skip(srcPath: string): boolean {\n const name = basename(srcPath);\n return SKIP.has(name) || name.startsWith('.tessera');\n}\n\n// `tessera duplicate <source> <new>` — copy an existing course verbatim within\n// the current workspace. Unlike `new`, there is no template stamping: the JS\n// config (including its title) is copied untouched.\nexport function runDuplicate(\n source: string | undefined,\n target: string | undefined,\n cwd: string,\n): number {\n if (\n source === '--help' ||\n source === '-h' ||\n target === '--help' ||\n target === '-h'\n ) {\n console.log(HELP);\n return 0;\n }\n if (!source || !target) {\n console.error('Usage: tessera duplicate <source> <new>');\n return 1;\n }\n\n const nameError = validateProjectName(target, 'Course name');\n if (nameError) {\n console.error(`[tessera duplicate] ${nameError}`);\n return 1;\n }\n\n const resolved = resolveCourse(cwd, source);\n if (!resolved.ok) {\n console.error(`[tessera duplicate] ${resolved.error}`);\n return 1;\n }\n const { courseRoot: srcDir, workspaceRoot } = resolved;\n\n const destDir = join(workspaceRoot, 'courses', target);\n if (existsSync(destDir)) {\n console.error(`[tessera duplicate] Course \"${target}\" already exists.`);\n return 1;\n }\n\n cpSync(srcDir, destDir, {\n recursive: true,\n filter: (src) => src === srcDir || !skip(src),\n });\n\n const rel = relative(workspaceRoot, destDir);\n const srcRel = relative(workspaceRoot, srcDir);\n console.log(\n `\\nCreated ${rel} (duplicated from ${srcRel}).\\n\\n` +\n `Remember to update the title in ${rel}/course.config.js.\\n\\n` +\n `Next steps:\\n pnpm tessera dev ${target}\\n`,\n );\n return 0;\n}\n","#!/usr/bin/env node\nimport { runValidate } from './validate-cli.js';\nimport { runA11y } from './a11y-cli.js';\nimport { runNew } from './new-cli.js';\nimport { runDuplicate } from './duplicate-cli.js';\nimport { resolveCourse } from './course-root.js';\n\nconst USAGE = `Usage: tessera <command> [course] [options]\n\nCommands:\n new <name> Scaffold a new course into courses/<name>\n duplicate <source> <new> Copy courses/<source> to courses/<new>\n dev [course] Start the Vite dev server\n export [course] Build and package the course for its LMS standard\n validate [course] Fast static structure checks\n a11y [course] Runtime accessibility audit (builds + drives Playwright)\n check [course] Run validate, then a11y\n\nRun a command from inside a course folder, or name the course explicitly.\n\na11y/check options:\n --threshold <minor|moderate|serious|critical> Failing impact (default: serious)\n --build Force a fresh build first`;\n\n// The course is a leading positional: `tessera <cmd> [course] [flags]`. Only the\n// first token can be the course, and only when it isn't a flag — otherwise a flag\n// value (e.g. the `serious` in `--threshold serious`) would be misread as a name.\nexport function splitCourseArg(rest: string[]): {\n course?: string;\n flags: string[];\n} {\n if (rest.length > 0 && !rest[0].startsWith('-')) {\n return { course: rest[0], flags: rest.slice(1) };\n }\n return { course: undefined, flags: rest };\n}\n\nexport async function main(\n argv: string[],\n cwd: string = process.cwd(),\n): Promise<number> {\n const [sub, ...rest] = argv;\n\n if (sub === 'new') return runNew(rest[0], cwd);\n if (sub === 'duplicate') return runDuplicate(rest[0], rest[1], cwd);\n\n switch (sub) {\n case 'dev':\n case 'export':\n case 'validate':\n case 'a11y':\n case 'check': {\n if (rest.includes('--help') || rest.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n const { course, flags } = splitCourseArg(rest);\n const resolved = resolveCourse(cwd, course);\n if (!resolved.ok) {\n console.error(`[tessera] ${resolved.error}`);\n return 1;\n }\n const { courseRoot, workspaceRoot } = resolved;\n\n switch (sub) {\n case 'dev': {\n const { runDev } = await import('./build-commands.js');\n return runDev(courseRoot, workspaceRoot);\n }\n case 'export': {\n const { runBuild } = await import('./build-commands.js');\n return runBuild(courseRoot, workspaceRoot);\n }\n case 'validate':\n return runValidate(courseRoot);\n case 'check': {\n const validateCode = runValidate(courseRoot);\n if (validateCode !== 0) return validateCode;\n return runA11y(courseRoot, workspaceRoot, flags);\n }\n case 'a11y':\n return runA11y(courseRoot, workspaceRoot, flags);\n }\n return 0;\n }\n case '--help':\n case '-h':\n console.log(USAGE);\n return 0;\n case undefined:\n console.error(`No command given.\\n\\n${USAGE}`);\n return 1;\n default:\n console.error(`Unknown command: ${sub}\\n\\n${USAGE}`);\n return 1;\n }\n}\n\n// import.meta.main is true only when this module is the program entry point,\n// and resolves symlinks itself (pnpm/npm bin shims) — Node >= 24.\nif (import.meta.main) {\n void main(process.argv.slice(2)).then((code) => process.exit(code));\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","names":[],"sources":["../../src/plugin/validate-cli.ts","../../src/plugin/a11y-cli.ts","../../src/plugin/project-name.ts","../../src/plugin/course-root.ts","../../src/plugin/template-copy.ts","../../src/plugin/new-cli.ts","../../src/plugin/duplicate-cli.ts","../../src/plugin/cli.ts"],"sourcesContent":["import { validateProject, reportValidationIssues } from './validation.js';\n\nexport function runValidate(projectRoot: string): number {\n const { errors, warnings } = validateProject(projectRoot);\n\n reportValidationIssues({ errors, warnings });\n\n if (errors.length > 0) {\n const summary =\n `Validation failed with ${errors.length} error(s)` +\n (warnings.length > 0 ? ` and ${warnings.length} warning(s)` : '') +\n '.';\n console.error(`\\n\\x1b[31m${summary}\\x1b[0m`);\n return 1;\n }\n\n if (warnings.length > 0) {\n console.log(\n `\\n\\x1b[33mValidation passed with ${warnings.length} warning(s).\\x1b[0m`,\n );\n } else {\n console.log(\n '\\x1b[32m[tessera]\\x1b[0m Validation passed — no issues found.',\n );\n }\n console.log(\n '\\x1b[2m[tessera] Static checks only. For a full runtime accessibility audit, run: pnpm exec tessera a11y\\x1b[0m',\n );\n return 0;\n}\n","import { runAudit, type AuditOptions, type ImpactLevel } from './a11y/audit.js';\n\nconst VALID_THRESHOLDS: ImpactLevel[] = [\n 'minor',\n 'moderate',\n 'serious',\n 'critical',\n];\n\nexport type ParsedA11yArgs =\n | { ok: true; args: AuditOptions }\n | { ok: false; error: string };\n\nexport function parseA11yArgs(argv: string[]): ParsedA11yArgs {\n let threshold: ImpactLevel | undefined;\n let rebuild = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n if (arg === '--threshold') {\n const value = argv[++i] as ImpactLevel;\n if (!VALID_THRESHOLDS.includes(value)) {\n return {\n ok: false,\n error: `--threshold must be one of: ${VALID_THRESHOLDS.join(', ')}`,\n };\n }\n threshold = value;\n } else if (arg === '--build') {\n rebuild = true;\n } else {\n return { ok: false, error: `Unknown argument: ${arg}` };\n }\n }\n\n const args: AuditOptions = { rebuild };\n if (threshold !== undefined) args.threshold = threshold;\n return { ok: true, args };\n}\n\nexport async function runA11y(\n projectRoot: string,\n workspaceRoot: string,\n argv: string[],\n): Promise<number> {\n const parsed = parseA11yArgs(argv);\n if (!parsed.ok) {\n console.error(`[tessera a11y] ${parsed.error}`);\n return 1;\n }\n return runAudit(projectRoot, workspaceRoot, parsed.args);\n}\n","// Pure, dependency-free helpers shared by the `tessera new` subcommand and the\n// `create-tessera` scaffolder. Kept import-free so create-tessera can bundle it\n// at build time without dragging in the rest of the plugin (vite, svelte, …).\n\n// npm package name rules: 1-214 chars, lowercase, must start with [a-z0-9],\n// allowed chars [a-z0-9._-], no leading dot or underscore.\nexport function validateProjectName(\n name: string,\n label = 'Project name',\n): string | null {\n if (!name) return `${label} is required`;\n if (name.length > 214) return `${label} must be 214 characters or fewer`;\n if (name !== name.toLowerCase()) return `${label} must be lowercase`;\n if (!/^[a-z0-9]/.test(name)) {\n return `${label} must start with a letter or digit`;\n }\n if (!/^[a-z0-9._-]+$/.test(name)) {\n return `${label} may only contain lowercase letters, digits, \"-\", \"_\", and \".\"`;\n }\n return null;\n}\n\nexport function toTitleCase(slug: string): string {\n return slug\n .split(/[-_.\\s]+/)\n .filter(Boolean)\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1))\n .join(' ');\n}\n","import { existsSync, readdirSync, statSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { validateProjectName } from './project-name.js';\n\nexport interface ResolvedCourse {\n ok: true;\n courseRoot: string;\n workspaceRoot: string;\n}\n\nexport type ResolveResult = ResolvedCourse | { ok: false; error: string };\n\nfunction isDir(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nfunction isCourse(dir: string): boolean {\n return existsSync(join(dir, 'course.config.js'));\n}\n\n// A workspace is the nearest ancestor holding a courses/ directory. The walk\n// includes the starting dir, so the workspace root resolves to itself.\nexport function findWorkspaceRoot(cwd: string): string | null {\n for (let dir = resolve(cwd); ; dir = dirname(dir)) {\n if (isDir(join(dir, 'courses'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) return null;\n }\n}\n\nfunction scanCourses(workspaceRoot: string): {\n courses: string[];\n malformed: string[];\n} {\n const coursesDir = join(workspaceRoot, 'courses');\n const courses: string[] = [];\n const malformed: string[] = [];\n try {\n for (const e of readdirSync(coursesDir, { withFileTypes: true })) {\n if (!e.isDirectory()) continue;\n const dir = join(coursesDir, e.name);\n if (isCourse(dir)) courses.push(e.name);\n else if (isDir(join(dir, 'pages'))) malformed.push(e.name);\n }\n } catch {\n return { courses, malformed };\n }\n return { courses: courses.sort(), malformed: malformed.sort() };\n}\n\nexport function listCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).courses;\n}\n\nexport function listMalformedCourses(workspaceRoot: string): string[] {\n return scanCourses(workspaceRoot).malformed;\n}\n\nconst NOT_A_WORKSPACE =\n 'Not inside a Tessera workspace — no `courses/` directory was found at or above the current directory.';\n\nfunction malformedHint(malformed: string[]): string {\n if (malformed.length === 0) return '';\n return (\n `\\nSkipped (missing course.config.js):\\n` +\n malformed.map((c) => ` courses/${c}`).join('\\n')\n );\n}\n\nfunction listHint(workspaceRoot: string): string {\n const { courses, malformed } = scanCourses(workspaceRoot);\n if (courses.length === 0) {\n return (\n '\\nNo courses found. Create one with `tessera new <name>`.' +\n malformedHint(malformed)\n );\n }\n return (\n `\\nAvailable courses:\\n${courses.map((c) => ` ${c}`).join('\\n')}` +\n '\\nName one (`tessera <command> <course>`) or cd into its folder.' +\n malformedHint(malformed)\n );\n}\n\n// A name argument always wins; otherwise the cwd must itself be a course. There\n// is deliberately no \"single course → use it implicitly\" rule, so a bare command\n// never changes meaning when a second course is added.\nexport function resolveCourse(cwd: string, name?: string): ResolveResult {\n const here = resolve(cwd);\n\n if (name) {\n const nameError = validateProjectName(name, 'the name');\n if (nameError) {\n return {\n ok: false,\n error: `Invalid course name \"${name}\" — ${nameError}.`,\n };\n }\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n const courseRoot = join(workspaceRoot, 'courses', name);\n if (!isCourse(courseRoot)) {\n return {\n ok: false,\n error: `Course \"${name}\" not found in courses/.${listHint(workspaceRoot)}`,\n };\n }\n return { ok: true, courseRoot, workspaceRoot };\n }\n\n if (isCourse(here)) {\n const workspaceRoot = findWorkspaceRoot(here) ?? dirname(dirname(here));\n return { ok: true, courseRoot: here, workspaceRoot };\n }\n\n const workspaceRoot = findWorkspaceRoot(here);\n if (!workspaceRoot) return { ok: false, error: NOT_A_WORKSPACE };\n return {\n ok: false,\n error: `No course specified.${listHint(workspaceRoot)}`,\n };\n}\n","import {\n copyFileSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n writeFileSync,\n} from 'node:fs';\nimport { join } from 'node:path';\n\n// npm's tarball packing strips/renames leading-dot files, so templates store\n// them prefixed and we restore the dot on copy. (create-vite convention.)\nconst RENAME: Record<string, string> = {\n _gitignore: '.gitignore',\n _gitkeep: '.gitkeep',\n};\n\n// Text files get token substitution; everything else is copied byte-for-byte.\nconst TEXT = /\\.(svelte|js|ts|json|css|md|html)$/;\n\nfunction applyTokens(s: string, tokens: Record<string, string>): string {\n return s.replace(/__([A-Z_]+)__/g, (m, key) =>\n key in tokens ? tokens[key] : m,\n );\n}\n\nexport function copyTemplate(\n srcDir: string,\n destDir: string,\n tokens: Record<string, string>,\n): void {\n mkdirSync(destDir, { recursive: true });\n for (const entry of readdirSync(srcDir, { withFileTypes: true })) {\n const src = join(srcDir, entry.name);\n const dest = join(destDir, RENAME[entry.name] ?? entry.name);\n if (entry.isDirectory()) {\n copyTemplate(src, dest, tokens);\n } else if (TEXT.test(entry.name)) {\n writeFileSync(dest, applyTokens(readFileSync(src, 'utf-8'), tokens));\n } else {\n copyFileSync(src, dest);\n }\n }\n}\n","import { existsSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { findWorkspaceRoot } from './course-root.js';\nimport { validateProjectName, toTitleCase } from './project-name.js';\nimport { copyTemplate } from './template-copy.js';\nimport { resolvePackageRoot } from './package-root.js';\n\n// `tessera new <name>` — stamp a new course into courses/<name> inside the\n// current workspace. No install step: the workspace already owns the deps.\nexport function runNew(name: string | undefined, cwd: string): number {\n if (name === '--help' || name === '-h') {\n console.log(\n 'Usage: tessera new <name>\\n\\n' +\n 'Scaffold a new course into courses/<name> inside the current workspace.',\n );\n return 0;\n }\n if (!name) {\n console.error('Usage: tessera new <name>');\n return 1;\n }\n\n const nameError = validateProjectName(name, 'Course name');\n if (nameError) {\n console.error(`[tessera new] ${nameError}`);\n return 1;\n }\n\n const workspaceRoot = findWorkspaceRoot(resolve(cwd));\n if (!workspaceRoot) {\n console.error(\n '[tessera new] Not inside a Tessera workspace — run this from a workspace (a directory with a `courses/` folder).',\n );\n return 1;\n }\n\n const courseDir = join(workspaceRoot, 'courses', name);\n if (existsSync(courseDir)) {\n console.error(`[tessera new] Course \"${name}\" already exists.`);\n return 1;\n }\n\n const templateDir = join(resolvePackageRoot(), 'templates', 'course');\n copyTemplate(templateDir, courseDir, {\n PROJECT_TITLE: toTitleCase(name),\n });\n\n const rel = relative(workspaceRoot, courseDir);\n console.log(`\\nCreated ${rel}.\\n\\nNext steps:\\n pnpm tessera dev ${name}\\n`);\n return 0;\n}\n","import { cpSync, existsSync } from 'node:fs';\nimport { basename, join, relative } from 'node:path';\nimport { resolveCourse } from './course-root.js';\nimport { validateProjectName } from './project-name.js';\n\nconst HELP =\n 'Usage: tessera duplicate <source> <new>\\n\\n' +\n 'Copy courses/<source>/ to courses/<new>/ within the current workspace.';\n\n// Generated/build artifacts that should never travel with a verbatim copy. The\n// a11y throwaway build and Vite's cache live under node_modules, so they're\n// already pruned by the node_modules skip; the rest are belt-and-suspenders.\nconst SKIP = new Set(['node_modules', 'dist', 'a11y-report.json', '.vite']);\n\nfunction skip(srcPath: string): boolean {\n const name = basename(srcPath);\n return SKIP.has(name) || name.startsWith('.tessera');\n}\n\n// `tessera duplicate <source> <new>` — copy an existing course verbatim within\n// the current workspace. Unlike `new`, there is no template stamping: the JS\n// config (including its title) is copied untouched.\nexport function runDuplicate(\n source: string | undefined,\n target: string | undefined,\n cwd: string,\n): number {\n if (\n source === '--help' ||\n source === '-h' ||\n target === '--help' ||\n target === '-h'\n ) {\n console.log(HELP);\n return 0;\n }\n if (!source || !target) {\n console.error('Usage: tessera duplicate <source> <new>');\n return 1;\n }\n\n const nameError = validateProjectName(target, 'Course name');\n if (nameError) {\n console.error(`[tessera duplicate] ${nameError}`);\n return 1;\n }\n\n const resolved = resolveCourse(cwd, source);\n if (!resolved.ok) {\n console.error(`[tessera duplicate] ${resolved.error}`);\n return 1;\n }\n const { courseRoot: srcDir, workspaceRoot } = resolved;\n\n const destDir = join(workspaceRoot, 'courses', target);\n if (existsSync(destDir)) {\n console.error(`[tessera duplicate] Course \"${target}\" already exists.`);\n return 1;\n }\n\n cpSync(srcDir, destDir, {\n recursive: true,\n filter: (src) => src === srcDir || !skip(src),\n });\n\n const rel = relative(workspaceRoot, destDir);\n const srcRel = relative(workspaceRoot, srcDir);\n console.log(\n `\\nCreated ${rel} (duplicated from ${srcRel}).\\n\\n` +\n `Remember to update the title in ${rel}/course.config.js.\\n\\n` +\n `Next steps:\\n pnpm tessera dev ${target}\\n`,\n );\n return 0;\n}\n","#!/usr/bin/env node\nimport { runValidate } from './validate-cli.js';\nimport { runA11y } from './a11y-cli.js';\nimport { runNew } from './new-cli.js';\nimport { runDuplicate } from './duplicate-cli.js';\nimport { resolveCourse } from './course-root.js';\n\nconst USAGE = `Usage: tessera <command> [course] [options]\n\nCommands:\n new <name> Scaffold a new course into courses/<name>\n duplicate <source> <new> Copy courses/<source> to courses/<new>\n dev [course] Start the Vite dev server\n export [course] Build and package the course for its LMS standard\n validate [course] Fast static structure checks\n a11y [course] Runtime accessibility audit (builds + drives Playwright)\n check [course] Run validate, then a11y\n\nRun a command from inside a course folder, or name the course explicitly.\n\na11y/check options:\n --threshold <minor|moderate|serious|critical> Failing impact (default: serious)\n --build Force a fresh build first`;\n\n// The course is a leading positional: `tessera <cmd> [course] [flags]`. Only the\n// first token can be the course, and only when it isn't a flag — otherwise a flag\n// value (e.g. the `serious` in `--threshold serious`) would be misread as a name.\nexport function splitCourseArg(rest: string[]): {\n course?: string;\n flags: string[];\n} {\n if (rest.length > 0 && !rest[0].startsWith('-')) {\n return { course: rest[0], flags: rest.slice(1) };\n }\n return { course: undefined, flags: rest };\n}\n\nexport async function main(\n argv: string[],\n cwd: string = process.cwd(),\n): Promise<number> {\n const [sub, ...rest] = argv;\n\n if (sub === 'new') return runNew(rest[0], cwd);\n if (sub === 'duplicate') return runDuplicate(rest[0], rest[1], cwd);\n\n switch (sub) {\n case 'dev':\n case 'export':\n case 'validate':\n case 'a11y':\n case 'check': {\n if (rest.includes('--help') || rest.includes('-h')) {\n console.log(USAGE);\n return 0;\n }\n const { course, flags } = splitCourseArg(rest);\n const resolved = resolveCourse(cwd, course);\n if (!resolved.ok) {\n console.error(`[tessera] ${resolved.error}`);\n return 1;\n }\n const { courseRoot, workspaceRoot } = resolved;\n\n switch (sub) {\n case 'dev': {\n const { runDev } = await import('./build-commands.js');\n return runDev(courseRoot, workspaceRoot);\n }\n case 'export': {\n const { runBuild } = await import('./build-commands.js');\n return runBuild(courseRoot, workspaceRoot);\n }\n case 'validate':\n return runValidate(courseRoot);\n case 'check': {\n const validateCode = runValidate(courseRoot);\n if (validateCode !== 0) return validateCode;\n return runA11y(courseRoot, workspaceRoot, flags);\n }\n case 'a11y':\n return runA11y(courseRoot, workspaceRoot, flags);\n }\n return 0;\n }\n case '--help':\n case '-h':\n console.log(USAGE);\n return 0;\n case undefined:\n console.error(`No command given.\\n\\n${USAGE}`);\n return 1;\n default:\n console.error(`Unknown command: ${sub}\\n\\n${USAGE}`);\n return 1;\n }\n}\n\n// import.meta.main is true only when this module is the program entry point,\n// and resolves symlinks itself (pnpm/npm bin shims) — Node >= 24.\nif (import.meta.main) {\n void main(process.argv.slice(2)).then((code) => process.exit(code));\n}\n"],"mappings":";;;;;AAEA,SAAgB,YAAY,aAA6B;CACvD,MAAM,EAAE,QAAQ,aAAa,gBAAgB,WAAW;CAExD,uBAAuB;EAAE;EAAQ;CAAS,CAAC;CAE3C,IAAI,OAAO,SAAS,GAAG;EACrB,MAAM,UACJ,0BAA0B,OAAO,OAAO,cACvC,SAAS,SAAS,IAAI,QAAQ,SAAS,OAAO,eAAe,MAC9D;EACF,QAAQ,MAAM,aAAa,QAAQ,QAAQ;EAC3C,OAAO;CACT;CAEA,IAAI,SAAS,SAAS,GACpB,QAAQ,IACN,oCAAoC,SAAS,OAAO,oBACtD;MAEA,QAAQ,IACN,+DACF;CAEF,QAAQ,IACN,iHACF;CACA,OAAO;AACT;;;AC3BA,MAAM,mBAAkC;CACtC;CACA;CACA;CACA;AACF;AAMA,SAAgB,cAAc,MAAgC;CAC5D,IAAI;CACJ,IAAI,UAAU;CAEd,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EACjB,IAAI,QAAQ,eAAe;GACzB,MAAM,QAAQ,KAAK,EAAE;GACrB,IAAI,CAAC,iBAAiB,SAAS,KAAK,GAClC,OAAO;IACL,IAAI;IACJ,OAAO,+BAA+B,iBAAiB,KAAK,IAAI;GAClE;GAEF,YAAY;EACd,OAAO,IAAI,QAAQ,WACjB,UAAU;OAEV,OAAO;GAAE,IAAI;GAAO,OAAO,qBAAqB;EAAM;CAE1D;CAEA,MAAM,OAAqB,EAAE,QAAQ;CACrC,IAAI,cAAc,KAAA,GAAW,KAAK,YAAY;CAC9C,OAAO;EAAE,IAAI;EAAM;CAAK;AAC1B;AAEA,eAAsB,QACpB,aACA,eACA,MACiB;CACjB,MAAM,SAAS,cAAc,IAAI;CACjC,IAAI,CAAC,OAAO,IAAI;EACd,QAAQ,MAAM,kBAAkB,OAAO,OAAO;EAC9C,OAAO;CACT;CACA,OAAO,SAAS,aAAa,eAAe,OAAO,IAAI;AACzD;;;AC7CA,SAAgB,oBACd,MACA,QAAQ,gBACO;CACf,IAAI,CAAC,MAAM,OAAO,GAAG,MAAM;CAC3B,IAAI,KAAK,SAAS,KAAK,OAAO,GAAG,MAAM;CACvC,IAAI,SAAS,KAAK,YAAY,GAAG,OAAO,GAAG,MAAM;CACjD,IAAI,CAAC,YAAY,KAAK,IAAI,GACxB,OAAO,GAAG,MAAM;CAElB,IAAI,CAAC,iBAAiB,KAAK,IAAI,GAC7B,OAAO,GAAG,MAAM;CAElB,OAAO;AACT;AAEA,SAAgB,YAAY,MAAsB;CAChD,OAAO,KACJ,MAAM,UAAU,EAChB,OAAO,OAAO,EACd,KAAK,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,EACjD,KAAK,GAAG;AACb;;;AChBA,SAAS,MAAM,MAAuB;CACpC,IAAI;EACF,OAAO,SAAS,IAAI,EAAE,YAAY;CACpC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,SAAS,KAAsB;CACtC,OAAO,WAAW,KAAK,KAAK,kBAAkB,CAAC;AACjD;AAIA,SAAgB,kBAAkB,KAA4B;CAC5D,KAAK,IAAI,MAAM,QAAQ,GAAG,IAAK,MAAM,QAAQ,GAAG,GAAG;EACjD,IAAI,MAAM,KAAK,KAAK,SAAS,CAAC,GAAG,OAAO;EAExC,IADe,QAAQ,GACd,MAAM,KAAK,OAAO;CAC7B;AACF;AAEA,SAAS,YAAY,eAGnB;CACA,MAAM,aAAa,KAAK,eAAe,SAAS;CAChD,MAAM,UAAoB,CAAC;CAC3B,MAAM,YAAsB,CAAC;CAC7B,IAAI;EACF,KAAK,MAAM,KAAK,YAAY,YAAY,EAAE,eAAe,KAAK,CAAC,GAAG;GAChE,IAAI,CAAC,EAAE,YAAY,GAAG;GACtB,MAAM,MAAM,KAAK,YAAY,EAAE,IAAI;GACnC,IAAI,SAAS,GAAG,GAAG,QAAQ,KAAK,EAAE,IAAI;QACjC,IAAI,MAAM,KAAK,KAAK,OAAO,CAAC,GAAG,UAAU,KAAK,EAAE,IAAI;EAC3D;CACF,QAAQ;EACN,OAAO;GAAE;GAAS;EAAU;CAC9B;CACA,OAAO;EAAE,SAAS,QAAQ,KAAK;EAAG,WAAW,UAAU,KAAK;CAAE;AAChE;AAUA,MAAM,kBACJ;AAEF,SAAS,cAAc,WAA6B;CAClD,IAAI,UAAU,WAAW,GAAG,OAAO;CACnC,OACE,4CACA,UAAU,KAAK,MAAM,aAAa,GAAG,EAAE,KAAK,IAAI;AAEpD;AAEA,SAAS,SAAS,eAA+B;CAC/C,MAAM,EAAE,SAAS,cAAc,YAAY,aAAa;CACxD,IAAI,QAAQ,WAAW,GACrB,OACE,8DACA,cAAc,SAAS;CAG3B,OACE,yBAAyB,QAAQ,KAAK,MAAM,KAAK,GAAG,EAAE,KAAK,IAAI,EAAA;oEAE/D,cAAc,SAAS;AAE3B;AAKA,SAAgB,cAAc,KAAa,MAA8B;CACvE,MAAM,OAAO,QAAQ,GAAG;CAExB,IAAI,MAAM;EACR,MAAM,YAAY,oBAAoB,MAAM,UAAU;EACtD,IAAI,WACF,OAAO;GACL,IAAI;GACJ,OAAO,wBAAwB,KAAK,MAAM,UAAU;EACtD;EAEF,MAAM,gBAAgB,kBAAkB,IAAI;EAC5C,IAAI,CAAC,eAAe,OAAO;GAAE,IAAI;GAAO,OAAO;EAAgB;EAC/D,MAAM,aAAa,KAAK,eAAe,WAAW,IAAI;EACtD,IAAI,CAAC,SAAS,UAAU,GACtB,OAAO;GACL,IAAI;GACJ,OAAO,WAAW,KAAK,0BAA0B,SAAS,aAAa;EACzE;EAEF,OAAO;GAAE,IAAI;GAAM;GAAY;EAAc;CAC/C;CAEA,IAAI,SAAS,IAAI,GAEf,OAAO;EAAE,IAAI;EAAM,YAAY;EAAM,eADf,kBAAkB,IAAI,KAAK,QAAQ,QAAQ,IAAI,CAAC;CACnB;CAGrD,MAAM,gBAAgB,kBAAkB,IAAI;CAC5C,IAAI,CAAC,eAAe,OAAO;EAAE,IAAI;EAAO,OAAO;CAAgB;CAC/D,OAAO;EACL,IAAI;EACJ,OAAO,uBAAuB,SAAS,aAAa;CACtD;AACF;;;AClHA,MAAM,SAAiC;CACrC,YAAY;CACZ,UAAU;AACZ;AAGA,MAAM,OAAO;AAEb,SAAS,YAAY,GAAW,QAAwC;CACtE,OAAO,EAAE,QAAQ,mBAAmB,GAAG,QACrC,OAAO,SAAS,OAAO,OAAO,CAChC;AACF;AAEA,SAAgB,aACd,QACA,SACA,QACM;CACN,UAAU,SAAS,EAAE,WAAW,KAAK,CAAC;CACtC,KAAK,MAAM,SAAS,YAAY,QAAQ,EAAE,eAAe,KAAK,CAAC,GAAG;EAChE,MAAM,MAAM,KAAK,QAAQ,MAAM,IAAI;EACnC,MAAM,OAAO,KAAK,SAAS,OAAO,MAAM,SAAS,MAAM,IAAI;EAC3D,IAAI,MAAM,YAAY,GACpB,aAAa,KAAK,MAAM,MAAM;OACzB,IAAI,KAAK,KAAK,MAAM,IAAI,GAC7B,cAAc,MAAM,YAAY,aAAa,KAAK,OAAO,GAAG,MAAM,CAAC;OAEnE,aAAa,KAAK,IAAI;CAE1B;AACF;;;ACjCA,SAAgB,OAAO,MAA0B,KAAqB;CACpE,IAAI,SAAS,YAAY,SAAS,MAAM;EACtC,QAAQ,IACN,sGAEF;EACA,OAAO;CACT;CACA,IAAI,CAAC,MAAM;EACT,QAAQ,MAAM,2BAA2B;EACzC,OAAO;CACT;CAEA,MAAM,YAAY,oBAAoB,MAAM,aAAa;CACzD,IAAI,WAAW;EACb,QAAQ,MAAM,iBAAiB,WAAW;EAC1C,OAAO;CACT;CAEA,MAAM,gBAAgB,kBAAkB,QAAQ,GAAG,CAAC;CACpD,IAAI,CAAC,eAAe;EAClB,QAAQ,MACN,kHACF;EACA,OAAO;CACT;CAEA,MAAM,YAAY,KAAK,eAAe,WAAW,IAAI;CACrD,IAAI,WAAW,SAAS,GAAG;EACzB,QAAQ,MAAM,yBAAyB,KAAK,kBAAkB;EAC9D,OAAO;CACT;CAGA,aADoB,KAAK,mBAAmB,GAAG,aAAa,QACrC,GAAG,WAAW,EACnC,eAAe,YAAY,IAAI,EACjC,CAAC;CAED,MAAM,MAAM,SAAS,eAAe,SAAS;CAC7C,QAAQ,IAAI,aAAa,IAAI,uCAAuC,KAAK,GAAG;CAC5E,OAAO;AACT;;;AC7CA,MAAM,OACJ;AAMF,MAAM,OAAO,IAAI,IAAI;CAAC;CAAgB;CAAQ;CAAoB;AAAO,CAAC;AAE1E,SAAS,KAAK,SAA0B;CACtC,MAAM,OAAO,SAAS,OAAO;CAC7B,OAAO,KAAK,IAAI,IAAI,KAAK,KAAK,WAAW,UAAU;AACrD;AAKA,SAAgB,aACd,QACA,QACA,KACQ;CACR,IACE,WAAW,YACX,WAAW,QACX,WAAW,YACX,WAAW,MACX;EACA,QAAQ,IAAI,IAAI;EAChB,OAAO;CACT;CACA,IAAI,CAAC,UAAU,CAAC,QAAQ;EACtB,QAAQ,MAAM,yCAAyC;EACvD,OAAO;CACT;CAEA,MAAM,YAAY,oBAAoB,QAAQ,aAAa;CAC3D,IAAI,WAAW;EACb,QAAQ,MAAM,uBAAuB,WAAW;EAChD,OAAO;CACT;CAEA,MAAM,WAAW,cAAc,KAAK,MAAM;CAC1C,IAAI,CAAC,SAAS,IAAI;EAChB,QAAQ,MAAM,uBAAuB,SAAS,OAAO;EACrD,OAAO;CACT;CACA,MAAM,EAAE,YAAY,QAAQ,kBAAkB;CAE9C,MAAM,UAAU,KAAK,eAAe,WAAW,MAAM;CACrD,IAAI,WAAW,OAAO,GAAG;EACvB,QAAQ,MAAM,+BAA+B,OAAO,kBAAkB;EACtE,OAAO;CACT;CAEA,OAAO,QAAQ,SAAS;EACtB,WAAW;EACX,SAAS,QAAQ,QAAQ,UAAU,CAAC,KAAK,GAAG;CAC9C,CAAC;CAED,MAAM,MAAM,SAAS,eAAe,OAAO;CAC3C,MAAM,SAAS,SAAS,eAAe,MAAM;CAC7C,QAAQ,IACN,aAAa,IAAI,oBAAoB,OAAO,wCACP,IAAI,wDACJ,OAAO,GAC9C;CACA,OAAO;AACT;;;AClEA,MAAM,QAAQ;;;;;;;;;;;;;;;;AAoBd,SAAgB,eAAe,MAG7B;CACA,IAAI,KAAK,SAAS,KAAK,CAAC,KAAK,GAAG,WAAW,GAAG,GAC5C,OAAO;EAAE,QAAQ,KAAK;EAAI,OAAO,KAAK,MAAM,CAAC;CAAE;CAEjD,OAAO;EAAE,QAAQ,KAAA;EAAW,OAAO;CAAK;AAC1C;AAEA,eAAsB,KACpB,MACA,MAAc,QAAQ,IAAI,GACT;CACjB,MAAM,CAAC,KAAK,GAAG,QAAQ;CAEvB,IAAI,QAAQ,OAAO,OAAO,OAAO,KAAK,IAAI,GAAG;CAC7C,IAAI,QAAQ,aAAa,OAAO,aAAa,KAAK,IAAI,KAAK,IAAI,GAAG;CAElE,QAAQ,KAAR;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK,SAAS;GACZ,IAAI,KAAK,SAAS,QAAQ,KAAK,KAAK,SAAS,IAAI,GAAG;IAClD,QAAQ,IAAI,KAAK;IACjB,OAAO;GACT;GACA,MAAM,EAAE,QAAQ,UAAU,eAAe,IAAI;GAC7C,MAAM,WAAW,cAAc,KAAK,MAAM;GAC1C,IAAI,CAAC,SAAS,IAAI;IAChB,QAAQ,MAAM,aAAa,SAAS,OAAO;IAC3C,OAAO;GACT;GACA,MAAM,EAAE,YAAY,kBAAkB;GAEtC,QAAQ,KAAR;IACE,KAAK,OAAO;KACV,MAAM,EAAE,WAAW,MAAM,OAAO;KAChC,OAAO,OAAO,YAAY,aAAa;IACzC;IACA,KAAK,UAAU;KACb,MAAM,EAAE,aAAa,MAAM,OAAO;KAClC,OAAO,SAAS,YAAY,aAAa;IAC3C;IACA,KAAK,YACH,OAAO,YAAY,UAAU;IAC/B,KAAK,SAAS;KACZ,MAAM,eAAe,YAAY,UAAU;KAC3C,IAAI,iBAAiB,GAAG,OAAO;KAC/B,OAAO,QAAQ,YAAY,eAAe,KAAK;IACjD;IACA,KAAK,QACH,OAAO,QAAQ,YAAY,eAAe,KAAK;GACnD;GACA,OAAO;EACT;EACA,KAAK;EACL,KAAK;GACH,QAAQ,IAAI,KAAK;GACjB,OAAO;EACT,KAAK,KAAA;GACH,QAAQ,MAAM,wBAAwB,OAAO;GAC7C,OAAO;EACT;GACE,QAAQ,MAAM,oBAAoB,IAAI,MAAM,OAAO;GACnD,OAAO;CACX;AACF;AAIA,IAAI,OAAO,KAAK,MACd,KAAU,QAAQ,KAAK,MAAM,CAAC,CAAC,EAAE,MAAM,SAAS,QAAQ,KAAK,IAAI,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/plugin/a11y/audit.ts","../../src/plugin/index.ts"],"mappings":";;;UAKiB,YAAA;;EAEf,SAAA,GAAY,WAAW;EAFI;EAI3B,OAAA;AAAA;AAAA,KAGU,WAAA;;;AAAW;AAiHvB;;iBAAsB,QAAA,CACpB,WAAA,UACA,aAAA,UACA,OAAA,GAAS,YAAA,GACR,OAAO;;;iBCbM,aAAA,KAAa,MAAA,QAAA,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../../src/plugin/a11y/audit.ts","../../src/plugin/index.ts"],"mappings":";;;UAOiB,YAAA;EAAA;EAEf,SAAA,GAAY,WAAW;;EAEvB,OAAA;AAAA;AAAA,KAGU,WAAA;;;;;;iBAoUU,QAAA,CACpB,WAAA,UACA,aAAA,UACA,OAAA,GAAS,YAAA,GACR,OAAO;;;iBClOM,aAAA,KAAa,MAAA,QAAA,MAAA"}
@@ -1,3 +1,3 @@
1
- import { n as runAudit } from "../audit-BA5o0ick.js";
2
- import { t as tesseraPlugin } from "../plugin-W_rk3Pit.js";
1
+ import { n as runAudit } from "../audit-B9VHgVjk.js";
2
+ import { t as tesseraPlugin } from "../plugin--8H9xQIl.js";
3
3
  export { runAudit, tesseraPlugin };
@@ -1,4 +1,4 @@
1
- import { a as isPlausibleLanguageTag, c as validateProject, i as isIgnored, l as generateManifest, o as normalizeA11y, r as resolvePackageRoot, s as reportValidationIssues, t as AUDIT_ENV_FLAG, u as readCourseConfig } from "./audit-BA5o0ick.js";
1
+ import { a as isPlausibleLanguageTag, c as validateProject, i as isIgnored, l as generateManifest, o as normalizeA11y, r as resolvePackageRoot, s as reportValidationIssues, t as AUDIT_ENV_FLAG, u as readCourseConfig } from "./audit-B9VHgVjk.js";
2
2
  import { svelte } from "@sveltejs/vite-plugin-svelte";
3
3
  import { isAbsolute, relative, resolve } from "node:path";
4
4
  import { cpSync, createWriteStream, existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -728,4 +728,4 @@ function tesseraFirstPagePreloadPlugin(manifestRef) {
728
728
  //#endregion
729
729
  export { tesseraPlugin as t };
730
730
 
731
- //# sourceMappingURL=plugin-W_rk3Pit.js.map
731
+ //# sourceMappingURL=plugin--8H9xQIl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-W_rk3Pit.js","names":[],"sources":["../src/runtime/slugify.ts","../src/plugin/export.ts","../src/plugin/override-plugin.ts","../src/plugin/layout.ts","../src/plugin/quiz.ts","../src/plugin/index.ts"],"sourcesContent":["/**\n * Slugify a string for use as a URL-safe / filename-safe identifier.\n * \"My Course Title\" → \"my-course-title\"\n *\n * Shared by the runtime (`WebAdapter` localStorage key) and the build-time\n * exporter (`runExport` zip filename). Both want identical, deterministic\n * output so a course's storage key matches its package name.\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n} from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { ZipArchive } from 'archiver';\nimport { slugify } from '../runtime/slugify.js';\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Recursively collect all file paths relative to a directory.\n */\nfunction collectFiles(dir: string, base: string = ''): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir)) {\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 /** Whitespace-separated namespace+XSD pairs for xsi:schemaLocation. */\n schemaLocation: string;\n}\n\nconst SCORM_DIALECTS: Record<'1.2' | '2004', ScormManifestDialect> = {\n '1.2': {\n rootNs: 'http://www.imsproject.org/xsd/imscp_rootv1p1p2',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_rootv1p2',\n schemaversion: '1.2',\n scormTypeAttr: 'scormtype',\n schemaLocation:\n 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd ' +\n 'http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd',\n },\n '2004': {\n rootNs: 'http://www.imsglobal.org/xsd/imscp_v1p1',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_v1p3',\n schemaversion: '2004 4th Edition',\n scormTypeAttr: 'scormType',\n schemaLocation:\n 'http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd',\n },\n};\n\nexport function generateScormManifest(\n version: '1.2' | '2004',\n config: ExportConfig,\n distDir: string,\n): string {\n const dialect = SCORM_DIALECTS[version];\n const title = escapeXml(config.title || '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 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"${dialect.schemaLocation}\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>${dialect.schemaversion}</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:${dialect.scormTypeAttr}=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function 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 // cmi5 §10.2.4 caps masteryScore at 4 decimals; avoid float drift like 0.7000000000000001.\n const masteryScore = Number(\n ((config.scoring?.passingScore ?? 70) / 100).toFixed(4),\n );\n // cmi5 §13.1.4 — `moveOn` decides which verb(s) the LMS treats as\n // satisfying the AU. For graded courses (completion gated on a quiz)\n // a learner who completes without passing should NOT receive credit, so\n // the LMS needs both a Completed AND a Passed before satisfaction.\n // Percentage-mode courses don't surface pass/fail, so completion alone\n // is the right signal.\n const moveOn =\n config.completion?.mode === 'quiz' ? 'CompletedAndPassed' : 'Completed';\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<courseStructure xmlns=\"https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd\">\n <course id=\"${courseId}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </course>\n <au id=\"${auId}\" launchMethod=\"AnyWindow\" moveOn=\"${moveOn}\" masteryScore=\"${masteryScore}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n <url>index.html</url>\n </au>\n</courseStructure>`;\n}\n\n// ---------- ZIP Packaging ----------\n\nexport async function createZip(\n distDir: string,\n outputPath: string,\n): Promise<number> {\n return new Promise((res, reject) => {\n const output = createWriteStream(outputPath);\n const archive = new ZipArchive({ zlib: { level: 9 } });\n\n output.on('close', () => {\n res(archive.pointer());\n });\n output.on('error', reject);\n archive.on('error', reject);\n\n archive.pipe(output);\n archive.directory(distDir, false);\n void archive.finalize();\n });\n}\n\n// ---------- Main Export ----------\n\n/**\n * Run the export process after Vite build completes.\n * Writes manifest XML into dist/, then packages into ZIP if needed.\n */\n/** Remove any previously built zips for this package to prevent accumulation. */\nfunction cleanOldZips(projectRoot: string, slug: string): void {\n try {\n for (const f of readdirSync(projectRoot)) {\n if (f.startsWith(`${slug}-`) && f.endsWith('.zip')) {\n try {\n unlinkSync(resolve(projectRoot, f));\n } catch {}\n }\n }\n } catch {}\n}\n\n/** Packaged (zipped) export targets: which manifest file to write and how. */\nconst PACKAGED_EXPORTS: Record<\n 'scorm12' | 'scorm2004' | 'cmi5',\n {\n manifestFile: string;\n label: string;\n generate: (config: ExportConfig, distDir: string) => string;\n }\n> = {\n scorm12: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 1.2',\n generate: generateSCORM12Manifest,\n },\n scorm2004: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 2004',\n generate: generateSCORM2004Manifest,\n },\n cmi5: {\n manifestFile: 'cmi5.xml',\n label: 'CMI5',\n generate: (config) => generateCMI5Xml(config),\n },\n};\n\nexport async function runExport(\n projectRoot: string,\n config: ExportConfig,\n): Promise<void> {\n const distDir = resolve(projectRoot, 'dist');\n const standard = config.export?.standard || 'web';\n const slug = slugify(config.title || 'tessera-course') || 'tessera-course';\n const version = config.version || '1.0.0';\n const zipName = `${slug}-${version}.zip`;\n const zipPath = resolve(projectRoot, zipName);\n\n if (standard === 'web') {\n const files = collectFiles(distDir);\n let totalSize = 0;\n for (const f of files) totalSize += statSync(resolve(distDir, f)).size;\n console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);\n return;\n }\n\n const spec = PACKAGED_EXPORTS[standard as keyof typeof PACKAGED_EXPORTS];\n if (!spec) return; // unknown standard — the validator rejects these upstream\n\n writeFileSync(\n resolve(distDir, spec.manifestFile),\n spec.generate(config, distDir),\n 'utf-8',\n );\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(`✓ ${spec.label} export: ${zipName} (${formatSize(zipSize)})`);\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { normalizePath } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport interface OverridePluginOptions {\n name: string;\n virtualId: string;\n projectFile: string;\n /** Built-in re-exported when the project file is absent; null export otherwise. */\n builtinFile?: string;\n}\n\n/**\n * A virtual module that resolves to a project-root override file when present,\n * and to the built-in (or a null export) otherwise. Shared by the layout and\n * quiz plugins — they differ only in the virtual id, file name, and built-in.\n */\nexport function createOverridePlugin({\n name,\n virtualId,\n projectFile,\n builtinFile,\n}: OverridePluginOptions): Plugin {\n const resolvedId = '\\0' + virtualId;\n const fallback = builtinFile\n ? `export { default } from '${normalizePath(builtinFile)}';`\n : 'export default null;';\n let filePath: string;\n\n return {\n name,\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n filePath = resolve(config.root, projectFile);\n },\n\n resolveId(id) {\n if (id === virtualId) return resolvedId;\n return null;\n },\n\n load(id) {\n if (id !== resolvedId) return null;\n if (existsSync(filePath)) {\n // Only watch when it exists — addWatchFile on a missing path makes\n // Vite's importAnalysis try to resolve it as a real import.\n this.addWatchFile(filePath);\n return `export { default } from '${normalizePath(filePath)}';`;\n }\n return fallback;\n },\n\n configureServer(server: ViteDevServer) {\n // Only add/unlink flips load()'s output between the override and the\n // fallback; a `change` leaves it identical and Svelte's own HMR handles\n // the underlying file.\n server.watcher.on('all', (event, changed) => {\n if (changed !== filePath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(resolvedId);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin } from 'vite';\nimport { createOverridePlugin } from './override-plugin.js';\n\nexport function tesseraLayoutPlugin(): Plugin {\n return createOverridePlugin({\n name: 'tessera:layout',\n virtualId: 'virtual:tessera-layout',\n projectFile: 'layout.svelte',\n });\n}\n","import type { Plugin } from 'vite';\nimport { resolve } from 'node:path';\nimport { createOverridePlugin } from './override-plugin.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nexport function tesseraQuizPlugin(): Plugin {\n const builtinQuiz = resolve(\n resolvePackageRoot(),\n 'src',\n 'components',\n 'Quiz.svelte',\n );\n return createOverridePlugin({\n name: 'tessera:quiz',\n virtualId: 'virtual:tessera-quiz',\n projectFile: 'quiz.svelte',\n builtinFile: builtinQuiz,\n });\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { svelte } from '@sveltejs/vite-plugin-svelte';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n cpSync,\n mkdirSync,\n} from 'node:fs';\nimport { generateManifest, readCourseConfig } from './manifest.js';\nimport type { Manifest } from './manifest.js';\nimport type { CourseConfig } from '../runtime/types.js';\nimport {\n DEFAULT_PASSING_SCORE,\n DEFAULT_PERCENTAGE_THRESHOLD,\n} from '../runtime/defaults.js';\nimport {\n validateProject,\n reportValidationIssues,\n normalizeA11y,\n isPlausibleLanguageTag,\n isIgnored,\n type A11ySettings,\n} from './validation.js';\nimport { runExport } from './export.js';\nimport { tesseraLayoutPlugin } from './layout.js';\nimport { tesseraQuizPlugin } from './quiz.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nimport { AUDIT_ENV_FLAG } from './a11y/audit.js';\n\nexport { runAudit } from './a11y/audit.js';\nexport type { AuditOptions, ImpactLevel } from './a11y/audit.js';\n\nfunction isAuditBuild(): boolean {\n return process.env[AUDIT_ENV_FLAG] === '1';\n}\n\n// Resolve the runtime directory where App.svelte lives\nfunction resolveRuntimeDir(): string {\n return resolve(resolvePackageRoot(), 'src', 'runtime');\n}\n\n// Resolve the framework styles directory\nfunction resolveStylesDir(): string {\n return resolve(resolvePackageRoot(), 'styles');\n}\n\n// Tier-1a state shared between the svelte() onwarn handler and the sibling\n// gate plugin. onwarn fires during transform (after the Tier-1b buildStart\n// gate), so a11y warnings are collected here and flushed/gated at buildEnd.\ninterface A11yCompilerState {\n warnings: string[];\n projectRoot: string;\n isBuild: boolean;\n settings: A11ySettings;\n}\n\n// Svelte's onwarn filename is relative to the vite root (e.g. `pages/x.svelte`)\n// in build and may be absolute or a virtual id elsewhere. Return the\n// project-relative path for a real author file, or null to skip framework /\n// node_modules / virtual modules — Tier 0 owns the framework's own warnings.\nfunction projectFileRel(\n filename: string | undefined,\n projectRoot: string,\n): string | null {\n if (!filename || !projectRoot) return null;\n if (\n filename.startsWith('\\0') ||\n filename.includes('virtual:') ||\n filename.includes('node_modules')\n ) {\n return null;\n }\n const abs = isAbsolute(filename) ? filename : resolve(projectRoot, filename);\n const rel = relative(projectRoot, abs);\n if (rel.startsWith('..') || isAbsolute(rel) || rel.includes('node_modules')) {\n return null;\n }\n return rel;\n}\n\ntype VirtualLoadCtx = { projectRoot: string; isBuild: boolean };\n\nfunction virtualModule(\n name: string,\n virtualId: string,\n load: (\n this: import('vite').Rollup.PluginContext,\n ctx: VirtualLoadCtx,\n ) => string | null,\n): Plugin {\n const resolvedId = '\\0' + virtualId;\n let projectRoot = '';\n let isBuild = false;\n return {\n name,\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n resolveId(id) {\n return id === virtualId ? resolvedId : null;\n },\n load(id) {\n return id === resolvedId\n ? load.call(this, { projectRoot, isBuild })\n : null;\n },\n };\n}\n\nexport function tesseraPlugin() {\n const manifestRef: { current: Manifest | null; root: string } = {\n current: null,\n root: '',\n };\n const a11y: A11yCompilerState = {\n warnings: [],\n projectRoot: '',\n isBuild: false,\n settings: normalizeA11y(undefined),\n };\n return [\n svelte({\n compilerOptions: { css: 'external' },\n onwarn(warning, defaultHandler) {\n if (warning.code?.startsWith('a11y')) {\n const rel = projectFileRel(warning.filename, a11y.projectRoot);\n if (rel !== null) {\n const msg = `[${warning.code}] ${rel}: ${warning.message}`;\n if (a11y.isBuild) {\n a11y.warnings.push(msg);\n } else if (!a11y.settings.ignore.includes(warning.code)) {\n reportValidationIssues({ errors: [], warnings: [msg] });\n }\n }\n return; // suppress the raw Vite print; we re-emit via the reporter\n }\n defaultHandler?.(warning);\n },\n }),\n tesseraA11yCompilerPlugin(a11y),\n tesseraValidationPlugin(),\n tesseraEntryPlugin(),\n tesseraConfigDefaultsPlugin(),\n tesseraConfigPlugin(),\n tesseraPagesPlugin(),\n tesseraManifestPlugin(manifestRef),\n tesseraLayoutPlugin(),\n tesseraQuizPlugin(),\n tesseraAdapterPlugin(),\n tesseraXAPISetupPlugin(),\n tesseraFirstPagePreloadPlugin(manifestRef),\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 outDir: string;\n let isBuild = false;\n\n return {\n name: 'tessera:entry',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n outDir = resolve(config.root, config.build.outDir);\n isBuild = config.command === 'build';\n },\n\n // For build mode: write index.html so Rollup can find it\n buildStart() {\n if (isBuild) {\n writeFileSync(\n resolve(projectRoot, 'index.html'),\n generateIndexHtml(readLanguage(projectRoot)),\n 'utf-8',\n );\n }\n },\n\n // For build mode: clean up temporary index.html and copy assets\n closeBundle() {\n if (isBuild) {\n const htmlPath = resolve(projectRoot, 'index.html');\n if (existsSync(htmlPath)) {\n try {\n unlinkSync(htmlPath);\n } catch {}\n }\n\n // Copy assets/ into the build's assets/ so $assets/ references resolve\n const assetsDir = resolve(projectRoot, 'assets');\n const distAssetsDir = resolve(outDir, 'assets');\n if (existsSync(assetsDir)) {\n mkdirSync(distAssetsDir, { recursive: true });\n cpSync(assetsDir, distAssetsDir, { recursive: true });\n }\n }\n },\n\n // Serve index.html for the dev server\n configureServer(server: ViteDevServer) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n if (req.url === '/' || req.url === '/index.html') {\n const html = generateIndexHtml(readLanguage(projectRoot));\n const transformed = await server.transformIndexHtml(req.url, html);\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(transformed);\n return;\n }\n next();\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;\n if (id === VIRTUAL_MAIN_ID || id === 'virtual:tessera-main')\n return RESOLVED_MAIN_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_ENTRY_ID || id === RESOLVED_MAIN_ID) {\n return generateEntryScript(appSveltePath, stylesDir, projectRoot);\n }\n return null;\n },\n };\n}\n\n// 'en' fallback applied here: the config default-merge runs later than buildStart.\n// Only a validated BCP-47 tag is interpolated into <html lang>, so a malformed\n// value (caught separately as a warning) can't ship a broken attribute.\nfunction readLanguage(projectRoot: string): string {\n const read = readCourseConfig(projectRoot);\n const lang = read.ok ? read.config.language : undefined;\n return isPlausibleLanguageTag(lang) ? lang : 'en';\n}\n\nfunction generateIndexHtml(lang: string): string {\n return `<!DOCTYPE html>\n<html lang=\"${lang}\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tessera Course</title>\n</head>\n<body>\n <div id=\"tessera-root\"></div>\n <script type=\"module\" src=\"/virtual:tessera-main\"></script>\n</body>\n</html>`;\n}\n\nfunction generateEntryScript(\n appSveltePath: string,\n frameworkStylesDir: string,\n projectRoot: string,\n): string {\n const normalizedPath = appSveltePath.replace(/\\\\/g, '/');\n\n // Framework CSS imports (theme → base → layout)\n const frameworkCssOrder = ['theme.css', 'base.css', 'layout.css'];\n const frameworkImports = frameworkCssOrder\n .map((file) => resolve(frameworkStylesDir, file).replace(/\\\\/g, '/'))\n .filter((path) => existsSync(path))\n .map((path) => `import '${path}';`)\n .join('\\n');\n\n // User CSS imports from project's styles/ directory\n const userStylesDir = resolve(projectRoot, 'styles');\n let userImports = '';\n if (existsSync(userStylesDir)) {\n const userCssFiles = readdirSync(userStylesDir)\n .filter((f) => f.endsWith('.css'))\n .sort();\n userImports = userCssFiles\n .map((f) => resolve(userStylesDir, f).replace(/\\\\/g, '/'))\n .map((path) => `import '${path}';`)\n .join('\\n');\n }\n\n return `// Framework styles\n${frameworkImports}\n// User styles\n${userImports}\n\nimport { mount } from 'svelte';\nimport App from '${normalizedPath}';\n\nmount(App, {\n target: document.getElementById('tessera-root'),\n});\n`;\n}\n\n// ---------- Config Plugin ----------\n\nconst VIRTUAL_CONFIG_ID = 'virtual:tessera-config';\n\nfunction completionDefaults(mode: string | undefined): {\n completion: Record<string, unknown>;\n passingScore: number;\n} {\n if (mode === 'manual') {\n return { completion: { mode: 'manual' }, passingScore: 0 };\n }\n return {\n completion: {\n mode: 'percentage',\n percentageThreshold: DEFAULT_PERCENTAGE_THRESHOLD,\n },\n passingScore: DEFAULT_PASSING_SCORE,\n };\n}\n\nfunction tesseraConfigDefaultsPlugin(): Plugin {\n return {\n name: 'tessera:config-defaults',\n enforce: 'pre',\n config(config) {\n const root = config.root || process.cwd();\n return {\n base: './',\n build: { assetsDir: 'tessera' },\n resolve: { alias: { $assets: resolve(root, 'assets') } },\n // tessera-learn ships .ts/.svelte.ts source; Vite's dep optimizer\n // doesn't run vite-plugin-svelte's preprocessor, so skip pre-bundling.\n optimizeDeps: { exclude: ['tessera-learn'] },\n };\n },\n };\n}\n\nfunction tesseraConfigPlugin(): Plugin {\n return virtualModule(\n 'tessera:config',\n VIRTUAL_CONFIG_ID,\n function ({ projectRoot }) {\n const configPath = resolve(projectRoot, 'course.config.js');\n if (existsSync(configPath)) this.addWatchFile(configPath);\n const read = readCourseConfig(projectRoot);\n const userConfig: Partial<CourseConfig> = read.ok ? read.config : {};\n const { completion, passingScore } = completionDefaults(\n userConfig.completion?.mode,\n );\n const merged = {\n title: userConfig.title || 'Untitled Course',\n ...userConfig,\n navigation: { mode: 'free', ...userConfig.navigation },\n completion: { ...completion, ...userConfig.completion },\n scoring: { passingScore, ...userConfig.scoring },\n export: { standard: 'web', ...userConfig.export },\n };\n return `export default ${JSON.stringify(merged)};`;\n },\n );\n}\n\n// ---------- Manifest Watch Helpers ----------\n\n/** Register all _meta.js and .svelte files under pagesDir as watch files for build mode. */\nfunction addWatchFiles(\n ctx: { addWatchFile(id: string): void },\n dir: string,\n): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n const full = resolve(dir, entry);\n if (statSync(full).isDirectory()) {\n addWatchFiles(ctx, full);\n } else if (entry.endsWith('.svelte') || entry === '_meta.js') {\n ctx.addWatchFile(full);\n }\n }\n}\n\n// ---------- Pages Plugin ----------\n\nconst VIRTUAL_PAGES_ID = 'virtual:tessera-pages';\n\n/**\n * Provides a virtual module that exports an import.meta.glob map for all .svelte\n * pages. This runs in the user's project context so the glob resolves against their\n * pages/ directory, and Vite can statically analyze it for code splitting.\n */\nfunction tesseraPagesPlugin(): Plugin {\n return virtualModule('tessera:pages', VIRTUAL_PAGES_ID, () => {\n return `export default import.meta.glob('/pages/**/*.svelte');`;\n });\n}\n\n// ---------- Validation Plugin ----------\n\nfunction tesseraValidationPlugin(): 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\n// Tier 1a: flush + gate the Svelte compiler's a11y warnings at buildEnd, after\n// every module is transformed. svelte() accepts `onwarn` but not arbitrary\n// Rollup hooks, so the gate lives here and shares the onwarn closure.\nfunction tesseraA11yCompilerPlugin(a11y: A11yCompilerState): Plugin {\n return {\n name: 'tessera:a11y-compiler',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n a11y.projectRoot = config.root;\n a11y.isBuild = config.command === 'build';\n const read = readCourseConfig(config.root);\n a11y.settings = normalizeA11y(read.ok ? read.config.a11y : undefined);\n },\n\n buildEnd() {\n if (!a11y.isBuild || a11y.warnings.length === 0) return;\n const ignored = new Set(a11y.settings.ignore);\n const warnings = a11y.warnings.filter((msg) => !isIgnored(msg, ignored));\n a11y.warnings = [];\n if (warnings.length === 0) return;\n if (a11y.settings.level === 'error') {\n reportValidationIssues({ errors: warnings, warnings: [] });\n throw new Error(\n `Tessera: ${warnings.length} a11y issue(s) with a11y.level: 'error'. Fix the errors above to continue.`,\n );\n }\n reportValidationIssues({ errors: [], warnings });\n },\n };\n}\n\nfunction runValidation(projectRoot: string): void {\n const result = validateProject(projectRoot);\n reportValidationIssues(result);\n if (result.errors.length > 0) {\n throw new Error(\n `Tessera validation failed with ${result.errors.length} error(s). Fix the errors above to continue.`,\n );\n }\n}\n\n// ---------- Export Plugin ----------\n\nfunction tesseraExportPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:export',\n enforce: 'post',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n async closeBundle() {\n if (!isBuild) return;\n if (isAuditBuild()) return;\n\n const read = readCourseConfig(projectRoot);\n if (!read.ok) {\n // Validation already required a parseable course.config.js — getting\n // here means it vanished or broke mid-build. Surface that loudly\n // rather than shipping a bundle with no LMS export silently.\n if (read.reason === 'missing') {\n throw new Error(\n '[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.',\n );\n }\n if (read.reason === 'no-export') {\n throw new Error(\n '[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.',\n );\n }\n throw new Error(\n `[tessera:export] course.config.js: failed to parse export-default object literal — ${(read.error as Error).message}`,\n );\n }\n\n await runExport(\n projectRoot,\n read.config as Parameters<typeof runExport>[1],\n );\n },\n };\n}\n\n// ---------- Manifest Plugin ----------\n\nconst VIRTUAL_MANIFEST_ID = 'virtual:tessera-manifest';\nconst RESOLVED_MANIFEST_ID = '\\0' + VIRTUAL_MANIFEST_ID;\n\nfunction tesseraManifestPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n let projectRoot: string;\n let pagesDir: string;\n\n function buildManifest(): Manifest {\n const m = generateManifest(pagesDir);\n manifestRef.current = m;\n return m;\n }\n\n return {\n name: 'tessera:manifest',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n pagesDir = resolve(projectRoot, 'pages');\n manifestRef.root = projectRoot;\n },\n\n configureServer(devServer: ViteDevServer) {\n // Watch the pages directory for changes\n devServer.watcher.on('all', (event, filePath) => {\n if (!filePath.startsWith(pagesDir)) return;\n\n // Rebuild manifest on relevant file changes\n const isRelevant =\n filePath.endsWith('.svelte') ||\n filePath.endsWith('_meta.js') ||\n event === 'addDir' ||\n event === 'unlinkDir';\n\n if (isRelevant) {\n manifestRef.current = null; // invalidate cache\n\n // Invalidate the virtual module to trigger HMR\n const mod = devServer.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);\n if (mod) {\n devServer.moduleGraph.invalidateModule(mod);\n devServer.ws.send({ type: 'full-reload' });\n }\n\n console.log(\n `[tessera] Manifest rebuilt (${event}: ${filePath.replace(projectRoot, '')})`,\n );\n }\n });\n },\n\n buildStart() {\n buildManifest();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_MANIFEST_ID) {\n if (!manifestRef.current) {\n buildManifest();\n }\n\n // Register watch files so Vite's built-in watcher (used in build --watch)\n // knows to re-trigger when pages/ content changes.\n addWatchFiles(this, pagesDir);\n\n // Encode as base64 to prevent Vite's import analysis from\n // scanning .svelte importPath strings as module imports.\n // Replace Infinity with 1e9 since JSON.stringify drops it.\n const json = JSON.stringify(manifestRef.current, (_key, value) =>\n value === Infinity ? 1e9 : value,\n );\n const b64 = Buffer.from(json).toString('base64');\n return `export default JSON.parse(atob(\"${b64}\"));`;\n }\n return null;\n },\n };\n}\n\nconst VIRTUAL_ADAPTER_ID = 'virtual:tessera-adapter';\n\nfunction tesseraAdapterPlugin(): Plugin {\n return virtualModule(\n 'tessera:adapter',\n VIRTUAL_ADAPTER_ID,\n ({ projectRoot, isBuild }) => {\n // In dev, defer to the runtime selector so its WebAdapter fallback\n // for unreachable LMS APIs keeps working.\n if (!isBuild) {\n return `export { createAdapter } from 'tessera-learn/runtime/adapters/index.js';`;\n }\n\n let standard = 'web';\n const read = readCourseConfig(projectRoot);\n if (read.ok && typeof read.config.export?.standard === 'string') {\n standard = read.config.export.standard;\n }\n\n // The audit renders headless with no LMS in the frame chain; the SCORM/\n // cmi5 adapters throw when their API is absent, so render with WebAdapter.\n if (isAuditBuild()) standard = 'web';\n\n switch (standard) {\n case 'scorm12':\n return `\nimport { SCORM12Adapter } from 'tessera-learn/runtime/adapters/scorm12.js';\nimport { findSCORM12API } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n const api = findSCORM12API();\n if (!api) throw new LMSAdapterError('scorm12', 'Tessera: SCORM 1.2 API not found in window.parent/opener chain. Course must be launched from a SCORM 1.2 LMS.');\n return new SCORM12Adapter(api);\n}\n`;\n case 'scorm2004':\n return `\nimport { SCORM2004Adapter } from 'tessera-learn/runtime/adapters/scorm2004.js';\nimport { findSCORM2004API } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n const api = findSCORM2004API();\n if (!api) throw new LMSAdapterError('scorm2004', 'Tessera: SCORM 2004 API not found in window.parent/opener chain. Course must be launched from a SCORM 2004 LMS.');\n return new SCORM2004Adapter(api);\n}\n`;\n case 'cmi5':\n return `\nimport { CMI5Adapter } from 'tessera-learn/runtime/adapters/cmi5.js';\nimport { hasCMI5LaunchParams } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n if (!hasCMI5LaunchParams()) throw new LMSAdapterError('cmi5', 'Tessera: cmi5 launch parameters not present on URL. Course must be launched from a cmi5-compliant LMS.');\n return new CMI5Adapter();\n}\n`;\n default:\n return `\nimport { WebAdapter } from 'tessera-learn/runtime/adapters/web.js';\nexport function createAdapter(config) {\n return new WebAdapter(config);\n}\n`;\n }\n },\n );\n}\n\nconst VIRTUAL_XAPI_SETUP_ID = 'virtual:tessera-xapi-setup';\n\nfunction tesseraXAPISetupPlugin(): Plugin {\n return virtualModule(\n 'tessera:xapi-setup',\n VIRTUAL_XAPI_SETUP_ID,\n ({ projectRoot, isBuild }) => {\n if (!isBuild) {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n // The audit runs offline — don't wire real LRS destinations into it.\n if (isAuditBuild()) {\n return `export async function buildXAPIClient() { return null; }`;\n }\n\n let standard = 'web';\n let hasXapi = false;\n const read = readCourseConfig(projectRoot);\n if (read.ok) {\n if (typeof read.config.export?.standard === 'string')\n standard = read.config.export.standard;\n hasXapi = read.config.xapi != null;\n }\n\n // cmi5 needs the publisher regardless of explicit xapi config (cmi5\n // adapter shares the publisher queue for its own LMS-required statements).\n if (hasXapi || standard === 'cmi5') {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n return `export async function buildXAPIClient() { return null; }`;\n },\n );\n}\n\nfunction tesseraFirstPagePreloadPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n return {\n name: 'tessera:first-page-preload',\n apply: 'build',\n transformIndexHtml: {\n order: 'post',\n handler(_html, ctx) {\n const firstPagePath = manifestRef.current?.pages[0]?.importPath;\n if (!firstPagePath || !ctx.bundle) return;\n const normalized = resolve(\n manifestRef.root,\n firstPagePath.replace(/^\\//, ''),\n ).replace(/\\\\/g, '/');\n const chunk = Object.values(ctx.bundle).find(\n (c): c is import('vite').Rollup.OutputChunk =>\n c.type === 'chunk' &&\n !!c.facadeModuleId &&\n c.facadeModuleId.replace(/\\\\/g, '/') === normalized,\n );\n if (!chunk) return;\n return [\n {\n tag: 'link',\n attrs: { rel: 'modulepreload', href: `./${chunk.fileName}` },\n injectTo: 'head',\n },\n ];\n },\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin--8H9xQIl.js","names":[],"sources":["../src/runtime/slugify.ts","../src/plugin/export.ts","../src/plugin/override-plugin.ts","../src/plugin/layout.ts","../src/plugin/quiz.ts","../src/plugin/index.ts"],"sourcesContent":["/**\n * Slugify a string for use as a URL-safe / filename-safe identifier.\n * \"My Course Title\" → \"my-course-title\"\n *\n * Shared by the runtime (`WebAdapter` localStorage key) and the build-time\n * exporter (`runExport` zip filename). Both want identical, deterministic\n * output so a course's storage key matches its package name.\n */\nexport function slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/[\\s_]+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n","import {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n} from 'node:fs';\nimport { resolve } from 'node:path';\nimport { createWriteStream } from 'node:fs';\nimport { createHash } from 'node:crypto';\nimport { ZipArchive } from 'archiver';\nimport { slugify } from '../runtime/slugify.js';\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, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&apos;');\n}\n\n/**\n * Recursively collect all file paths relative to a directory.\n */\nfunction collectFiles(dir: string, base: string = ''): string[] {\n const files: string[] = [];\n if (!existsSync(dir)) return files;\n\n for (const entry of readdirSync(dir)) {\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 /** Whitespace-separated namespace+XSD pairs for xsi:schemaLocation. */\n schemaLocation: string;\n}\n\nconst SCORM_DIALECTS: Record<'1.2' | '2004', ScormManifestDialect> = {\n '1.2': {\n rootNs: 'http://www.imsproject.org/xsd/imscp_rootv1p1p2',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_rootv1p2',\n schemaversion: '1.2',\n scormTypeAttr: 'scormtype',\n schemaLocation:\n 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd ' +\n 'http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd',\n },\n '2004': {\n rootNs: 'http://www.imsglobal.org/xsd/imscp_v1p1',\n adlcpNs: 'http://www.adlnet.org/xsd/adlcp_v1p3',\n schemaversion: '2004 4th Edition',\n scormTypeAttr: 'scormType',\n schemaLocation:\n 'http://www.imsglobal.org/xsd/imscp_v1p1 imscp_v1p1.xsd ' +\n 'http://www.adlnet.org/xsd/adlcp_v1p3 adlcp_v1p3.xsd',\n },\n};\n\nexport function generateScormManifest(\n version: '1.2' | '2004',\n config: ExportConfig,\n distDir: string,\n): string {\n const dialect = SCORM_DIALECTS[version];\n const title = escapeXml(config.title || '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 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n xsi:schemaLocation=\"${dialect.schemaLocation}\">\n <metadata>\n <schema>ADL SCORM</schema>\n <schemaversion>${dialect.schemaversion}</schemaversion>\n </metadata>\n <organizations default=\"org-1\">\n <organization identifier=\"org-1\">\n <title>${title}</title>\n <item identifier=\"item-1\" identifierref=\"res-1\">\n <title>${title}</title>\n </item>\n </organization>\n </organizations>\n <resources>\n <resource identifier=\"res-1\" type=\"webcontent\" adlcp:${dialect.scormTypeAttr}=\"sco\" href=\"index.html\">\n${fileElements}\n </resource>\n </resources>\n</manifest>`;\n}\n\nexport function 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 // cmi5 §10.2.4 caps masteryScore at 4 decimals; avoid float drift like 0.7000000000000001.\n const masteryScore = Number(\n ((config.scoring?.passingScore ?? 70) / 100).toFixed(4),\n );\n // cmi5 §13.1.4 — `moveOn` decides which verb(s) the LMS treats as\n // satisfying the AU. For graded courses (completion gated on a quiz)\n // a learner who completes without passing should NOT receive credit, so\n // the LMS needs both a Completed AND a Passed before satisfaction.\n // Percentage-mode courses don't surface pass/fail, so completion alone\n // is the right signal.\n const moveOn =\n config.completion?.mode === 'quiz' ? 'CompletedAndPassed' : 'Completed';\n\n return `<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<courseStructure xmlns=\"https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd\">\n <course id=\"${courseId}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n </course>\n <au id=\"${auId}\" launchMethod=\"AnyWindow\" moveOn=\"${moveOn}\" masteryScore=\"${masteryScore}\">\n <title><langstring lang=\"en-US\">${title}</langstring></title>\n <description><langstring lang=\"en-US\">${description}</langstring></description>\n <url>index.html</url>\n </au>\n</courseStructure>`;\n}\n\n// ---------- ZIP Packaging ----------\n\nexport async function createZip(\n distDir: string,\n outputPath: string,\n): Promise<number> {\n return new Promise((res, reject) => {\n const output = createWriteStream(outputPath);\n const archive = new ZipArchive({ zlib: { level: 9 } });\n\n output.on('close', () => {\n res(archive.pointer());\n });\n output.on('error', reject);\n archive.on('error', reject);\n\n archive.pipe(output);\n archive.directory(distDir, false);\n void archive.finalize();\n });\n}\n\n// ---------- Main Export ----------\n\n/**\n * Run the export process after Vite build completes.\n * Writes manifest XML into dist/, then packages into ZIP if needed.\n */\n/** Remove any previously built zips for this package to prevent accumulation. */\nfunction cleanOldZips(projectRoot: string, slug: string): void {\n try {\n for (const f of readdirSync(projectRoot)) {\n if (f.startsWith(`${slug}-`) && f.endsWith('.zip')) {\n try {\n unlinkSync(resolve(projectRoot, f));\n } catch {}\n }\n }\n } catch {}\n}\n\n/** Packaged (zipped) export targets: which manifest file to write and how. */\nconst PACKAGED_EXPORTS: Record<\n 'scorm12' | 'scorm2004' | 'cmi5',\n {\n manifestFile: string;\n label: string;\n generate: (config: ExportConfig, distDir: string) => string;\n }\n> = {\n scorm12: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 1.2',\n generate: generateSCORM12Manifest,\n },\n scorm2004: {\n manifestFile: 'imsmanifest.xml',\n label: 'SCORM 2004',\n generate: generateSCORM2004Manifest,\n },\n cmi5: {\n manifestFile: 'cmi5.xml',\n label: 'CMI5',\n generate: (config) => generateCMI5Xml(config),\n },\n};\n\nexport async function runExport(\n projectRoot: string,\n config: ExportConfig,\n): Promise<void> {\n const distDir = resolve(projectRoot, 'dist');\n const standard = config.export?.standard || 'web';\n const slug = slugify(config.title || 'tessera-course') || 'tessera-course';\n const version = config.version || '1.0.0';\n const zipName = `${slug}-${version}.zip`;\n const zipPath = resolve(projectRoot, zipName);\n\n if (standard === 'web') {\n const files = collectFiles(distDir);\n let totalSize = 0;\n for (const f of files) totalSize += statSync(resolve(distDir, f)).size;\n console.log(`✓ Web export: dist/ (${formatSize(totalSize)})`);\n return;\n }\n\n const spec = PACKAGED_EXPORTS[standard as keyof typeof PACKAGED_EXPORTS];\n if (!spec) return; // unknown standard — the validator rejects these upstream\n\n writeFileSync(\n resolve(distDir, spec.manifestFile),\n spec.generate(config, distDir),\n 'utf-8',\n );\n cleanOldZips(projectRoot, slug);\n const zipSize = await createZip(distDir, zipPath);\n console.log(`✓ ${spec.label} export: ${zipName} (${formatSize(zipSize)})`);\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { normalizePath } from 'vite';\nimport { existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nexport interface OverridePluginOptions {\n name: string;\n virtualId: string;\n projectFile: string;\n /** Built-in re-exported when the project file is absent; null export otherwise. */\n builtinFile?: string;\n}\n\n/**\n * A virtual module that resolves to a project-root override file when present,\n * and to the built-in (or a null export) otherwise. Shared by the layout and\n * quiz plugins — they differ only in the virtual id, file name, and built-in.\n */\nexport function createOverridePlugin({\n name,\n virtualId,\n projectFile,\n builtinFile,\n}: OverridePluginOptions): Plugin {\n const resolvedId = '\\0' + virtualId;\n const fallback = builtinFile\n ? `export { default } from '${normalizePath(builtinFile)}';`\n : 'export default null;';\n let filePath: string;\n\n return {\n name,\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n filePath = resolve(config.root, projectFile);\n },\n\n resolveId(id) {\n if (id === virtualId) return resolvedId;\n return null;\n },\n\n load(id) {\n if (id !== resolvedId) return null;\n if (existsSync(filePath)) {\n // Only watch when it exists — addWatchFile on a missing path makes\n // Vite's importAnalysis try to resolve it as a real import.\n this.addWatchFile(filePath);\n return `export { default } from '${normalizePath(filePath)}';`;\n }\n return fallback;\n },\n\n configureServer(server: ViteDevServer) {\n // Only add/unlink flips load()'s output between the override and the\n // fallback; a `change` leaves it identical and Svelte's own HMR handles\n // the underlying file.\n server.watcher.on('all', (event, changed) => {\n if (changed !== filePath) return;\n if (event !== 'add' && event !== 'unlink') return;\n const mod = server.moduleGraph.getModuleById(resolvedId);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n });\n },\n };\n}\n","import type { Plugin } from 'vite';\nimport { createOverridePlugin } from './override-plugin.js';\n\nexport function tesseraLayoutPlugin(): Plugin {\n return createOverridePlugin({\n name: 'tessera:layout',\n virtualId: 'virtual:tessera-layout',\n projectFile: 'layout.svelte',\n });\n}\n","import type { Plugin } from 'vite';\nimport { resolve } from 'node:path';\nimport { createOverridePlugin } from './override-plugin.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nexport function tesseraQuizPlugin(): Plugin {\n const builtinQuiz = resolve(\n resolvePackageRoot(),\n 'src',\n 'components',\n 'Quiz.svelte',\n );\n return createOverridePlugin({\n name: 'tessera:quiz',\n virtualId: 'virtual:tessera-quiz',\n projectFile: 'quiz.svelte',\n builtinFile: builtinQuiz,\n });\n}\n","import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';\nimport { svelte } from '@sveltejs/vite-plugin-svelte';\nimport { resolve, relative, isAbsolute } from 'node:path';\nimport {\n existsSync,\n readdirSync,\n statSync,\n writeFileSync,\n unlinkSync,\n cpSync,\n mkdirSync,\n} from 'node:fs';\nimport { generateManifest, readCourseConfig } from './manifest.js';\nimport type { Manifest } from './manifest.js';\nimport type { CourseConfig } from '../runtime/types.js';\nimport {\n DEFAULT_PASSING_SCORE,\n DEFAULT_PERCENTAGE_THRESHOLD,\n} from '../runtime/defaults.js';\nimport {\n validateProject,\n reportValidationIssues,\n normalizeA11y,\n isPlausibleLanguageTag,\n isIgnored,\n type A11ySettings,\n} from './validation.js';\nimport { runExport } from './export.js';\nimport { tesseraLayoutPlugin } from './layout.js';\nimport { tesseraQuizPlugin } from './quiz.js';\nimport { resolvePackageRoot } from './package-root.js';\n\nimport { AUDIT_ENV_FLAG } from './a11y/audit.js';\n\nexport { runAudit } from './a11y/audit.js';\nexport type { AuditOptions, ImpactLevel } from './a11y/audit.js';\n\nfunction isAuditBuild(): boolean {\n return process.env[AUDIT_ENV_FLAG] === '1';\n}\n\n// Resolve the runtime directory where App.svelte lives\nfunction resolveRuntimeDir(): string {\n return resolve(resolvePackageRoot(), 'src', 'runtime');\n}\n\n// Resolve the framework styles directory\nfunction resolveStylesDir(): string {\n return resolve(resolvePackageRoot(), 'styles');\n}\n\n// Tier-1a state shared between the svelte() onwarn handler and the sibling\n// gate plugin. onwarn fires during transform (after the Tier-1b buildStart\n// gate), so a11y warnings are collected here and flushed/gated at buildEnd.\ninterface A11yCompilerState {\n warnings: string[];\n projectRoot: string;\n isBuild: boolean;\n settings: A11ySettings;\n}\n\n// Svelte's onwarn filename is relative to the vite root (e.g. `pages/x.svelte`)\n// in build and may be absolute or a virtual id elsewhere. Return the\n// project-relative path for a real author file, or null to skip framework /\n// node_modules / virtual modules — Tier 0 owns the framework's own warnings.\nfunction projectFileRel(\n filename: string | undefined,\n projectRoot: string,\n): string | null {\n if (!filename || !projectRoot) return null;\n if (\n filename.startsWith('\\0') ||\n filename.includes('virtual:') ||\n filename.includes('node_modules')\n ) {\n return null;\n }\n const abs = isAbsolute(filename) ? filename : resolve(projectRoot, filename);\n const rel = relative(projectRoot, abs);\n if (rel.startsWith('..') || isAbsolute(rel) || rel.includes('node_modules')) {\n return null;\n }\n return rel;\n}\n\ntype VirtualLoadCtx = { projectRoot: string; isBuild: boolean };\n\nfunction virtualModule(\n name: string,\n virtualId: string,\n load: (\n this: import('vite').Rollup.PluginContext,\n ctx: VirtualLoadCtx,\n ) => string | null,\n): Plugin {\n const resolvedId = '\\0' + virtualId;\n let projectRoot = '';\n let isBuild = false;\n return {\n name,\n enforce: 'pre',\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n resolveId(id) {\n return id === virtualId ? resolvedId : null;\n },\n load(id) {\n return id === resolvedId\n ? load.call(this, { projectRoot, isBuild })\n : null;\n },\n };\n}\n\nexport function tesseraPlugin() {\n const manifestRef: { current: Manifest | null; root: string } = {\n current: null,\n root: '',\n };\n const a11y: A11yCompilerState = {\n warnings: [],\n projectRoot: '',\n isBuild: false,\n settings: normalizeA11y(undefined),\n };\n return [\n svelte({\n compilerOptions: { css: 'external' },\n onwarn(warning, defaultHandler) {\n if (warning.code?.startsWith('a11y')) {\n const rel = projectFileRel(warning.filename, a11y.projectRoot);\n if (rel !== null) {\n const msg = `[${warning.code}] ${rel}: ${warning.message}`;\n if (a11y.isBuild) {\n a11y.warnings.push(msg);\n } else if (!a11y.settings.ignore.includes(warning.code)) {\n reportValidationIssues({ errors: [], warnings: [msg] });\n }\n }\n return; // suppress the raw Vite print; we re-emit via the reporter\n }\n defaultHandler?.(warning);\n },\n }),\n tesseraA11yCompilerPlugin(a11y),\n tesseraValidationPlugin(),\n tesseraEntryPlugin(),\n tesseraConfigDefaultsPlugin(),\n tesseraConfigPlugin(),\n tesseraPagesPlugin(),\n tesseraManifestPlugin(manifestRef),\n tesseraLayoutPlugin(),\n tesseraQuizPlugin(),\n tesseraAdapterPlugin(),\n tesseraXAPISetupPlugin(),\n tesseraFirstPagePreloadPlugin(manifestRef),\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 outDir: string;\n let isBuild = false;\n\n return {\n name: 'tessera:entry',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n outDir = resolve(config.root, config.build.outDir);\n isBuild = config.command === 'build';\n },\n\n // For build mode: write index.html so Rollup can find it\n buildStart() {\n if (isBuild) {\n writeFileSync(\n resolve(projectRoot, 'index.html'),\n generateIndexHtml(readLanguage(projectRoot)),\n 'utf-8',\n );\n }\n },\n\n // For build mode: clean up temporary index.html and copy assets\n closeBundle() {\n if (isBuild) {\n const htmlPath = resolve(projectRoot, 'index.html');\n if (existsSync(htmlPath)) {\n try {\n unlinkSync(htmlPath);\n } catch {}\n }\n\n // Copy assets/ into the build's assets/ so $assets/ references resolve\n const assetsDir = resolve(projectRoot, 'assets');\n const distAssetsDir = resolve(outDir, 'assets');\n if (existsSync(assetsDir)) {\n mkdirSync(distAssetsDir, { recursive: true });\n cpSync(assetsDir, distAssetsDir, { recursive: true });\n }\n }\n },\n\n // Serve index.html for the dev server\n configureServer(server: ViteDevServer) {\n return () => {\n server.middlewares.use(async (req, res, next) => {\n if (req.url === '/' || req.url === '/index.html') {\n const html = generateIndexHtml(readLanguage(projectRoot));\n const transformed = await server.transformIndexHtml(req.url, html);\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(transformed);\n return;\n }\n next();\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_ENTRY_ID) return RESOLVED_ENTRY_ID;\n if (id === VIRTUAL_MAIN_ID || id === 'virtual:tessera-main')\n return RESOLVED_MAIN_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_ENTRY_ID || id === RESOLVED_MAIN_ID) {\n return generateEntryScript(appSveltePath, stylesDir, projectRoot);\n }\n return null;\n },\n };\n}\n\n// 'en' fallback applied here: the config default-merge runs later than buildStart.\n// Only a validated BCP-47 tag is interpolated into <html lang>, so a malformed\n// value (caught separately as a warning) can't ship a broken attribute.\nfunction readLanguage(projectRoot: string): string {\n const read = readCourseConfig(projectRoot);\n const lang = read.ok ? read.config.language : undefined;\n return isPlausibleLanguageTag(lang) ? lang : 'en';\n}\n\nfunction generateIndexHtml(lang: string): string {\n return `<!DOCTYPE html>\n<html lang=\"${lang}\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Tessera Course</title>\n</head>\n<body>\n <div id=\"tessera-root\"></div>\n <script type=\"module\" src=\"/virtual:tessera-main\"></script>\n</body>\n</html>`;\n}\n\nfunction generateEntryScript(\n appSveltePath: string,\n frameworkStylesDir: string,\n projectRoot: string,\n): string {\n const normalizedPath = appSveltePath.replace(/\\\\/g, '/');\n\n // Framework CSS imports (theme → base → layout)\n const frameworkCssOrder = ['theme.css', 'base.css', 'layout.css'];\n const frameworkImports = frameworkCssOrder\n .map((file) => resolve(frameworkStylesDir, file).replace(/\\\\/g, '/'))\n .filter((path) => existsSync(path))\n .map((path) => `import '${path}';`)\n .join('\\n');\n\n // User CSS imports from project's styles/ directory\n const userStylesDir = resolve(projectRoot, 'styles');\n let userImports = '';\n if (existsSync(userStylesDir)) {\n const userCssFiles = readdirSync(userStylesDir)\n .filter((f) => f.endsWith('.css'))\n .sort();\n userImports = userCssFiles\n .map((f) => resolve(userStylesDir, f).replace(/\\\\/g, '/'))\n .map((path) => `import '${path}';`)\n .join('\\n');\n }\n\n return `// Framework styles\n${frameworkImports}\n// User styles\n${userImports}\n\nimport { mount } from 'svelte';\nimport App from '${normalizedPath}';\n\nmount(App, {\n target: document.getElementById('tessera-root'),\n});\n`;\n}\n\n// ---------- Config Plugin ----------\n\nconst VIRTUAL_CONFIG_ID = 'virtual:tessera-config';\n\nfunction completionDefaults(mode: string | undefined): {\n completion: Record<string, unknown>;\n passingScore: number;\n} {\n if (mode === 'manual') {\n return { completion: { mode: 'manual' }, passingScore: 0 };\n }\n return {\n completion: {\n mode: 'percentage',\n percentageThreshold: DEFAULT_PERCENTAGE_THRESHOLD,\n },\n passingScore: DEFAULT_PASSING_SCORE,\n };\n}\n\nfunction tesseraConfigDefaultsPlugin(): Plugin {\n return {\n name: 'tessera:config-defaults',\n enforce: 'pre',\n config(config) {\n const root = config.root || process.cwd();\n return {\n base: './',\n build: { assetsDir: 'tessera' },\n resolve: { alias: { $assets: resolve(root, 'assets') } },\n // tessera-learn ships .ts/.svelte.ts source; Vite's dep optimizer\n // doesn't run vite-plugin-svelte's preprocessor, so skip pre-bundling.\n optimizeDeps: { exclude: ['tessera-learn'] },\n };\n },\n };\n}\n\nfunction tesseraConfigPlugin(): Plugin {\n return virtualModule(\n 'tessera:config',\n VIRTUAL_CONFIG_ID,\n function ({ projectRoot }) {\n const configPath = resolve(projectRoot, 'course.config.js');\n if (existsSync(configPath)) this.addWatchFile(configPath);\n const read = readCourseConfig(projectRoot);\n const userConfig: Partial<CourseConfig> = read.ok ? read.config : {};\n const { completion, passingScore } = completionDefaults(\n userConfig.completion?.mode,\n );\n const merged = {\n title: userConfig.title || 'Untitled Course',\n ...userConfig,\n navigation: { mode: 'free', ...userConfig.navigation },\n completion: { ...completion, ...userConfig.completion },\n scoring: { passingScore, ...userConfig.scoring },\n export: { standard: 'web', ...userConfig.export },\n };\n return `export default ${JSON.stringify(merged)};`;\n },\n );\n}\n\n// ---------- Manifest Watch Helpers ----------\n\n/** Register all _meta.js and .svelte files under pagesDir as watch files for build mode. */\nfunction addWatchFiles(\n ctx: { addWatchFile(id: string): void },\n dir: string,\n): void {\n if (!existsSync(dir)) return;\n for (const entry of readdirSync(dir)) {\n const full = resolve(dir, entry);\n if (statSync(full).isDirectory()) {\n addWatchFiles(ctx, full);\n } else if (entry.endsWith('.svelte') || entry === '_meta.js') {\n ctx.addWatchFile(full);\n }\n }\n}\n\n// ---------- Pages Plugin ----------\n\nconst VIRTUAL_PAGES_ID = 'virtual:tessera-pages';\n\n/**\n * Provides a virtual module that exports an import.meta.glob map for all .svelte\n * pages. This runs in the user's project context so the glob resolves against their\n * pages/ directory, and Vite can statically analyze it for code splitting.\n */\nfunction tesseraPagesPlugin(): Plugin {\n return virtualModule('tessera:pages', VIRTUAL_PAGES_ID, () => {\n return `export default import.meta.glob('/pages/**/*.svelte');`;\n });\n}\n\n// ---------- Validation Plugin ----------\n\nfunction tesseraValidationPlugin(): 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\n// Tier 1a: flush + gate the Svelte compiler's a11y warnings at buildEnd, after\n// every module is transformed. svelte() accepts `onwarn` but not arbitrary\n// Rollup hooks, so the gate lives here and shares the onwarn closure.\nfunction tesseraA11yCompilerPlugin(a11y: A11yCompilerState): Plugin {\n return {\n name: 'tessera:a11y-compiler',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n a11y.projectRoot = config.root;\n a11y.isBuild = config.command === 'build';\n const read = readCourseConfig(config.root);\n a11y.settings = normalizeA11y(read.ok ? read.config.a11y : undefined);\n },\n\n buildEnd() {\n if (!a11y.isBuild || a11y.warnings.length === 0) return;\n const ignored = new Set(a11y.settings.ignore);\n const warnings = a11y.warnings.filter((msg) => !isIgnored(msg, ignored));\n a11y.warnings = [];\n if (warnings.length === 0) return;\n if (a11y.settings.level === 'error') {\n reportValidationIssues({ errors: warnings, warnings: [] });\n throw new Error(\n `Tessera: ${warnings.length} a11y issue(s) with a11y.level: 'error'. Fix the errors above to continue.`,\n );\n }\n reportValidationIssues({ errors: [], warnings });\n },\n };\n}\n\nfunction runValidation(projectRoot: string): void {\n const result = validateProject(projectRoot);\n reportValidationIssues(result);\n if (result.errors.length > 0) {\n throw new Error(\n `Tessera validation failed with ${result.errors.length} error(s). Fix the errors above to continue.`,\n );\n }\n}\n\n// ---------- Export Plugin ----------\n\nfunction tesseraExportPlugin(): Plugin {\n let projectRoot: string;\n let isBuild = false;\n\n return {\n name: 'tessera:export',\n enforce: 'post',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n isBuild = config.command === 'build';\n },\n\n async closeBundle() {\n if (!isBuild) return;\n if (isAuditBuild()) return;\n\n const read = readCourseConfig(projectRoot);\n if (!read.ok) {\n // Validation already required a parseable course.config.js — getting\n // here means it vanished or broke mid-build. Surface that loudly\n // rather than shipping a bundle with no LMS export silently.\n if (read.reason === 'missing') {\n throw new Error(\n '[tessera:export] course.config.js not found at closeBundle. The file must exist for the export step to run.',\n );\n }\n if (read.reason === 'no-export') {\n throw new Error(\n '[tessera:export] course.config.js: could not locate `export default { ... }`. Cannot determine export.standard.',\n );\n }\n throw new Error(\n `[tessera:export] course.config.js: failed to parse export-default object literal — ${(read.error as Error).message}`,\n );\n }\n\n await runExport(\n projectRoot,\n read.config as Parameters<typeof runExport>[1],\n );\n },\n };\n}\n\n// ---------- Manifest Plugin ----------\n\nconst VIRTUAL_MANIFEST_ID = 'virtual:tessera-manifest';\nconst RESOLVED_MANIFEST_ID = '\\0' + VIRTUAL_MANIFEST_ID;\n\nfunction tesseraManifestPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n let projectRoot: string;\n let pagesDir: string;\n\n function buildManifest(): Manifest {\n const m = generateManifest(pagesDir);\n manifestRef.current = m;\n return m;\n }\n\n return {\n name: 'tessera:manifest',\n enforce: 'pre',\n\n configResolved(config: ResolvedConfig) {\n projectRoot = config.root;\n pagesDir = resolve(projectRoot, 'pages');\n manifestRef.root = projectRoot;\n },\n\n configureServer(devServer: ViteDevServer) {\n // Watch the pages directory for changes\n devServer.watcher.on('all', (event, filePath) => {\n if (!filePath.startsWith(pagesDir)) return;\n\n // Rebuild manifest on relevant file changes\n const isRelevant =\n filePath.endsWith('.svelte') ||\n filePath.endsWith('_meta.js') ||\n event === 'addDir' ||\n event === 'unlinkDir';\n\n if (isRelevant) {\n manifestRef.current = null; // invalidate cache\n\n // Invalidate the virtual module to trigger HMR\n const mod = devServer.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);\n if (mod) {\n devServer.moduleGraph.invalidateModule(mod);\n devServer.ws.send({ type: 'full-reload' });\n }\n\n console.log(\n `[tessera] Manifest rebuilt (${event}: ${filePath.replace(projectRoot, '')})`,\n );\n }\n });\n },\n\n buildStart() {\n buildManifest();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_MANIFEST_ID) {\n if (!manifestRef.current) {\n buildManifest();\n }\n\n // Register watch files so Vite's built-in watcher (used in build --watch)\n // knows to re-trigger when pages/ content changes.\n addWatchFiles(this, pagesDir);\n\n // Encode as base64 to prevent Vite's import analysis from\n // scanning .svelte importPath strings as module imports.\n // Replace Infinity with 1e9 since JSON.stringify drops it.\n const json = JSON.stringify(manifestRef.current, (_key, value) =>\n value === Infinity ? 1e9 : value,\n );\n const b64 = Buffer.from(json).toString('base64');\n return `export default JSON.parse(atob(\"${b64}\"));`;\n }\n return null;\n },\n };\n}\n\nconst VIRTUAL_ADAPTER_ID = 'virtual:tessera-adapter';\n\nfunction tesseraAdapterPlugin(): Plugin {\n return virtualModule(\n 'tessera:adapter',\n VIRTUAL_ADAPTER_ID,\n ({ projectRoot, isBuild }) => {\n // In dev, defer to the runtime selector so its WebAdapter fallback\n // for unreachable LMS APIs keeps working.\n if (!isBuild) {\n return `export { createAdapter } from 'tessera-learn/runtime/adapters/index.js';`;\n }\n\n let standard = 'web';\n const read = readCourseConfig(projectRoot);\n if (read.ok && typeof read.config.export?.standard === 'string') {\n standard = read.config.export.standard;\n }\n\n // The audit renders headless with no LMS in the frame chain; the SCORM/\n // cmi5 adapters throw when their API is absent, so render with WebAdapter.\n if (isAuditBuild()) standard = 'web';\n\n switch (standard) {\n case 'scorm12':\n return `\nimport { SCORM12Adapter } from 'tessera-learn/runtime/adapters/scorm12.js';\nimport { findSCORM12API } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n const api = findSCORM12API();\n if (!api) throw new LMSAdapterError('scorm12', 'Tessera: SCORM 1.2 API not found in window.parent/opener chain. Course must be launched from a SCORM 1.2 LMS.');\n return new SCORM12Adapter(api);\n}\n`;\n case 'scorm2004':\n return `\nimport { SCORM2004Adapter } from 'tessera-learn/runtime/adapters/scorm2004.js';\nimport { findSCORM2004API } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n const api = findSCORM2004API();\n if (!api) throw new LMSAdapterError('scorm2004', 'Tessera: SCORM 2004 API not found in window.parent/opener chain. Course must be launched from a SCORM 2004 LMS.');\n return new SCORM2004Adapter(api);\n}\n`;\n case 'cmi5':\n return `\nimport { CMI5Adapter } from 'tessera-learn/runtime/adapters/cmi5.js';\nimport { hasCMI5LaunchParams } from 'tessera-learn/runtime/adapters/discovery.js';\nimport { LMSAdapterError } from 'tessera-learn/runtime/adapters/index.js';\nexport function createAdapter() {\n if (!hasCMI5LaunchParams()) throw new LMSAdapterError('cmi5', 'Tessera: cmi5 launch parameters not present on URL. Course must be launched from a cmi5-compliant LMS.');\n return new CMI5Adapter();\n}\n`;\n default:\n return `\nimport { WebAdapter } from 'tessera-learn/runtime/adapters/web.js';\nexport function createAdapter(config) {\n return new WebAdapter(config);\n}\n`;\n }\n },\n );\n}\n\nconst VIRTUAL_XAPI_SETUP_ID = 'virtual:tessera-xapi-setup';\n\nfunction tesseraXAPISetupPlugin(): Plugin {\n return virtualModule(\n 'tessera:xapi-setup',\n VIRTUAL_XAPI_SETUP_ID,\n ({ projectRoot, isBuild }) => {\n if (!isBuild) {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n // The audit runs offline — don't wire real LRS destinations into it.\n if (isAuditBuild()) {\n return `export async function buildXAPIClient() { return null; }`;\n }\n\n let standard = 'web';\n let hasXapi = false;\n const read = readCourseConfig(projectRoot);\n if (read.ok) {\n if (typeof read.config.export?.standard === 'string')\n standard = read.config.export.standard;\n hasXapi = read.config.xapi != null;\n }\n\n // cmi5 needs the publisher regardless of explicit xapi config (cmi5\n // adapter shares the publisher queue for its own LMS-required statements).\n if (hasXapi || standard === 'cmi5') {\n return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;\n }\n\n return `export async function buildXAPIClient() { return null; }`;\n },\n );\n}\n\nfunction tesseraFirstPagePreloadPlugin(manifestRef: {\n current: Manifest | null;\n root: string;\n}): Plugin {\n return {\n name: 'tessera:first-page-preload',\n apply: 'build',\n transformIndexHtml: {\n order: 'post',\n handler(_html, ctx) {\n const firstPagePath = manifestRef.current?.pages[0]?.importPath;\n if (!firstPagePath || !ctx.bundle) return;\n const normalized = resolve(\n manifestRef.root,\n firstPagePath.replace(/^\\//, ''),\n ).replace(/\\\\/g, '/');\n const chunk = Object.values(ctx.bundle).find(\n (c): c is import('vite').Rollup.OutputChunk =>\n c.type === 'chunk' &&\n !!c.facadeModuleId &&\n c.facadeModuleId.replace(/\\\\/g, '/') === normalized,\n );\n if (!chunk) return;\n return [\n {\n tag: 'link',\n attrs: { rel: 'modulepreload', href: `./${chunk.fileName}` },\n injectTo: 'head',\n },\n ];\n },\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tessera-learn",
3
- "version": "0.2.0",
3
+ "version": "0.2.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",
@@ -1,8 +1,7 @@
1
1
  <script>
2
- import { onMount } from 'svelte';
3
2
  import { useQuestion } from '../runtime/hooks.svelte.js';
4
3
  import { questionId } from './util.js';
5
- import LockedBanner from './LockedBanner.svelte';
4
+ import QuestionShell from './QuestionShell.svelte';
6
5
  import ResultIcon from './ResultIcon.svelte';
7
6
  import RetryButton from './RetryButton.svelte';
8
7
 
@@ -56,10 +55,6 @@
56
55
  // `q.mode` is fixed for the lifetime of the widget; capture once.
57
56
  const inQuiz = q.mode === 'quiz';
58
57
 
59
- onMount(() => {
60
- if (inQuiz) q.setRender(renderQuestion);
61
- });
62
-
63
58
  function handleInput(e) {
64
59
  if (q.locked) return;
65
60
  inputValue = e.target.value;
@@ -79,7 +74,7 @@
79
74
  }
80
75
  </script>
81
76
 
82
- {#snippet fitbContent()}
77
+ <QuestionShell {q} class="tessera-fitb">
83
78
  <label class="tessera-fitb-question" for={inputId}>{question}</label>
84
79
 
85
80
  <div class="tessera-fitb-input-wrapper">
@@ -138,28 +133,9 @@
138
133
  {/if}
139
134
  </div>
140
135
  {/if}
141
- {/snippet}
142
-
143
- {#if !inQuiz}
144
- <div class="tessera-fitb">
145
- {@render fitbContent()}
146
- </div>
147
- {/if}
148
-
149
- {#snippet renderQuestion()}
150
- <div class="tessera-fitb">
151
- {#if q.isLockedCorrect}
152
- <LockedBanner />
153
- {/if}
154
- {@render fitbContent()}
155
- </div>
156
- {/snippet}
136
+ </QuestionShell>
157
137
 
158
138
  <style>
159
- .tessera-fitb {
160
- padding: var(--tessera-spacing-md) 0;
161
- }
162
-
163
139
  .tessera-fitb-question {
164
140
  display: block;
165
141
  font-size: 1.125rem;