@lessonkit/lxpack 0.8.1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import {
2
+ telemetryEventToLessonkit
3
+ } from "./chunk-PSUSESH3.js";
4
+
1
5
  // src/validateDescriptor.ts
2
6
  import { validateId } from "@lessonkit/core";
3
7
 
@@ -268,6 +272,37 @@ function mapLessonkitIds(descriptor) {
268
272
  return { courseId, lessonIds, checkIds };
269
273
  }
270
274
 
275
+ // src/assessments.ts
276
+ function slugChoiceId(text, index) {
277
+ const base = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32);
278
+ const stem = base.length ? base : "choice";
279
+ return `${stem}-${index + 1}`;
280
+ }
281
+ function assessmentDescriptorToLxpack(assessment) {
282
+ const choices = assessment.choices.map((text, index) => {
283
+ const id = slugChoiceId(text, index);
284
+ return {
285
+ id,
286
+ text,
287
+ correct: text === assessment.answer
288
+ };
289
+ });
290
+ return {
291
+ id: assessment.checkId,
292
+ passingScore: assessment.passingScore ?? 1,
293
+ questions: [
294
+ {
295
+ id: "q1",
296
+ prompt: assessment.question,
297
+ choices
298
+ }
299
+ ]
300
+ };
301
+ }
302
+ function extractAssessments(descriptor) {
303
+ return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
304
+ }
305
+
271
306
  // src/interchange.ts
272
307
  function resolveSpaLessons(descriptor) {
273
308
  const mapped = mapLessonkitIds(descriptor);
@@ -291,6 +326,8 @@ function resolveSpaLessons(descriptor) {
291
326
  function descriptorToInterchange(descriptor) {
292
327
  const mapped = mapLessonkitIds(descriptor);
293
328
  const spaLessons = resolveSpaLessons(descriptor);
329
+ const runtime = descriptor.theme ? themeToLxpackRuntime(descriptor.theme) : void 0;
330
+ const assessments = extractAssessments(descriptor);
294
331
  return {
295
332
  format: "lessonkit",
296
333
  version: "1",
@@ -304,121 +341,52 @@ function descriptorToInterchange(descriptor) {
304
341
  type: "spa",
305
342
  path: l.path
306
343
  })),
307
- tracking: descriptor.tracking
344
+ tracking: descriptor.tracking,
345
+ runtime: runtime ? {
346
+ theme: runtime.theme,
347
+ cssVariables: runtime.cssVariables
348
+ } : void 0,
349
+ assessments: assessments.length ? assessments : void 0
308
350
  };
309
351
  }
310
352
 
311
- // src/assessments.ts
312
- function slugChoiceId(text, index) {
313
- const base = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32);
314
- const stem = base.length ? base : "choice";
315
- return `${stem}-${index + 1}`;
316
- }
317
- function assessmentDescriptorToLxpack(assessment) {
318
- const choices = assessment.choices.map((text, index) => {
319
- const id = slugChoiceId(text, index);
320
- return {
321
- id,
322
- text,
323
- correct: text === assessment.answer
324
- };
325
- });
326
- return {
327
- id: assessment.checkId,
328
- passingScore: assessment.passingScore ?? 1,
329
- questions: [
330
- {
331
- id: "q1",
332
- prompt: assessment.question,
333
- choices
334
- }
335
- ]
336
- };
337
- }
338
- function extractAssessments(descriptor) {
339
- return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
340
- }
341
-
342
353
  // src/writeProject.ts
343
- import { access, cp, mkdir, rm, writeFile } from "fs/promises";
344
- import { dirname, join, resolve as resolve3 } from "path";
354
+ import { join, resolve as resolve4 } from "path";
355
+ import { materializeLessonkitProject } from "@lxpack/validators";
345
356
 
346
- // src/assessmentYaml.ts
347
- function yamlQuote(value) {
348
- return JSON.stringify(value);
349
- }
350
- function emitAssessmentYaml(assessment) {
351
- const lx = assessmentDescriptorToLxpack(assessment);
352
- const lines = [];
353
- lines.push(`id: ${lx.id}`);
354
- lines.push(`passingScore: ${lx.passingScore}`);
355
- lines.push("questions:");
356
- for (const question of lx.questions) {
357
- lines.push(` - id: ${question.id}`);
358
- lines.push(` prompt: ${yamlQuote(question.prompt)}`);
359
- lines.push(" choices:");
360
- for (const choice of question.choices) {
361
- lines.push(` - id: ${choice.id}`);
362
- lines.push(` text: ${yamlQuote(choice.text)}`);
363
- if (choice.correct) lines.push(" correct: true");
357
+ // src/spaDirs.ts
358
+ import { access } from "fs/promises";
359
+ import { resolve as resolve3 } from "path";
360
+ async function resolveSpaDirs(options) {
361
+ const { descriptor, spaDistDir, lessonSpaDirs, projectRoot } = options;
362
+ const spaLessons = resolveSpaLessons(descriptor);
363
+ if (descriptor.layout === "single-spa") {
364
+ const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? "dist";
365
+ const srcDist = projectRoot ? resolve3(projectRoot, spaDistRelative) : resolve3(spaDistRelative);
366
+ if (projectRoot) {
367
+ assertResolvedPathUnderRoot(resolve3(projectRoot), srcDist);
364
368
  }
365
- }
366
- return `${lines.join("\n")}
367
- `;
368
- }
369
-
370
- // src/yaml.ts
371
- function yamlQuote2(value) {
372
- if (/[:#\n\r]/.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
373
- return JSON.stringify(value);
374
- }
375
- return value;
376
- }
377
- function emitCourseYaml(opts) {
378
- const lines = [];
379
- lines.push(`title: ${yamlQuote2(opts.title)}`);
380
- lines.push(`version: ${yamlQuote2(opts.version)}`);
381
- if (opts.description) lines.push(`description: ${yamlQuote2(opts.description)}`);
382
- if (opts.runtime) {
383
- lines.push("runtime:");
384
- lines.push(` theme: ${yamlQuote2(opts.runtime.theme)}`);
385
- if (opts.runtime.cssVariables && Object.keys(opts.runtime.cssVariables).length) {
386
- lines.push(" cssVariables:");
387
- for (const [key, value] of Object.entries(opts.runtime.cssVariables).sort(
388
- ([a], [b]) => a.localeCompare(b)
389
- )) {
390
- lines.push(` ${key}: ${JSON.stringify(String(value))}`);
391
- }
369
+ try {
370
+ await access(srcDist);
371
+ } catch {
372
+ throw new Error(`spaDistDir not found: ${srcDist}`);
392
373
  }
393
- }
394
- if (opts.tracking?.completion?.threshold !== void 0) {
395
- lines.push("tracking:");
396
- lines.push(" completion:");
397
- lines.push(` threshold: ${opts.tracking.completion.threshold}`);
398
- }
399
- lines.push("lessons:");
400
- for (const lesson of opts.lessons) {
401
- lines.push(` - id: ${lesson.id}`);
402
- lines.push(` title: ${yamlQuote2(lesson.title)}`);
403
- lines.push(` type: spa`);
404
- lines.push(` path: ${lesson.path}`);
405
- }
406
- if (opts.assessments.length) {
407
- lines.push("assessments:");
408
- for (const assessment of opts.assessments) {
409
- lines.push(` - id: ${assessment.id}`);
410
- lines.push(` file: ${assessment.file}`);
374
+ const lessonId = spaLessons[0]?.id ?? "main";
375
+ return { [lessonId]: srcDist };
376
+ }
377
+ const dirs = {};
378
+ const lessonDirs = lessonSpaDirs ?? {};
379
+ for (const lesson of descriptor.lessons) {
380
+ const src = lessonDirs[lesson.id];
381
+ if (!src) {
382
+ throw new Error(`lessonSpaDirs missing build output for lesson "${lesson.id}"`);
411
383
  }
384
+ dirs[lesson.id] = resolve3(src);
412
385
  }
413
- return `${lines.join("\n")}
414
- `;
386
+ return dirs;
415
387
  }
416
388
 
417
389
  // src/writeProject.ts
418
- async function copyDir(src, dest) {
419
- await mkdir(dirname(dest), { recursive: true });
420
- await cp(src, dest, { recursive: true });
421
- }
422
390
  async function writeLxpackProject(options) {
423
391
  const validation = validateDescriptor(options.descriptor);
424
392
  if (!validation.ok) {
@@ -427,97 +395,46 @@ async function writeLxpackProject(options) {
427
395
  );
428
396
  }
429
397
  const descriptor = validation.descriptor;
430
- const outDir = resolve3(options.outDir);
431
- await mkdir(outDir, { recursive: true });
432
- const spaLessons = resolveSpaLessons(descriptor);
433
- const runtime = descriptor.theme ? themeToLxpackRuntime(descriptor.theme) : void 0;
434
- const assessments = (descriptor.assessments ?? []).map((a) => ({
435
- id: a.checkId,
436
- file: `assessments/${a.checkId}.yaml`
437
- }));
438
- if (descriptor.layout === "single-spa") {
439
- const spaDistRelative = options.spaDistDir ?? descriptor.spaDistDir ?? "dist";
440
- const srcDist = options.projectRoot ? resolve3(options.projectRoot, spaDistRelative) : resolve3(spaDistRelative);
441
- if (options.projectRoot) {
442
- assertResolvedPathUnderRoot(resolve3(options.projectRoot), srcDist);
443
- }
444
- try {
445
- await access(srcDist);
446
- } catch {
447
- throw new Error(`spaDistDir not found: ${srcDist}`);
448
- }
449
- const destDist = join(outDir, "dist");
450
- await rm(destDist, { recursive: true, force: true });
451
- await copyDir(srcDist, destDist);
452
- } else {
453
- const lessonDirs = options.lessonSpaDirs ?? {};
454
- for (const lesson of descriptor.lessons) {
455
- const src = lessonDirs[lesson.id];
456
- if (!src) {
457
- throw new Error(`lessonSpaDirs missing build output for lesson "${lesson.id}"`);
458
- }
459
- const dest = join(outDir, lesson.spaPath);
460
- assertResolvedPathUnderRoot(outDir, dest);
461
- await rm(dest, { recursive: true, force: true });
462
- await copyDir(resolve3(src), dest);
463
- }
464
- }
465
- if (assessments.length) {
466
- const assessmentsDir = join(outDir, "assessments");
467
- await mkdir(assessmentsDir, { recursive: true });
468
- for (const assessment of descriptor.assessments ?? []) {
469
- await writeFile(
470
- join(outDir, `assessments/${assessment.checkId}.yaml`),
471
- emitAssessmentYaml(assessment),
472
- "utf-8"
473
- );
474
- }
398
+ const outDir = resolve4(options.outDir);
399
+ const spaDirs = await resolveSpaDirs({ ...options, descriptor });
400
+ const interchange = descriptorToInterchange(descriptor);
401
+ const materialized = await materializeLessonkitProject({
402
+ interchange,
403
+ spaDirs,
404
+ courseDir: outDir,
405
+ writeAuthoringFiles: true
406
+ });
407
+ if (!materialized.ok) {
408
+ throw new Error(
409
+ materialized.issues.map((i) => `${i.path ?? ""}: ${i.message}`.trim()).join("; ")
410
+ );
475
411
  }
476
- const courseYamlPath = join(outDir, "course.yaml");
477
- await writeFile(
478
- courseYamlPath,
479
- emitCourseYaml({
480
- title: descriptor.title,
481
- version: descriptor.version ?? "1.0.0",
482
- runtime,
483
- tracking: descriptor.tracking,
484
- lessons: spaLessons.map((l) => ({
485
- id: l.id,
486
- title: l.title,
487
- type: "spa",
488
- path: l.path
489
- })),
490
- assessments
491
- }),
492
- "utf-8"
493
- );
494
- const lessonkitJsonPath = join(outDir, "lessonkit.json");
495
- await writeFile(
496
- lessonkitJsonPath,
497
- `${JSON.stringify(descriptorToInterchange(descriptor), null, 2)}
498
- `,
499
- "utf-8"
500
- );
501
- return { outDir, courseYamlPath, lessonkitJsonPath };
412
+ const courseDir = materialized.courseDir;
413
+ return {
414
+ outDir: courseDir,
415
+ courseYamlPath: join(courseDir, "course.yaml"),
416
+ lessonkitJsonPath: join(courseDir, "lessonkit.json")
417
+ };
502
418
  }
503
419
 
504
420
  // src/packageCourse.ts
505
421
  import * as fsp from "fs/promises";
506
- import { dirname as dirname2, join as join2, resolve as resolve4 } from "path";
422
+ import { dirname, join as join2, resolve as resolve5 } from "path";
507
423
  import { tmpdir } from "os";
508
424
  import {
509
425
  buildCourse,
426
+ packageLessonkit,
510
427
  validateCourse
511
428
  } from "@lxpack/api";
512
429
  async function validateLessonkitProject(options) {
513
430
  return validateCourse({
514
- courseDir: resolve4(options.courseDir),
431
+ courseDir: resolve5(options.courseDir),
515
432
  target: options.target
516
433
  });
517
434
  }
518
435
  async function buildLessonkitProject(options) {
519
436
  return buildCourse({
520
- courseDir: resolve4(options.courseDir),
437
+ courseDir: resolve5(options.courseDir),
521
438
  target: options.target,
522
439
  output: options.output,
523
440
  dir: options.dir,
@@ -556,7 +473,7 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
556
473
  }
557
474
  async function packageLessonkitCourse(options) {
558
475
  const { target, output, dir, outputBaseDir, ...writeOpts } = options;
559
- const outDir = resolve4(writeOpts.outDir);
476
+ const outDir = resolve5(writeOpts.outDir);
560
477
  const descriptorValidation = validateDescriptor(writeOpts.descriptor);
561
478
  if (!descriptorValidation.ok) {
562
479
  return {
@@ -573,39 +490,47 @@ async function packageLessonkitCourse(options) {
573
490
  const stagingDir = await fsp.mkdtemp(join2(tmpdir(), "lessonkit-lxpack-"));
574
491
  let promoted = false;
575
492
  try {
576
- const written = await writeLxpackProject({ ...writeOpts, descriptor, outDir: stagingDir });
577
- const courseDir = written.outDir;
578
- const assessments = extractAssessments(descriptor);
579
- const validation = await validateLessonkitProject({ courseDir, target });
580
- if (!validation.ok) {
493
+ let spaDirs;
494
+ try {
495
+ spaDirs = await resolveSpaDirs({ ...writeOpts, descriptor });
496
+ } catch (err) {
581
497
  return {
582
498
  ok: false,
583
499
  courseDir: outDir,
584
500
  target,
585
- validation,
586
- issues: validation.issues.map((i) => ({
587
- path: i.path,
588
- message: i.message,
589
- severity: i.severity
590
- }))
501
+ issues: [
502
+ {
503
+ path: "spaDirs",
504
+ message: err instanceof Error ? err.message : String(err)
505
+ }
506
+ ]
591
507
  };
592
508
  }
509
+ const interchange = descriptorToInterchange(descriptor);
593
510
  const outputBase = outputBaseDir ?? ".lxpack/out";
594
- await fsp.mkdir(join2(courseDir, outputBase), { recursive: true });
511
+ await fsp.mkdir(join2(stagingDir, outputBase), { recursive: true });
595
512
  const defaultOutput = output ?? (dir ? join2(outputBase, target) : join2(outputBase, `course-${target}.zip`));
596
- const build = await buildLessonkitProject({
597
- courseDir,
513
+ const build = await packageLessonkit({
514
+ interchange,
515
+ spaDirs,
598
516
  target,
599
- output: defaultOutput.startsWith("/") ? defaultOutput : join2(courseDir, defaultOutput),
517
+ courseDir: stagingDir,
518
+ output: defaultOutput,
600
519
  dir,
601
- assessments: assessments.length ? assessments : void 0
520
+ outputBaseDir,
521
+ outputAnchorDir: stagingDir,
522
+ writeAuthoringFiles: true
602
523
  });
603
524
  if (!build.ok) {
525
+ const validation2 = {
526
+ ok: false,
527
+ issues: build.issues
528
+ };
604
529
  return {
605
530
  ok: false,
606
531
  courseDir: outDir,
607
532
  target,
608
- validation,
533
+ validation: validation2,
609
534
  build,
610
535
  issues: build.issues.map((i) => ({
611
536
  path: i.path,
@@ -614,15 +539,17 @@ async function packageLessonkitCourse(options) {
614
539
  }))
615
540
  };
616
541
  }
617
- await fsp.mkdir(dirname2(outDir), { recursive: true });
618
- await promoteStagingToOutDir(stagingDir, outDir);
619
- promoted = true;
542
+ const validation = {
543
+ ok: true,
544
+ manifest: build.manifest,
545
+ issues: build.issues
546
+ };
547
+ const stagingRoot = await fsp.realpath(stagingDir);
620
548
  const remapArtifactPath = (artifactPath) => {
621
549
  if (!artifactPath) return void 0;
622
- const resolved = resolve4(artifactPath);
623
- const stagingResolved = resolve4(stagingDir);
624
- if (resolved === stagingResolved || resolved.startsWith(stagingResolved + "/")) {
625
- return join2(outDir, resolved.slice(stagingResolved.length + 1));
550
+ const resolved = resolve5(artifactPath);
551
+ if (resolved === stagingRoot || resolved.startsWith(`${stagingRoot}/`)) {
552
+ return join2(outDir, resolved.slice(stagingRoot.length + 1));
626
553
  }
627
554
  return artifactPath;
628
555
  };
@@ -630,6 +557,9 @@ async function packageLessonkitCourse(options) {
630
557
  "outputPath" in build ? build.outputPath : void 0
631
558
  );
632
559
  const remappedOutputDir = remapArtifactPath("outputDir" in build ? build.outputDir : void 0);
560
+ await fsp.mkdir(dirname(outDir), { recursive: true });
561
+ await promoteStagingToOutDir(stagingDir, outDir);
562
+ promoted = true;
633
563
  const remappedBuild = { ...build };
634
564
  if ("outputPath" in remappedBuild && remappedOutputPath !== void 0) {
635
565
  remappedBuild.outputPath = remappedOutputPath;
@@ -653,15 +583,34 @@ async function packageLessonkitCourse(options) {
653
583
  }
654
584
  }
655
585
  }
586
+
587
+ // src/index.ts
588
+ import {
589
+ LESSONKIT_TELEMETRY_EVENTS,
590
+ mapLessonkitTelemetryToBridgeAction,
591
+ mapLessonkitTelemetryToLxpack
592
+ } from "@lxpack/tracking-schema";
593
+ import {
594
+ lessonkitInterchangeSchema,
595
+ materializeLessonkitProject as materializeLessonkitProject2,
596
+ parseLessonkitInterchange
597
+ } from "@lxpack/validators";
656
598
  export {
599
+ LESSONKIT_TELEMETRY_EVENTS,
657
600
  assessmentDescriptorToLxpack,
658
601
  buildLessonkitProject,
659
602
  descriptorToInterchange,
660
603
  extractAssessments,
604
+ lessonkitInterchangeSchema,
661
605
  mapLessonkitIds,
606
+ mapLessonkitTelemetryToBridgeAction,
607
+ mapLessonkitTelemetryToLxpack,
608
+ materializeLessonkitProject2 as materializeLessonkitProject,
662
609
  packageLessonkitCourse,
610
+ parseLessonkitInterchange,
663
611
  resolveSafePackageOutputOverride,
664
612
  resolveSpaLessons,
613
+ telemetryEventToLessonkit,
665
614
  themeToLxpackRuntime,
666
615
  validateDescriptor,
667
616
  validateLessonkitProject,
@@ -0,0 +1,9 @@
1
+ import { TelemetryEvent } from '@lessonkit/core';
2
+ import { LessonkitTelemetryEvent } from '@lxpack/tracking-schema';
3
+
4
+ /**
5
+ * Map a `@lessonkit/core` telemetry event to the LXPack LessonKit telemetry shape.
6
+ */
7
+ declare function telemetryEventToLessonkit(event: TelemetryEvent): LessonkitTelemetryEvent | null;
8
+
9
+ export { telemetryEventToLessonkit as t };
@@ -0,0 +1,9 @@
1
+ import { TelemetryEvent } from '@lessonkit/core';
2
+ import { LessonkitTelemetryEvent } from '@lxpack/tracking-schema';
3
+
4
+ /**
5
+ * Map a `@lessonkit/core` telemetry event to the LXPack LessonKit telemetry shape.
6
+ */
7
+ declare function telemetryEventToLessonkit(event: TelemetryEvent): LessonkitTelemetryEvent | null;
8
+
9
+ export { telemetryEventToLessonkit as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lessonkit/lxpack",
3
- "version": "0.8.1",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "LXPack export adapter for LessonKit courses (SCORM, standalone, xAPI, cmi5).",
6
6
  "license": "Apache-2.0",
@@ -53,9 +53,12 @@
53
53
  "lint": "echo \"(no lint configured yet)\""
54
54
  },
55
55
  "dependencies": {
56
- "@lessonkit/core": "0.8.1",
57
- "@lessonkit/themes": "0.8.1",
58
- "@lxpack/api": "^0.4.0"
56
+ "@lessonkit/core": "0.9.1",
57
+ "@lessonkit/themes": "0.9.1",
58
+ "@lxpack/api": "^0.6.0",
59
+ "@lxpack/spa-bridge": "^0.6.0",
60
+ "@lxpack/tracking-schema": "^0.6.0",
61
+ "@lxpack/validators": "^0.6.0"
59
62
  },
60
63
  "devDependencies": {
61
64
  "@types/node": "^22.13.10",