@lessonkit/lxpack 0.6.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 +40 -0
- package/dist/bridge.cjs +76 -0
- package/dist/bridge.d.cts +46 -0
- package/dist/bridge.d.ts +46 -0
- package/dist/bridge.js +46 -0
- package/dist/index.cjs +577 -0
- package/dist/index.d.cts +184 -0
- package/dist/index.d.ts +184 -0
- package/dist/index.js +543 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
assessmentDescriptorToLxpack: () => assessmentDescriptorToLxpack,
|
|
24
|
+
buildLessonkitProject: () => buildLessonkitProject,
|
|
25
|
+
descriptorToInterchange: () => descriptorToInterchange,
|
|
26
|
+
extractAssessments: () => extractAssessments,
|
|
27
|
+
mapLessonkitIds: () => mapLessonkitIds,
|
|
28
|
+
packageLessonkitCourse: () => packageLessonkitCourse,
|
|
29
|
+
resolveSpaLessons: () => resolveSpaLessons,
|
|
30
|
+
themeToLxpackRuntime: () => themeToLxpackRuntime,
|
|
31
|
+
validateDescriptor: () => validateDescriptor,
|
|
32
|
+
validateLessonkitProject: () => validateLessonkitProject,
|
|
33
|
+
writeLxpackProject: () => writeLxpackProject
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/validateDescriptor.ts
|
|
38
|
+
var import_core = require("@lessonkit/core");
|
|
39
|
+
|
|
40
|
+
// src/spaPath.ts
|
|
41
|
+
var import_node_path = require("path");
|
|
42
|
+
function isSafeRelativeSpaPath(spaPath) {
|
|
43
|
+
if (!spaPath.length || spaPath.includes("\0")) return false;
|
|
44
|
+
if (spaPath.startsWith("/") || spaPath.startsWith("\\")) return false;
|
|
45
|
+
if (/^[a-zA-Z]:[/\\]/.test(spaPath)) return false;
|
|
46
|
+
const segments = spaPath.split(/[/\\]/);
|
|
47
|
+
if (segments.some((s) => s === "..")) return false;
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
function assertResolvedPathUnderRoot(root, target) {
|
|
51
|
+
const rootResolved = (0, import_node_path.resolve)(root);
|
|
52
|
+
const targetResolved = (0, import_node_path.resolve)(target);
|
|
53
|
+
const prefix = rootResolved.endsWith(import_node_path.sep) ? rootResolved : rootResolved + import_node_path.sep;
|
|
54
|
+
if (targetResolved !== rootResolved && !targetResolved.startsWith(prefix)) {
|
|
55
|
+
throw new Error(`unsafe path escapes project root: ${target}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/validateDescriptor.ts
|
|
60
|
+
function normalizeDescriptor(input) {
|
|
61
|
+
const course = (0, import_core.validateId)(input.courseId, "courseId");
|
|
62
|
+
if (!course.ok) throw new Error("normalizeDescriptor called with invalid courseId");
|
|
63
|
+
return {
|
|
64
|
+
...input,
|
|
65
|
+
courseId: course.id,
|
|
66
|
+
title: input.title.trim(),
|
|
67
|
+
version: input.version?.trim() || void 0,
|
|
68
|
+
spaLessonId: input.spaLessonId?.trim() || void 0,
|
|
69
|
+
lessons: input.lessons.map((lesson) => {
|
|
70
|
+
const idResult = (0, import_core.validateId)(lesson.id, "lessonId");
|
|
71
|
+
if (!idResult.ok) throw new Error("normalizeDescriptor called with invalid lesson id");
|
|
72
|
+
return {
|
|
73
|
+
...lesson,
|
|
74
|
+
id: idResult.id,
|
|
75
|
+
title: lesson.title.trim(),
|
|
76
|
+
spaPath: lesson.spaPath?.trim() || void 0
|
|
77
|
+
};
|
|
78
|
+
}),
|
|
79
|
+
assessments: input.assessments?.map((assessment) => {
|
|
80
|
+
const check = (0, import_core.validateId)(assessment.checkId, "checkId");
|
|
81
|
+
if (!check.ok) throw new Error("normalizeDescriptor called with invalid checkId");
|
|
82
|
+
return {
|
|
83
|
+
...assessment,
|
|
84
|
+
checkId: check.id,
|
|
85
|
+
question: assessment.question.trim(),
|
|
86
|
+
choices: assessment.choices.map((c) => c.trim()).filter((c) => c.length > 0),
|
|
87
|
+
answer: assessment.answer.trim()
|
|
88
|
+
};
|
|
89
|
+
})
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function validateDescriptor(input) {
|
|
93
|
+
const issues = [];
|
|
94
|
+
const course = (0, import_core.validateId)(input.courseId, "courseId");
|
|
95
|
+
if (!course.ok) issues.push(...course.issues.map((i) => ({ path: i.path, message: i.message })));
|
|
96
|
+
if (!input.title?.trim()) {
|
|
97
|
+
issues.push({ path: "title", message: "title is required" });
|
|
98
|
+
}
|
|
99
|
+
if (!input.lessons?.length) {
|
|
100
|
+
issues.push({ path: "lessons", message: "at least one lesson is required" });
|
|
101
|
+
}
|
|
102
|
+
if (input.layout === "single-spa" && (input.lessons?.length ?? 0) > 1) {
|
|
103
|
+
issues.push({
|
|
104
|
+
path: "lessons",
|
|
105
|
+
message: "single-spa layout packages one SPA lesson; remove extra lesson entries or use per-lesson-spa"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const lessonIds = /* @__PURE__ */ new Set();
|
|
109
|
+
const spaPaths = /* @__PURE__ */ new Set();
|
|
110
|
+
for (const [index, lesson] of (input.lessons ?? []).entries()) {
|
|
111
|
+
const path = `lessons[${index}]`;
|
|
112
|
+
const lessonResult = (0, import_core.validateId)(lesson.id, `${path}.id`);
|
|
113
|
+
if (!lessonResult.ok) {
|
|
114
|
+
issues.push(...lessonResult.issues.map((i) => ({ path: i.path, message: i.message })));
|
|
115
|
+
} else if (lessonIds.has(lessonResult.id)) {
|
|
116
|
+
issues.push({ path: `${path}.id`, message: "duplicate lesson id" });
|
|
117
|
+
} else {
|
|
118
|
+
lessonIds.add(lessonResult.id);
|
|
119
|
+
}
|
|
120
|
+
if (!lesson.title?.trim()) {
|
|
121
|
+
issues.push({ path: `${path}.title`, message: "lesson title is required" });
|
|
122
|
+
}
|
|
123
|
+
if (input.layout === "per-lesson-spa") {
|
|
124
|
+
const spaPath = lesson.spaPath?.trim();
|
|
125
|
+
if (!spaPath) {
|
|
126
|
+
issues.push({
|
|
127
|
+
path: `${path}.spaPath`,
|
|
128
|
+
message: "spaPath is required for per-lesson-spa layout"
|
|
129
|
+
});
|
|
130
|
+
} else if (!isSafeRelativeSpaPath(spaPath)) {
|
|
131
|
+
issues.push({
|
|
132
|
+
path: `${path}.spaPath`,
|
|
133
|
+
message: "spaPath must be a relative path without '..' segments or absolute prefixes"
|
|
134
|
+
});
|
|
135
|
+
} else if (spaPaths.has(spaPath)) {
|
|
136
|
+
issues.push({ path: `${path}.spaPath`, message: "duplicate spaPath" });
|
|
137
|
+
} else {
|
|
138
|
+
spaPaths.add(spaPath);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (input.layout === "single-spa" && input.spaLessonId?.trim()) {
|
|
143
|
+
const spaId = input.spaLessonId.trim();
|
|
144
|
+
const spaResult = (0, import_core.validateId)(spaId, "spaLessonId");
|
|
145
|
+
if (!spaResult.ok) {
|
|
146
|
+
issues.push(...spaResult.issues.map((i) => ({ path: i.path, message: i.message })));
|
|
147
|
+
} else if (!lessonIds.has(spaResult.id)) {
|
|
148
|
+
issues.push({
|
|
149
|
+
path: "spaLessonId",
|
|
150
|
+
message: "spaLessonId must match a lesson id in lessons"
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const checkIds = /* @__PURE__ */ new Set();
|
|
155
|
+
for (const [index, assessment] of (input.assessments ?? []).entries()) {
|
|
156
|
+
const path = `assessments[${index}]`;
|
|
157
|
+
const check = (0, import_core.validateId)(assessment.checkId, `${path}.checkId`);
|
|
158
|
+
if (!check.ok) {
|
|
159
|
+
issues.push(...check.issues.map((i) => ({ path: i.path, message: i.message })));
|
|
160
|
+
} else if (checkIds.has(check.id)) {
|
|
161
|
+
issues.push({ path: `${path}.checkId`, message: "duplicate checkId" });
|
|
162
|
+
} else {
|
|
163
|
+
checkIds.add(check.id);
|
|
164
|
+
}
|
|
165
|
+
if (!assessment.question?.trim()) {
|
|
166
|
+
issues.push({ path: `${path}.question`, message: "question is required" });
|
|
167
|
+
}
|
|
168
|
+
const trimmedChoices = (assessment.choices ?? []).map((c) => c.trim()).filter((c) => c.length > 0);
|
|
169
|
+
if (!trimmedChoices.length) {
|
|
170
|
+
issues.push({
|
|
171
|
+
path: `${path}.choices`,
|
|
172
|
+
message: "at least one non-empty choice is required"
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (!assessment.answer?.trim()) {
|
|
176
|
+
issues.push({ path: `${path}.answer`, message: "answer is required" });
|
|
177
|
+
} else if (trimmedChoices.length && !trimmedChoices.includes(assessment.answer.trim())) {
|
|
178
|
+
issues.push({ path: `${path}.answer`, message: "answer must match a choice" });
|
|
179
|
+
}
|
|
180
|
+
const passingScore = assessment.passingScore;
|
|
181
|
+
if (passingScore !== void 0) {
|
|
182
|
+
if (!(passingScore > 0)) {
|
|
183
|
+
issues.push({
|
|
184
|
+
path: `${path}.passingScore`,
|
|
185
|
+
message: "passingScore must be greater than 0"
|
|
186
|
+
});
|
|
187
|
+
} else if (trimmedChoices.length && passingScore > trimmedChoices.length) {
|
|
188
|
+
issues.push({
|
|
189
|
+
path: `${path}.passingScore`,
|
|
190
|
+
message: "passingScore must not exceed the number of choices"
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (issues.length) return { ok: false, issues };
|
|
196
|
+
return { ok: true, descriptor: normalizeDescriptor(input) };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/mapIds.ts
|
|
200
|
+
var import_core2 = require("@lessonkit/core");
|
|
201
|
+
function mapLessonkitIds(descriptor) {
|
|
202
|
+
const courseId = (0, import_core2.assertValidId)(descriptor.courseId, "courseId");
|
|
203
|
+
const lessonIds = descriptor.lessons.map((l) => (0, import_core2.assertValidId)(l.id, "lessonId"));
|
|
204
|
+
const checkIds = (descriptor.assessments ?? []).map(
|
|
205
|
+
(a) => (0, import_core2.assertValidId)(a.checkId, "checkId")
|
|
206
|
+
);
|
|
207
|
+
return { courseId, lessonIds, checkIds };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// src/theme.ts
|
|
211
|
+
var import_themes = require("@lessonkit/themes");
|
|
212
|
+
function themeToLxpackRuntime(input) {
|
|
213
|
+
const theme = input.theme ?? (0, import_themes.getPresetTheme)(input.preset ?? "default");
|
|
214
|
+
const raw = (0, import_themes.themeToCssVariables)(theme);
|
|
215
|
+
const cssVariables = {};
|
|
216
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
217
|
+
cssVariables[key] = String(value);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
theme: theme.name,
|
|
221
|
+
cssVariables
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/interchange.ts
|
|
226
|
+
function resolveSpaLessons(descriptor) {
|
|
227
|
+
const mapped = mapLessonkitIds(descriptor);
|
|
228
|
+
if (descriptor.layout === "single-spa") {
|
|
229
|
+
const spaLessonId = descriptor.spaLessonId ?? mapped.lessonIds[0] ?? "main";
|
|
230
|
+
const firstLesson = descriptor.lessons.find((l) => l.id === spaLessonId);
|
|
231
|
+
return [
|
|
232
|
+
{
|
|
233
|
+
id: spaLessonId,
|
|
234
|
+
title: firstLesson?.title ?? descriptor.title,
|
|
235
|
+
path: "dist"
|
|
236
|
+
}
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
return descriptor.lessons.map((lesson) => ({
|
|
240
|
+
id: lesson.id,
|
|
241
|
+
title: lesson.title,
|
|
242
|
+
path: lesson.spaPath
|
|
243
|
+
}));
|
|
244
|
+
}
|
|
245
|
+
function descriptorToInterchange(descriptor) {
|
|
246
|
+
const mapped = mapLessonkitIds(descriptor);
|
|
247
|
+
const spaLessons = resolveSpaLessons(descriptor);
|
|
248
|
+
return {
|
|
249
|
+
format: "lessonkit",
|
|
250
|
+
version: "1",
|
|
251
|
+
course: {
|
|
252
|
+
id: mapped.courseId,
|
|
253
|
+
title: descriptor.title
|
|
254
|
+
},
|
|
255
|
+
lessons: spaLessons.map((l) => ({
|
|
256
|
+
id: l.id,
|
|
257
|
+
title: l.title,
|
|
258
|
+
type: "spa",
|
|
259
|
+
path: l.path
|
|
260
|
+
})),
|
|
261
|
+
tracking: descriptor.tracking
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/assessments.ts
|
|
266
|
+
function slugChoiceId(text, index) {
|
|
267
|
+
const base = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 32);
|
|
268
|
+
const stem = base.length ? base : "choice";
|
|
269
|
+
return `${stem}-${index + 1}`;
|
|
270
|
+
}
|
|
271
|
+
function assessmentDescriptorToLxpack(assessment) {
|
|
272
|
+
const choices = assessment.choices.map((text, index) => {
|
|
273
|
+
const id = slugChoiceId(text, index);
|
|
274
|
+
return {
|
|
275
|
+
id,
|
|
276
|
+
text,
|
|
277
|
+
correct: text === assessment.answer
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
return {
|
|
281
|
+
id: assessment.checkId,
|
|
282
|
+
passingScore: assessment.passingScore ?? 1,
|
|
283
|
+
questions: [
|
|
284
|
+
{
|
|
285
|
+
id: "q1",
|
|
286
|
+
prompt: assessment.question,
|
|
287
|
+
choices
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function extractAssessments(descriptor) {
|
|
293
|
+
return (descriptor.assessments ?? []).map(assessmentDescriptorToLxpack);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/writeProject.ts
|
|
297
|
+
var import_promises = require("fs/promises");
|
|
298
|
+
var import_node_path2 = require("path");
|
|
299
|
+
|
|
300
|
+
// src/assessmentYaml.ts
|
|
301
|
+
function yamlQuote(value) {
|
|
302
|
+
return JSON.stringify(value);
|
|
303
|
+
}
|
|
304
|
+
function emitAssessmentYaml(assessment) {
|
|
305
|
+
const lx = assessmentDescriptorToLxpack(assessment);
|
|
306
|
+
const lines = [];
|
|
307
|
+
lines.push(`id: ${lx.id}`);
|
|
308
|
+
lines.push(`passingScore: ${lx.passingScore}`);
|
|
309
|
+
lines.push("questions:");
|
|
310
|
+
for (const question of lx.questions) {
|
|
311
|
+
lines.push(` - id: ${question.id}`);
|
|
312
|
+
lines.push(` prompt: ${yamlQuote(question.prompt)}`);
|
|
313
|
+
lines.push(" choices:");
|
|
314
|
+
for (const choice of question.choices) {
|
|
315
|
+
lines.push(` - id: ${choice.id}`);
|
|
316
|
+
lines.push(` text: ${yamlQuote(choice.text)}`);
|
|
317
|
+
if (choice.correct) lines.push(" correct: true");
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return `${lines.join("\n")}
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/yaml.ts
|
|
325
|
+
function yamlQuote2(value) {
|
|
326
|
+
if (/[:#\n\r]/.test(value) || value.startsWith(" ") || value.endsWith(" ")) {
|
|
327
|
+
return JSON.stringify(value);
|
|
328
|
+
}
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
function emitCourseYaml(opts) {
|
|
332
|
+
const lines = [];
|
|
333
|
+
lines.push(`title: ${yamlQuote2(opts.title)}`);
|
|
334
|
+
lines.push(`version: ${yamlQuote2(opts.version)}`);
|
|
335
|
+
if (opts.description) lines.push(`description: ${yamlQuote2(opts.description)}`);
|
|
336
|
+
if (opts.runtime) {
|
|
337
|
+
lines.push("runtime:");
|
|
338
|
+
lines.push(` theme: ${yamlQuote2(opts.runtime.theme)}`);
|
|
339
|
+
if (opts.runtime.cssVariables && Object.keys(opts.runtime.cssVariables).length) {
|
|
340
|
+
lines.push(" cssVariables:");
|
|
341
|
+
for (const [key, value] of Object.entries(opts.runtime.cssVariables).sort(
|
|
342
|
+
([a], [b]) => a.localeCompare(b)
|
|
343
|
+
)) {
|
|
344
|
+
lines.push(` ${key}: ${JSON.stringify(String(value))}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (opts.tracking?.completion?.threshold !== void 0) {
|
|
349
|
+
lines.push("tracking:");
|
|
350
|
+
lines.push(" completion:");
|
|
351
|
+
lines.push(` threshold: ${opts.tracking.completion.threshold}`);
|
|
352
|
+
}
|
|
353
|
+
lines.push("lessons:");
|
|
354
|
+
for (const lesson of opts.lessons) {
|
|
355
|
+
lines.push(` - id: ${lesson.id}`);
|
|
356
|
+
lines.push(` title: ${yamlQuote2(lesson.title)}`);
|
|
357
|
+
lines.push(` type: spa`);
|
|
358
|
+
lines.push(` path: ${lesson.path}`);
|
|
359
|
+
}
|
|
360
|
+
if (opts.assessments.length) {
|
|
361
|
+
lines.push("assessments:");
|
|
362
|
+
for (const assessment of opts.assessments) {
|
|
363
|
+
lines.push(` - id: ${assessment.id}`);
|
|
364
|
+
lines.push(` file: ${assessment.file}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
return `${lines.join("\n")}
|
|
368
|
+
`;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// src/writeProject.ts
|
|
372
|
+
async function copyDir(src, dest) {
|
|
373
|
+
await (0, import_promises.mkdir)((0, import_node_path2.dirname)(dest), { recursive: true });
|
|
374
|
+
await (0, import_promises.cp)(src, dest, { recursive: true });
|
|
375
|
+
}
|
|
376
|
+
async function writeLxpackProject(options) {
|
|
377
|
+
const validation = validateDescriptor(options.descriptor);
|
|
378
|
+
if (!validation.ok) {
|
|
379
|
+
throw new Error(
|
|
380
|
+
validation.issues.map((i) => `${i.path}: ${i.message}`).join("; ")
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
const descriptor = validation.descriptor;
|
|
384
|
+
const outDir = (0, import_node_path2.resolve)(options.outDir);
|
|
385
|
+
await (0, import_promises.mkdir)(outDir, { recursive: true });
|
|
386
|
+
const spaLessons = resolveSpaLessons(descriptor);
|
|
387
|
+
const runtime = descriptor.theme ? themeToLxpackRuntime(descriptor.theme) : void 0;
|
|
388
|
+
const assessments = (descriptor.assessments ?? []).map((a) => ({
|
|
389
|
+
id: a.checkId,
|
|
390
|
+
file: `assessments/${a.checkId}.yaml`
|
|
391
|
+
}));
|
|
392
|
+
if (descriptor.layout === "single-spa") {
|
|
393
|
+
const srcDist = (0, import_node_path2.resolve)(options.spaDistDir ?? descriptor.spaDistDir ?? "dist");
|
|
394
|
+
try {
|
|
395
|
+
await (0, import_promises.access)(srcDist);
|
|
396
|
+
} catch {
|
|
397
|
+
throw new Error(`spaDistDir not found: ${srcDist}`);
|
|
398
|
+
}
|
|
399
|
+
const destDist = (0, import_node_path2.join)(outDir, "dist");
|
|
400
|
+
await (0, import_promises.rm)(destDist, { recursive: true, force: true });
|
|
401
|
+
await copyDir(srcDist, destDist);
|
|
402
|
+
} else {
|
|
403
|
+
const lessonDirs = options.lessonSpaDirs ?? {};
|
|
404
|
+
for (const lesson of descriptor.lessons) {
|
|
405
|
+
const src = lessonDirs[lesson.id];
|
|
406
|
+
if (!src) {
|
|
407
|
+
throw new Error(`lessonSpaDirs missing build output for lesson "${lesson.id}"`);
|
|
408
|
+
}
|
|
409
|
+
const dest = (0, import_node_path2.join)(outDir, lesson.spaPath);
|
|
410
|
+
assertResolvedPathUnderRoot(outDir, dest);
|
|
411
|
+
await (0, import_promises.rm)(dest, { recursive: true, force: true });
|
|
412
|
+
await copyDir((0, import_node_path2.resolve)(src), dest);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (assessments.length) {
|
|
416
|
+
const assessmentsDir = (0, import_node_path2.join)(outDir, "assessments");
|
|
417
|
+
await (0, import_promises.mkdir)(assessmentsDir, { recursive: true });
|
|
418
|
+
for (const assessment of descriptor.assessments ?? []) {
|
|
419
|
+
await (0, import_promises.writeFile)(
|
|
420
|
+
(0, import_node_path2.join)(outDir, `assessments/${assessment.checkId}.yaml`),
|
|
421
|
+
emitAssessmentYaml(assessment),
|
|
422
|
+
"utf-8"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const courseYamlPath = (0, import_node_path2.join)(outDir, "course.yaml");
|
|
427
|
+
await (0, import_promises.writeFile)(
|
|
428
|
+
courseYamlPath,
|
|
429
|
+
emitCourseYaml({
|
|
430
|
+
title: descriptor.title,
|
|
431
|
+
version: descriptor.version ?? "1.0.0",
|
|
432
|
+
runtime,
|
|
433
|
+
tracking: descriptor.tracking,
|
|
434
|
+
lessons: spaLessons.map((l) => ({
|
|
435
|
+
id: l.id,
|
|
436
|
+
title: l.title,
|
|
437
|
+
type: "spa",
|
|
438
|
+
path: l.path
|
|
439
|
+
})),
|
|
440
|
+
assessments
|
|
441
|
+
}),
|
|
442
|
+
"utf-8"
|
|
443
|
+
);
|
|
444
|
+
const lessonkitJsonPath = (0, import_node_path2.join)(outDir, "lessonkit.json");
|
|
445
|
+
await (0, import_promises.writeFile)(
|
|
446
|
+
lessonkitJsonPath,
|
|
447
|
+
`${JSON.stringify(descriptorToInterchange(descriptor), null, 2)}
|
|
448
|
+
`,
|
|
449
|
+
"utf-8"
|
|
450
|
+
);
|
|
451
|
+
return { outDir, courseYamlPath, lessonkitJsonPath };
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// src/packageCourse.ts
|
|
455
|
+
var import_promises2 = require("fs/promises");
|
|
456
|
+
var import_node_path3 = require("path");
|
|
457
|
+
var import_node_os = require("os");
|
|
458
|
+
var import_api = require("@lxpack/api");
|
|
459
|
+
async function validateLessonkitProject(options) {
|
|
460
|
+
return (0, import_api.validateCourse)({
|
|
461
|
+
courseDir: (0, import_node_path3.resolve)(options.courseDir),
|
|
462
|
+
target: options.target
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
async function buildLessonkitProject(options) {
|
|
466
|
+
return (0, import_api.buildCourse)({
|
|
467
|
+
courseDir: (0, import_node_path3.resolve)(options.courseDir),
|
|
468
|
+
target: options.target,
|
|
469
|
+
output: options.output,
|
|
470
|
+
dir: options.dir,
|
|
471
|
+
outputBaseDir: options.outputBaseDir,
|
|
472
|
+
assessments: options.assessments
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async function packageLessonkitCourse(options) {
|
|
476
|
+
const { target, output, dir, outputBaseDir, ...writeOpts } = options;
|
|
477
|
+
const outDir = (0, import_node_path3.resolve)(writeOpts.outDir);
|
|
478
|
+
const descriptorValidation = validateDescriptor(writeOpts.descriptor);
|
|
479
|
+
if (!descriptorValidation.ok) {
|
|
480
|
+
return {
|
|
481
|
+
ok: false,
|
|
482
|
+
courseDir: outDir,
|
|
483
|
+
target,
|
|
484
|
+
issues: descriptorValidation.issues.map((i) => ({
|
|
485
|
+
path: i.path,
|
|
486
|
+
message: i.message
|
|
487
|
+
}))
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const descriptor = descriptorValidation.descriptor;
|
|
491
|
+
const stagingDir = await (0, import_promises2.mkdtemp)((0, import_node_path3.join)((0, import_node_os.tmpdir)(), "lessonkit-lxpack-"));
|
|
492
|
+
let promoted = false;
|
|
493
|
+
try {
|
|
494
|
+
const written = await writeLxpackProject({ ...writeOpts, descriptor, outDir: stagingDir });
|
|
495
|
+
const courseDir = written.outDir;
|
|
496
|
+
const assessments = extractAssessments(descriptor);
|
|
497
|
+
const validation = await validateLessonkitProject({ courseDir, target });
|
|
498
|
+
if (!validation.ok) {
|
|
499
|
+
return {
|
|
500
|
+
ok: false,
|
|
501
|
+
courseDir: outDir,
|
|
502
|
+
target,
|
|
503
|
+
validation,
|
|
504
|
+
issues: validation.issues.map((i) => ({
|
|
505
|
+
path: i.path,
|
|
506
|
+
message: i.message,
|
|
507
|
+
severity: i.severity
|
|
508
|
+
}))
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
const outputBase = outputBaseDir ?? ".lxpack/out";
|
|
512
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.join)(courseDir, outputBase), { recursive: true });
|
|
513
|
+
const defaultOutput = output ?? (dir ? (0, import_node_path3.join)(outputBase, target) : (0, import_node_path3.join)(outputBase, `course-${target}.zip`));
|
|
514
|
+
const build = await buildLessonkitProject({
|
|
515
|
+
courseDir,
|
|
516
|
+
target,
|
|
517
|
+
output: defaultOutput.startsWith("/") ? defaultOutput : (0, import_node_path3.join)(courseDir, defaultOutput),
|
|
518
|
+
dir,
|
|
519
|
+
assessments: assessments.length ? assessments : void 0
|
|
520
|
+
});
|
|
521
|
+
if (!build.ok) {
|
|
522
|
+
return {
|
|
523
|
+
ok: false,
|
|
524
|
+
courseDir: outDir,
|
|
525
|
+
target,
|
|
526
|
+
validation,
|
|
527
|
+
build,
|
|
528
|
+
issues: build.issues.map((i) => ({
|
|
529
|
+
path: i.path,
|
|
530
|
+
message: i.message,
|
|
531
|
+
severity: i.severity
|
|
532
|
+
}))
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
await (0, import_promises2.rm)(outDir, { recursive: true, force: true });
|
|
536
|
+
await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(outDir), { recursive: true });
|
|
537
|
+
await (0, import_promises2.rename)(stagingDir, outDir);
|
|
538
|
+
promoted = true;
|
|
539
|
+
const remapArtifactPath = (artifactPath) => {
|
|
540
|
+
if (!artifactPath) return void 0;
|
|
541
|
+
const resolved = (0, import_node_path3.resolve)(artifactPath);
|
|
542
|
+
const stagingResolved = (0, import_node_path3.resolve)(stagingDir);
|
|
543
|
+
if (resolved === stagingResolved || resolved.startsWith(stagingResolved + "/")) {
|
|
544
|
+
return (0, import_node_path3.join)(outDir, resolved.slice(stagingResolved.length + 1));
|
|
545
|
+
}
|
|
546
|
+
return artifactPath;
|
|
547
|
+
};
|
|
548
|
+
return {
|
|
549
|
+
ok: true,
|
|
550
|
+
courseDir: outDir,
|
|
551
|
+
target,
|
|
552
|
+
outputPath: remapArtifactPath("outputPath" in build ? build.outputPath : void 0),
|
|
553
|
+
outputDir: remapArtifactPath("outputDir" in build ? build.outputDir : void 0),
|
|
554
|
+
fileCount: build.fileCount,
|
|
555
|
+
validation,
|
|
556
|
+
build
|
|
557
|
+
};
|
|
558
|
+
} finally {
|
|
559
|
+
if (!promoted) {
|
|
560
|
+
await (0, import_promises2.rm)(stagingDir, { recursive: true, force: true }).catch(() => void 0);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
565
|
+
0 && (module.exports = {
|
|
566
|
+
assessmentDescriptorToLxpack,
|
|
567
|
+
buildLessonkitProject,
|
|
568
|
+
descriptorToInterchange,
|
|
569
|
+
extractAssessments,
|
|
570
|
+
mapLessonkitIds,
|
|
571
|
+
packageLessonkitCourse,
|
|
572
|
+
resolveSpaLessons,
|
|
573
|
+
themeToLxpackRuntime,
|
|
574
|
+
validateDescriptor,
|
|
575
|
+
validateLessonkitProject,
|
|
576
|
+
writeLxpackProject
|
|
577
|
+
});
|