@lessonkit/lxpack 1.0.2 → 1.1.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/bridge.cjs +21 -0
- package/dist/bridge.d.cts +0 -1
- package/dist/bridge.d.ts +0 -1
- package/dist/bridge.js +21 -0
- package/dist/index.cjs +149 -31
- package/dist/index.d.cts +24 -7
- package/dist/index.d.ts +24 -7
- package/dist/index.js +149 -31
- package/package.json +3 -3
package/dist/bridge.cjs
CHANGED
|
@@ -155,10 +155,31 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
|
+
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
159
|
+
const data = event.data;
|
|
160
|
+
const scaled = normalizeAssessmentScore({
|
|
161
|
+
score: data.score,
|
|
162
|
+
maxScore: data.maxScore
|
|
163
|
+
});
|
|
164
|
+
if (scaled === null) return;
|
|
165
|
+
bridge.submitAssessment?.({
|
|
166
|
+
id: data.checkId,
|
|
167
|
+
score: scaled,
|
|
168
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
169
|
+
passingScore: data.passingScore,
|
|
170
|
+
maxScore: data.maxScore
|
|
171
|
+
}),
|
|
172
|
+
maxScore: data.maxScore
|
|
173
|
+
});
|
|
174
|
+
}
|
|
158
175
|
function forwardTelemetryToBridge(event, mode = "auto", parentWindow) {
|
|
159
176
|
if (mode === "off") return;
|
|
160
177
|
const bridge = getBridge(parentWindow);
|
|
161
178
|
if (!bridge) return;
|
|
179
|
+
if (event.name === "assessment_completed") {
|
|
180
|
+
forwardAssessmentCompletedToBridge(bridge, event);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
162
183
|
const lessonkitEvent = telemetryEventToLessonkit(event);
|
|
163
184
|
if (!lessonkitEvent) return;
|
|
164
185
|
const action = (0, import_tracking_schema3.mapLessonkitTelemetryToBridgeAction)(lessonkitEvent);
|
package/dist/bridge.d.cts
CHANGED
|
@@ -24,7 +24,6 @@ declare function normalizeAssessmentPassingScore(opts?: {
|
|
|
24
24
|
type LxpackBridgeMode = "auto" | "off";
|
|
25
25
|
/** Apply a mapped bridge action to an LXPack bridge instance. */
|
|
26
26
|
declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction>): void;
|
|
27
|
-
/** Resolve bridge and dispatch a telemetry-derived action. */
|
|
28
27
|
declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window): void;
|
|
29
28
|
declare function createLxpackBridge(): LxpackBridgeV1 | null;
|
|
30
29
|
declare function notifyLxpackLessonComplete(lessonId: LessonId): boolean;
|
package/dist/bridge.d.ts
CHANGED
|
@@ -24,7 +24,6 @@ declare function normalizeAssessmentPassingScore(opts?: {
|
|
|
24
24
|
type LxpackBridgeMode = "auto" | "off";
|
|
25
25
|
/** Apply a mapped bridge action to an LXPack bridge instance. */
|
|
26
26
|
declare function dispatchBridgeAction(bridge: LxpackBridgeV1, action: ReturnType<typeof mapLessonkitTelemetryToBridgeAction>): void;
|
|
27
|
-
/** Resolve bridge and dispatch a telemetry-derived action. */
|
|
28
27
|
declare function forwardTelemetryToBridge(event: TelemetryEvent, mode?: LxpackBridgeMode, parentWindow?: Window): void;
|
|
29
28
|
declare function createLxpackBridge(): LxpackBridgeV1 | null;
|
|
30
29
|
declare function notifyLxpackLessonComplete(lessonId: LessonId): boolean;
|
package/dist/bridge.js
CHANGED
|
@@ -93,10 +93,31 @@ function dispatchBridgeActionInner(bridge, action) {
|
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
|
+
function forwardAssessmentCompletedToBridge(bridge, event) {
|
|
97
|
+
const data = event.data;
|
|
98
|
+
const scaled = normalizeAssessmentScore({
|
|
99
|
+
score: data.score,
|
|
100
|
+
maxScore: data.maxScore
|
|
101
|
+
});
|
|
102
|
+
if (scaled === null) return;
|
|
103
|
+
bridge.submitAssessment?.({
|
|
104
|
+
id: data.checkId,
|
|
105
|
+
score: scaled,
|
|
106
|
+
passingScore: normalizeAssessmentPassingScore({
|
|
107
|
+
passingScore: data.passingScore,
|
|
108
|
+
maxScore: data.maxScore
|
|
109
|
+
}),
|
|
110
|
+
maxScore: data.maxScore
|
|
111
|
+
});
|
|
112
|
+
}
|
|
96
113
|
function forwardTelemetryToBridge(event, mode = "auto", parentWindow) {
|
|
97
114
|
if (mode === "off") return;
|
|
98
115
|
const bridge = getBridge(parentWindow);
|
|
99
116
|
if (!bridge) return;
|
|
117
|
+
if (event.name === "assessment_completed") {
|
|
118
|
+
forwardAssessmentCompletedToBridge(bridge, event);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
100
121
|
const lessonkitEvent = telemetryEventToLessonkit(event);
|
|
101
122
|
if (!lessonkitEvent) return;
|
|
102
123
|
const action = mapLessonkitTelemetryToBridgeAction2(lessonkitEvent);
|
package/dist/index.cjs
CHANGED
|
@@ -87,7 +87,8 @@ function assertResolvedPathUnderRoot(root, target) {
|
|
|
87
87
|
const targetResolved = resolveComparablePath(target);
|
|
88
88
|
const prefix = rootResolved.endsWith(import_node_path.sep) ? rootResolved : rootResolved + import_node_path.sep;
|
|
89
89
|
const win32Prefix = rootResolved.endsWith(import_node_path.win32.sep) ? rootResolved : rootResolved + import_node_path.win32.sep;
|
|
90
|
-
if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) &&
|
|
90
|
+
if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) && /* v8 ignore next */
|
|
91
|
+
!targetResolved.startsWith(win32Prefix)) {
|
|
91
92
|
throw new Error(`unsafe path escapes project root: ${target}`);
|
|
92
93
|
}
|
|
93
94
|
}
|
|
@@ -168,13 +169,36 @@ function parseAssessmentDescriptor(raw) {
|
|
|
168
169
|
if (!isRecord(raw)) {
|
|
169
170
|
return { checkId: "", question: "", choices: [], answer: "" };
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
+
const base = {
|
|
172
173
|
checkId: typeof raw.checkId === "string" ? raw.checkId : "",
|
|
173
174
|
question: typeof raw.question === "string" ? raw.question : "",
|
|
174
|
-
choices: Array.isArray(raw.choices) ? raw.choices.filter((c) => typeof c === "string") : [],
|
|
175
|
-
answer: typeof raw.answer === "string" ? raw.answer : "",
|
|
176
175
|
passingScore: typeof raw.passingScore === "number" ? raw.passingScore : void 0
|
|
177
176
|
};
|
|
177
|
+
const kind = raw.kind;
|
|
178
|
+
if (kind === "trueFalse") {
|
|
179
|
+
return {
|
|
180
|
+
kind: "trueFalse",
|
|
181
|
+
...base,
|
|
182
|
+
answer: typeof raw.answer === "boolean" ? raw.answer : raw.answer === "true"
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (kind === "fillInBlanks") {
|
|
186
|
+
return {
|
|
187
|
+
kind: "fillInBlanks",
|
|
188
|
+
...base,
|
|
189
|
+
template: typeof raw.template === "string" ? raw.template : "",
|
|
190
|
+
blanks: Array.isArray(raw.blanks) ? raw.blanks.filter((b) => isRecord(b)).map((b) => ({
|
|
191
|
+
id: typeof b.id === "string" ? b.id : "",
|
|
192
|
+
answer: typeof b.answer === "string" ? b.answer : ""
|
|
193
|
+
})) : void 0
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
kind: kind === "mcq" ? "mcq" : void 0,
|
|
198
|
+
...base,
|
|
199
|
+
choices: Array.isArray(raw.choices) ? raw.choices.filter((c) => typeof c === "string") : [],
|
|
200
|
+
answer: typeof raw.answer === "string" ? raw.answer : ""
|
|
201
|
+
};
|
|
178
202
|
}
|
|
179
203
|
function parseCourseDescriptorInput(input) {
|
|
180
204
|
if (!isRecord(input)) return null;
|
|
@@ -237,10 +261,26 @@ function normalizeDescriptor(input) {
|
|
|
237
261
|
assessments: input.assessments?.map((assessment) => {
|
|
238
262
|
const check = (0, import_core.validateId)(assessment.checkId, "checkId");
|
|
239
263
|
if (!check.ok) throw new Error("normalizeDescriptor called with invalid checkId");
|
|
264
|
+
const question = assessment.question.trim();
|
|
265
|
+
if (assessment.kind === "trueFalse") {
|
|
266
|
+
return { ...assessment, checkId: check.id, question };
|
|
267
|
+
}
|
|
268
|
+
if (assessment.kind === "fillInBlanks") {
|
|
269
|
+
return {
|
|
270
|
+
...assessment,
|
|
271
|
+
checkId: check.id,
|
|
272
|
+
question,
|
|
273
|
+
template: assessment.template.trim(),
|
|
274
|
+
blanks: assessment.blanks?.map((b) => ({
|
|
275
|
+
id: b.id.trim(),
|
|
276
|
+
answer: b.answer.trim()
|
|
277
|
+
}))
|
|
278
|
+
};
|
|
279
|
+
}
|
|
240
280
|
return {
|
|
241
281
|
...assessment,
|
|
242
282
|
checkId: check.id,
|
|
243
|
-
question
|
|
283
|
+
question,
|
|
244
284
|
choices: assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0),
|
|
245
285
|
answer: assessment.answer.trim()
|
|
246
286
|
};
|
|
@@ -383,17 +423,28 @@ function validateDescriptorParsed(input) {
|
|
|
383
423
|
if (!assessment.question?.trim()) {
|
|
384
424
|
issues.push({ path: `${path}.question`, message: "question is required" });
|
|
385
425
|
}
|
|
386
|
-
const
|
|
387
|
-
if (
|
|
388
|
-
|
|
389
|
-
path: `${path}.
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
} else if (
|
|
396
|
-
|
|
426
|
+
const kind = assessment.kind ?? "mcq";
|
|
427
|
+
if (kind === "trueFalse" && assessment.kind === "trueFalse") {
|
|
428
|
+
if (typeof assessment.answer !== "boolean") {
|
|
429
|
+
issues.push({ path: `${path}.answer`, message: "answer must be a boolean for trueFalse" });
|
|
430
|
+
}
|
|
431
|
+
} else if (kind === "fillInBlanks" && assessment.kind === "fillInBlanks") {
|
|
432
|
+
if (!assessment.template?.trim()) {
|
|
433
|
+
issues.push({ path: `${path}.template`, message: "template is required for fillInBlanks" });
|
|
434
|
+
}
|
|
435
|
+
} else if ("choices" in assessment && "answer" in assessment && typeof assessment.answer === "string") {
|
|
436
|
+
const trimmedChoices = assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0);
|
|
437
|
+
if (!trimmedChoices.length) {
|
|
438
|
+
issues.push({
|
|
439
|
+
path: `${path}.choices`,
|
|
440
|
+
message: "at least one non-empty choice is required"
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (!assessment.answer.trim()) {
|
|
444
|
+
issues.push({ path: `${path}.answer`, message: "answer is required" });
|
|
445
|
+
} else if (trimmedChoices.length && !trimmedChoices.includes(assessment.answer.trim())) {
|
|
446
|
+
issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
|
|
447
|
+
}
|
|
397
448
|
}
|
|
398
449
|
const passingScore = assessment.passingScore;
|
|
399
450
|
if (passingScore !== void 0 && !(Number.isFinite(passingScore) && passingScore > 0)) {
|
|
@@ -476,7 +527,7 @@ function slugChoiceId(text, index) {
|
|
|
476
527
|
const stem = base.length ? base : "choice";
|
|
477
528
|
return `${stem}-${index + 1}`;
|
|
478
529
|
}
|
|
479
|
-
function
|
|
530
|
+
function mcqToLxpack(assessment) {
|
|
480
531
|
const choices = assessment.choices.map((text, index) => {
|
|
481
532
|
const id = slugChoiceId(text, index);
|
|
482
533
|
return {
|
|
@@ -497,8 +548,30 @@ function assessmentDescriptorToLxpack(assessment) {
|
|
|
497
548
|
]
|
|
498
549
|
};
|
|
499
550
|
}
|
|
551
|
+
function assessmentDescriptorToLxpack(assessment) {
|
|
552
|
+
const kind = assessment.kind ?? "mcq";
|
|
553
|
+
if (kind === "trueFalse" && assessment.kind === "trueFalse") {
|
|
554
|
+
const choices = ["True", "False"];
|
|
555
|
+
const answerText = assessment.answer ? "True" : "False";
|
|
556
|
+
return mcqToLxpack({
|
|
557
|
+
kind: "mcq",
|
|
558
|
+
checkId: assessment.checkId,
|
|
559
|
+
question: assessment.question,
|
|
560
|
+
choices,
|
|
561
|
+
answer: answerText,
|
|
562
|
+
passingScore: assessment.passingScore
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
if (kind === "fillInBlanks") {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
if ("choices" in assessment && "answer" in assessment && typeof assessment.answer === "string") {
|
|
569
|
+
return mcqToLxpack(assessment);
|
|
570
|
+
}
|
|
571
|
+
return null;
|
|
572
|
+
}
|
|
500
573
|
function extractAssessments(descriptor) {
|
|
501
|
-
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
|
|
574
|
+
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack).filter((a) => a !== null);
|
|
502
575
|
}
|
|
503
576
|
|
|
504
577
|
// src/interchange.ts
|
|
@@ -571,7 +644,8 @@ async function resolveSpaDirs(options) {
|
|
|
571
644
|
const { descriptor, spaDistDir, lessonSpaDirs, projectRoot } = options;
|
|
572
645
|
const spaLessons = resolveSpaLessons(descriptor);
|
|
573
646
|
if (descriptor.layout === "single-spa") {
|
|
574
|
-
const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ??
|
|
647
|
+
const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? /* v8 ignore next */
|
|
648
|
+
"dist";
|
|
575
649
|
const srcDist = projectRoot ? (0, import_node_path3.resolve)(projectRoot, spaDistRelative) : (0, import_node_path3.resolve)(spaDistRelative);
|
|
576
650
|
if (projectRoot) {
|
|
577
651
|
assertRealPathUnderRoot((0, import_node_path3.resolve)(projectRoot), srcDist);
|
|
@@ -586,7 +660,8 @@ async function resolveSpaDirs(options) {
|
|
|
586
660
|
} catch {
|
|
587
661
|
throw new Error(`spaDistDir must contain index.html: ${(0, import_node_path3.join)(srcDist, "index.html")}`);
|
|
588
662
|
}
|
|
589
|
-
const lessonId = spaLessons[0]?.id ??
|
|
663
|
+
const lessonId = spaLessons[0]?.id ?? /* v8 ignore next */
|
|
664
|
+
"main";
|
|
590
665
|
return { [lessonId]: srcDist };
|
|
591
666
|
}
|
|
592
667
|
const dirs = {};
|
|
@@ -670,7 +745,15 @@ function validatePackageInputs(options) {
|
|
|
670
745
|
ok: false,
|
|
671
746
|
courseDir: outDir,
|
|
672
747
|
target,
|
|
673
|
-
issues: [
|
|
748
|
+
issues: [
|
|
749
|
+
{
|
|
750
|
+
path: "outDir",
|
|
751
|
+
message: (
|
|
752
|
+
/* v8 ignore next */
|
|
753
|
+
err instanceof Error ? err.message : String(err)
|
|
754
|
+
)
|
|
755
|
+
}
|
|
756
|
+
]
|
|
674
757
|
};
|
|
675
758
|
}
|
|
676
759
|
}
|
|
@@ -702,7 +785,10 @@ function validatePackageInputs(options) {
|
|
|
702
785
|
issues: [
|
|
703
786
|
{
|
|
704
787
|
path: "outputBaseDir",
|
|
705
|
-
message:
|
|
788
|
+
message: (
|
|
789
|
+
/* v8 ignore next */
|
|
790
|
+
err instanceof Error ? err.message : String(err)
|
|
791
|
+
)
|
|
706
792
|
}
|
|
707
793
|
]
|
|
708
794
|
};
|
|
@@ -717,7 +803,15 @@ function validatePackageInputs(options) {
|
|
|
717
803
|
ok: false,
|
|
718
804
|
courseDir: outDir,
|
|
719
805
|
target,
|
|
720
|
-
issues: [
|
|
806
|
+
issues: [
|
|
807
|
+
{
|
|
808
|
+
path: "output",
|
|
809
|
+
message: (
|
|
810
|
+
/* v8 ignore next */
|
|
811
|
+
err instanceof Error ? err.message : String(err)
|
|
812
|
+
)
|
|
813
|
+
}
|
|
814
|
+
]
|
|
721
815
|
};
|
|
722
816
|
}
|
|
723
817
|
}
|
|
@@ -806,7 +900,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
806
900
|
try {
|
|
807
901
|
await renameOrCopy(tmpPromote, failedPromote2);
|
|
808
902
|
} catch {
|
|
809
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
903
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
904
|
+
/* v8 ignore next */
|
|
905
|
+
() => void 0
|
|
906
|
+
);
|
|
810
907
|
}
|
|
811
908
|
const promoteMsg = promoteError instanceof Error ? promoteError.message : String(promoteError);
|
|
812
909
|
const restoreMsg = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
@@ -822,7 +919,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
822
919
|
`[lessonkit/lxpack] failed to restore ${stagingDir} after promote error:`,
|
|
823
920
|
restoreError instanceof Error ? restoreError.message : restoreError
|
|
824
921
|
);
|
|
825
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
922
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
923
|
+
/* v8 ignore next */
|
|
924
|
+
() => void 0
|
|
925
|
+
);
|
|
826
926
|
}
|
|
827
927
|
throw promoteError;
|
|
828
928
|
}
|
|
@@ -830,12 +930,18 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
830
930
|
try {
|
|
831
931
|
await renameOrCopy(tmpPromote, failedPromote);
|
|
832
932
|
} catch {
|
|
833
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
933
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
934
|
+
/* v8 ignore next */
|
|
935
|
+
() => void 0
|
|
936
|
+
);
|
|
834
937
|
}
|
|
835
938
|
throw promoteError;
|
|
836
939
|
}
|
|
837
940
|
if (backup) {
|
|
838
|
-
await fsp.rm(backup, { recursive: true, force: true }).catch(
|
|
941
|
+
await fsp.rm(backup, { recursive: true, force: true }).catch(
|
|
942
|
+
/* v8 ignore next */
|
|
943
|
+
() => void 0
|
|
944
|
+
);
|
|
839
945
|
}
|
|
840
946
|
}
|
|
841
947
|
|
|
@@ -898,7 +1004,10 @@ async function buildStagingPackage(options) {
|
|
|
898
1004
|
outputDir: "outputDir" in build ? build.outputDir : void 0
|
|
899
1005
|
};
|
|
900
1006
|
} catch (err) {
|
|
901
|
-
await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1007
|
+
await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1008
|
+
/* v8 ignore next */
|
|
1009
|
+
() => void 0
|
|
1010
|
+
);
|
|
902
1011
|
throw err;
|
|
903
1012
|
}
|
|
904
1013
|
}
|
|
@@ -964,7 +1073,10 @@ async function packageLessonkitCourse(options) {
|
|
|
964
1073
|
outputBaseDir
|
|
965
1074
|
});
|
|
966
1075
|
if (!staged.ok) {
|
|
967
|
-
await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(
|
|
1076
|
+
await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(
|
|
1077
|
+
/* v8 ignore next */
|
|
1078
|
+
() => void 0
|
|
1079
|
+
);
|
|
968
1080
|
const validation2 = staged.build ? { ok: false, issues: staged.build.issues } : void 0;
|
|
969
1081
|
return {
|
|
970
1082
|
ok: false,
|
|
@@ -982,7 +1094,10 @@ async function packageLessonkitCourse(options) {
|
|
|
982
1094
|
validateArtifactInStaging(stagingRoot, staged.outputDir, "outputDir")
|
|
983
1095
|
].filter((issue) => issue != null);
|
|
984
1096
|
if (artifactIssues.length > 0) {
|
|
985
|
-
await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1097
|
+
await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1098
|
+
/* v8 ignore next */
|
|
1099
|
+
() => void 0
|
|
1100
|
+
);
|
|
986
1101
|
return {
|
|
987
1102
|
ok: false,
|
|
988
1103
|
courseDir: outDir,
|
|
@@ -1084,7 +1199,10 @@ function parseLessonkitManifest(raw, label = "lessonkit.json", projectRoot) {
|
|
|
1084
1199
|
if (!validation.ok) {
|
|
1085
1200
|
for (const i of validation.issues) {
|
|
1086
1201
|
issues.push({
|
|
1087
|
-
path:
|
|
1202
|
+
path: (
|
|
1203
|
+
/* v8 ignore next */
|
|
1204
|
+
i.path.startsWith("course.") ? i.path : `course.${i.path}`
|
|
1205
|
+
),
|
|
1088
1206
|
message: i.message
|
|
1089
1207
|
});
|
|
1090
1208
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -14,13 +14,34 @@ type LessonDescriptor = {
|
|
|
14
14
|
/** Built SPA folder relative to the LXPack project root (`per-lesson-spa` only). */
|
|
15
15
|
spaPath?: string;
|
|
16
16
|
};
|
|
17
|
-
type
|
|
17
|
+
type McqAssessmentDescriptor = {
|
|
18
|
+
kind?: "mcq";
|
|
18
19
|
checkId: CheckId;
|
|
19
20
|
question: string;
|
|
20
21
|
choices: string[];
|
|
21
22
|
answer: string;
|
|
22
23
|
passingScore?: number;
|
|
23
24
|
};
|
|
25
|
+
type TrueFalseAssessmentDescriptor = {
|
|
26
|
+
kind: "trueFalse";
|
|
27
|
+
checkId: CheckId;
|
|
28
|
+
question: string;
|
|
29
|
+
answer: boolean;
|
|
30
|
+
passingScore?: number;
|
|
31
|
+
};
|
|
32
|
+
type FillInBlanksAssessmentDescriptor = {
|
|
33
|
+
kind: "fillInBlanks";
|
|
34
|
+
checkId: CheckId;
|
|
35
|
+
question: string;
|
|
36
|
+
template: string;
|
|
37
|
+
blanks?: Array<{
|
|
38
|
+
id: string;
|
|
39
|
+
answer: string;
|
|
40
|
+
}>;
|
|
41
|
+
passingScore?: number;
|
|
42
|
+
};
|
|
43
|
+
/** Discriminated assessment entries in lessonkit.json (defaults to MCQ when kind omitted). */
|
|
44
|
+
type AssessmentDescriptor = McqAssessmentDescriptor | TrueFalseAssessmentDescriptor | FillInBlanksAssessmentDescriptor;
|
|
24
45
|
type LessonkitCourseDescriptor = {
|
|
25
46
|
courseId: CourseId;
|
|
26
47
|
title: string;
|
|
@@ -120,7 +141,7 @@ type LxpackInjectedAssessment = {
|
|
|
120
141
|
}>;
|
|
121
142
|
}>;
|
|
122
143
|
};
|
|
123
|
-
declare function assessmentDescriptorToLxpack(assessment: AssessmentDescriptor): LxpackInjectedAssessment;
|
|
144
|
+
declare function assessmentDescriptorToLxpack(assessment: AssessmentDescriptor): LxpackInjectedAssessment | null;
|
|
124
145
|
declare function extractAssessments(descriptor: LessonkitCourseDescriptor): LxpackInjectedAssessment[];
|
|
125
146
|
|
|
126
147
|
type WriteLxpackProjectOptions = {
|
|
@@ -149,10 +170,6 @@ type WriteLxpackProjectResult = {
|
|
|
149
170
|
*/
|
|
150
171
|
declare function writeLxpackProject(options: WriteLxpackProjectOptions): Promise<WriteLxpackProjectResult>;
|
|
151
172
|
|
|
152
|
-
/**
|
|
153
|
-
* Atomically replace `outDir` with the packaged tree at `stagingDir`.
|
|
154
|
-
* Restores the previous `outDir` when promote fails after a backup rename.
|
|
155
|
-
*/
|
|
156
173
|
declare function promoteStagingToOutDir(stagingDir: string, outDir: string): Promise<void>;
|
|
157
174
|
|
|
158
175
|
type BuildStagingPackageOptions = WriteLxpackProjectOptions & {
|
|
@@ -273,4 +290,4 @@ type ParseManifestResult = {
|
|
|
273
290
|
declare function parseLessonkitManifest(raw: unknown, label?: string, projectRoot?: string): ParseManifestResult;
|
|
274
291
|
declare function loadLessonkitManifestFromFile(readJson: () => Promise<unknown>, label?: string, projectRoot?: string): Promise<ParseManifestResult>;
|
|
275
292
|
|
|
276
|
-
export { type AssessmentDescriptor, type BuildLessonkitProjectOptions, type BuildStagingPackageOptions, type BuildStagingPackageResult, type DescriptorValidationIssue, type DescriptorValidationResult, type LessonDescriptor, type LessonkitCourseDescriptor, type LessonkitManifest, type LessonkitManifestPaths, type LxpackInjectedAssessment, type LxpackRuntimeTheme, type ManifestParseIssue, type MappedLessonkitIds, type PackageLessonkitCourseOptions, type PackageLessonkitCourseResult, type PackageValidationIssue, type ParseManifestResult, type ProjectPathsInput, type SpaLayout, type SpaLessonEntry, type ValidateLessonkitProjectOptions, type ValidatePackageInputsResult, type ValidationIssue, type WriteLxpackProjectOptions, type WriteLxpackProjectResult, assessmentDescriptorToLxpack, buildLessonkitProject, buildStagingPackage, descriptorToInterchange, ensureOutDirParent, extractAssessments, loadLessonkitManifestFromFile, mapLessonkitIds, packageLessonkitCourse, parseLessonkitManifest, promoteStagingToOutDir, remapArtifactPaths, resolveSafePackageOutputOverride, resolveSpaLessons, themeToLxpackRuntime, validateDescriptor, validateDescriptorForTarget, validateLessonkitProject, validatePackageInputs, validateProjectPaths, writeLxpackProject };
|
|
293
|
+
export { type AssessmentDescriptor, type BuildLessonkitProjectOptions, type BuildStagingPackageOptions, type BuildStagingPackageResult, type DescriptorValidationIssue, type DescriptorValidationResult, type FillInBlanksAssessmentDescriptor, type LessonDescriptor, type LessonkitCourseDescriptor, type LessonkitManifest, type LessonkitManifestPaths, type LxpackInjectedAssessment, type LxpackRuntimeTheme, type ManifestParseIssue, type MappedLessonkitIds, type McqAssessmentDescriptor, type PackageLessonkitCourseOptions, type PackageLessonkitCourseResult, type PackageValidationIssue, type ParseManifestResult, type ProjectPathsInput, type SpaLayout, type SpaLessonEntry, type TrueFalseAssessmentDescriptor, type ValidateLessonkitProjectOptions, type ValidatePackageInputsResult, type ValidationIssue, type WriteLxpackProjectOptions, type WriteLxpackProjectResult, assessmentDescriptorToLxpack, buildLessonkitProject, buildStagingPackage, descriptorToInterchange, ensureOutDirParent, extractAssessments, loadLessonkitManifestFromFile, mapLessonkitIds, packageLessonkitCourse, parseLessonkitManifest, promoteStagingToOutDir, remapArtifactPaths, resolveSafePackageOutputOverride, resolveSpaLessons, themeToLxpackRuntime, validateDescriptor, validateDescriptorForTarget, validateLessonkitProject, validatePackageInputs, validateProjectPaths, writeLxpackProject };
|
package/dist/index.d.ts
CHANGED
|
@@ -14,13 +14,34 @@ type LessonDescriptor = {
|
|
|
14
14
|
/** Built SPA folder relative to the LXPack project root (`per-lesson-spa` only). */
|
|
15
15
|
spaPath?: string;
|
|
16
16
|
};
|
|
17
|
-
type
|
|
17
|
+
type McqAssessmentDescriptor = {
|
|
18
|
+
kind?: "mcq";
|
|
18
19
|
checkId: CheckId;
|
|
19
20
|
question: string;
|
|
20
21
|
choices: string[];
|
|
21
22
|
answer: string;
|
|
22
23
|
passingScore?: number;
|
|
23
24
|
};
|
|
25
|
+
type TrueFalseAssessmentDescriptor = {
|
|
26
|
+
kind: "trueFalse";
|
|
27
|
+
checkId: CheckId;
|
|
28
|
+
question: string;
|
|
29
|
+
answer: boolean;
|
|
30
|
+
passingScore?: number;
|
|
31
|
+
};
|
|
32
|
+
type FillInBlanksAssessmentDescriptor = {
|
|
33
|
+
kind: "fillInBlanks";
|
|
34
|
+
checkId: CheckId;
|
|
35
|
+
question: string;
|
|
36
|
+
template: string;
|
|
37
|
+
blanks?: Array<{
|
|
38
|
+
id: string;
|
|
39
|
+
answer: string;
|
|
40
|
+
}>;
|
|
41
|
+
passingScore?: number;
|
|
42
|
+
};
|
|
43
|
+
/** Discriminated assessment entries in lessonkit.json (defaults to MCQ when kind omitted). */
|
|
44
|
+
type AssessmentDescriptor = McqAssessmentDescriptor | TrueFalseAssessmentDescriptor | FillInBlanksAssessmentDescriptor;
|
|
24
45
|
type LessonkitCourseDescriptor = {
|
|
25
46
|
courseId: CourseId;
|
|
26
47
|
title: string;
|
|
@@ -120,7 +141,7 @@ type LxpackInjectedAssessment = {
|
|
|
120
141
|
}>;
|
|
121
142
|
}>;
|
|
122
143
|
};
|
|
123
|
-
declare function assessmentDescriptorToLxpack(assessment: AssessmentDescriptor): LxpackInjectedAssessment;
|
|
144
|
+
declare function assessmentDescriptorToLxpack(assessment: AssessmentDescriptor): LxpackInjectedAssessment | null;
|
|
124
145
|
declare function extractAssessments(descriptor: LessonkitCourseDescriptor): LxpackInjectedAssessment[];
|
|
125
146
|
|
|
126
147
|
type WriteLxpackProjectOptions = {
|
|
@@ -149,10 +170,6 @@ type WriteLxpackProjectResult = {
|
|
|
149
170
|
*/
|
|
150
171
|
declare function writeLxpackProject(options: WriteLxpackProjectOptions): Promise<WriteLxpackProjectResult>;
|
|
151
172
|
|
|
152
|
-
/**
|
|
153
|
-
* Atomically replace `outDir` with the packaged tree at `stagingDir`.
|
|
154
|
-
* Restores the previous `outDir` when promote fails after a backup rename.
|
|
155
|
-
*/
|
|
156
173
|
declare function promoteStagingToOutDir(stagingDir: string, outDir: string): Promise<void>;
|
|
157
174
|
|
|
158
175
|
type BuildStagingPackageOptions = WriteLxpackProjectOptions & {
|
|
@@ -273,4 +290,4 @@ type ParseManifestResult = {
|
|
|
273
290
|
declare function parseLessonkitManifest(raw: unknown, label?: string, projectRoot?: string): ParseManifestResult;
|
|
274
291
|
declare function loadLessonkitManifestFromFile(readJson: () => Promise<unknown>, label?: string, projectRoot?: string): Promise<ParseManifestResult>;
|
|
275
292
|
|
|
276
|
-
export { type AssessmentDescriptor, type BuildLessonkitProjectOptions, type BuildStagingPackageOptions, type BuildStagingPackageResult, type DescriptorValidationIssue, type DescriptorValidationResult, type LessonDescriptor, type LessonkitCourseDescriptor, type LessonkitManifest, type LessonkitManifestPaths, type LxpackInjectedAssessment, type LxpackRuntimeTheme, type ManifestParseIssue, type MappedLessonkitIds, type PackageLessonkitCourseOptions, type PackageLessonkitCourseResult, type PackageValidationIssue, type ParseManifestResult, type ProjectPathsInput, type SpaLayout, type SpaLessonEntry, type ValidateLessonkitProjectOptions, type ValidatePackageInputsResult, type ValidationIssue, type WriteLxpackProjectOptions, type WriteLxpackProjectResult, assessmentDescriptorToLxpack, buildLessonkitProject, buildStagingPackage, descriptorToInterchange, ensureOutDirParent, extractAssessments, loadLessonkitManifestFromFile, mapLessonkitIds, packageLessonkitCourse, parseLessonkitManifest, promoteStagingToOutDir, remapArtifactPaths, resolveSafePackageOutputOverride, resolveSpaLessons, themeToLxpackRuntime, validateDescriptor, validateDescriptorForTarget, validateLessonkitProject, validatePackageInputs, validateProjectPaths, writeLxpackProject };
|
|
293
|
+
export { type AssessmentDescriptor, type BuildLessonkitProjectOptions, type BuildStagingPackageOptions, type BuildStagingPackageResult, type DescriptorValidationIssue, type DescriptorValidationResult, type FillInBlanksAssessmentDescriptor, type LessonDescriptor, type LessonkitCourseDescriptor, type LessonkitManifest, type LessonkitManifestPaths, type LxpackInjectedAssessment, type LxpackRuntimeTheme, type ManifestParseIssue, type MappedLessonkitIds, type McqAssessmentDescriptor, type PackageLessonkitCourseOptions, type PackageLessonkitCourseResult, type PackageValidationIssue, type ParseManifestResult, type ProjectPathsInput, type SpaLayout, type SpaLessonEntry, type TrueFalseAssessmentDescriptor, type ValidateLessonkitProjectOptions, type ValidatePackageInputsResult, type ValidationIssue, type WriteLxpackProjectOptions, type WriteLxpackProjectResult, assessmentDescriptorToLxpack, buildLessonkitProject, buildStagingPackage, descriptorToInterchange, ensureOutDirParent, extractAssessments, loadLessonkitManifestFromFile, mapLessonkitIds, packageLessonkitCourse, parseLessonkitManifest, promoteStagingToOutDir, remapArtifactPaths, resolveSafePackageOutputOverride, resolveSpaLessons, themeToLxpackRuntime, validateDescriptor, validateDescriptorForTarget, validateLessonkitProject, validatePackageInputs, validateProjectPaths, writeLxpackProject };
|
package/dist/index.js
CHANGED
|
@@ -28,7 +28,8 @@ function assertResolvedPathUnderRoot(root, target) {
|
|
|
28
28
|
const targetResolved = resolveComparablePath(target);
|
|
29
29
|
const prefix = rootResolved.endsWith(sep) ? rootResolved : rootResolved + sep;
|
|
30
30
|
const win32Prefix = rootResolved.endsWith(win32.sep) ? rootResolved : rootResolved + win32.sep;
|
|
31
|
-
if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) &&
|
|
31
|
+
if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix) && /* v8 ignore next */
|
|
32
|
+
!targetResolved.startsWith(win32Prefix)) {
|
|
32
33
|
throw new Error(`unsafe path escapes project root: ${target}`);
|
|
33
34
|
}
|
|
34
35
|
}
|
|
@@ -109,13 +110,36 @@ function parseAssessmentDescriptor(raw) {
|
|
|
109
110
|
if (!isRecord(raw)) {
|
|
110
111
|
return { checkId: "", question: "", choices: [], answer: "" };
|
|
111
112
|
}
|
|
112
|
-
|
|
113
|
+
const base = {
|
|
113
114
|
checkId: typeof raw.checkId === "string" ? raw.checkId : "",
|
|
114
115
|
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
116
|
passingScore: typeof raw.passingScore === "number" ? raw.passingScore : void 0
|
|
118
117
|
};
|
|
118
|
+
const kind = raw.kind;
|
|
119
|
+
if (kind === "trueFalse") {
|
|
120
|
+
return {
|
|
121
|
+
kind: "trueFalse",
|
|
122
|
+
...base,
|
|
123
|
+
answer: typeof raw.answer === "boolean" ? raw.answer : raw.answer === "true"
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (kind === "fillInBlanks") {
|
|
127
|
+
return {
|
|
128
|
+
kind: "fillInBlanks",
|
|
129
|
+
...base,
|
|
130
|
+
template: typeof raw.template === "string" ? raw.template : "",
|
|
131
|
+
blanks: Array.isArray(raw.blanks) ? raw.blanks.filter((b) => isRecord(b)).map((b) => ({
|
|
132
|
+
id: typeof b.id === "string" ? b.id : "",
|
|
133
|
+
answer: typeof b.answer === "string" ? b.answer : ""
|
|
134
|
+
})) : void 0
|
|
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
|
+
};
|
|
119
143
|
}
|
|
120
144
|
function parseCourseDescriptorInput(input) {
|
|
121
145
|
if (!isRecord(input)) return null;
|
|
@@ -178,10 +202,26 @@ function normalizeDescriptor(input) {
|
|
|
178
202
|
assessments: input.assessments?.map((assessment) => {
|
|
179
203
|
const check = validateId(assessment.checkId, "checkId");
|
|
180
204
|
if (!check.ok) throw new Error("normalizeDescriptor called with invalid checkId");
|
|
205
|
+
const question = assessment.question.trim();
|
|
206
|
+
if (assessment.kind === "trueFalse") {
|
|
207
|
+
return { ...assessment, checkId: check.id, question };
|
|
208
|
+
}
|
|
209
|
+
if (assessment.kind === "fillInBlanks") {
|
|
210
|
+
return {
|
|
211
|
+
...assessment,
|
|
212
|
+
checkId: check.id,
|
|
213
|
+
question,
|
|
214
|
+
template: assessment.template.trim(),
|
|
215
|
+
blanks: assessment.blanks?.map((b) => ({
|
|
216
|
+
id: b.id.trim(),
|
|
217
|
+
answer: b.answer.trim()
|
|
218
|
+
}))
|
|
219
|
+
};
|
|
220
|
+
}
|
|
181
221
|
return {
|
|
182
222
|
...assessment,
|
|
183
223
|
checkId: check.id,
|
|
184
|
-
question
|
|
224
|
+
question,
|
|
185
225
|
choices: assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0),
|
|
186
226
|
answer: assessment.answer.trim()
|
|
187
227
|
};
|
|
@@ -324,17 +364,28 @@ function validateDescriptorParsed(input) {
|
|
|
324
364
|
if (!assessment.question?.trim()) {
|
|
325
365
|
issues.push({ path: `${path}.question`, message: "question is required" });
|
|
326
366
|
}
|
|
327
|
-
const
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
path: `${path}.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
} else if (
|
|
337
|
-
|
|
367
|
+
const kind = assessment.kind ?? "mcq";
|
|
368
|
+
if (kind === "trueFalse" && assessment.kind === "trueFalse") {
|
|
369
|
+
if (typeof assessment.answer !== "boolean") {
|
|
370
|
+
issues.push({ path: `${path}.answer`, message: "answer must be a boolean for trueFalse" });
|
|
371
|
+
}
|
|
372
|
+
} else if (kind === "fillInBlanks" && assessment.kind === "fillInBlanks") {
|
|
373
|
+
if (!assessment.template?.trim()) {
|
|
374
|
+
issues.push({ path: `${path}.template`, message: "template is required for fillInBlanks" });
|
|
375
|
+
}
|
|
376
|
+
} else if ("choices" in assessment && "answer" in assessment && typeof assessment.answer === "string") {
|
|
377
|
+
const trimmedChoices = assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0);
|
|
378
|
+
if (!trimmedChoices.length) {
|
|
379
|
+
issues.push({
|
|
380
|
+
path: `${path}.choices`,
|
|
381
|
+
message: "at least one non-empty choice is required"
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
if (!assessment.answer.trim()) {
|
|
385
|
+
issues.push({ path: `${path}.answer`, message: "answer is required" });
|
|
386
|
+
} else if (trimmedChoices.length && !trimmedChoices.includes(assessment.answer.trim())) {
|
|
387
|
+
issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
|
|
388
|
+
}
|
|
338
389
|
}
|
|
339
390
|
const passingScore = assessment.passingScore;
|
|
340
391
|
if (passingScore !== void 0 && !(Number.isFinite(passingScore) && passingScore > 0)) {
|
|
@@ -417,7 +468,7 @@ function slugChoiceId(text, index) {
|
|
|
417
468
|
const stem = base.length ? base : "choice";
|
|
418
469
|
return `${stem}-${index + 1}`;
|
|
419
470
|
}
|
|
420
|
-
function
|
|
471
|
+
function mcqToLxpack(assessment) {
|
|
421
472
|
const choices = assessment.choices.map((text, index) => {
|
|
422
473
|
const id = slugChoiceId(text, index);
|
|
423
474
|
return {
|
|
@@ -438,8 +489,30 @@ function assessmentDescriptorToLxpack(assessment) {
|
|
|
438
489
|
]
|
|
439
490
|
};
|
|
440
491
|
}
|
|
492
|
+
function assessmentDescriptorToLxpack(assessment) {
|
|
493
|
+
const kind = assessment.kind ?? "mcq";
|
|
494
|
+
if (kind === "trueFalse" && assessment.kind === "trueFalse") {
|
|
495
|
+
const choices = ["True", "False"];
|
|
496
|
+
const answerText = assessment.answer ? "True" : "False";
|
|
497
|
+
return mcqToLxpack({
|
|
498
|
+
kind: "mcq",
|
|
499
|
+
checkId: assessment.checkId,
|
|
500
|
+
question: assessment.question,
|
|
501
|
+
choices,
|
|
502
|
+
answer: answerText,
|
|
503
|
+
passingScore: assessment.passingScore
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
if (kind === "fillInBlanks") {
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
if ("choices" in assessment && "answer" in assessment && typeof assessment.answer === "string") {
|
|
510
|
+
return mcqToLxpack(assessment);
|
|
511
|
+
}
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
441
514
|
function extractAssessments(descriptor) {
|
|
442
|
-
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
|
|
515
|
+
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack).filter((a) => a !== null);
|
|
443
516
|
}
|
|
444
517
|
|
|
445
518
|
// src/interchange.ts
|
|
@@ -512,7 +585,8 @@ async function resolveSpaDirs(options) {
|
|
|
512
585
|
const { descriptor, spaDistDir, lessonSpaDirs, projectRoot } = options;
|
|
513
586
|
const spaLessons = resolveSpaLessons(descriptor);
|
|
514
587
|
if (descriptor.layout === "single-spa") {
|
|
515
|
-
const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ??
|
|
588
|
+
const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? /* v8 ignore next */
|
|
589
|
+
"dist";
|
|
516
590
|
const srcDist = projectRoot ? resolve3(projectRoot, spaDistRelative) : resolve3(spaDistRelative);
|
|
517
591
|
if (projectRoot) {
|
|
518
592
|
assertRealPathUnderRoot(resolve3(projectRoot), srcDist);
|
|
@@ -527,7 +601,8 @@ async function resolveSpaDirs(options) {
|
|
|
527
601
|
} catch {
|
|
528
602
|
throw new Error(`spaDistDir must contain index.html: ${join(srcDist, "index.html")}`);
|
|
529
603
|
}
|
|
530
|
-
const lessonId = spaLessons[0]?.id ??
|
|
604
|
+
const lessonId = spaLessons[0]?.id ?? /* v8 ignore next */
|
|
605
|
+
"main";
|
|
531
606
|
return { [lessonId]: srcDist };
|
|
532
607
|
}
|
|
533
608
|
const dirs = {};
|
|
@@ -614,7 +689,15 @@ function validatePackageInputs(options) {
|
|
|
614
689
|
ok: false,
|
|
615
690
|
courseDir: outDir,
|
|
616
691
|
target,
|
|
617
|
-
issues: [
|
|
692
|
+
issues: [
|
|
693
|
+
{
|
|
694
|
+
path: "outDir",
|
|
695
|
+
message: (
|
|
696
|
+
/* v8 ignore next */
|
|
697
|
+
err instanceof Error ? err.message : String(err)
|
|
698
|
+
)
|
|
699
|
+
}
|
|
700
|
+
]
|
|
618
701
|
};
|
|
619
702
|
}
|
|
620
703
|
}
|
|
@@ -646,7 +729,10 @@ function validatePackageInputs(options) {
|
|
|
646
729
|
issues: [
|
|
647
730
|
{
|
|
648
731
|
path: "outputBaseDir",
|
|
649
|
-
message:
|
|
732
|
+
message: (
|
|
733
|
+
/* v8 ignore next */
|
|
734
|
+
err instanceof Error ? err.message : String(err)
|
|
735
|
+
)
|
|
650
736
|
}
|
|
651
737
|
]
|
|
652
738
|
};
|
|
@@ -661,7 +747,15 @@ function validatePackageInputs(options) {
|
|
|
661
747
|
ok: false,
|
|
662
748
|
courseDir: outDir,
|
|
663
749
|
target,
|
|
664
|
-
issues: [
|
|
750
|
+
issues: [
|
|
751
|
+
{
|
|
752
|
+
path: "output",
|
|
753
|
+
message: (
|
|
754
|
+
/* v8 ignore next */
|
|
755
|
+
err instanceof Error ? err.message : String(err)
|
|
756
|
+
)
|
|
757
|
+
}
|
|
758
|
+
]
|
|
665
759
|
};
|
|
666
760
|
}
|
|
667
761
|
}
|
|
@@ -750,7 +844,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
750
844
|
try {
|
|
751
845
|
await renameOrCopy(tmpPromote, failedPromote2);
|
|
752
846
|
} catch {
|
|
753
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
847
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
848
|
+
/* v8 ignore next */
|
|
849
|
+
() => void 0
|
|
850
|
+
);
|
|
754
851
|
}
|
|
755
852
|
const promoteMsg = promoteError instanceof Error ? promoteError.message : String(promoteError);
|
|
756
853
|
const restoreMsg = restoreError instanceof Error ? restoreError.message : String(restoreError);
|
|
@@ -766,7 +863,10 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
766
863
|
`[lessonkit/lxpack] failed to restore ${stagingDir} after promote error:`,
|
|
767
864
|
restoreError instanceof Error ? restoreError.message : restoreError
|
|
768
865
|
);
|
|
769
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
866
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
867
|
+
/* v8 ignore next */
|
|
868
|
+
() => void 0
|
|
869
|
+
);
|
|
770
870
|
}
|
|
771
871
|
throw promoteError;
|
|
772
872
|
}
|
|
@@ -774,12 +874,18 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
774
874
|
try {
|
|
775
875
|
await renameOrCopy(tmpPromote, failedPromote);
|
|
776
876
|
} catch {
|
|
777
|
-
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
877
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(
|
|
878
|
+
/* v8 ignore next */
|
|
879
|
+
() => void 0
|
|
880
|
+
);
|
|
778
881
|
}
|
|
779
882
|
throw promoteError;
|
|
780
883
|
}
|
|
781
884
|
if (backup) {
|
|
782
|
-
await fsp.rm(backup, { recursive: true, force: true }).catch(
|
|
885
|
+
await fsp.rm(backup, { recursive: true, force: true }).catch(
|
|
886
|
+
/* v8 ignore next */
|
|
887
|
+
() => void 0
|
|
888
|
+
);
|
|
783
889
|
}
|
|
784
890
|
}
|
|
785
891
|
|
|
@@ -842,7 +948,10 @@ async function buildStagingPackage(options) {
|
|
|
842
948
|
outputDir: "outputDir" in build ? build.outputDir : void 0
|
|
843
949
|
};
|
|
844
950
|
} catch (err) {
|
|
845
|
-
await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
951
|
+
await fsp2.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
952
|
+
/* v8 ignore next */
|
|
953
|
+
() => void 0
|
|
954
|
+
);
|
|
846
955
|
throw err;
|
|
847
956
|
}
|
|
848
957
|
}
|
|
@@ -908,7 +1017,10 @@ async function packageLessonkitCourse(options) {
|
|
|
908
1017
|
outputBaseDir
|
|
909
1018
|
});
|
|
910
1019
|
if (!staged.ok) {
|
|
911
|
-
await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(
|
|
1020
|
+
await fsp3.rm(staged.stagingDir, { recursive: true, force: true }).catch(
|
|
1021
|
+
/* v8 ignore next */
|
|
1022
|
+
() => void 0
|
|
1023
|
+
);
|
|
912
1024
|
const validation2 = staged.build ? { ok: false, issues: staged.build.issues } : void 0;
|
|
913
1025
|
return {
|
|
914
1026
|
ok: false,
|
|
@@ -926,7 +1038,10 @@ async function packageLessonkitCourse(options) {
|
|
|
926
1038
|
validateArtifactInStaging(stagingRoot, staged.outputDir, "outputDir")
|
|
927
1039
|
].filter((issue) => issue != null);
|
|
928
1040
|
if (artifactIssues.length > 0) {
|
|
929
|
-
await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1041
|
+
await fsp3.rm(stagingDir, { recursive: true, force: true }).catch(
|
|
1042
|
+
/* v8 ignore next */
|
|
1043
|
+
() => void 0
|
|
1044
|
+
);
|
|
930
1045
|
return {
|
|
931
1046
|
ok: false,
|
|
932
1047
|
courseDir: outDir,
|
|
@@ -1028,7 +1143,10 @@ function parseLessonkitManifest(raw, label = "lessonkit.json", projectRoot) {
|
|
|
1028
1143
|
if (!validation.ok) {
|
|
1029
1144
|
for (const i of validation.issues) {
|
|
1030
1145
|
issues.push({
|
|
1031
|
-
path:
|
|
1146
|
+
path: (
|
|
1147
|
+
/* v8 ignore next */
|
|
1148
|
+
i.path.startsWith("course.") ? i.path : `course.${i.path}`
|
|
1149
|
+
),
|
|
1032
1150
|
message: i.message
|
|
1033
1151
|
});
|
|
1034
1152
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lessonkit/lxpack",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "LXPack export adapter for LessonKit courses (SCORM, standalone, xAPI, cmi5).",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"lint": "echo \"(no lint configured yet)\""
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@lessonkit/core": "1.0
|
|
59
|
-
"@lessonkit/themes": "1.0
|
|
58
|
+
"@lessonkit/core": "1.1.0",
|
|
59
|
+
"@lessonkit/themes": "1.1.0",
|
|
60
60
|
"@lxpack/api": "^0.6.2",
|
|
61
61
|
"@lxpack/spa-bridge": "^0.6.2",
|
|
62
62
|
"@lxpack/tracking-schema": "^0.6.2",
|