@lessonkit/lxpack 1.0.2 → 1.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/dist/index.js CHANGED
@@ -2,8 +2,187 @@ import {
2
2
  telemetryEventToLessonkit
3
3
  } from "./chunk-DYQI222N.js";
4
4
 
5
- // src/validateDescriptor.ts
5
+ // src/descriptor/normalize.ts
6
6
  import { validateId } from "@lessonkit/core";
7
+ function normalizeDescriptor(input) {
8
+ const course = validateId(input.courseId, "courseId");
9
+ if (!course.ok) throw new Error("normalizeDescriptor called with invalid courseId");
10
+ return {
11
+ ...input,
12
+ courseId: course.id,
13
+ title: input.title.trim(),
14
+ version: input.version?.trim() || void 0,
15
+ spaLessonId: input.spaLessonId?.trim() || void 0,
16
+ lessons: input.lessons.map((lesson) => {
17
+ const idResult = validateId(lesson.id, "lessonId");
18
+ if (!idResult.ok) throw new Error("normalizeDescriptor called with invalid lesson id");
19
+ return {
20
+ ...lesson,
21
+ id: idResult.id,
22
+ title: lesson.title.trim(),
23
+ spaPath: lesson.spaPath?.trim() || void 0
24
+ };
25
+ }),
26
+ assessments: input.assessments?.map((assessment) => {
27
+ const check = validateId(assessment.checkId, "checkId");
28
+ if (!check.ok) throw new Error("normalizeDescriptor called with invalid checkId");
29
+ const question = assessment.question.trim();
30
+ if (assessment.kind === "trueFalse") {
31
+ return { ...assessment, checkId: check.id, question };
32
+ }
33
+ if (assessment.kind === "fillInBlanks") {
34
+ return {
35
+ ...assessment,
36
+ checkId: check.id,
37
+ question,
38
+ template: assessment.template.trim(),
39
+ blanks: assessment.blanks?.map((b) => ({
40
+ id: b.id.trim(),
41
+ answer: b.answer.trim()
42
+ }))
43
+ };
44
+ }
45
+ if (assessment.kind === "findHotspot") {
46
+ return {
47
+ ...assessment,
48
+ checkId: check.id,
49
+ question,
50
+ src: assessment.src.trim(),
51
+ alt: assessment.alt.trim(),
52
+ correctTargetId: assessment.correctTargetId.trim()
53
+ };
54
+ }
55
+ if (assessment.kind === "findMultipleHotspots") {
56
+ return {
57
+ ...assessment,
58
+ checkId: check.id,
59
+ question,
60
+ src: assessment.src.trim(),
61
+ alt: assessment.alt.trim(),
62
+ correctTargetIds: assessment.correctTargetIds.map((id) => id.trim()).filter((id) => id.length > 0)
63
+ };
64
+ }
65
+ const mcq = assessment;
66
+ return {
67
+ ...mcq,
68
+ checkId: check.id,
69
+ question,
70
+ choices: mcq.choices.map((c) => c.trim()).filter((c) => c.length > 0),
71
+ answer: mcq.answer.trim()
72
+ };
73
+ })
74
+ };
75
+ }
76
+
77
+ // src/descriptor/parseInput.ts
78
+ function isRecord(value) {
79
+ return typeof value === "object" && value !== null && !Array.isArray(value);
80
+ }
81
+ function parseLessonDescriptor(raw) {
82
+ if (!isRecord(raw)) {
83
+ return { id: "", title: "" };
84
+ }
85
+ return {
86
+ id: typeof raw.id === "string" ? raw.id : "",
87
+ title: typeof raw.title === "string" ? raw.title : "",
88
+ spaPath: typeof raw.spaPath === "string" ? raw.spaPath : void 0
89
+ };
90
+ }
91
+ function parseAssessmentDescriptor(raw) {
92
+ if (!isRecord(raw)) {
93
+ return { checkId: "", question: "", choices: [], answer: "" };
94
+ }
95
+ const base = {
96
+ checkId: typeof raw.checkId === "string" ? raw.checkId : "",
97
+ question: typeof raw.question === "string" ? raw.question : "",
98
+ passingScore: typeof raw.passingScore === "number" ? raw.passingScore : void 0
99
+ };
100
+ const kind = raw.kind;
101
+ if (kind === "trueFalse") {
102
+ return {
103
+ kind: "trueFalse",
104
+ ...base,
105
+ answer: typeof raw.answer === "boolean" ? raw.answer : raw.answer === "true"
106
+ };
107
+ }
108
+ if (kind === "fillInBlanks") {
109
+ return {
110
+ kind: "fillInBlanks",
111
+ ...base,
112
+ template: typeof raw.template === "string" ? raw.template : "",
113
+ blanks: Array.isArray(raw.blanks) ? raw.blanks.filter((b) => isRecord(b)).map((b) => ({
114
+ id: typeof b.id === "string" ? b.id : "",
115
+ answer: typeof b.answer === "string" ? b.answer : ""
116
+ })) : void 0
117
+ };
118
+ }
119
+ if (kind === "findHotspot") {
120
+ return {
121
+ kind: "findHotspot",
122
+ ...base,
123
+ src: typeof raw.src === "string" ? raw.src : "",
124
+ alt: typeof raw.alt === "string" ? raw.alt : "",
125
+ correctTargetId: typeof raw.correctTargetId === "string" ? raw.correctTargetId : ""
126
+ };
127
+ }
128
+ if (kind === "findMultipleHotspots") {
129
+ return {
130
+ kind: "findMultipleHotspots",
131
+ ...base,
132
+ src: typeof raw.src === "string" ? raw.src : "",
133
+ alt: typeof raw.alt === "string" ? raw.alt : "",
134
+ correctTargetIds: Array.isArray(raw.correctTargetIds) ? raw.correctTargetIds.filter((id) => typeof id === "string") : []
135
+ };
136
+ }
137
+ return {
138
+ kind: kind === "mcq" ? "mcq" : void 0,
139
+ ...base,
140
+ choices: Array.isArray(raw.choices) ? raw.choices.filter((c) => typeof c === "string") : [],
141
+ answer: typeof raw.answer === "string" ? raw.answer : ""
142
+ };
143
+ }
144
+ function parseCourseDescriptorInput(input) {
145
+ if (!isRecord(input)) return null;
146
+ const trackingRaw = input.tracking;
147
+ let tracking;
148
+ if (isRecord(trackingRaw)) {
149
+ const completionRaw = trackingRaw.completion;
150
+ const xapiRaw = trackingRaw.xapi;
151
+ tracking = {
152
+ completion: isRecord(completionRaw) ? {
153
+ threshold: typeof completionRaw.threshold === "number" ? completionRaw.threshold : void 0
154
+ } : void 0,
155
+ xapi: isRecord(xapiRaw) ? {
156
+ activityIri: typeof xapiRaw.activityIri === "string" ? xapiRaw.activityIri : void 0
157
+ } : void 0
158
+ };
159
+ }
160
+ const themeRaw = input.theme;
161
+ let theme;
162
+ if (isRecord(themeRaw)) {
163
+ theme = {
164
+ preset: typeof themeRaw.preset === "string" ? themeRaw.preset : void 0
165
+ };
166
+ if (isRecord(themeRaw.theme)) {
167
+ theme.theme = themeRaw.theme;
168
+ }
169
+ }
170
+ return {
171
+ courseId: typeof input.courseId === "string" ? input.courseId : "",
172
+ title: typeof input.title === "string" ? input.title : "",
173
+ version: typeof input.version === "string" ? input.version : void 0,
174
+ layout: typeof input.layout === "string" ? input.layout : void 0,
175
+ lessons: Array.isArray(input.lessons) ? input.lessons.map(parseLessonDescriptor) : [],
176
+ assessments: Array.isArray(input.assessments) ? input.assessments.map(parseAssessmentDescriptor) : void 0,
177
+ theme,
178
+ tracking,
179
+ spaDistDir: typeof input.spaDistDir === "string" ? input.spaDistDir : void 0,
180
+ spaLessonId: typeof input.spaLessonId === "string" ? input.spaLessonId : void 0
181
+ };
182
+ }
183
+
184
+ // src/descriptor/validateCourse.ts
185
+ import { validateId as validateId3 } from "@lessonkit/core";
7
186
 
8
187
  // src/spaPath.ts
9
188
  import { realpathSync } from "fs";
@@ -28,7 +207,8 @@ function assertResolvedPathUnderRoot(root, target) {
28
207
  const targetResolved = resolveComparablePath(target);
29
208
  const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
30
209
  const win32Prefix = rootResolved.endsWith(win32.sep) ? rootResolved : rootResolved + win32.sep;
31
- if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) && !targetResolved.startsWith(win32Prefix)) {
210
+ if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) && /* v8 ignore next */
211
+ !targetResolved.startsWith(win32Prefix)) {
32
212
  throw new Error(`unsafe path escapes project root: ${target}`);
33
213
  }
34
214
  }
@@ -89,133 +269,69 @@ function themeToLxpackRuntime(input) {
89
269
  };
90
270
  }
91
271
 
92
- // src/validateDescriptor.ts
93
- var VALID_LAYOUTS = ["single-spa", "per-lesson-spa"];
94
- var VALID_THEME_PRESETS = ["default", "light", "dark", "brand"];
95
- function isRecord(value) {
96
- return typeof value === "object" && value !== null && !Array.isArray(value);
97
- }
98
- function parseLessonDescriptor(raw) {
99
- if (!isRecord(raw)) {
100
- return { id: "", title: "" };
272
+ // src/descriptor/validateAssessments.ts
273
+ import { validateId as validateId2 } from "@lessonkit/core";
274
+ var validateMcqLike = (assessment, path, issues) => {
275
+ if (!("choices" in assessment) || !("answer" in assessment) || typeof assessment.answer !== "string") {
276
+ return;
101
277
  }
102
- return {
103
- id: typeof raw.id === "string" ? raw.id : "",
104
- title: typeof raw.title === "string" ? raw.title : "",
105
- spaPath: typeof raw.spaPath === "string" ? raw.spaPath : void 0
106
- };
107
- }
108
- function parseAssessmentDescriptor(raw) {
109
- if (!isRecord(raw)) {
110
- return { checkId: "", question: "", choices: [], answer: "" };
278
+ const trimmedChoices = assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0);
279
+ if (!trimmedChoices.length) {
280
+ issues.push({ path: `${path}.choices`, message: "at least one non-empty choice is required" });
111
281
  }
112
- return {
113
- checkId: typeof raw.checkId === "string" ? raw.checkId : "",
114
- question: typeof raw.question === "string" ? raw.question : "",
115
- choices: Array.isArray(raw.choices) ? raw.choices.filter((c) => typeof c === "string") : [],
116
- answer: typeof raw.answer === "string" ? raw.answer : "",
117
- passingScore: typeof raw.passingScore === "number" ? raw.passingScore : void 0
118
- };
119
- }
120
- function parseCourseDescriptorInput(input) {
121
- if (!isRecord(input)) return null;
122
- const trackingRaw = input.tracking;
123
- let tracking;
124
- if (isRecord(trackingRaw)) {
125
- const completionRaw = trackingRaw.completion;
126
- const xapiRaw = trackingRaw.xapi;
127
- tracking = {
128
- completion: isRecord(completionRaw) ? {
129
- threshold: typeof completionRaw.threshold === "number" ? completionRaw.threshold : void 0
130
- } : void 0,
131
- xapi: isRecord(xapiRaw) ? {
132
- activityIri: typeof xapiRaw.activityIri === "string" ? xapiRaw.activityIri : void 0
133
- } : void 0
134
- };
282
+ if (!assessment.answer.trim()) {
283
+ issues.push({ path: `${path}.answer`, message: "answer is required" });
284
+ } else if (trimmedChoices.length && !trimmedChoices.includes(assessment.answer.trim())) {
285
+ issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
135
286
  }
136
- const themeRaw = input.theme;
137
- let theme;
138
- if (isRecord(themeRaw)) {
139
- theme = {
140
- preset: typeof themeRaw.preset === "string" ? themeRaw.preset : void 0
141
- };
142
- if (isRecord(themeRaw.theme)) {
143
- theme.theme = themeRaw.theme;
287
+ };
288
+ var ASSESSMENT_VALIDATORS = {
289
+ mcq: validateMcqLike,
290
+ trueFalse: (assessment, path, issues) => {
291
+ if (assessment.kind === "trueFalse" && typeof assessment.answer !== "boolean") {
292
+ issues.push({ path: `${path}.answer`, message: "answer must be a boolean for trueFalse" });
144
293
  }
294
+ },
295
+ fillInBlanks: (assessment, path, issues) => {
296
+ if (assessment.kind === "fillInBlanks" && !assessment.template?.trim()) {
297
+ issues.push({ path: `${path}.template`, message: "template is required for fillInBlanks" });
298
+ }
299
+ },
300
+ findHotspot: () => {
301
+ },
302
+ findMultipleHotspots: () => {
145
303
  }
146
- return {
147
- courseId: typeof input.courseId === "string" ? input.courseId : "",
148
- title: typeof input.title === "string" ? input.title : "",
149
- version: typeof input.version === "string" ? input.version : void 0,
150
- layout: typeof input.layout === "string" ? input.layout : void 0,
151
- lessons: Array.isArray(input.lessons) ? input.lessons.map(parseLessonDescriptor) : [],
152
- assessments: Array.isArray(input.assessments) ? input.assessments.map(parseAssessmentDescriptor) : void 0,
153
- theme,
154
- tracking,
155
- spaDistDir: typeof input.spaDistDir === "string" ? input.spaDistDir : void 0,
156
- spaLessonId: typeof input.spaLessonId === "string" ? input.spaLessonId : void 0
157
- };
158
- }
159
- function normalizeDescriptor(input) {
160
- const course = validateId(input.courseId, "courseId");
161
- if (!course.ok) throw new Error("normalizeDescriptor called with invalid courseId");
162
- return {
163
- ...input,
164
- courseId: course.id,
165
- title: input.title.trim(),
166
- version: input.version?.trim() || void 0,
167
- spaLessonId: input.spaLessonId?.trim() || void 0,
168
- lessons: input.lessons.map((lesson) => {
169
- const idResult = validateId(lesson.id, "lessonId");
170
- if (!idResult.ok) throw new Error("normalizeDescriptor called with invalid lesson id");
171
- return {
172
- ...lesson,
173
- id: idResult.id,
174
- title: lesson.title.trim(),
175
- spaPath: lesson.spaPath?.trim() || void 0
176
- };
177
- }),
178
- assessments: input.assessments?.map((assessment) => {
179
- const check = validateId(assessment.checkId, "checkId");
180
- if (!check.ok) throw new Error("normalizeDescriptor called with invalid checkId");
181
- return {
182
- ...assessment,
183
- checkId: check.id,
184
- question: assessment.question.trim(),
185
- choices: assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0),
186
- answer: assessment.answer.trim()
187
- };
188
- })
189
- };
190
- }
191
- function validateDescriptor(input) {
192
- const parsed = parseCourseDescriptorInput(input);
193
- if (parsed === null) {
194
- return { ok: false, issues: [{ path: "course", message: "must be an object" }] };
195
- }
196
- return validateDescriptorParsed(parsed);
197
- }
198
- function validateDescriptorForTarget(input, target) {
199
- const result = validateDescriptor(input);
200
- if (!result.ok || !target) return result;
201
- if (target !== "xapi" && target !== "cmi5") return result;
202
- const activityIri = result.descriptor.tracking?.xapi?.activityIri?.trim();
203
- if (!activityIri) {
204
- return {
205
- ok: false,
206
- issues: [
207
- {
208
- path: "course.tracking.xapi.activityIri",
209
- message: "tracking.xapi.activityIri is required for xapi and cmi5 export targets"
210
- }
211
- ]
212
- };
304
+ };
305
+ function validateAssessmentEntry(assessment, index, issues, checkIds) {
306
+ const path = `assessments[${index}]`;
307
+ const check = validateId2(assessment.checkId, `${path}.checkId`);
308
+ if (!check.ok) {
309
+ issues.push(...check.issues.map((i) => ({ path: i.path, message: i.message })));
310
+ } else if (checkIds.has(check.id)) {
311
+ issues.push({ path: `${path}.checkId`, message: "duplicate checkId" });
312
+ } else {
313
+ checkIds.add(check.id);
314
+ }
315
+ if (!assessment.question?.trim()) {
316
+ issues.push({ path: `${path}.question`, message: "question is required" });
317
+ }
318
+ const kind = assessment.kind ?? "mcq";
319
+ ASSESSMENT_VALIDATORS[kind](assessment, path, issues);
320
+ const passingScore = assessment.passingScore;
321
+ if (passingScore !== void 0 && !(Number.isFinite(passingScore) && passingScore > 0)) {
322
+ issues.push({
323
+ path: `${path}.passingScore`,
324
+ message: "passingScore must be greater than 0 (absolute point threshold)"
325
+ });
213
326
  }
214
- return result;
215
327
  }
216
- function validateDescriptorParsed(input) {
328
+
329
+ // src/descriptor/validateCourse.ts
330
+ var VALID_LAYOUTS = ["single-spa", "per-lesson-spa"];
331
+ var VALID_THEME_PRESETS = ["default", "light", "dark", "brand"];
332
+ function validateCourseDescriptor(input) {
217
333
  const issues = [];
218
- const course = validateId(input.courseId, "courseId");
334
+ const course = validateId3(input.courseId, "courseId");
219
335
  if (!course.ok) issues.push(...course.issues.map((i) => ({ path: i.path, message: i.message })));
220
336
  if (!input.title?.trim()) {
221
337
  issues.push({ path: "title", message: "title is required" });
@@ -268,7 +384,7 @@ function validateDescriptorParsed(input) {
268
384
  const spaPaths = /* @__PURE__ */ new Set();
269
385
  for (const [index, lesson] of (input.lessons ?? []).entries()) {
270
386
  const path = `lessons[${index}]`;
271
- const lessonResult = validateId(lesson.id, `${path}.id`);
387
+ const lessonResult = validateId3(lesson.id, `${path}.id`);
272
388
  if (!lessonResult.ok) {
273
389
  issues.push(...lessonResult.issues.map((i) => ({ path: i.path, message: i.message })));
274
390
  } else if (lessonIds.has(lessonResult.id)) {
@@ -300,7 +416,7 @@ function validateDescriptorParsed(input) {
300
416
  }
301
417
  if (layout === "single-spa" && input.spaLessonId?.trim()) {
302
418
  const spaId = input.spaLessonId.trim();
303
- const spaResult = validateId(spaId, "spaLessonId");
419
+ const spaResult = validateId3(spaId, "spaLessonId");
304
420
  if (!spaResult.ok) {
305
421
  issues.push(...spaResult.issues.map((i) => ({ path: i.path, message: i.message })));
306
422
  } else if (!lessonIds.has(spaResult.id)) {
@@ -312,41 +428,48 @@ function validateDescriptorParsed(input) {
312
428
  }
313
429
  const checkIds = /* @__PURE__ */ new Set();
314
430
  for (const [index, assessment] of (input.assessments ?? []).entries()) {
315
- const path = `assessments[${index}]`;
316
- const check = validateId(assessment.checkId, `${path}.checkId`);
317
- if (!check.ok) {
318
- issues.push(...check.issues.map((i) => ({ path: i.path, message: i.message })));
319
- } else if (checkIds.has(check.id)) {
320
- issues.push({ path: `${path}.checkId`, message: "duplicate checkId" });
321
- } else {
322
- checkIds.add(check.id);
323
- }
324
- if (!assessment.question?.trim()) {
325
- issues.push({ path: `${path}.question`, message: "question is required" });
326
- }
327
- const trimmedChoices = (assessment.choices ?? []).map((c) => c.trim()).filter((c) => c.length > 0);
328
- if (!trimmedChoices.length) {
329
- issues.push({
330
- path: `${path}.choices`,
331
- message: "at least one non-empty choice is required"
332
- });
333
- }
334
- if (!assessment.answer?.trim()) {
335
- issues.push({ path: `${path}.answer`, message: "answer is required" });
336
- } else if (trimmedChoices.length && !trimmedChoices.includes(assessment.answer.trim())) {
337
- issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
338
- }
339
- const passingScore = assessment.passingScore;
340
- if (passingScore !== void 0 && !(Number.isFinite(passingScore) && passingScore > 0)) {
341
- issues.push({
342
- path: `${path}.passingScore`,
343
- message: "passingScore must be greater than 0 (absolute point threshold)"
344
- });
345
- }
431
+ validateAssessmentEntry(assessment, index, issues, checkIds);
432
+ }
433
+ return issues;
434
+ }
435
+
436
+ // src/descriptor/validateForTarget.ts
437
+ function validateDescriptorForExportTarget(descriptor, target) {
438
+ if (target !== "xapi" && target !== "cmi5") return [];
439
+ const activityIri = descriptor.tracking?.xapi?.activityIri?.trim();
440
+ if (!activityIri) {
441
+ return [
442
+ {
443
+ path: "course.tracking.xapi.activityIri",
444
+ message: "tracking.xapi.activityIri is required for xapi and cmi5 export targets"
445
+ }
446
+ ];
346
447
  }
448
+ return [];
449
+ }
450
+
451
+ // src/validateDescriptor.ts
452
+ function validateDescriptorParsed(input) {
453
+ const issues = validateCourseDescriptor(input);
347
454
  if (issues.length) return { ok: false, issues };
348
455
  return { ok: true, descriptor: normalizeDescriptor(input) };
349
456
  }
457
+ function validateDescriptor(input) {
458
+ const parsed = parseCourseDescriptorInput(input);
459
+ if (parsed === null) {
460
+ return { ok: false, issues: [{ path: "course", message: "must be an object" }] };
461
+ }
462
+ return validateDescriptorParsed(parsed);
463
+ }
464
+ function validateDescriptorForTarget(input, target) {
465
+ const result = validateDescriptor(input);
466
+ if (!result.ok || !target) return result;
467
+ const targetIssues = validateDescriptorForExportTarget(result.descriptor, target);
468
+ if (targetIssues.length) {
469
+ return { ok: false, issues: targetIssues };
470
+ }
471
+ return result;
472
+ }
350
473
 
351
474
  // src/validateProjectPaths.ts
352
475
  import { isAbsolute as isAbsolute2, resolve as resolve2 } from "path";
@@ -417,7 +540,7 @@ function slugChoiceId(text, index) {
417
540
  const stem = base.length ? base : "choice";
418
541
  return `${stem}-${index + 1}`;
419
542
  }
420
- function assessmentDescriptorToLxpack(assessment) {
543
+ function mcqToLxpack(assessment) {
421
544
  const choices = assessment.choices.map((text, index) => {
422
545
  const id = slugChoiceId(text, index);
423
546
  return {
@@ -438,8 +561,43 @@ function assessmentDescriptorToLxpack(assessment) {
438
561
  ]
439
562
  };
440
563
  }
564
+ function assessmentDescriptorToLxpack(assessment) {
565
+ const kind = assessment.kind ?? "mcq";
566
+ if (kind === "trueFalse" && assessment.kind === "trueFalse") {
567
+ const choices = ["True", "False"];
568
+ const answerText = assessment.answer ? "True" : "False";
569
+ return mcqToLxpack({
570
+ kind: "mcq",
571
+ checkId: assessment.checkId,
572
+ question: assessment.question,
573
+ choices,
574
+ answer: answerText,
575
+ passingScore: assessment.passingScore
576
+ });
577
+ }
578
+ if (kind === "fillInBlanks") {
579
+ return null;
580
+ }
581
+ if (kind === "findHotspot" && assessment.kind === "findHotspot") {
582
+ return mcqToLxpack({
583
+ kind: "mcq",
584
+ checkId: assessment.checkId,
585
+ question: assessment.question,
586
+ choices: [assessment.correctTargetId, "other"],
587
+ answer: assessment.correctTargetId,
588
+ passingScore: assessment.passingScore
589
+ });
590
+ }
591
+ if (kind === "findMultipleHotspots") {
592
+ return null;
593
+ }
594
+ if ("choices" in assessment && "answer" in assessment && typeof assessment.answer === "string") {
595
+ return mcqToLxpack(assessment);
596
+ }
597
+ return null;
598
+ }
441
599
  function extractAssessments(descriptor) {
442
- return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
600
+ return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack).filter((a) => a !== null);
443
601
  }
444
602
 
445
603
  // src/interchange.ts
@@ -512,7 +670,8 @@ async function resolveSpaDirs(options) {
512
670
  const { descriptor, spaDistDir, lessonSpaDirs, projectRoot } = options;
513
671
  const spaLessons = resolveSpaLessons(descriptor);
514
672
  if (descriptor.layout === "single-spa") {
515
- const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? "dist";
673
+ const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? /* v8 ignore next */
674
+ "dist";
516
675
  const srcDist = projectRoot ? resolve3(projectRoot, spaDistRelative) : resolve3(spaDistRelative);
517
676
  if (projectRoot) {
518
677
  assertRealPathUnderRoot(resolve3(projectRoot), srcDist);
@@ -527,7 +686,8 @@ async function resolveSpaDirs(options) {
527
686
  } catch {
528
687
  throw new Error(`spaDistDir must contain index.html: ${join(srcDist, "index.html")}`);
529
688
  }
530
- const lessonId = spaLessons[0]?.id ?? "main";
689
+ const lessonId = spaLessons[0]?.id ?? /* v8 ignore next */
690
+ "main";
531
691
  return { [lessonId]: srcDist };
532
692
  }
533
693
  const dirs = {};
@@ -614,7 +774,15 @@ function validatePackageInputs(options) {
614
774
  ok: false,
615
775
  courseDir: outDir,
616
776
  target,
617
- issues: [{ path: "outDir", message: err instanceof Error ? err.message : String(err) }]
777
+ issues: [
778
+ {
779
+ path: "outDir",
780
+ message: (
781
+ /* v8 ignore next */
782
+ err instanceof Error ? err.message : String(err)
783
+ )
784
+ }
785
+ ]
618
786
  };
619
787
  }
620
788
  }
@@ -646,7 +814,10 @@ function validatePackageInputs(options) {
646
814
  issues: [
647
815
  {
648
816
  path: "outputBaseDir",
649
- message: err instanceof Error ? err.message : String(err)
817
+ message: (
818
+ /* v8 ignore next */
819
+ err instanceof Error ? err.message : String(err)
820
+ )
650
821
  }
651
822
  ]
652
823
  };
@@ -661,7 +832,15 @@ function validatePackageInputs(options) {
661
832
  ok: false,
662
833
  courseDir: outDir,
663
834
  target,
664
- issues: [{ path: "output", message: err instanceof Error ? err.message : String(err) }]
835
+ issues: [
836
+ {
837
+ path: "output",
838
+ message: (
839
+ /* v8 ignore next */
840
+ err instanceof Error ? err.message : String(err)
841
+ )
842
+ }
843
+ ]
665
844
  };
666
845
  }
667
846
  }
@@ -750,7 +929,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
750
929
  try {
751
930
  await renameOrCopy(tmpPromote, failedPromote2);
752
931
  } catch {
753
- await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(() => void 0);
932
+ await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
933
+ /* v8 ignore next */
934
+ () => void 0
935
+ );
754
936
  }
755
937
  const promoteMsg = promoteError instanceof Error ? promoteError.message : String(promoteError);
756
938
  const restoreMsg = restoreError instanceof Error ? restoreError.message : String(restoreError);
@@ -766,7 +948,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
766
948
  `[lessonkit/lxpack] failed to restore ${stagingDir} after promote error:`,
767
949
  restoreError instanceof Error ? restoreError.message : restoreError
768
950
  );
769
- await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(() => void 0);
951
+ await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
952
+ /* v8 ignore next */
953
+ () => void 0
954
+ );
770
955
  }
771
956
  throw promoteError;
772
957
  }
@@ -774,12 +959,18 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
774
959
  try {
775
960
  await renameOrCopy(tmpPromote, failedPromote);
776
961
  } catch {
777
- await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(() => void 0);
962
+ await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
963
+ /* v8 ignore next */
964
+ () => void 0
965
+ );
778
966
  }
779
967
  throw promoteError;
780
968
  }
781
969
  if (backup) {
782
- await fsp.rm(backup, { recursive: true, force: true }).catch(() => void 0);
970
+ await fsp.rm(backup, { recursive: true, force: true }).catch(
971
+ /* v8 ignore next */
972
+ () => void 0
973
+ );
783
974
  }
784
975
  }
785
976
 
@@ -842,7 +1033,10 @@ async function buildStagingPackage(options) {
842
1033
  outputDir: "outputDir" in build ? build.outputDir : void 0
843
1034
  };
844
1035
  } catch (err) {
845
- await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
1036
+ await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(
1037
+ /* v8 ignore next */
1038
+ () => void 0
1039
+ );
846
1040
  throw err;
847
1041
  }
848
1042
  }
@@ -908,7 +1102,10 @@ async function packageLessonkitCourse(options) {
908
1102
  outputBaseDir
909
1103
  });
910
1104
  if (!staged.ok) {
911
- await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(() => void 0);
1105
+ await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(
1106
+ /* v8 ignore next */
1107
+ () => void 0
1108
+ );
912
1109
  const validation2 = staged.build ? { ok: false, issues: staged.build.issues } : void 0;
913
1110
  return {
914
1111
  ok: false,
@@ -926,7 +1123,10 @@ async function packageLessonkitCourse(options) {
926
1123
  validateArtifactInStaging(stagingRoot, staged.outputDir, "outputDir")
927
1124
  ].filter((issue) => issue != null);
928
1125
  if (artifactIssues.length > 0) {
929
- await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
1126
+ await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(
1127
+ /* v8 ignore next */
1128
+ () => void 0
1129
+ );
930
1130
  return {
931
1131
  ok: false,
932
1132
  courseDir: outDir,
@@ -1028,7 +1228,10 @@ function parseLessonkitManifest(raw, label = "lessonkit.json", projectRoot) {
1028
1228
  if (!validation.ok) {
1029
1229
  for (const i of validation.issues) {
1030
1230
  issues.push({
1031
- path: i.path.startsWith("course.") ? i.path : `course.${i.path}`,
1231
+ path: (
1232
+ /* v8 ignore next */
1233
+ i.path.startsWith("course.") ? i.path : `course.${i.path}`
1234
+ ),
1032
1235
  message: i.message
1033
1236
  });
1034
1237
  }