@lessonkit/lxpack 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.cjs +18 -9
- package/dist/bridge.js +2 -2
- package/dist/chunk-DYQI222N.js +41 -0
- package/dist/index.cjs +199 -66
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +183 -60
- package/lessonkit-manifest.v1.json +100 -0
- package/package.json +8 -6
- package/dist/chunk-PSUSESH3.js +0 -32
package/dist/bridge.cjs
CHANGED
|
@@ -49,6 +49,15 @@ var import_tracking_schema3 = require("@lxpack/tracking-schema");
|
|
|
49
49
|
// src/telemetry.ts
|
|
50
50
|
var import_tracking_schema = require("@lxpack/tracking-schema");
|
|
51
51
|
var SUPPORTED = new Set(import_tracking_schema.LESSONKIT_TELEMETRY_EVENTS);
|
|
52
|
+
function isQuizAnsweredData(data) {
|
|
53
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
54
|
+
}
|
|
55
|
+
function isQuizCompletedData(data) {
|
|
56
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
57
|
+
}
|
|
58
|
+
function isInteractionData(data) {
|
|
59
|
+
return typeof data === "object" && data !== null;
|
|
60
|
+
}
|
|
52
61
|
function telemetryEventToLessonkit(event) {
|
|
53
62
|
if (!SUPPORTED.has(event.name)) {
|
|
54
63
|
return null;
|
|
@@ -60,16 +69,16 @@ function telemetryEventToLessonkit(event) {
|
|
|
60
69
|
};
|
|
61
70
|
if (name === "quiz_completed" || name === "quiz_answered") {
|
|
62
71
|
const data = event.data;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
if (isQuizAnsweredData(data) || isQuizCompletedData(data)) {
|
|
73
|
+
mapped.assessmentId = data.checkId;
|
|
74
|
+
if ("score" in data) {
|
|
75
|
+
mapped.score = data.score;
|
|
76
|
+
mapped.maxScore = data.maxScore;
|
|
77
|
+
mapped.passingScore = data.passingScore;
|
|
78
|
+
}
|
|
70
79
|
mapped.data = data;
|
|
71
80
|
}
|
|
72
|
-
} else if (name === "interaction" && event.data) {
|
|
81
|
+
} else if (name === "interaction" && event.data && isInteractionData(event.data)) {
|
|
73
82
|
mapped.data = event.data;
|
|
74
83
|
}
|
|
75
84
|
return mapped;
|
|
@@ -94,7 +103,7 @@ function getBridge(parentWindow) {
|
|
|
94
103
|
if (typeof window === "undefined") return null;
|
|
95
104
|
const parent = parentWindow ?? window.parent;
|
|
96
105
|
if (!parent || parent === window) return null;
|
|
97
|
-
return parent.lxpack ?? null;
|
|
106
|
+
return parent.lxpackBridge?.v1 ?? parent.lxpack ?? null;
|
|
98
107
|
}
|
|
99
108
|
function isDevEnvironment() {
|
|
100
109
|
const g = globalThis;
|
package/dist/bridge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
telemetryEventToLessonkit
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-DYQI222N.js";
|
|
4
4
|
|
|
5
5
|
// src/bridge.ts
|
|
6
6
|
import {
|
|
@@ -41,7 +41,7 @@ function getBridge(parentWindow) {
|
|
|
41
41
|
if (typeof window === "undefined") return null;
|
|
42
42
|
const parent = parentWindow ?? window.parent;
|
|
43
43
|
if (!parent || parent === window) return null;
|
|
44
|
-
return parent.lxpack ?? null;
|
|
44
|
+
return parent.lxpackBridge?.v1 ?? parent.lxpack ?? null;
|
|
45
45
|
}
|
|
46
46
|
function isDevEnvironment() {
|
|
47
47
|
const g = globalThis;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// src/telemetry.ts
|
|
2
|
+
import { LESSONKIT_TELEMETRY_EVENTS } from "@lxpack/tracking-schema";
|
|
3
|
+
var SUPPORTED = new Set(LESSONKIT_TELEMETRY_EVENTS);
|
|
4
|
+
function isQuizAnsweredData(data) {
|
|
5
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
6
|
+
}
|
|
7
|
+
function isQuizCompletedData(data) {
|
|
8
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
9
|
+
}
|
|
10
|
+
function isInteractionData(data) {
|
|
11
|
+
return typeof data === "object" && data !== null;
|
|
12
|
+
}
|
|
13
|
+
function telemetryEventToLessonkit(event) {
|
|
14
|
+
if (!SUPPORTED.has(event.name)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const name = event.name;
|
|
18
|
+
const mapped = {
|
|
19
|
+
name,
|
|
20
|
+
lessonId: event.lessonId
|
|
21
|
+
};
|
|
22
|
+
if (name === "quiz_completed" || name === "quiz_answered") {
|
|
23
|
+
const data = event.data;
|
|
24
|
+
if (isQuizAnsweredData(data) || isQuizCompletedData(data)) {
|
|
25
|
+
mapped.assessmentId = data.checkId;
|
|
26
|
+
if ("score" in data) {
|
|
27
|
+
mapped.score = data.score;
|
|
28
|
+
mapped.maxScore = data.maxScore;
|
|
29
|
+
mapped.passingScore = data.passingScore;
|
|
30
|
+
}
|
|
31
|
+
mapped.data = data;
|
|
32
|
+
}
|
|
33
|
+
} else if (name === "interaction" && event.data && isInteractionData(event.data)) {
|
|
34
|
+
mapped.data = event.data;
|
|
35
|
+
}
|
|
36
|
+
return mapped;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
telemetryEventToLessonkit
|
|
41
|
+
};
|
package/dist/index.cjs
CHANGED
|
@@ -53,6 +53,7 @@ __export(index_exports, {
|
|
|
53
53
|
telemetryEventToLessonkit: () => telemetryEventToLessonkit,
|
|
54
54
|
themeToLxpackRuntime: () => themeToLxpackRuntime,
|
|
55
55
|
validateDescriptor: () => validateDescriptor,
|
|
56
|
+
validateDescriptorForTarget: () => validateDescriptorForTarget,
|
|
56
57
|
validateLessonkitProject: () => validateLessonkitProject,
|
|
57
58
|
validatePackageInputs: () => validatePackageInputs,
|
|
58
59
|
validateProjectPaths: () => validateProjectPaths,
|
|
@@ -75,10 +76,11 @@ function resolveComparablePath(p) {
|
|
|
75
76
|
function isSafeRelativeSpaPath(spaPath) {
|
|
76
77
|
if (!spaPath.length || spaPath.includes("\0")) return false;
|
|
77
78
|
if (spaPath.startsWith("/") || spaPath.startsWith("\\")) return false;
|
|
78
|
-
if (/^[a-zA-Z]
|
|
79
|
-
|
|
79
|
+
if (/^[a-zA-Z]:/.test(spaPath)) return false;
|
|
80
|
+
if (spaPath === "." || spaPath === "./") return false;
|
|
81
|
+
const segments = spaPath.split(/[/\\]/).filter((s) => s.length > 0 && s !== ".");
|
|
80
82
|
if (segments.some((s) => s === "..")) return false;
|
|
81
|
-
return
|
|
83
|
+
return segments.length > 0;
|
|
82
84
|
}
|
|
83
85
|
function assertResolvedPathUnderRoot(root, target) {
|
|
84
86
|
const rootResolved = resolveComparablePath(root);
|
|
@@ -110,13 +112,25 @@ function assertRealPathUnderRoot(root, target) {
|
|
|
110
112
|
}
|
|
111
113
|
assertResolvedPathUnderRoot(rootReal, targetCheck);
|
|
112
114
|
}
|
|
115
|
+
function normalizePathForComparison(p) {
|
|
116
|
+
const resolved = resolveComparablePath(p);
|
|
117
|
+
return /^[a-zA-Z]:[/\\]/.test(resolved) ? resolved.toLowerCase() : resolved;
|
|
118
|
+
}
|
|
119
|
+
function relativePathUnderRoot(root, target) {
|
|
120
|
+
const rootResolved = normalizePathForComparison(root);
|
|
121
|
+
const targetResolved = normalizePathForComparison(target);
|
|
122
|
+
if (/^[a-zA-Z]:[/\\]/.test(rootResolved)) {
|
|
123
|
+
return import_node_path.win32.relative(rootResolved, targetResolved);
|
|
124
|
+
}
|
|
125
|
+
return (0, import_node_path.relative)(rootResolved, targetResolved);
|
|
126
|
+
}
|
|
113
127
|
function isResolvedPathUnderRoot(root, target) {
|
|
114
|
-
const rootResolved =
|
|
115
|
-
const targetResolved =
|
|
128
|
+
const rootResolved = normalizePathForComparison(root);
|
|
129
|
+
const targetResolved = normalizePathForComparison(target);
|
|
116
130
|
if (targetResolved === rootResolved) return true;
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
return
|
|
131
|
+
const rel = relativePathUnderRoot(root, target);
|
|
132
|
+
if (!rel) return true;
|
|
133
|
+
return !rel.startsWith("..") && !(0, import_node_path.isAbsolute)(rel);
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
// src/theme.ts
|
|
@@ -137,6 +151,70 @@ function themeToLxpackRuntime(input) {
|
|
|
137
151
|
// src/validateDescriptor.ts
|
|
138
152
|
var VALID_LAYOUTS = ["single-spa", "per-lesson-spa"];
|
|
139
153
|
var VALID_THEME_PRESETS = ["default", "light", "dark", "brand"];
|
|
154
|
+
function isRecord(value) {
|
|
155
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
156
|
+
}
|
|
157
|
+
function parseLessonDescriptor(raw) {
|
|
158
|
+
if (!isRecord(raw)) {
|
|
159
|
+
return { id: "", title: "" };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
id: typeof raw.id === "string" ? raw.id : "",
|
|
163
|
+
title: typeof raw.title === "string" ? raw.title : "",
|
|
164
|
+
spaPath: typeof raw.spaPath === "string" ? raw.spaPath : void 0
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
function parseAssessmentDescriptor(raw) {
|
|
168
|
+
if (!isRecord(raw)) {
|
|
169
|
+
return { checkId: "", question: "", choices: [], answer: "" };
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
checkId: typeof raw.checkId === "string" ? raw.checkId : "",
|
|
173
|
+
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
|
+
passingScore: typeof raw.passingScore === "number" ? raw.passingScore : void 0
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseCourseDescriptorInput(input) {
|
|
180
|
+
if (!isRecord(input)) return null;
|
|
181
|
+
const trackingRaw = input.tracking;
|
|
182
|
+
let tracking;
|
|
183
|
+
if (isRecord(trackingRaw)) {
|
|
184
|
+
const completionRaw = trackingRaw.completion;
|
|
185
|
+
const xapiRaw = trackingRaw.xapi;
|
|
186
|
+
tracking = {
|
|
187
|
+
completion: isRecord(completionRaw) ? {
|
|
188
|
+
threshold: typeof completionRaw.threshold === "number" ? completionRaw.threshold : void 0
|
|
189
|
+
} : void 0,
|
|
190
|
+
xapi: isRecord(xapiRaw) ? {
|
|
191
|
+
activityIri: typeof xapiRaw.activityIri === "string" ? xapiRaw.activityIri : void 0
|
|
192
|
+
} : void 0
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
const themeRaw = input.theme;
|
|
196
|
+
let theme;
|
|
197
|
+
if (isRecord(themeRaw)) {
|
|
198
|
+
theme = {
|
|
199
|
+
preset: typeof themeRaw.preset === "string" ? themeRaw.preset : void 0
|
|
200
|
+
};
|
|
201
|
+
if (isRecord(themeRaw.theme)) {
|
|
202
|
+
theme.theme = themeRaw.theme;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
courseId: typeof input.courseId === "string" ? input.courseId : "",
|
|
207
|
+
title: typeof input.title === "string" ? input.title : "",
|
|
208
|
+
version: typeof input.version === "string" ? input.version : void 0,
|
|
209
|
+
layout: typeof input.layout === "string" ? input.layout : void 0,
|
|
210
|
+
lessons: Array.isArray(input.lessons) ? input.lessons.map(parseLessonDescriptor) : [],
|
|
211
|
+
assessments: Array.isArray(input.assessments) ? input.assessments.map(parseAssessmentDescriptor) : void 0,
|
|
212
|
+
theme,
|
|
213
|
+
tracking,
|
|
214
|
+
spaDistDir: typeof input.spaDistDir === "string" ? input.spaDistDir : void 0,
|
|
215
|
+
spaLessonId: typeof input.spaLessonId === "string" ? input.spaLessonId : void 0
|
|
216
|
+
};
|
|
217
|
+
}
|
|
140
218
|
function normalizeDescriptor(input) {
|
|
141
219
|
const course = (0, import_core.validateId)(input.courseId, "courseId");
|
|
142
220
|
if (!course.ok) throw new Error("normalizeDescriptor called with invalid courseId");
|
|
@@ -170,6 +248,31 @@ function normalizeDescriptor(input) {
|
|
|
170
248
|
};
|
|
171
249
|
}
|
|
172
250
|
function validateDescriptor(input) {
|
|
251
|
+
const parsed = parseCourseDescriptorInput(input);
|
|
252
|
+
if (parsed === null) {
|
|
253
|
+
return { ok: false, issues: [{ path: "course", message: "must be an object" }] };
|
|
254
|
+
}
|
|
255
|
+
return validateDescriptorParsed(parsed);
|
|
256
|
+
}
|
|
257
|
+
function validateDescriptorForTarget(input, target) {
|
|
258
|
+
const result = validateDescriptor(input);
|
|
259
|
+
if (!result.ok || !target) return result;
|
|
260
|
+
if (target !== "xapi" && target !== "cmi5") return result;
|
|
261
|
+
const activityIri = result.descriptor.tracking?.xapi?.activityIri?.trim();
|
|
262
|
+
if (!activityIri) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
issues: [
|
|
266
|
+
{
|
|
267
|
+
path: "course.tracking.xapi.activityIri",
|
|
268
|
+
message: "tracking.xapi.activityIri is required for xapi and cmi5 export targets"
|
|
269
|
+
}
|
|
270
|
+
]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return result;
|
|
274
|
+
}
|
|
275
|
+
function validateDescriptorParsed(input) {
|
|
173
276
|
const issues = [];
|
|
174
277
|
const course = (0, import_core.validateId)(input.courseId, "courseId");
|
|
175
278
|
if (!course.ok) issues.push(...course.issues.map((i) => ({ path: i.path, message: i.message })));
|
|
@@ -315,7 +418,7 @@ function validatePathField(value, fieldPath, projectRoot, issues) {
|
|
|
315
418
|
return;
|
|
316
419
|
}
|
|
317
420
|
try {
|
|
318
|
-
|
|
421
|
+
assertRealPathUnderRoot(projectRoot, (0, import_node_path2.resolve)(projectRoot, value));
|
|
319
422
|
} catch {
|
|
320
423
|
issues.push({
|
|
321
424
|
path: fieldPath,
|
|
@@ -345,14 +448,14 @@ function resolveSafePackageOutputOverride(projectRoot, override) {
|
|
|
345
448
|
}
|
|
346
449
|
if ((0, import_node_path2.isAbsolute)(trimmed)) {
|
|
347
450
|
const resolved2 = (0, import_node_path2.resolve)(trimmed);
|
|
348
|
-
|
|
451
|
+
assertRealPathUnderRoot(root, resolved2);
|
|
349
452
|
return resolved2;
|
|
350
453
|
}
|
|
351
454
|
if (!isSafeRelativeSpaPath(trimmed)) {
|
|
352
455
|
throw new Error(`unsafe output path: ${override}`);
|
|
353
456
|
}
|
|
354
457
|
const resolved = (0, import_node_path2.resolve)(root, trimmed);
|
|
355
|
-
|
|
458
|
+
assertRealPathUnderRoot(root, resolved);
|
|
356
459
|
return resolved;
|
|
357
460
|
}
|
|
358
461
|
|
|
@@ -399,6 +502,18 @@ function extractAssessments(descriptor) {
|
|
|
399
502
|
}
|
|
400
503
|
|
|
401
504
|
// src/interchange.ts
|
|
505
|
+
function mapDescriptorTracking(tracking) {
|
|
506
|
+
if (!tracking) return void 0;
|
|
507
|
+
const mapped = {};
|
|
508
|
+
if (tracking.completion?.threshold !== void 0) {
|
|
509
|
+
mapped.completion = { threshold: tracking.completion.threshold };
|
|
510
|
+
}
|
|
511
|
+
const activityIri = tracking.xapi?.activityIri?.trim();
|
|
512
|
+
if (activityIri) {
|
|
513
|
+
mapped.xapi = { activityIri };
|
|
514
|
+
}
|
|
515
|
+
return Object.keys(mapped).length > 0 ? mapped : void 0;
|
|
516
|
+
}
|
|
402
517
|
function resolveSpaLessons(descriptor) {
|
|
403
518
|
const mapped = mapLessonkitIds(descriptor);
|
|
404
519
|
if (descriptor.layout === "single-spa") {
|
|
@@ -436,7 +551,7 @@ function descriptorToInterchange(descriptor) {
|
|
|
436
551
|
type: "spa",
|
|
437
552
|
path: l.path
|
|
438
553
|
})),
|
|
439
|
-
tracking: descriptor.tracking,
|
|
554
|
+
tracking: mapDescriptorTracking(descriptor.tracking),
|
|
440
555
|
runtime: runtime ? {
|
|
441
556
|
theme: runtime.theme,
|
|
442
557
|
cssVariables: runtime.cssVariables
|
|
@@ -513,7 +628,7 @@ async function writeLxpackProject(options) {
|
|
|
513
628
|
const descriptor = validation.descriptor;
|
|
514
629
|
const outDir = (0, import_node_path4.resolve)(options.outDir);
|
|
515
630
|
if (options.projectRoot) {
|
|
516
|
-
|
|
631
|
+
assertRealPathUnderRoot((0, import_node_path4.resolve)(options.projectRoot), outDir);
|
|
517
632
|
}
|
|
518
633
|
const spaDirs = await resolveSpaDirs({ ...options, descriptor });
|
|
519
634
|
const interchange = descriptorToInterchange(descriptor);
|
|
@@ -537,7 +652,7 @@ async function writeLxpackProject(options) {
|
|
|
537
652
|
}
|
|
538
653
|
|
|
539
654
|
// src/packageCourse.ts
|
|
540
|
-
var
|
|
655
|
+
var import_node_path8 = require("path");
|
|
541
656
|
var fsp3 = __toESM(require("fs/promises"), 1);
|
|
542
657
|
var import_api2 = require("@lxpack/api");
|
|
543
658
|
|
|
@@ -549,7 +664,7 @@ function validatePackageInputs(options) {
|
|
|
549
664
|
const projectRoot = options.projectRoot ? (0, import_node_path5.resolve)(options.projectRoot) : void 0;
|
|
550
665
|
if (projectRoot) {
|
|
551
666
|
try {
|
|
552
|
-
|
|
667
|
+
assertRealPathUnderRoot(projectRoot, outDir);
|
|
553
668
|
} catch (err) {
|
|
554
669
|
return {
|
|
555
670
|
ok: false,
|
|
@@ -596,7 +711,7 @@ function validatePackageInputs(options) {
|
|
|
596
711
|
if (projectRoot && output) {
|
|
597
712
|
const resolvedOutput = (0, import_node_path5.resolve)(projectRoot, output);
|
|
598
713
|
try {
|
|
599
|
-
|
|
714
|
+
assertRealPathUnderRoot(projectRoot, resolvedOutput);
|
|
600
715
|
} catch (err) {
|
|
601
716
|
return {
|
|
602
717
|
ok: false,
|
|
@@ -625,17 +740,21 @@ function remapArtifactPaths(stagingRoot, outDir, artifactPath) {
|
|
|
625
740
|
if (!isResolvedPathUnderRoot(stagingRoot, resolved)) {
|
|
626
741
|
return artifactPath;
|
|
627
742
|
}
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
|
|
743
|
+
const rel = relativePathUnderRoot(stagingRoot, resolved);
|
|
744
|
+
if (rel.startsWith("..") || (0, import_node_path5.isAbsolute)(rel)) {
|
|
745
|
+
return artifactPath;
|
|
746
|
+
}
|
|
747
|
+
if (!rel) return outDir;
|
|
631
748
|
if (/^[a-zA-Z]:[/\\]/.test(outDir)) {
|
|
632
|
-
return import_node_path5.win32.join(outDir,
|
|
749
|
+
return import_node_path5.win32.join(outDir, rel.replace(/\//g, import_node_path5.win32.sep));
|
|
633
750
|
}
|
|
634
|
-
return (0, import_node_path5.join)(outDir,
|
|
751
|
+
return (0, import_node_path5.join)(outDir, rel);
|
|
635
752
|
}
|
|
636
753
|
|
|
637
754
|
// src/packaging/promote.ts
|
|
638
755
|
var fsp = __toESM(require("fs/promises"), 1);
|
|
756
|
+
var import_node_crypto = require("crypto");
|
|
757
|
+
var import_node_path6 = require("path");
|
|
639
758
|
async function pathExists(path) {
|
|
640
759
|
try {
|
|
641
760
|
await fsp.access(path);
|
|
@@ -654,22 +773,36 @@ async function renameOrCopy(from, to) {
|
|
|
654
773
|
await fsp.rm(from, { recursive: true, force: true });
|
|
655
774
|
}
|
|
656
775
|
}
|
|
776
|
+
async function assertNoLegacyPromoteArtifacts(outDir) {
|
|
777
|
+
const legacyTmp = `${outDir}.tmp-promote`;
|
|
778
|
+
const legacyBak = `${outDir}.bak`;
|
|
779
|
+
const stale = [];
|
|
780
|
+
if (await pathExists(legacyTmp)) stale.push(legacyTmp);
|
|
781
|
+
if (await pathExists(legacyBak)) stale.push(legacyBak);
|
|
782
|
+
if (stale.length) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
`[lessonkit/lxpack] cannot promote: remove stale packaging artifacts from a previous failed run: ${stale.join(", ")}`
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
657
788
|
async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
658
|
-
|
|
659
|
-
const
|
|
789
|
+
await assertNoLegacyPromoteArtifacts(outDir);
|
|
790
|
+
const parent = (0, import_node_path6.dirname)(outDir);
|
|
791
|
+
const tmpPromote = await fsp.mkdtemp((0, import_node_path6.join)(parent, ".lk-promote-"));
|
|
660
792
|
await renameOrCopy(stagingDir, tmpPromote);
|
|
661
793
|
const hadOutDir = await pathExists(outDir);
|
|
662
|
-
|
|
794
|
+
const backup = hadOutDir ? await fsp.mkdtemp((0, import_node_path6.join)(parent, ".lk-backup-")) : void 0;
|
|
795
|
+
if (hadOutDir && backup) {
|
|
663
796
|
await renameOrCopy(outDir, backup);
|
|
664
797
|
}
|
|
665
798
|
try {
|
|
666
799
|
await renameOrCopy(tmpPromote, outDir);
|
|
667
800
|
} catch (promoteError) {
|
|
668
|
-
if (hadOutDir) {
|
|
801
|
+
if (hadOutDir && backup) {
|
|
669
802
|
try {
|
|
670
803
|
await renameOrCopy(backup, outDir);
|
|
671
804
|
} catch (restoreError) {
|
|
672
|
-
const failedPromote2 =
|
|
805
|
+
const failedPromote2 = (0, import_node_path6.join)(parent, `.lk-failed-promote-${(0, import_node_crypto.randomUUID)()}`);
|
|
673
806
|
try {
|
|
674
807
|
await renameOrCopy(tmpPromote, failedPromote2);
|
|
675
808
|
} catch {
|
|
@@ -693,7 +826,7 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
693
826
|
}
|
|
694
827
|
throw promoteError;
|
|
695
828
|
}
|
|
696
|
-
const failedPromote =
|
|
829
|
+
const failedPromote = (0, import_node_path6.join)(parent, `.lk-failed-promote-${(0, import_node_crypto.randomUUID)()}`);
|
|
697
830
|
try {
|
|
698
831
|
await renameOrCopy(tmpPromote, failedPromote);
|
|
699
832
|
} catch {
|
|
@@ -701,19 +834,19 @@ async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
|
701
834
|
}
|
|
702
835
|
throw promoteError;
|
|
703
836
|
}
|
|
704
|
-
if (
|
|
837
|
+
if (backup) {
|
|
705
838
|
await fsp.rm(backup, { recursive: true, force: true }).catch(() => void 0);
|
|
706
839
|
}
|
|
707
840
|
}
|
|
708
841
|
|
|
709
842
|
// src/packaging/staging.ts
|
|
710
843
|
var fsp2 = __toESM(require("fs/promises"), 1);
|
|
711
|
-
var
|
|
844
|
+
var import_node_path7 = require("path");
|
|
712
845
|
var import_node_os = require("os");
|
|
713
846
|
var import_api = require("@lxpack/api");
|
|
714
847
|
async function buildStagingPackage(options) {
|
|
715
848
|
const { target, output, dir, outputBaseDir, descriptor, ...writeOpts } = options;
|
|
716
|
-
const stagingDir = await fsp2.mkdtemp((0,
|
|
849
|
+
const stagingDir = await fsp2.mkdtemp((0, import_node_path7.join)((0, import_node_os.tmpdir)(), "lessonkit-lxpack-"));
|
|
717
850
|
try {
|
|
718
851
|
let spaDirs;
|
|
719
852
|
try {
|
|
@@ -732,8 +865,8 @@ async function buildStagingPackage(options) {
|
|
|
732
865
|
}
|
|
733
866
|
const interchange = descriptorToInterchange(descriptor);
|
|
734
867
|
const outputBase = outputBaseDir ?? ".lxpack/out";
|
|
735
|
-
await fsp2.mkdir((0,
|
|
736
|
-
const defaultOutput = output ?? (dir ? (0,
|
|
868
|
+
await fsp2.mkdir((0, import_node_path7.join)(stagingDir, outputBase), { recursive: true });
|
|
869
|
+
const defaultOutput = output ?? (dir ? (0, import_node_path7.join)(outputBase, target) : (0, import_node_path7.join)(outputBase, `course-${target}.zip`));
|
|
737
870
|
const build = await (0, import_api.packageLessonkit)({
|
|
738
871
|
interchange,
|
|
739
872
|
spaDirs,
|
|
@@ -770,25 +903,26 @@ async function buildStagingPackage(options) {
|
|
|
770
903
|
}
|
|
771
904
|
}
|
|
772
905
|
async function ensureOutDirParent(outDir) {
|
|
773
|
-
await fsp2.mkdir((0,
|
|
906
|
+
await fsp2.mkdir((0, import_node_path7.dirname)(outDir), { recursive: true });
|
|
774
907
|
}
|
|
775
908
|
|
|
776
909
|
// src/packageCourse.ts
|
|
777
910
|
async function validateLessonkitProject(options) {
|
|
778
911
|
return (0, import_api2.validateCourse)({
|
|
779
|
-
courseDir: (0,
|
|
912
|
+
courseDir: (0, import_node_path8.resolve)(options.courseDir),
|
|
780
913
|
target: options.target
|
|
781
914
|
});
|
|
782
915
|
}
|
|
783
916
|
async function buildLessonkitProject(options) {
|
|
784
|
-
|
|
785
|
-
courseDir: (0,
|
|
917
|
+
const buildOptions = {
|
|
918
|
+
courseDir: (0, import_node_path8.resolve)(options.courseDir),
|
|
786
919
|
target: options.target,
|
|
787
920
|
output: options.output,
|
|
788
921
|
dir: options.dir,
|
|
789
922
|
outputBaseDir: options.outputBaseDir,
|
|
790
923
|
assessments: options.assessments
|
|
791
|
-
}
|
|
924
|
+
};
|
|
925
|
+
return (0, import_api2.buildCourse)(buildOptions);
|
|
792
926
|
}
|
|
793
927
|
async function packageLessonkitCourse(options) {
|
|
794
928
|
const { target, output, dir, outputBaseDir, ...writeOpts } = options;
|
|
@@ -808,11 +942,11 @@ async function packageLessonkitCourse(options) {
|
|
|
808
942
|
};
|
|
809
943
|
}
|
|
810
944
|
const outDir = inputValidation.outDir;
|
|
811
|
-
const descriptorValidation =
|
|
945
|
+
const descriptorValidation = validateDescriptorForTarget(writeOpts.descriptor, target);
|
|
812
946
|
if (!descriptorValidation.ok) {
|
|
813
947
|
return {
|
|
814
948
|
ok: false,
|
|
815
|
-
courseDir: outDir,
|
|
949
|
+
courseDir: (0, import_node_path8.resolve)(writeOpts.outDir),
|
|
816
950
|
target,
|
|
817
951
|
issues: descriptorValidation.issues.map((i) => ({
|
|
818
952
|
path: i.path,
|
|
@@ -821,22 +955,6 @@ async function packageLessonkitCourse(options) {
|
|
|
821
955
|
};
|
|
822
956
|
}
|
|
823
957
|
const descriptor = descriptorValidation.descriptor;
|
|
824
|
-
if (target === "xapi" || target === "cmi5") {
|
|
825
|
-
const activityIri = descriptor.tracking?.xapi?.activityIri?.trim();
|
|
826
|
-
if (!activityIri) {
|
|
827
|
-
return {
|
|
828
|
-
ok: false,
|
|
829
|
-
courseDir: outDir,
|
|
830
|
-
target,
|
|
831
|
-
issues: [
|
|
832
|
-
{
|
|
833
|
-
path: "course.tracking.xapi.activityIri",
|
|
834
|
-
message: "tracking.xapi.activityIri is required for xapi and cmi5 export targets"
|
|
835
|
-
}
|
|
836
|
-
]
|
|
837
|
-
};
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
958
|
const staged = await buildStagingPackage({
|
|
841
959
|
...writeOpts,
|
|
842
960
|
descriptor,
|
|
@@ -930,14 +1048,19 @@ function parseLessonkitManifest(raw, label = "lessonkit.json", projectRoot) {
|
|
|
930
1048
|
}
|
|
931
1049
|
const config = raw;
|
|
932
1050
|
const issues = [];
|
|
933
|
-
|
|
1051
|
+
let schemaVersion = config.schemaVersion;
|
|
1052
|
+
if (schemaVersion === "1") {
|
|
1053
|
+
schemaVersion = 1;
|
|
1054
|
+
}
|
|
1055
|
+
if (schemaVersion !== 1) {
|
|
934
1056
|
issues.push({
|
|
935
1057
|
path: "schemaVersion",
|
|
936
1058
|
message: `must be 1 (got ${String(config.schemaVersion)})`
|
|
937
1059
|
});
|
|
938
1060
|
}
|
|
939
|
-
const
|
|
940
|
-
|
|
1061
|
+
const nameRaw = config.name;
|
|
1062
|
+
const name = typeof nameRaw === "string" ? nameRaw.trim() : "";
|
|
1063
|
+
if (!name) {
|
|
941
1064
|
issues.push({ path: "name", message: "must be a non-empty string" });
|
|
942
1065
|
}
|
|
943
1066
|
const courseRaw = config.course;
|
|
@@ -1026,6 +1149,15 @@ var import_tracking_schema2 = require("@lxpack/tracking-schema");
|
|
|
1026
1149
|
// src/telemetry.ts
|
|
1027
1150
|
var import_tracking_schema = require("@lxpack/tracking-schema");
|
|
1028
1151
|
var SUPPORTED = new Set(import_tracking_schema.LESSONKIT_TELEMETRY_EVENTS);
|
|
1152
|
+
function isQuizAnsweredData(data) {
|
|
1153
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
1154
|
+
}
|
|
1155
|
+
function isQuizCompletedData(data) {
|
|
1156
|
+
return typeof data === "object" && data !== null && typeof data.checkId === "string";
|
|
1157
|
+
}
|
|
1158
|
+
function isInteractionData(data) {
|
|
1159
|
+
return typeof data === "object" && data !== null;
|
|
1160
|
+
}
|
|
1029
1161
|
function telemetryEventToLessonkit(event) {
|
|
1030
1162
|
if (!SUPPORTED.has(event.name)) {
|
|
1031
1163
|
return null;
|
|
@@ -1037,16 +1169,16 @@ function telemetryEventToLessonkit(event) {
|
|
|
1037
1169
|
};
|
|
1038
1170
|
if (name === "quiz_completed" || name === "quiz_answered") {
|
|
1039
1171
|
const data = event.data;
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1172
|
+
if (isQuizAnsweredData(data) || isQuizCompletedData(data)) {
|
|
1173
|
+
mapped.assessmentId = data.checkId;
|
|
1174
|
+
if ("score" in data) {
|
|
1175
|
+
mapped.score = data.score;
|
|
1176
|
+
mapped.maxScore = data.maxScore;
|
|
1177
|
+
mapped.passingScore = data.passingScore;
|
|
1178
|
+
}
|
|
1047
1179
|
mapped.data = data;
|
|
1048
1180
|
}
|
|
1049
|
-
} else if (name === "interaction" && event.data) {
|
|
1181
|
+
} else if (name === "interaction" && event.data && isInteractionData(event.data)) {
|
|
1050
1182
|
mapped.data = event.data;
|
|
1051
1183
|
}
|
|
1052
1184
|
return mapped;
|
|
@@ -1079,6 +1211,7 @@ var import_validators2 = require("@lxpack/validators");
|
|
|
1079
1211
|
telemetryEventToLessonkit,
|
|
1080
1212
|
themeToLxpackRuntime,
|
|
1081
1213
|
validateDescriptor,
|
|
1214
|
+
validateDescriptorForTarget,
|
|
1082
1215
|
validateLessonkitProject,
|
|
1083
1216
|
validatePackageInputs,
|
|
1084
1217
|
validateProjectPaths,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { CheckId, CourseId, LessonId } from '@lessonkit/core';
|
|
2
2
|
import { ThemePresetName, LessonkitThemeV1 } from '@lessonkit/themes';
|
|
3
|
-
import { LessonkitInterchangeV1 } from '@lxpack/validators';
|
|
4
|
-
export { LessonkitInterchangeV1, MaterializeLessonkitOptions, MaterializeLessonkitResult, lessonkitInterchangeSchema, materializeLessonkitProject, parseLessonkitInterchange } from '@lxpack/validators';
|
|
5
3
|
import { ExportTarget, BuildCourseResult, ValidateCourseResult } from '@lxpack/api';
|
|
6
4
|
export { ExportTarget } from '@lxpack/api';
|
|
5
|
+
import { LessonkitInterchangeV1 } from '@lxpack/validators';
|
|
6
|
+
export { LessonkitInterchangeV1, MaterializeLessonkitOptions, MaterializeLessonkitResult, lessonkitInterchangeSchema, materializeLessonkitProject, parseLessonkitInterchange } from '@lxpack/validators';
|
|
7
7
|
export { LESSONKIT_TELEMETRY_EVENTS, LessonkitBridgeAction, LessonkitTelemetryEvent, LessonkitTelemetryEventName, TrackingSchemaEvent, mapLessonkitTelemetryToBridgeAction, mapLessonkitTelemetryToLxpack } from '@lxpack/tracking-schema';
|
|
8
8
|
export { t as telemetryEventToLessonkit } from './telemetry-gCxlwc7I.cjs';
|
|
9
9
|
|
|
@@ -53,10 +53,14 @@ type MappedLessonkitIds = {
|
|
|
53
53
|
checkIds: CheckId[];
|
|
54
54
|
};
|
|
55
55
|
|
|
56
|
-
|
|
56
|
+
/** Shared validation issue shape across lxpack parsers. */
|
|
57
|
+
type ValidationIssue = {
|
|
57
58
|
path: string;
|
|
58
59
|
message: string;
|
|
60
|
+
severity?: string;
|
|
59
61
|
};
|
|
62
|
+
|
|
63
|
+
type DescriptorValidationIssue = ValidationIssue;
|
|
60
64
|
type DescriptorValidationResult = {
|
|
61
65
|
ok: true;
|
|
62
66
|
descriptor: LessonkitCourseDescriptor;
|
|
@@ -64,7 +68,8 @@ type DescriptorValidationResult = {
|
|
|
64
68
|
ok: false;
|
|
65
69
|
issues: DescriptorValidationIssue[];
|
|
66
70
|
};
|
|
67
|
-
declare function validateDescriptor(input:
|
|
71
|
+
declare function validateDescriptor(input: unknown): DescriptorValidationResult;
|
|
72
|
+
declare function validateDescriptorForTarget(input: unknown, target?: ExportTarget): DescriptorValidationResult;
|
|
68
73
|
|
|
69
74
|
type ProjectPathsInput = {
|
|
70
75
|
spaDistDir?: string;
|
|
@@ -94,7 +99,7 @@ declare function themeToLxpackRuntime(input: {
|
|
|
94
99
|
}): LxpackRuntimeTheme;
|
|
95
100
|
|
|
96
101
|
type SpaLessonEntry = {
|
|
97
|
-
id:
|
|
102
|
+
id: LessonId;
|
|
98
103
|
title: string;
|
|
99
104
|
path: string;
|
|
100
105
|
};
|
|
@@ -188,7 +193,7 @@ type BuildLessonkitProjectOptions = {
|
|
|
188
193
|
output?: string;
|
|
189
194
|
dir?: boolean;
|
|
190
195
|
outputBaseDir?: string;
|
|
191
|
-
assessments?:
|
|
196
|
+
assessments?: LxpackInjectedAssessment[];
|
|
192
197
|
};
|
|
193
198
|
type PackageLessonkitCourseOptions = WriteLxpackProjectOptions & {
|
|
194
199
|
target: ExportTarget;
|
|
@@ -268,4 +273,4 @@ type ParseManifestResult = {
|
|
|
268
273
|
declare function parseLessonkitManifest(raw: unknown, label?: string, projectRoot?: string): ParseManifestResult;
|
|
269
274
|
declare function loadLessonkitManifestFromFile(readJson: () => Promise<unknown>, label?: string, projectRoot?: string): Promise<ParseManifestResult>;
|
|
270
275
|
|
|
271
|
-
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 WriteLxpackProjectOptions, type WriteLxpackProjectResult, assessmentDescriptorToLxpack, buildLessonkitProject, buildStagingPackage, descriptorToInterchange, ensureOutDirParent, extractAssessments, loadLessonkitManifestFromFile, mapLessonkitIds, packageLessonkitCourse, parseLessonkitManifest, promoteStagingToOutDir, remapArtifactPaths, resolveSafePackageOutputOverride, resolveSpaLessons, themeToLxpackRuntime, validateDescriptor, validateLessonkitProject, validatePackageInputs, validateProjectPaths, writeLxpackProject };
|
|
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 };
|