tessera-learn 0.0.13 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1794 -0
- package/README.md +5 -5
- package/dist/{validation-B-xTvM9B.js → audit-BA5o0ick.js} +605 -269
- package/dist/audit-BA5o0ick.js.map +1 -0
- package/dist/build-commands-C0OnV-Vg.js +27 -0
- package/dist/build-commands-C0OnV-Vg.js.map +1 -0
- package/dist/inline-config-CroQ-_2Y.js +31 -0
- package/dist/inline-config-CroQ-_2Y.js.map +1 -0
- package/dist/plugin/cli.d.ts +9 -1
- package/dist/plugin/cli.d.ts.map +1 -0
- package/dist/plugin/cli.js +326 -17
- package/dist/plugin/cli.js.map +1 -1
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.d.ts.map +1 -1
- package/dist/plugin/index.js +2 -763
- package/dist/plugin-W_rk3Pit.js +731 -0
- package/dist/plugin-W_rk3Pit.js.map +1 -0
- package/package.json +21 -9
- package/src/components/FillInTheBlank.svelte +2 -2
- package/src/components/Matching.svelte +2 -2
- package/src/components/MultipleChoice.svelte +2 -2
- package/src/components/RevealModal.svelte +48 -103
- package/src/components/Sorting.svelte +2 -2
- package/src/components/util.ts +9 -0
- package/src/plugin/a11y/audit.ts +40 -8
- package/src/plugin/a11y-cli.ts +39 -22
- package/src/plugin/ast.ts +276 -0
- package/src/plugin/build-commands.ts +31 -0
- package/src/plugin/cli.ts +96 -21
- package/src/plugin/course-root.ts +98 -0
- package/src/plugin/duplicate-cli.ts +74 -0
- package/src/plugin/index.ts +87 -122
- package/src/plugin/inline-config.ts +54 -0
- package/src/plugin/manifest.ts +103 -136
- package/src/plugin/new-cli.ts +51 -0
- package/src/plugin/package-root.ts +24 -0
- package/src/plugin/project-name.ts +29 -0
- package/src/plugin/quiz.ts +8 -9
- package/src/plugin/template-copy.ts +43 -0
- package/src/plugin/validate-cli.ts +30 -0
- package/src/plugin/validation.ts +152 -244
- package/src/runtime/App.svelte +11 -97
- package/src/runtime/Sidebar.svelte +3 -1
- package/src/runtime/adapters/cmi5.ts +6 -10
- package/src/runtime/adapters/format.ts +6 -0
- package/src/runtime/adapters/retry.ts +1 -1
- package/src/runtime/adapters/scorm2004.ts +2 -4
- package/src/runtime/branding.ts +90 -0
- package/src/runtime/defaults.ts +3 -0
- package/src/runtime/hooks.svelte.ts +16 -53
- package/src/runtime/interaction-format.ts +3 -8
- package/src/runtime/progress.svelte.ts +47 -83
- package/src/runtime/xapi/derive-actor.ts +41 -48
- package/src/runtime/xapi/publisher.ts +14 -14
- package/src/runtime/xapi/setup.ts +39 -46
- package/templates/course/course.config.js +11 -0
- package/templates/course/layout.svelte +116 -0
- package/templates/course/pages/01-getting-started/01-welcome/_meta.js +1 -0
- package/templates/course/pages/01-getting-started/01-welcome/welcome.svelte +19 -0
- package/templates/course/pages/01-getting-started/_meta.js +1 -0
- package/templates/course/styles/custom.css +5 -0
- package/dist/audit-BBJpQGqb.js +0 -204
- package/dist/audit-BBJpQGqb.js.map +0 -1
- package/dist/plugin/a11y-cli.d.ts +0 -1
- package/dist/plugin/a11y-cli.js +0 -36
- package/dist/plugin/a11y-cli.js.map +0 -1
- package/dist/plugin/index.js.map +0 -1
- package/dist/validation-B-xTvM9B.js.map +0 -1
package/src/plugin/index.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig, ViteDevServer } from 'vite';
|
|
2
2
|
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
3
|
-
import {
|
|
4
|
-
import { dirname, resolve, relative, isAbsolute } from 'node:path';
|
|
3
|
+
import { resolve, relative, isAbsolute } from 'node:path';
|
|
5
4
|
import {
|
|
6
5
|
existsSync,
|
|
7
6
|
readdirSync,
|
|
@@ -14,6 +13,10 @@ import {
|
|
|
14
13
|
import { generateManifest, readCourseConfig } from './manifest.js';
|
|
15
14
|
import type { Manifest } from './manifest.js';
|
|
16
15
|
import type { CourseConfig } from '../runtime/types.js';
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_PASSING_SCORE,
|
|
18
|
+
DEFAULT_PERCENTAGE_THRESHOLD,
|
|
19
|
+
} from '../runtime/defaults.js';
|
|
17
20
|
import {
|
|
18
21
|
validateProject,
|
|
19
22
|
reportValidationIssues,
|
|
@@ -25,6 +28,7 @@ import {
|
|
|
25
28
|
import { runExport } from './export.js';
|
|
26
29
|
import { tesseraLayoutPlugin } from './layout.js';
|
|
27
30
|
import { tesseraQuizPlugin } from './quiz.js';
|
|
31
|
+
import { resolvePackageRoot } from './package-root.js';
|
|
28
32
|
|
|
29
33
|
import { AUDIT_ENV_FLAG } from './a11y/audit.js';
|
|
30
34
|
|
|
@@ -35,19 +39,14 @@ function isAuditBuild(): boolean {
|
|
|
35
39
|
return process.env[AUDIT_ENV_FLAG] === '1';
|
|
36
40
|
}
|
|
37
41
|
|
|
38
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
39
|
-
const __dirname = dirname(__filename);
|
|
40
|
-
|
|
41
42
|
// Resolve the runtime directory where App.svelte lives
|
|
42
43
|
function resolveRuntimeDir(): string {
|
|
43
|
-
|
|
44
|
-
return resolve(packageRoot, 'src', 'runtime');
|
|
44
|
+
return resolve(resolvePackageRoot(), 'src', 'runtime');
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
// Resolve the framework styles directory
|
|
48
48
|
function resolveStylesDir(): string {
|
|
49
|
-
|
|
50
|
-
return resolve(packageRoot, 'styles');
|
|
49
|
+
return resolve(resolvePackageRoot(), 'styles');
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
// Tier-1a state shared between the svelte() onwarn handler and the sibling
|
|
@@ -84,6 +83,37 @@ function projectFileRel(
|
|
|
84
83
|
return rel;
|
|
85
84
|
}
|
|
86
85
|
|
|
86
|
+
type VirtualLoadCtx = { projectRoot: string; isBuild: boolean };
|
|
87
|
+
|
|
88
|
+
function virtualModule(
|
|
89
|
+
name: string,
|
|
90
|
+
virtualId: string,
|
|
91
|
+
load: (
|
|
92
|
+
this: import('vite').Rollup.PluginContext,
|
|
93
|
+
ctx: VirtualLoadCtx,
|
|
94
|
+
) => string | null,
|
|
95
|
+
): Plugin {
|
|
96
|
+
const resolvedId = '\0' + virtualId;
|
|
97
|
+
let projectRoot = '';
|
|
98
|
+
let isBuild = false;
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
enforce: 'pre',
|
|
102
|
+
configResolved(config: ResolvedConfig) {
|
|
103
|
+
projectRoot = config.root;
|
|
104
|
+
isBuild = config.command === 'build';
|
|
105
|
+
},
|
|
106
|
+
resolveId(id) {
|
|
107
|
+
return id === virtualId ? resolvedId : null;
|
|
108
|
+
},
|
|
109
|
+
load(id) {
|
|
110
|
+
return id === resolvedId
|
|
111
|
+
? load.call(this, { projectRoot, isBuild })
|
|
112
|
+
: null;
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
87
117
|
export function tesseraPlugin() {
|
|
88
118
|
const manifestRef: { current: Manifest | null; root: string } = {
|
|
89
119
|
current: null,
|
|
@@ -117,6 +147,7 @@ export function tesseraPlugin() {
|
|
|
117
147
|
tesseraA11yCompilerPlugin(a11y),
|
|
118
148
|
tesseraValidationPlugin(),
|
|
119
149
|
tesseraEntryPlugin(),
|
|
150
|
+
tesseraConfigDefaultsPlugin(),
|
|
120
151
|
tesseraConfigPlugin(),
|
|
121
152
|
tesseraPagesPlugin(),
|
|
122
153
|
tesseraManifestPlugin(manifestRef),
|
|
@@ -287,7 +318,6 @@ mount(App, {
|
|
|
287
318
|
// ---------- Config Plugin ----------
|
|
288
319
|
|
|
289
320
|
const VIRTUAL_CONFIG_ID = 'virtual:tessera-config';
|
|
290
|
-
const RESOLVED_CONFIG_ID = '\0' + VIRTUAL_CONFIG_ID;
|
|
291
321
|
|
|
292
322
|
function completionDefaults(mode: string | undefined): {
|
|
293
323
|
completion: Record<string, unknown>;
|
|
@@ -297,72 +327,55 @@ function completionDefaults(mode: string | undefined): {
|
|
|
297
327
|
return { completion: { mode: 'manual' }, passingScore: 0 };
|
|
298
328
|
}
|
|
299
329
|
return {
|
|
300
|
-
completion: {
|
|
301
|
-
|
|
330
|
+
completion: {
|
|
331
|
+
mode: 'percentage',
|
|
332
|
+
percentageThreshold: DEFAULT_PERCENTAGE_THRESHOLD,
|
|
333
|
+
},
|
|
334
|
+
passingScore: DEFAULT_PASSING_SCORE,
|
|
302
335
|
};
|
|
303
336
|
}
|
|
304
337
|
|
|
305
|
-
function
|
|
306
|
-
let projectRoot: string;
|
|
307
|
-
|
|
338
|
+
function tesseraConfigDefaultsPlugin(): Plugin {
|
|
308
339
|
return {
|
|
309
|
-
name: 'tessera:config',
|
|
340
|
+
name: 'tessera:config-defaults',
|
|
310
341
|
enforce: 'pre',
|
|
311
|
-
|
|
312
342
|
config(config) {
|
|
313
343
|
const root = config.root || process.cwd();
|
|
314
|
-
|
|
315
344
|
return {
|
|
316
345
|
base: './',
|
|
317
|
-
build: {
|
|
318
|
-
|
|
319
|
-
},
|
|
320
|
-
resolve: {
|
|
321
|
-
alias: {
|
|
322
|
-
$assets: resolve(root, 'assets'),
|
|
323
|
-
},
|
|
324
|
-
},
|
|
346
|
+
build: { assetsDir: 'tessera' },
|
|
347
|
+
resolve: { alias: { $assets: resolve(root, 'assets') } },
|
|
325
348
|
// tessera-learn ships .ts/.svelte.ts source; Vite's dep optimizer
|
|
326
349
|
// doesn't run vite-plugin-svelte's preprocessor, so skip pre-bundling.
|
|
327
|
-
optimizeDeps: {
|
|
328
|
-
exclude: ['tessera-learn'],
|
|
329
|
-
},
|
|
350
|
+
optimizeDeps: { exclude: ['tessera-learn'] },
|
|
330
351
|
};
|
|
331
352
|
},
|
|
353
|
+
};
|
|
354
|
+
}
|
|
332
355
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
...userConfig,
|
|
355
|
-
navigation: { mode: 'free', ...userConfig.navigation },
|
|
356
|
-
completion: { ...completion, ...userConfig.completion },
|
|
357
|
-
scoring: { passingScore, ...userConfig.scoring },
|
|
358
|
-
export: { standard: 'web', ...userConfig.export },
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
return `export default ${JSON.stringify(merged)};`;
|
|
362
|
-
}
|
|
363
|
-
return null;
|
|
356
|
+
function tesseraConfigPlugin(): Plugin {
|
|
357
|
+
return virtualModule(
|
|
358
|
+
'tessera:config',
|
|
359
|
+
VIRTUAL_CONFIG_ID,
|
|
360
|
+
function ({ projectRoot }) {
|
|
361
|
+
const configPath = resolve(projectRoot, 'course.config.js');
|
|
362
|
+
if (existsSync(configPath)) this.addWatchFile(configPath);
|
|
363
|
+
const read = readCourseConfig(projectRoot);
|
|
364
|
+
const userConfig: Partial<CourseConfig> = read.ok ? read.config : {};
|
|
365
|
+
const { completion, passingScore } = completionDefaults(
|
|
366
|
+
userConfig.completion?.mode,
|
|
367
|
+
);
|
|
368
|
+
const merged = {
|
|
369
|
+
title: userConfig.title || 'Untitled Course',
|
|
370
|
+
...userConfig,
|
|
371
|
+
navigation: { mode: 'free', ...userConfig.navigation },
|
|
372
|
+
completion: { ...completion, ...userConfig.completion },
|
|
373
|
+
scoring: { passingScore, ...userConfig.scoring },
|
|
374
|
+
export: { standard: 'web', ...userConfig.export },
|
|
375
|
+
};
|
|
376
|
+
return `export default ${JSON.stringify(merged)};`;
|
|
364
377
|
},
|
|
365
|
-
|
|
378
|
+
);
|
|
366
379
|
}
|
|
367
380
|
|
|
368
381
|
// ---------- Manifest Watch Helpers ----------
|
|
@@ -386,7 +399,6 @@ function addWatchFiles(
|
|
|
386
399
|
// ---------- Pages Plugin ----------
|
|
387
400
|
|
|
388
401
|
const VIRTUAL_PAGES_ID = 'virtual:tessera-pages';
|
|
389
|
-
const RESOLVED_PAGES_ID = '\0' + VIRTUAL_PAGES_ID;
|
|
390
402
|
|
|
391
403
|
/**
|
|
392
404
|
* Provides a virtual module that exports an import.meta.glob map for all .svelte
|
|
@@ -394,22 +406,9 @@ const RESOLVED_PAGES_ID = '\0' + VIRTUAL_PAGES_ID;
|
|
|
394
406
|
* pages/ directory, and Vite can statically analyze it for code splitting.
|
|
395
407
|
*/
|
|
396
408
|
function tesseraPagesPlugin(): Plugin {
|
|
397
|
-
return {
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
resolveId(id) {
|
|
402
|
-
if (id === VIRTUAL_PAGES_ID) return RESOLVED_PAGES_ID;
|
|
403
|
-
return null;
|
|
404
|
-
},
|
|
405
|
-
|
|
406
|
-
load(id) {
|
|
407
|
-
if (id === RESOLVED_PAGES_ID) {
|
|
408
|
-
return `export default import.meta.glob('/pages/**/*.svelte');`;
|
|
409
|
-
}
|
|
410
|
-
return null;
|
|
411
|
-
},
|
|
412
|
-
};
|
|
409
|
+
return virtualModule('tessera:pages', VIRTUAL_PAGES_ID, () => {
|
|
410
|
+
return `export default import.meta.glob('/pages/**/*.svelte');`;
|
|
411
|
+
});
|
|
413
412
|
}
|
|
414
413
|
|
|
415
414
|
// ---------- Validation Plugin ----------
|
|
@@ -620,29 +619,12 @@ function tesseraManifestPlugin(manifestRef: {
|
|
|
620
619
|
}
|
|
621
620
|
|
|
622
621
|
const VIRTUAL_ADAPTER_ID = 'virtual:tessera-adapter';
|
|
623
|
-
const RESOLVED_ADAPTER_ID = '\0' + VIRTUAL_ADAPTER_ID;
|
|
624
622
|
|
|
625
623
|
function tesseraAdapterPlugin(): Plugin {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
name: 'tessera:adapter',
|
|
631
|
-
enforce: 'pre',
|
|
632
|
-
|
|
633
|
-
configResolved(config: ResolvedConfig) {
|
|
634
|
-
projectRoot = config.root;
|
|
635
|
-
isBuild = config.command === 'build';
|
|
636
|
-
},
|
|
637
|
-
|
|
638
|
-
resolveId(id) {
|
|
639
|
-
if (id === VIRTUAL_ADAPTER_ID) return RESOLVED_ADAPTER_ID;
|
|
640
|
-
return null;
|
|
641
|
-
},
|
|
642
|
-
|
|
643
|
-
load(id) {
|
|
644
|
-
if (id !== RESOLVED_ADAPTER_ID) return null;
|
|
645
|
-
|
|
624
|
+
return virtualModule(
|
|
625
|
+
'tessera:adapter',
|
|
626
|
+
VIRTUAL_ADAPTER_ID,
|
|
627
|
+
({ projectRoot, isBuild }) => {
|
|
646
628
|
// In dev, defer to the runtime selector so its WebAdapter fallback
|
|
647
629
|
// for unreachable LMS APIs keeps working.
|
|
648
630
|
if (!isBuild) {
|
|
@@ -701,33 +683,16 @@ export function createAdapter(config) {
|
|
|
701
683
|
`;
|
|
702
684
|
}
|
|
703
685
|
},
|
|
704
|
-
|
|
686
|
+
);
|
|
705
687
|
}
|
|
706
688
|
|
|
707
689
|
const VIRTUAL_XAPI_SETUP_ID = 'virtual:tessera-xapi-setup';
|
|
708
|
-
const RESOLVED_XAPI_SETUP_ID = '\0' + VIRTUAL_XAPI_SETUP_ID;
|
|
709
690
|
|
|
710
691
|
function tesseraXAPISetupPlugin(): Plugin {
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
name: 'tessera:xapi-setup',
|
|
716
|
-
enforce: 'pre',
|
|
717
|
-
|
|
718
|
-
configResolved(config: ResolvedConfig) {
|
|
719
|
-
projectRoot = config.root;
|
|
720
|
-
isBuild = config.command === 'build';
|
|
721
|
-
},
|
|
722
|
-
|
|
723
|
-
resolveId(id) {
|
|
724
|
-
if (id === VIRTUAL_XAPI_SETUP_ID) return RESOLVED_XAPI_SETUP_ID;
|
|
725
|
-
return null;
|
|
726
|
-
},
|
|
727
|
-
|
|
728
|
-
load(id) {
|
|
729
|
-
if (id !== RESOLVED_XAPI_SETUP_ID) return null;
|
|
730
|
-
|
|
692
|
+
return virtualModule(
|
|
693
|
+
'tessera:xapi-setup',
|
|
694
|
+
VIRTUAL_XAPI_SETUP_ID,
|
|
695
|
+
({ projectRoot, isBuild }) => {
|
|
731
696
|
if (!isBuild) {
|
|
732
697
|
return `export { buildXAPIClient } from 'tessera-learn/runtime/xapi/setup.js';`;
|
|
733
698
|
}
|
|
@@ -754,7 +719,7 @@ function tesseraXAPISetupPlugin(): Plugin {
|
|
|
754
719
|
|
|
755
720
|
return `export async function buildXAPIClient() { return null; }`;
|
|
756
721
|
},
|
|
757
|
-
|
|
722
|
+
);
|
|
758
723
|
}
|
|
759
724
|
|
|
760
725
|
function tesseraFirstPagePreloadPlugin(manifestRef: {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import type { ConfigEnv, InlineConfig } from 'vite';
|
|
5
|
+
import { tesseraPlugin } from './index.js';
|
|
6
|
+
|
|
7
|
+
// Base Vite config for every Tessera command (dev, export, a11y build).
|
|
8
|
+
// configFile:false disables Vite's own discovery — there is no vite.config.js —
|
|
9
|
+
// and tesseraPlugin() supplies the Svelte compiler, so this is the full plugin set.
|
|
10
|
+
//
|
|
11
|
+
// $shared points at the workspace-level design system, which lives outside the
|
|
12
|
+
// per-course Vite root, so it is wired here (where workspaceRoot is known) rather
|
|
13
|
+
// than next to $assets in the plugin. server.fs.allow must list workspaceRoot or
|
|
14
|
+
// the dev server's fs.strict gate refuses to serve $shared files.
|
|
15
|
+
export function buildInlineConfig(
|
|
16
|
+
projectRoot: string,
|
|
17
|
+
workspaceRoot: string,
|
|
18
|
+
): InlineConfig {
|
|
19
|
+
return {
|
|
20
|
+
root: projectRoot,
|
|
21
|
+
configFile: false,
|
|
22
|
+
plugins: [tesseraPlugin()],
|
|
23
|
+
resolve: { alias: { $shared: resolve(workspaceRoot, 'shared') } },
|
|
24
|
+
server: { fs: { allow: [workspaceRoot] } },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Optional author-owned escape hatch, never scaffolded or reconciled. A *partial*
|
|
29
|
+
// Vite config the caller merges on top of buildInlineConfig(), so tesseraPlugin()
|
|
30
|
+
// stays wired in and the author only writes the delta.
|
|
31
|
+
export async function loadUserConfig(
|
|
32
|
+
projectRoot: string,
|
|
33
|
+
env: ConfigEnv,
|
|
34
|
+
): Promise<InlineConfig | null> {
|
|
35
|
+
const configPath = resolve(projectRoot, 'tessera.config.js');
|
|
36
|
+
if (!existsSync(configPath)) return null;
|
|
37
|
+
const mod = await import(pathToFileURL(configPath).href);
|
|
38
|
+
const config = mod.default ?? mod;
|
|
39
|
+
// mergeConfig throws on a function, so resolve Vite's callback form first.
|
|
40
|
+
return (
|
|
41
|
+
typeof config === 'function' ? await config(env) : config
|
|
42
|
+
) as InlineConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function resolveTesseraConfig(
|
|
46
|
+
projectRoot: string,
|
|
47
|
+
workspaceRoot: string,
|
|
48
|
+
env: ConfigEnv,
|
|
49
|
+
): Promise<InlineConfig> {
|
|
50
|
+
const vite = await import('vite');
|
|
51
|
+
const base = buildInlineConfig(projectRoot, workspaceRoot);
|
|
52
|
+
const user = await loadUserConfig(projectRoot, env);
|
|
53
|
+
return user ? vite.mergeConfig(base, user) : base;
|
|
54
|
+
}
|