@lxpack/validators 0.2.2 → 0.3.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 +3 -1
- package/dist/index.d.ts +59 -1
- package/dist/index.js +68 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/eddiethedean/lxpack/blob/main/LICENSE)
|
|
6
6
|
[](https://nodejs.org/)
|
|
7
7
|
|
|
8
|
-
Zod schemas and filesystem validation for LXPack course manifests — including flow, variables,
|
|
8
|
+
Zod schemas and filesystem validation for LXPack course manifests — including flow, variables, component lessons, and xAPI tracking (v0.3.0).
|
|
9
9
|
|
|
10
10
|
Part of [LXPack](https://github.com/eddiethedean/lxpack) — an AI-native learning experience compiler and runtime.
|
|
11
11
|
|
|
@@ -90,6 +90,8 @@ isPathContained(courseDir, abs); // true if inside course root
|
|
|
90
90
|
| Export | Description |
|
|
91
91
|
|--------|-------------|
|
|
92
92
|
| `validateCourse(dir)` | Parse `course.yaml`, validate schema, flow, files, symlink containment |
|
|
93
|
+
| `validateXapiTracking(manifest)` | Require HTTPS `tracking.xapi.activityIri` for xapi/cmi5 exports |
|
|
94
|
+
| `getCourseActivityIri(manifest)` | Read course activity IRI from manifest |
|
|
93
95
|
| `loadManifest(courseDir)` | Load and parse `course.yaml` |
|
|
94
96
|
| `buildRuntimeAssessmentBundle(dir, manifest)` | Load assessments; split learner view, keys, configs, feedback |
|
|
95
97
|
| `toLearnerAssessment(assessment)` | Strip `correct` from choices; extract config and feedback maps |
|
package/dist/index.d.ts
CHANGED
|
@@ -343,6 +343,16 @@ declare const variableDefSchema: z.ZodEffects<z.ZodObject<{
|
|
|
343
343
|
default: string | number | boolean;
|
|
344
344
|
type?: "string" | "number" | "boolean" | undefined;
|
|
345
345
|
}>;
|
|
346
|
+
declare const xapiTrackingSchema: z.ZodObject<{
|
|
347
|
+
activityIri: z.ZodEffects<z.ZodString, string, string>;
|
|
348
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
349
|
+
}, "strict", z.ZodTypeAny, {
|
|
350
|
+
activityIri: string;
|
|
351
|
+
displayName?: string | undefined;
|
|
352
|
+
}, {
|
|
353
|
+
activityIri: string;
|
|
354
|
+
displayName?: string | undefined;
|
|
355
|
+
}>;
|
|
346
356
|
declare const trackingSchema: z.ZodOptional<z.ZodObject<{
|
|
347
357
|
completion: z.ZodOptional<z.ZodObject<{
|
|
348
358
|
threshold: z.ZodDefault<z.ZodNumber>;
|
|
@@ -351,15 +361,34 @@ declare const trackingSchema: z.ZodOptional<z.ZodObject<{
|
|
|
351
361
|
}, {
|
|
352
362
|
threshold?: number | undefined;
|
|
353
363
|
}>>;
|
|
364
|
+
xapi: z.ZodOptional<z.ZodObject<{
|
|
365
|
+
activityIri: z.ZodEffects<z.ZodString, string, string>;
|
|
366
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
367
|
+
}, "strict", z.ZodTypeAny, {
|
|
368
|
+
activityIri: string;
|
|
369
|
+
displayName?: string | undefined;
|
|
370
|
+
}, {
|
|
371
|
+
activityIri: string;
|
|
372
|
+
displayName?: string | undefined;
|
|
373
|
+
}>>;
|
|
354
374
|
}, "strict", z.ZodTypeAny, {
|
|
355
375
|
completion?: {
|
|
356
376
|
threshold: number;
|
|
357
377
|
} | undefined;
|
|
378
|
+
xapi?: {
|
|
379
|
+
activityIri: string;
|
|
380
|
+
displayName?: string | undefined;
|
|
381
|
+
} | undefined;
|
|
358
382
|
}, {
|
|
359
383
|
completion?: {
|
|
360
384
|
threshold?: number | undefined;
|
|
361
385
|
} | undefined;
|
|
386
|
+
xapi?: {
|
|
387
|
+
activityIri: string;
|
|
388
|
+
displayName?: string | undefined;
|
|
389
|
+
} | undefined;
|
|
362
390
|
}>>;
|
|
391
|
+
type XapiTrackingConfig = z.infer<typeof xapiTrackingSchema>;
|
|
363
392
|
declare const runtimeConfigSchema: z.ZodOptional<z.ZodObject<{
|
|
364
393
|
theme: z.ZodDefault<z.ZodString>;
|
|
365
394
|
}, "strict", z.ZodTypeAny, {
|
|
@@ -387,14 +416,32 @@ declare const courseManifestSchema: z.ZodObject<{
|
|
|
387
416
|
}, {
|
|
388
417
|
threshold?: number | undefined;
|
|
389
418
|
}>>;
|
|
419
|
+
xapi: z.ZodOptional<z.ZodObject<{
|
|
420
|
+
activityIri: z.ZodEffects<z.ZodString, string, string>;
|
|
421
|
+
displayName: z.ZodOptional<z.ZodString>;
|
|
422
|
+
}, "strict", z.ZodTypeAny, {
|
|
423
|
+
activityIri: string;
|
|
424
|
+
displayName?: string | undefined;
|
|
425
|
+
}, {
|
|
426
|
+
activityIri: string;
|
|
427
|
+
displayName?: string | undefined;
|
|
428
|
+
}>>;
|
|
390
429
|
}, "strict", z.ZodTypeAny, {
|
|
391
430
|
completion?: {
|
|
392
431
|
threshold: number;
|
|
393
432
|
} | undefined;
|
|
433
|
+
xapi?: {
|
|
434
|
+
activityIri: string;
|
|
435
|
+
displayName?: string | undefined;
|
|
436
|
+
} | undefined;
|
|
394
437
|
}, {
|
|
395
438
|
completion?: {
|
|
396
439
|
threshold?: number | undefined;
|
|
397
440
|
} | undefined;
|
|
441
|
+
xapi?: {
|
|
442
|
+
activityIri: string;
|
|
443
|
+
displayName?: string | undefined;
|
|
444
|
+
} | undefined;
|
|
398
445
|
}>>;
|
|
399
446
|
variables: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodObject<{
|
|
400
447
|
default: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
@@ -509,6 +556,10 @@ declare const courseManifestSchema: z.ZodObject<{
|
|
|
509
556
|
completion?: {
|
|
510
557
|
threshold: number;
|
|
511
558
|
} | undefined;
|
|
559
|
+
xapi?: {
|
|
560
|
+
activityIri: string;
|
|
561
|
+
displayName?: string | undefined;
|
|
562
|
+
} | undefined;
|
|
512
563
|
} | undefined;
|
|
513
564
|
variables?: Record<string, {
|
|
514
565
|
default: string | number | boolean;
|
|
@@ -550,6 +601,10 @@ declare const courseManifestSchema: z.ZodObject<{
|
|
|
550
601
|
completion?: {
|
|
551
602
|
threshold?: number | undefined;
|
|
552
603
|
} | undefined;
|
|
604
|
+
xapi?: {
|
|
605
|
+
activityIri: string;
|
|
606
|
+
displayName?: string | undefined;
|
|
607
|
+
} | undefined;
|
|
553
608
|
} | undefined;
|
|
554
609
|
variables?: Record<string, {
|
|
555
610
|
default: string | number | boolean;
|
|
@@ -675,4 +730,7 @@ declare function enumerateActivities(manifest: CourseManifest): CourseActivity[]
|
|
|
675
730
|
|
|
676
731
|
declare function escapeHtml(text: string): string;
|
|
677
732
|
|
|
678
|
-
|
|
733
|
+
declare function validateXapiTracking(manifest: CourseManifest): ValidationIssue[];
|
|
734
|
+
declare function getCourseActivityIri(manifest: CourseManifest): string | undefined;
|
|
735
|
+
|
|
736
|
+
export { type Assessment, type AssessmentRef, type AssessmentRuntimeConfig, BUILTIN_COMPONENT_IDS, type BuiltinComponentId, type ComponentLesson, type Condition, type CourseActivity, type CourseManifest, type FlowRule, type LearnerAssessment, type LearnerChoice, type LearnerQuestion, type Lesson, type QuestionFeedback, type RuntimeAssessmentBundle, type ShowFeedback, type ValidationIssue, type ValidationResult, type VariableDef, type XapiTrackingConfig, activityIdSchema, assertResolvedPathContained, assessmentQuestionSchema, assessmentRefSchema, assessmentSchema, buildActivityOrder, buildRuntimeAssessmentBundle, buildRuntimeAssessmentBundleFromParsed, collectActivityIds, collectAssessmentIds, collectInteractionIds, componentLessonSchema, conditionSchema, courseManifestSchema, detectFlowCycles, enumerateActivities, escapeHtml, flowRuleSchema, formatErrorMessage, formatIssuePath, getCourseActivityIri, htmlLessonSchema, isBuiltinComponentId, isPathContained, lessonSchema, loadManifest, loadParsedAssessments, markdownLessonSchema, resolveCoursePath, runtimeConfigSchema, showFeedbackSchema, toLearnerAssessment, trackingSchema, validateCourse, validateFlow, validateXapiTracking, variableDefSchema, xapiTrackingSchema };
|
package/dist/index.js
CHANGED
|
@@ -150,10 +150,17 @@ var variableDefSchema = z2.object({
|
|
|
150
150
|
});
|
|
151
151
|
}
|
|
152
152
|
});
|
|
153
|
+
var xapiTrackingSchema = z2.object({
|
|
154
|
+
activityIri: z2.string().url().refine((u) => u.startsWith("https://"), {
|
|
155
|
+
message: "activityIri must be an https URL"
|
|
156
|
+
}),
|
|
157
|
+
displayName: z2.string().min(1).optional()
|
|
158
|
+
}).strict();
|
|
153
159
|
var trackingSchema = z2.object({
|
|
154
160
|
completion: z2.object({
|
|
155
161
|
threshold: z2.number().min(0).max(1).default(0.9)
|
|
156
|
-
}).strict().optional()
|
|
162
|
+
}).strict().optional(),
|
|
163
|
+
xapi: xapiTrackingSchema.optional()
|
|
157
164
|
}).strict().optional();
|
|
158
165
|
var runtimeConfigSchema = z2.object({
|
|
159
166
|
theme: z2.string().default("modern")
|
|
@@ -612,8 +619,30 @@ function validateMarkdownLesson(courseDir, lesson) {
|
|
|
612
619
|
// src/validate/lesson-html.ts
|
|
613
620
|
import { existsSync as existsSync3, statSync as statSync3 } from "fs";
|
|
614
621
|
import { join } from "path";
|
|
622
|
+
var HTML_LESSON_PATH_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_./-]*$/;
|
|
623
|
+
function validateHtmlLessonPath(path) {
|
|
624
|
+
if (/["'<>]/.test(path) || /\s/.test(path)) {
|
|
625
|
+
return "HTML interaction path contains invalid characters (quotes, angle brackets, or whitespace)";
|
|
626
|
+
}
|
|
627
|
+
if (path.includes("..")) {
|
|
628
|
+
return "HTML interaction path must not contain '..' segments";
|
|
629
|
+
}
|
|
630
|
+
if (!HTML_LESSON_PATH_PATTERN.test(path)) {
|
|
631
|
+
return "HTML interaction path must start with a letter and use only letters, numbers, /, _, ., and -";
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
615
635
|
function validateHtmlLesson(courseDir, lesson) {
|
|
616
636
|
const issues = [];
|
|
637
|
+
const pathError = validateHtmlLessonPath(lesson.path);
|
|
638
|
+
if (pathError) {
|
|
639
|
+
issues.push({
|
|
640
|
+
path: `lessons.${lesson.id}.path`,
|
|
641
|
+
message: pathError,
|
|
642
|
+
severity: "error"
|
|
643
|
+
});
|
|
644
|
+
return issues;
|
|
645
|
+
}
|
|
617
646
|
const resolved = resolveCoursePath(courseDir, lesson.path);
|
|
618
647
|
if (!resolved.ok) {
|
|
619
648
|
issues.push({
|
|
@@ -860,6 +889,40 @@ function enumerateActivities(manifest) {
|
|
|
860
889
|
function escapeHtml(text) {
|
|
861
890
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
862
891
|
}
|
|
892
|
+
|
|
893
|
+
// src/xapi-validate.ts
|
|
894
|
+
function validateXapiTracking(manifest) {
|
|
895
|
+
const issues = [];
|
|
896
|
+
const xapi = manifest.tracking?.xapi;
|
|
897
|
+
if (!xapi) {
|
|
898
|
+
issues.push({
|
|
899
|
+
path: "tracking.xapi",
|
|
900
|
+
message: "tracking.xapi.activityIri is required for xapi/cmi5 export targets",
|
|
901
|
+
severity: "error"
|
|
902
|
+
});
|
|
903
|
+
return issues;
|
|
904
|
+
}
|
|
905
|
+
try {
|
|
906
|
+
const url = new URL(xapi.activityIri);
|
|
907
|
+
if (url.protocol !== "https:") {
|
|
908
|
+
issues.push({
|
|
909
|
+
path: "tracking.xapi.activityIri",
|
|
910
|
+
message: "activityIri must use https",
|
|
911
|
+
severity: "error"
|
|
912
|
+
});
|
|
913
|
+
}
|
|
914
|
+
} catch {
|
|
915
|
+
issues.push({
|
|
916
|
+
path: "tracking.xapi.activityIri",
|
|
917
|
+
message: "activityIri must be a valid URL",
|
|
918
|
+
severity: "error"
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
return issues;
|
|
922
|
+
}
|
|
923
|
+
function getCourseActivityIri(manifest) {
|
|
924
|
+
return manifest.tracking?.xapi?.activityIri;
|
|
925
|
+
}
|
|
863
926
|
export {
|
|
864
927
|
BUILTIN_COMPONENT_IDS,
|
|
865
928
|
activityIdSchema,
|
|
@@ -882,6 +945,7 @@ export {
|
|
|
882
945
|
flowRuleSchema,
|
|
883
946
|
formatErrorMessage,
|
|
884
947
|
formatIssuePath,
|
|
948
|
+
getCourseActivityIri,
|
|
885
949
|
htmlLessonSchema,
|
|
886
950
|
isBuiltinComponentId,
|
|
887
951
|
isPathContained,
|
|
@@ -896,5 +960,7 @@ export {
|
|
|
896
960
|
trackingSchema,
|
|
897
961
|
validateCourse,
|
|
898
962
|
validateFlow,
|
|
899
|
-
|
|
963
|
+
validateXapiTracking,
|
|
964
|
+
variableDefSchema,
|
|
965
|
+
xapiTrackingSchema
|
|
900
966
|
};
|