@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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![License](https://img.shields.io/github/license/eddiethedean/lxpack)](https://github.com/eddiethedean/lxpack/blob/main/LICENSE)
6
6
  [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen)](https://nodejs.org/)
7
7
 
8
- Zod schemas and filesystem validation for LXPack course manifests — including flow, variables, and component lessons (v0.2.2).
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
- 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, activityIdSchema, assertResolvedPathContained, assessmentQuestionSchema, assessmentRefSchema, assessmentSchema, buildActivityOrder, buildRuntimeAssessmentBundle, buildRuntimeAssessmentBundleFromParsed, collectActivityIds, collectAssessmentIds, collectInteractionIds, componentLessonSchema, conditionSchema, courseManifestSchema, detectFlowCycles, enumerateActivities, escapeHtml, flowRuleSchema, formatErrorMessage, formatIssuePath, htmlLessonSchema, isBuiltinComponentId, isPathContained, lessonSchema, loadManifest, loadParsedAssessments, markdownLessonSchema, resolveCoursePath, runtimeConfigSchema, showFeedbackSchema, toLearnerAssessment, trackingSchema, validateCourse, validateFlow, variableDefSchema };
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
- variableDefSchema
963
+ validateXapiTracking,
964
+ variableDefSchema,
965
+ xapiTrackingSchema
900
966
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lxpack/validators",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "Course manifest validation for LXPack",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {