@lessonkit/lxpack 0.8.0 → 0.9.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/README.md +1 -1
- package/dist/bridge.cjs +69 -9
- package/dist/bridge.d.cts +15 -27
- package/dist/bridge.d.ts +15 -27
- package/dist/bridge.js +46 -8
- package/dist/chunk-PSUSESH3.js +32 -0
- package/dist/index.cjs +325 -230
- package/dist/index.d.cts +23 -20
- package/dist/index.d.ts +23 -20
- package/dist/index.js +287 -229
- package/dist/telemetry-gCxlwc7I.d.cts +9 -0
- package/dist/telemetry-gCxlwc7I.d.ts +9 -0
- package/package.json +7 -4
package/dist/index.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,21 +17,38 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
21
31
|
var index_exports = {};
|
|
22
32
|
__export(index_exports, {
|
|
33
|
+
LESSONKIT_TELEMETRY_EVENTS: () => import_tracking_schema2.LESSONKIT_TELEMETRY_EVENTS,
|
|
23
34
|
assessmentDescriptorToLxpack: () => assessmentDescriptorToLxpack,
|
|
24
35
|
buildLessonkitProject: () => buildLessonkitProject,
|
|
25
36
|
descriptorToInterchange: () => descriptorToInterchange,
|
|
26
37
|
extractAssessments: () => extractAssessments,
|
|
38
|
+
lessonkitInterchangeSchema: () => import_validators2.lessonkitInterchangeSchema,
|
|
27
39
|
mapLessonkitIds: () => mapLessonkitIds,
|
|
40
|
+
mapLessonkitTelemetryToBridgeAction: () => import_tracking_schema2.mapLessonkitTelemetryToBridgeAction,
|
|
41
|
+
mapLessonkitTelemetryToLxpack: () => import_tracking_schema2.mapLessonkitTelemetryToLxpack,
|
|
42
|
+
materializeLessonkitProject: () => import_validators2.materializeLessonkitProject,
|
|
28
43
|
packageLessonkitCourse: () => packageLessonkitCourse,
|
|
44
|
+
parseLessonkitInterchange: () => import_validators2.parseLessonkitInterchange,
|
|
45
|
+
resolveSafePackageOutputOverride: () => resolveSafePackageOutputOverride,
|
|
29
46
|
resolveSpaLessons: () => resolveSpaLessons,
|
|
47
|
+
telemetryEventToLessonkit: () => telemetryEventToLessonkit,
|
|
30
48
|
themeToLxpackRuntime: () => themeToLxpackRuntime,
|
|
31
49
|
validateDescriptor: () => validateDescriptor,
|
|
32
50
|
validateLessonkitProject: () => validateLessonkitProject,
|
|
51
|
+
validateProjectPaths: () => validateProjectPaths,
|
|
33
52
|
writeLxpackProject: () => writeLxpackProject
|
|
34
53
|
});
|
|
35
54
|
module.exports = __toCommonJS(index_exports);
|
|
@@ -43,8 +62,8 @@ function isSafeRelativeSpaPath(spaPath) {
|
|
|
43
62
|
if (!spaPath.length || spaPath.includes("\0")) return false;
|
|
44
63
|
if (spaPath.startsWith("/") || spaPath.startsWith("\\")) return false;
|
|
45
64
|
if (/^[a-zA-Z]:[/\\]/.test(spaPath)) return false;
|
|
46
|
-
const segments = spaPath.split(/[/\\]/);
|
|
47
|
-
if (segments.some((s) => s === "..")) return false;
|
|
65
|
+
const segments = spaPath.split(/[/\\]/).filter((s) => s.length > 0);
|
|
66
|
+
if (segments.some((s) => s === ".." || s === ".")) return false;
|
|
48
67
|
return true;
|
|
49
68
|
}
|
|
50
69
|
function assertResolvedPathUnderRoot(root, target) {
|
|
@@ -56,6 +75,21 @@ function assertResolvedPathUnderRoot(root, target) {
|
|
|
56
75
|
}
|
|
57
76
|
}
|
|
58
77
|
|
|
78
|
+
// src/theme.ts
|
|
79
|
+
var import_themes = require("@lessonkit/themes");
|
|
80
|
+
function themeToLxpackRuntime(input) {
|
|
81
|
+
const theme = input.theme ?? (0, import_themes.getPresetTheme)(input.preset ?? "default");
|
|
82
|
+
const raw = (0, import_themes.themeToCssVariables)(theme);
|
|
83
|
+
const cssVariables = {};
|
|
84
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
85
|
+
cssVariables[key] = String(value);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
theme: theme.name,
|
|
89
|
+
cssVariables
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
59
93
|
// src/validateDescriptor.ts
|
|
60
94
|
var VALID_LAYOUTS = ["single-spa", "per-lesson-spa"];
|
|
61
95
|
var VALID_THEME_PRESETS = ["default", "light", "dark", "brand"];
|
|
@@ -117,6 +151,25 @@ function validateDescriptor(input) {
|
|
|
117
151
|
message: `unknown preset; use one of: ${VALID_THEME_PRESETS.join(", ")}`
|
|
118
152
|
});
|
|
119
153
|
}
|
|
154
|
+
if (input.theme?.theme) {
|
|
155
|
+
try {
|
|
156
|
+
themeToLxpackRuntime({ preset: themePreset, theme: input.theme.theme });
|
|
157
|
+
} catch (err) {
|
|
158
|
+
issues.push({
|
|
159
|
+
path: "theme.theme",
|
|
160
|
+
message: err instanceof Error ? err.message : "invalid custom theme"
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const completionThreshold = input.tracking?.completion?.threshold;
|
|
165
|
+
if (completionThreshold !== void 0) {
|
|
166
|
+
if (!Number.isFinite(completionThreshold) || completionThreshold < 0 || completionThreshold > 1) {
|
|
167
|
+
issues.push({
|
|
168
|
+
path: "tracking.completion.threshold",
|
|
169
|
+
message: "threshold must be a finite number between 0 and 1"
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
120
173
|
if (layout === "single-spa" && (input.lessons?.length ?? 0) > 1) {
|
|
121
174
|
issues.push({
|
|
122
175
|
path: "lessons",
|
|
@@ -196,24 +249,69 @@ function validateDescriptor(input) {
|
|
|
196
249
|
issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
|
|
197
250
|
}
|
|
198
251
|
const passingScore = assessment.passingScore;
|
|
199
|
-
if (passingScore !== void 0) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
} else if (trimmedChoices.length && passingScore > trimmedChoices.length) {
|
|
206
|
-
issues.push({
|
|
207
|
-
path: `${path}.passingScore`,
|
|
208
|
-
message: "passingScore must not exceed the number of choices"
|
|
209
|
-
});
|
|
210
|
-
}
|
|
252
|
+
if (passingScore !== void 0 && !(passingScore > 0)) {
|
|
253
|
+
issues.push({
|
|
254
|
+
path: `${path}.passingScore`,
|
|
255
|
+
message: "passingScore must be greater than 0 (absolute point threshold)"
|
|
256
|
+
});
|
|
211
257
|
}
|
|
212
258
|
}
|
|
213
259
|
if (issues.length) return { ok: false, issues };
|
|
214
260
|
return { ok: true, descriptor: normalizeDescriptor(input) };
|
|
215
261
|
}
|
|
216
262
|
|
|
263
|
+
// src/validateProjectPaths.ts
|
|
264
|
+
var import_node_path2 = require("path");
|
|
265
|
+
function validatePathField(value, fieldPath, projectRoot, issues) {
|
|
266
|
+
if (!isSafeRelativeSpaPath(value)) {
|
|
267
|
+
issues.push({
|
|
268
|
+
path: fieldPath,
|
|
269
|
+
message: "path must be relative without '..' segments or absolute prefixes"
|
|
270
|
+
});
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
assertResolvedPathUnderRoot(projectRoot, (0, import_node_path2.resolve)(projectRoot, value));
|
|
275
|
+
} catch {
|
|
276
|
+
issues.push({
|
|
277
|
+
path: fieldPath,
|
|
278
|
+
message: "path must resolve inside the project root"
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function validateProjectPaths(projectRoot, paths) {
|
|
283
|
+
const issues = [];
|
|
284
|
+
const root = (0, import_node_path2.resolve)(projectRoot);
|
|
285
|
+
if (paths.spaDistDir?.trim()) {
|
|
286
|
+
validatePathField(paths.spaDistDir.trim(), "paths.spaDistDir", root, issues);
|
|
287
|
+
}
|
|
288
|
+
if (paths.lxpackOutDir?.trim()) {
|
|
289
|
+
validatePathField(paths.lxpackOutDir.trim(), "paths.lxpackOutDir", root, issues);
|
|
290
|
+
}
|
|
291
|
+
if (paths.outputBaseDir?.trim()) {
|
|
292
|
+
validatePathField(paths.outputBaseDir.trim(), "paths.outputBaseDir", root, issues);
|
|
293
|
+
}
|
|
294
|
+
return issues;
|
|
295
|
+
}
|
|
296
|
+
function resolveSafePackageOutputOverride(projectRoot, override) {
|
|
297
|
+
const root = (0, import_node_path2.resolve)(projectRoot);
|
|
298
|
+
const trimmed = override.trim();
|
|
299
|
+
if (!trimmed) {
|
|
300
|
+
throw new Error("output override must be a non-empty path");
|
|
301
|
+
}
|
|
302
|
+
if ((0, import_node_path2.isAbsolute)(trimmed)) {
|
|
303
|
+
const resolved2 = (0, import_node_path2.resolve)(trimmed);
|
|
304
|
+
assertResolvedPathUnderRoot(root, resolved2);
|
|
305
|
+
return resolved2;
|
|
306
|
+
}
|
|
307
|
+
if (!isSafeRelativeSpaPath(trimmed)) {
|
|
308
|
+
throw new Error(`unsafe output path: ${override}`);
|
|
309
|
+
}
|
|
310
|
+
const resolved = (0, import_node_path2.resolve)(root, trimmed);
|
|
311
|
+
assertResolvedPathUnderRoot(root, resolved);
|
|
312
|
+
return resolved;
|
|
313
|
+
}
|
|
314
|
+
|
|
217
315
|
// src/mapIds.ts
|
|
218
316
|
var import_core2 = require("@lessonkit/core");
|
|
219
317
|
function mapLessonkitIds(descriptor) {
|
|
@@ -225,20 +323,36 @@ function mapLessonkitIds(descriptor) {
|
|
|
225
323
|
return { courseId, lessonIds, checkIds };
|
|
226
324
|
}
|
|
227
325
|
|
|
228
|
-
// src/
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
326
|
+
// src/assessments.ts
|
|
327
|
+
function slugChoiceId(text, index) {
|
|
328
|
+
const base = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32);
|
|
329
|
+
const stem = base.length ? base : "choice";
|
|
330
|
+
return `${stem}-${index + 1}`;
|
|
331
|
+
}
|
|
332
|
+
function assessmentDescriptorToLxpack(assessment) {
|
|
333
|
+
const choices = assessment.choices.map((text, index) => {
|
|
334
|
+
const id = slugChoiceId(text, index);
|
|
335
|
+
return {
|
|
336
|
+
id,
|
|
337
|
+
text,
|
|
338
|
+
correct: text === assessment.answer
|
|
339
|
+
};
|
|
340
|
+
});
|
|
237
341
|
return {
|
|
238
|
-
|
|
239
|
-
|
|
342
|
+
id: assessment.checkId,
|
|
343
|
+
passingScore: assessment.passingScore ?? 1,
|
|
344
|
+
questions: [
|
|
345
|
+
{
|
|
346
|
+
id: "q1",
|
|
347
|
+
prompt: assessment.question,
|
|
348
|
+
choices
|
|
349
|
+
}
|
|
350
|
+
]
|
|
240
351
|
};
|
|
241
352
|
}
|
|
353
|
+
function extractAssessments(descriptor) {
|
|
354
|
+
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
|
|
355
|
+
}
|
|
242
356
|
|
|
243
357
|
// src/interchange.ts
|
|
244
358
|
function resolveSpaLessons(descriptor) {
|
|
@@ -263,6 +377,8 @@ function resolveSpaLessons(descriptor) {
|
|
|
263
377
|
function descriptorToInterchange(descriptor) {
|
|
264
378
|
const mapped = mapLessonkitIds(descriptor);
|
|
265
379
|
const spaLessons = resolveSpaLessons(descriptor);
|
|
380
|
+
const runtime = descriptor.theme ? themeToLxpackRuntime(descriptor.theme) : void 0;
|
|
381
|
+
const assessments = extractAssessments(descriptor);
|
|
266
382
|
return {
|
|
267
383
|
format: "lessonkit",
|
|
268
384
|
version: "1",
|
|
@@ -276,121 +392,52 @@ function descriptorToInterchange(descriptor) {
|
|
|
276
392
|
type: "spa",
|
|
277
393
|
path: l.path
|
|
278
394
|
})),
|
|
279
|
-
tracking: descriptor.tracking
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const base = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32);
|
|
286
|
-
const stem = base.length ? base : "choice";
|
|
287
|
-
return `${stem}-${index + 1}`;
|
|
288
|
-
}
|
|
289
|
-
function assessmentDescriptorToLxpack(assessment) {
|
|
290
|
-
const choices = assessment.choices.map((text, index) => {
|
|
291
|
-
const id = slugChoiceId(text, index);
|
|
292
|
-
return {
|
|
293
|
-
id,
|
|
294
|
-
text,
|
|
295
|
-
correct: text === assessment.answer
|
|
296
|
-
};
|
|
297
|
-
});
|
|
298
|
-
return {
|
|
299
|
-
id: assessment.checkId,
|
|
300
|
-
passingScore: assessment.passingScore ?? 1,
|
|
301
|
-
questions: [
|
|
302
|
-
{
|
|
303
|
-
id: "q1",
|
|
304
|
-
prompt: assessment.question,
|
|
305
|
-
choices
|
|
306
|
-
}
|
|
307
|
-
]
|
|
395
|
+
tracking: descriptor.tracking,
|
|
396
|
+
runtime: runtime ? {
|
|
397
|
+
theme: runtime.theme,
|
|
398
|
+
cssVariables: runtime.cssVariables
|
|
399
|
+
} : void 0,
|
|
400
|
+
assessments: assessments.length ? assessments : void 0
|
|
308
401
|
};
|
|
309
402
|
}
|
|
310
|
-
function extractAssessments(descriptor) {
|
|
311
|
-
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
|
|
312
|
-
}
|
|
313
403
|
|
|
314
404
|
// src/writeProject.ts
|
|
315
|
-
var
|
|
316
|
-
var
|
|
405
|
+
var import_node_path4 = require("path");
|
|
406
|
+
var import_validators = require("@lxpack/validators");
|
|
317
407
|
|
|
318
|
-
// src/
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
lines.push(` - id: ${question.id}`);
|
|
330
|
-
lines.push(` prompt: ${yamlQuote(question.prompt)}`);
|
|
331
|
-
lines.push(" choices:");
|
|
332
|
-
for (const choice of question.choices) {
|
|
333
|
-
lines.push(` - id: ${choice.id}`);
|
|
334
|
-
lines.push(` text: ${yamlQuote(choice.text)}`);
|
|
335
|
-
if (choice.correct) lines.push(" correct: true");
|
|
408
|
+
// src/spaDirs.ts
|
|
409
|
+
var import_promises = require("fs/promises");
|
|
410
|
+
var import_node_path3 = require("path");
|
|
411
|
+
async function resolveSpaDirs(options) {
|
|
412
|
+
const { descriptor, spaDistDir, lessonSpaDirs, projectRoot } = options;
|
|
413
|
+
const spaLessons = resolveSpaLessons(descriptor);
|
|
414
|
+
if (descriptor.layout === "single-spa") {
|
|
415
|
+
const spaDistRelative = spaDistDir ?? descriptor.spaDistDir ?? "dist";
|
|
416
|
+
const srcDist = projectRoot ? (0, import_node_path3.resolve)(projectRoot, spaDistRelative) : (0, import_node_path3.resolve)(spaDistRelative);
|
|
417
|
+
if (projectRoot) {
|
|
418
|
+
assertResolvedPathUnderRoot((0, import_node_path3.resolve)(projectRoot), srcDist);
|
|
336
419
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// src/yaml.ts
|
|
343
|
-
function yamlQuote2(value) {
|
|
344
|
-
if (/[:#\n\r]/.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
|
|
345
|
-
return JSON.stringify(value);
|
|
346
|
-
}
|
|
347
|
-
return value;
|
|
348
|
-
}
|
|
349
|
-
function emitCourseYaml(opts) {
|
|
350
|
-
const lines = [];
|
|
351
|
-
lines.push(`title: ${yamlQuote2(opts.title)}`);
|
|
352
|
-
lines.push(`version: ${yamlQuote2(opts.version)}`);
|
|
353
|
-
if (opts.description) lines.push(`description: ${yamlQuote2(opts.description)}`);
|
|
354
|
-
if (opts.runtime) {
|
|
355
|
-
lines.push("runtime:");
|
|
356
|
-
lines.push(` theme: ${yamlQuote2(opts.runtime.theme)}`);
|
|
357
|
-
if (opts.runtime.cssVariables && Object.keys(opts.runtime.cssVariables).length) {
|
|
358
|
-
lines.push(" cssVariables:");
|
|
359
|
-
for (const [key, value] of Object.entries(opts.runtime.cssVariables).sort(
|
|
360
|
-
([a], [b]) => a.localeCompare(b)
|
|
361
|
-
)) {
|
|
362
|
-
lines.push(` ${key}: ${JSON.stringify(String(value))}`);
|
|
363
|
-
}
|
|
420
|
+
try {
|
|
421
|
+
await (0, import_promises.access)(srcDist);
|
|
422
|
+
} catch {
|
|
423
|
+
throw new Error(`spaDistDir not found: ${srcDist}`);
|
|
364
424
|
}
|
|
425
|
+
const lessonId = spaLessons[0]?.id ?? "main";
|
|
426
|
+
return { [lessonId]: srcDist };
|
|
365
427
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
for (const lesson of opts.lessons) {
|
|
373
|
-
lines.push(` - id: ${lesson.id}`);
|
|
374
|
-
lines.push(` title: ${yamlQuote2(lesson.title)}`);
|
|
375
|
-
lines.push(` type: spa`);
|
|
376
|
-
lines.push(` path: ${lesson.path}`);
|
|
377
|
-
}
|
|
378
|
-
if (opts.assessments.length) {
|
|
379
|
-
lines.push("assessments:");
|
|
380
|
-
for (const assessment of opts.assessments) {
|
|
381
|
-
lines.push(` - id: ${assessment.id}`);
|
|
382
|
-
lines.push(` file: ${assessment.file}`);
|
|
428
|
+
const dirs = {};
|
|
429
|
+
const lessonDirs = lessonSpaDirs ?? {};
|
|
430
|
+
for (const lesson of descriptor.lessons) {
|
|
431
|
+
const src = lessonDirs[lesson.id];
|
|
432
|
+
if (!src) {
|
|
433
|
+
throw new Error(`lessonSpaDirs missing build output for lesson "${lesson.id}"`);
|
|
383
434
|
}
|
|
435
|
+
dirs[lesson.id] = (0, import_node_path3.resolve)(src);
|
|
384
436
|
}
|
|
385
|
-
return
|
|
386
|
-
`;
|
|
437
|
+
return dirs;
|
|
387
438
|
}
|
|
388
439
|
|
|
389
440
|
// src/writeProject.ts
|
|
390
|
-
async function copyDir(src, dest) {
|
|
391
|
-
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(dest), { recursive: true });
|
|
392
|
-
await (0, import_promises.cp)(src, dest, { recursive: true });
|
|
393
|
-
}
|
|
394
441
|
async function writeLxpackProject(options) {
|
|
395
442
|
const validation = validateDescriptor(options.descriptor);
|
|
396
443
|
if (!validation.ok) {
|
|
@@ -399,90 +446,42 @@ async function writeLxpackProject(options) {
|
|
|
399
446
|
);
|
|
400
447
|
}
|
|
401
448
|
const descriptor = validation.descriptor;
|
|
402
|
-
const outDir = (0,
|
|
403
|
-
await (
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
throw new Error(`spaDistDir not found: ${srcDist}`);
|
|
416
|
-
}
|
|
417
|
-
const destDist = (0, import_node_path2.join)(outDir, "dist");
|
|
418
|
-
await (0, import_promises.rm)(destDist, { recursive: true, force: true });
|
|
419
|
-
await copyDir(srcDist, destDist);
|
|
420
|
-
} else {
|
|
421
|
-
const lessonDirs = options.lessonSpaDirs ?? {};
|
|
422
|
-
for (const lesson of descriptor.lessons) {
|
|
423
|
-
const src = lessonDirs[lesson.id];
|
|
424
|
-
if (!src) {
|
|
425
|
-
throw new Error(`lessonSpaDirs missing build output for lesson "${lesson.id}"`);
|
|
426
|
-
}
|
|
427
|
-
const dest = (0, import_node_path2.join)(outDir, lesson.spaPath);
|
|
428
|
-
assertResolvedPathUnderRoot(outDir, dest);
|
|
429
|
-
await (0, import_promises.rm)(dest, { recursive: true, force: true });
|
|
430
|
-
await copyDir((0, import_node_path2.resolve)(src), dest);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (assessments.length) {
|
|
434
|
-
const assessmentsDir = (0, import_node_path2.join)(outDir, "assessments");
|
|
435
|
-
await (0, import_promises.mkdir)(assessmentsDir, { recursive: true });
|
|
436
|
-
for (const assessment of descriptor.assessments ?? []) {
|
|
437
|
-
await (0, import_promises.writeFile)(
|
|
438
|
-
(0, import_node_path2.join)(outDir, `assessments/${assessment.checkId}.yaml`),
|
|
439
|
-
emitAssessmentYaml(assessment),
|
|
440
|
-
"utf-8"
|
|
441
|
-
);
|
|
442
|
-
}
|
|
449
|
+
const outDir = (0, import_node_path4.resolve)(options.outDir);
|
|
450
|
+
const spaDirs = await resolveSpaDirs({ ...options, descriptor });
|
|
451
|
+
const interchange = descriptorToInterchange(descriptor);
|
|
452
|
+
const materialized = await (0, import_validators.materializeLessonkitProject)({
|
|
453
|
+
interchange,
|
|
454
|
+
spaDirs,
|
|
455
|
+
courseDir: outDir,
|
|
456
|
+
writeAuthoringFiles: true
|
|
457
|
+
});
|
|
458
|
+
if (!materialized.ok) {
|
|
459
|
+
throw new Error(
|
|
460
|
+
materialized.issues.map((i) => `${i.path ?? ""}: ${i.message}`.trim()).join("; ")
|
|
461
|
+
);
|
|
443
462
|
}
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
runtime,
|
|
451
|
-
tracking: descriptor.tracking,
|
|
452
|
-
lessons: spaLessons.map((l) => ({
|
|
453
|
-
id: l.id,
|
|
454
|
-
title: l.title,
|
|
455
|
-
type: "spa",
|
|
456
|
-
path: l.path
|
|
457
|
-
})),
|
|
458
|
-
assessments
|
|
459
|
-
}),
|
|
460
|
-
"utf-8"
|
|
461
|
-
);
|
|
462
|
-
const lessonkitJsonPath = (0, import_node_path2.join)(outDir, "lessonkit.json");
|
|
463
|
-
await (0, import_promises.writeFile)(
|
|
464
|
-
lessonkitJsonPath,
|
|
465
|
-
`${JSON.stringify(descriptorToInterchange(descriptor), null, 2)}
|
|
466
|
-
`,
|
|
467
|
-
"utf-8"
|
|
468
|
-
);
|
|
469
|
-
return { outDir, courseYamlPath, lessonkitJsonPath };
|
|
463
|
+
const courseDir = materialized.courseDir;
|
|
464
|
+
return {
|
|
465
|
+
outDir: courseDir,
|
|
466
|
+
courseYamlPath: (0, import_node_path4.join)(courseDir, "course.yaml"),
|
|
467
|
+
lessonkitJsonPath: (0, import_node_path4.join)(courseDir, "lessonkit.json")
|
|
468
|
+
};
|
|
470
469
|
}
|
|
471
470
|
|
|
472
471
|
// src/packageCourse.ts
|
|
473
|
-
var
|
|
474
|
-
var
|
|
472
|
+
var fsp = __toESM(require("fs/promises"), 1);
|
|
473
|
+
var import_node_path5 = require("path");
|
|
475
474
|
var import_node_os = require("os");
|
|
476
475
|
var import_api = require("@lxpack/api");
|
|
477
476
|
async function validateLessonkitProject(options) {
|
|
478
477
|
return (0, import_api.validateCourse)({
|
|
479
|
-
courseDir: (0,
|
|
478
|
+
courseDir: (0, import_node_path5.resolve)(options.courseDir),
|
|
480
479
|
target: options.target
|
|
481
480
|
});
|
|
482
481
|
}
|
|
483
482
|
async function buildLessonkitProject(options) {
|
|
484
483
|
return (0, import_api.buildCourse)({
|
|
485
|
-
courseDir: (0,
|
|
484
|
+
courseDir: (0, import_node_path5.resolve)(options.courseDir),
|
|
486
485
|
target: options.target,
|
|
487
486
|
output: options.output,
|
|
488
487
|
dir: options.dir,
|
|
@@ -490,9 +489,38 @@ async function buildLessonkitProject(options) {
|
|
|
490
489
|
assessments: options.assessments
|
|
491
490
|
});
|
|
492
491
|
}
|
|
492
|
+
async function pathExists(path) {
|
|
493
|
+
try {
|
|
494
|
+
await fsp.access(path);
|
|
495
|
+
return true;
|
|
496
|
+
} catch {
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
async function promoteStagingToOutDir(stagingDir, outDir) {
|
|
501
|
+
const tmpPromote = `${outDir}.tmp-promote`;
|
|
502
|
+
const backup = `${outDir}.bak`;
|
|
503
|
+
await fsp.rename(stagingDir, tmpPromote);
|
|
504
|
+
const hadOutDir = await pathExists(outDir);
|
|
505
|
+
if (hadOutDir) {
|
|
506
|
+
await fsp.rename(outDir, backup);
|
|
507
|
+
}
|
|
508
|
+
try {
|
|
509
|
+
await fsp.rename(tmpPromote, outDir);
|
|
510
|
+
} catch (promoteError) {
|
|
511
|
+
if (hadOutDir) {
|
|
512
|
+
await fsp.rename(backup, outDir).catch(() => void 0);
|
|
513
|
+
}
|
|
514
|
+
await fsp.rm(tmpPromote, { recursive: true, force: true }).catch(() => void 0);
|
|
515
|
+
throw promoteError;
|
|
516
|
+
}
|
|
517
|
+
if (hadOutDir) {
|
|
518
|
+
await fsp.rm(backup, { recursive: true, force: true }).catch(() => void 0);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
493
521
|
async function packageLessonkitCourse(options) {
|
|
494
522
|
const { target, output, dir, outputBaseDir, ...writeOpts } = options;
|
|
495
|
-
const outDir = (0,
|
|
523
|
+
const outDir = (0, import_node_path5.resolve)(writeOpts.outDir);
|
|
496
524
|
const descriptorValidation = validateDescriptor(writeOpts.descriptor);
|
|
497
525
|
if (!descriptorValidation.ok) {
|
|
498
526
|
return {
|
|
@@ -506,42 +534,50 @@ async function packageLessonkitCourse(options) {
|
|
|
506
534
|
};
|
|
507
535
|
}
|
|
508
536
|
const descriptor = descriptorValidation.descriptor;
|
|
509
|
-
const stagingDir = await
|
|
537
|
+
const stagingDir = await fsp.mkdtemp((0, import_node_path5.join)((0, import_node_os.tmpdir)(), "lessonkit-lxpack-"));
|
|
510
538
|
let promoted = false;
|
|
511
539
|
try {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
if (!validation.ok) {
|
|
540
|
+
let spaDirs;
|
|
541
|
+
try {
|
|
542
|
+
spaDirs = await resolveSpaDirs({ ...writeOpts, descriptor });
|
|
543
|
+
} catch (err) {
|
|
517
544
|
return {
|
|
518
545
|
ok: false,
|
|
519
546
|
courseDir: outDir,
|
|
520
547
|
target,
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
548
|
+
issues: [
|
|
549
|
+
{
|
|
550
|
+
path: "spaDirs",
|
|
551
|
+
message: err instanceof Error ? err.message : String(err)
|
|
552
|
+
}
|
|
553
|
+
]
|
|
527
554
|
};
|
|
528
555
|
}
|
|
556
|
+
const interchange = descriptorToInterchange(descriptor);
|
|
529
557
|
const outputBase = outputBaseDir ?? ".lxpack/out";
|
|
530
|
-
await
|
|
531
|
-
const defaultOutput = output ?? (dir ? (0,
|
|
532
|
-
const build = await
|
|
533
|
-
|
|
558
|
+
await fsp.mkdir((0, import_node_path5.join)(stagingDir, outputBase), { recursive: true });
|
|
559
|
+
const defaultOutput = output ?? (dir ? (0, import_node_path5.join)(outputBase, target) : (0, import_node_path5.join)(outputBase, `course-${target}.zip`));
|
|
560
|
+
const build = await (0, import_api.packageLessonkit)({
|
|
561
|
+
interchange,
|
|
562
|
+
spaDirs,
|
|
534
563
|
target,
|
|
535
|
-
|
|
564
|
+
courseDir: stagingDir,
|
|
565
|
+
output: defaultOutput,
|
|
536
566
|
dir,
|
|
537
|
-
|
|
567
|
+
outputBaseDir,
|
|
568
|
+
outputAnchorDir: stagingDir,
|
|
569
|
+
writeAuthoringFiles: true
|
|
538
570
|
});
|
|
539
571
|
if (!build.ok) {
|
|
572
|
+
const validation2 = {
|
|
573
|
+
ok: false,
|
|
574
|
+
issues: build.issues
|
|
575
|
+
};
|
|
540
576
|
return {
|
|
541
577
|
ok: false,
|
|
542
578
|
courseDir: outDir,
|
|
543
579
|
target,
|
|
544
|
-
validation,
|
|
580
|
+
validation: validation2,
|
|
545
581
|
build,
|
|
546
582
|
issues: build.issues.map((i) => ({
|
|
547
583
|
path: i.path,
|
|
@@ -550,46 +586,105 @@ async function packageLessonkitCourse(options) {
|
|
|
550
586
|
}))
|
|
551
587
|
};
|
|
552
588
|
}
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
589
|
+
const validation = {
|
|
590
|
+
ok: true,
|
|
591
|
+
manifest: build.manifest,
|
|
592
|
+
issues: build.issues
|
|
593
|
+
};
|
|
594
|
+
const stagingRoot = await fsp.realpath(stagingDir);
|
|
557
595
|
const remapArtifactPath = (artifactPath) => {
|
|
558
596
|
if (!artifactPath) return void 0;
|
|
559
|
-
const resolved = (0,
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
return (0, import_node_path3.join)(outDir, resolved.slice(stagingResolved.length + 1));
|
|
597
|
+
const resolved = (0, import_node_path5.resolve)(artifactPath);
|
|
598
|
+
if (resolved === stagingRoot || resolved.startsWith(`${stagingRoot}/`)) {
|
|
599
|
+
return (0, import_node_path5.join)(outDir, resolved.slice(stagingRoot.length + 1));
|
|
563
600
|
}
|
|
564
601
|
return artifactPath;
|
|
565
602
|
};
|
|
603
|
+
const remappedOutputPath = remapArtifactPath(
|
|
604
|
+
"outputPath" in build ? build.outputPath : void 0
|
|
605
|
+
);
|
|
606
|
+
const remappedOutputDir = remapArtifactPath("outputDir" in build ? build.outputDir : void 0);
|
|
607
|
+
await fsp.mkdir((0, import_node_path5.dirname)(outDir), { recursive: true });
|
|
608
|
+
await promoteStagingToOutDir(stagingDir, outDir);
|
|
609
|
+
promoted = true;
|
|
610
|
+
const remappedBuild = { ...build };
|
|
611
|
+
if ("outputPath" in remappedBuild && remappedOutputPath !== void 0) {
|
|
612
|
+
remappedBuild.outputPath = remappedOutputPath;
|
|
613
|
+
}
|
|
614
|
+
if ("outputDir" in remappedBuild && remappedOutputDir !== void 0) {
|
|
615
|
+
remappedBuild.outputDir = remappedOutputDir;
|
|
616
|
+
}
|
|
566
617
|
return {
|
|
567
618
|
ok: true,
|
|
568
619
|
courseDir: outDir,
|
|
569
620
|
target,
|
|
570
|
-
outputPath:
|
|
571
|
-
outputDir:
|
|
621
|
+
outputPath: remappedOutputPath,
|
|
622
|
+
outputDir: remappedOutputDir,
|
|
572
623
|
fileCount: build.fileCount,
|
|
573
624
|
validation,
|
|
574
|
-
build
|
|
625
|
+
build: remappedBuild
|
|
575
626
|
};
|
|
576
627
|
} finally {
|
|
577
628
|
if (!promoted) {
|
|
578
|
-
await
|
|
629
|
+
await fsp.rm(stagingDir, { recursive: true, force: true }).catch(() => void 0);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/index.ts
|
|
635
|
+
var import_tracking_schema2 = require("@lxpack/tracking-schema");
|
|
636
|
+
|
|
637
|
+
// src/telemetry.ts
|
|
638
|
+
var import_tracking_schema = require("@lxpack/tracking-schema");
|
|
639
|
+
var SUPPORTED = new Set(import_tracking_schema.LESSONKIT_TELEMETRY_EVENTS);
|
|
640
|
+
function telemetryEventToLessonkit(event) {
|
|
641
|
+
if (!SUPPORTED.has(event.name)) {
|
|
642
|
+
return null;
|
|
643
|
+
}
|
|
644
|
+
const name = event.name;
|
|
645
|
+
const mapped = {
|
|
646
|
+
name,
|
|
647
|
+
lessonId: event.lessonId
|
|
648
|
+
};
|
|
649
|
+
if (name === "quiz_completed" || name === "quiz_answered") {
|
|
650
|
+
const data = event.data;
|
|
651
|
+
mapped.assessmentId = data?.checkId;
|
|
652
|
+
if (data && "score" in data) {
|
|
653
|
+
mapped.score = data.score;
|
|
654
|
+
mapped.maxScore = data.maxScore;
|
|
655
|
+
mapped.passingScore = data.passingScore;
|
|
579
656
|
}
|
|
657
|
+
if (data) {
|
|
658
|
+
mapped.data = data;
|
|
659
|
+
}
|
|
660
|
+
} else if (name === "interaction" && event.data) {
|
|
661
|
+
mapped.data = event.data;
|
|
580
662
|
}
|
|
663
|
+
return mapped;
|
|
581
664
|
}
|
|
665
|
+
|
|
666
|
+
// src/index.ts
|
|
667
|
+
var import_validators2 = require("@lxpack/validators");
|
|
582
668
|
// Annotate the CommonJS export names for ESM import in node:
|
|
583
669
|
0 && (module.exports = {
|
|
670
|
+
LESSONKIT_TELEMETRY_EVENTS,
|
|
584
671
|
assessmentDescriptorToLxpack,
|
|
585
672
|
buildLessonkitProject,
|
|
586
673
|
descriptorToInterchange,
|
|
587
674
|
extractAssessments,
|
|
675
|
+
lessonkitInterchangeSchema,
|
|
588
676
|
mapLessonkitIds,
|
|
677
|
+
mapLessonkitTelemetryToBridgeAction,
|
|
678
|
+
mapLessonkitTelemetryToLxpack,
|
|
679
|
+
materializeLessonkitProject,
|
|
589
680
|
packageLessonkitCourse,
|
|
681
|
+
parseLessonkitInterchange,
|
|
682
|
+
resolveSafePackageOutputOverride,
|
|
590
683
|
resolveSpaLessons,
|
|
684
|
+
telemetryEventToLessonkit,
|
|
591
685
|
themeToLxpackRuntime,
|
|
592
686
|
validateDescriptor,
|
|
593
687
|
validateLessonkitProject,
|
|
688
|
+
validateProjectPaths,
|
|
594
689
|
writeLxpackProject
|
|
595
690
|
});
|