npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@lxpack/validators

v0.7.0

Published

Course manifest validation for LXPack

Readme

@lxpack/validators

Documentation npm version CI License Node.js

Zod schemas and filesystem validation for LXPack course manifests — flow, variables, component lessons, and xAPI tracking.

Part of LXPack — an AI-native learning experience compiler and runtime. Docs: course.yaml · Troubleshooting.

| Related | Package | |---------|---------| | CLI | @lxpack/cli | | Programmatic API | @lxpack/api | | Packaging | @lxpack/scorm | | Runtime | @lxpack/runtime | | Components | @lxpack/components |

Install

npm install @lxpack/validators

Requires Node.js 18 or 20 (18+).

Usage

import {
  validateCourse,
  validateCourseManifest,
  loadManifest,
  buildRuntimeAssessmentBundle,
  buildRuntimeAssessmentBundleFromData,
  courseManifestSchema,
  type ValidationResult,
} from "@lxpack/validators";

const result: ValidationResult = await validateCourse("/path/to/my-course");

if (!result.valid) {
  for (const issue of result.issues) {
    console.error(`${issue.path}: ${issue.message}`);
  }
} else {
  const { manifest } = result;
  const bundle = await buildRuntimeAssessmentBundle("/path/to/my-course", manifest);
  // bundle.assessments — learner-facing questions (no correct flags)
  // bundle.answerKeys — scoring keys for the runtime
  // bundle.configs — maxAttempts, shuffleChoices, showFeedback per assessment
  // bundle.feedback — questionId → explanation text for feedback modes
}

Load and parse only

const loaded = await loadManifest("/path/to/my-course");
if (Array.isArray(loaded)) {
  // validation issues
} else {
  const { manifest } = loaded;
}

Schema-only validation

const parsed = courseManifestSchema.safeParse(manifestObject);

Flow validation

import { validateFlow, detectFlowCycles, collectActivityIds } from "@lxpack/validators";

validateCourse runs flow checks when manifest.flow is present: valid goto targets, known condition shapes, and cycle detection.

Safe path resolution

import { resolveCoursePath, isPathContained } from "@lxpack/validators";

const abs = resolveCoursePath(courseDir, "lessons/intro.md");
isPathContained(courseDir, abs); // true if inside course root

Exports

| Export | Description | |--------|-------------| | validateCourse(dir) | Parse course.yaml, validate schema, flow, files, symlink containment | | validateCourseManifest(dir, manifest, options?) | Validate an in-memory manifest (optional assessmentData instead of on-disk YAML) | | validateXapiTracking(manifest) | Require HTTPS tracking.xapi.activityIri for xapi/cmi5 exports | | getCourseActivityIri(manifest) | Read course activity IRI from manifest | | loadManifest(courseDir) | Load and parse course.yaml | | buildRuntimeAssessmentBundle(dir, manifest) | Load assessments; split learner view, keys, configs, feedback | | buildRuntimeAssessmentBundleFromData(manifest, data) | Same bundle shape from in-memory assessment objects | | loadParsedAssessmentsFromData(manifest, data) | Parse injected assessment payloads with manifest cross-checks | | toLearnerAssessment(assessment) | Strip correct from choices; build string \| string[] answer keys; extract config and feedback maps | | AnswerKeyValue, SelectionMode | Types for per-question answer keys and selection mode | | validateFlow(manifest) | Flow rule and target validation | | detectFlowCycles(manifest) | Flow-jump cycle detection for branching graphs | | collectActivityIds(manifest) | Lesson and assessment IDs for flow targets | | conditionSchema, flowRuleSchema | Zod schemas for flow conditions and rules | | BUILTIN_COMPONENT_IDS, isBuiltinComponentId | Allowed built-in component lesson IDs | | resolveCoursePath(dir, relativePath) | Resolve a path safely inside the course directory | | isPathContained(root, target) | Whether target stays under root | | courseManifestSchema | Zod schema for the full course manifest | | lessonSchema, assessmentSchema, variableDefSchema, … | Strict sub-schemas | | CourseManifest, Lesson, Assessment, FlowRule, VariableDef, RuntimeAssessmentBundle | TypeScript types |

What gets validated

Author-facing rules: Course structure · Quizzes and assessments · Branching and paths.

  • Manifest shape: lessons, assessments, optional variables and flow, tracking rules
  • Lesson types: markdown (file), html (path), spa (path with index.html), component (component + optional props)
  • SPA lessons: path containment, required index.html; warns when SPA HTML calls window.lxpack instead of window.parent.lxpackBridge.v1
  • runtime.cssVariables — optional map of CSS custom properties for the learner shell
  • Component IDs: built-in IDs or course overrides under components/<id>/
  • Flow rules: condition grammar, goto targets that reference known activity IDs, acyclic flow (errors for cycles)
  • Assessment YAML: strict MCQ schemas (single- or multi-select); optional selectionMode, maxAttempts, shuffleChoices, showFeedback; explanation per question
  • Duplicate lesson IDs
  • Path containment — referenced files must stay inside the course directory (including via symlinks)
  • On-disk assets: files exist and assessment paths are regular files

Assessment packaging

Author assessments live as YAML under assessments/ in the course repo. At build/preview time:

  1. buildRuntimeAssessmentBundle() reads each assessment file.
  2. Learner payload — questions and choices without correct flags.
  3. Answer keysquestionId → choiceId (single-select) or questionId → choiceId[] (multi-select) for scoring. Additive shape: AnswerKeyValue = string | string[] — existing single-select courses keep string keys.
  4. Configs — per-assessment quiz behavior (maxAttempts, shuffleChoices, showFeedback).
  5. FeedbackquestionId → explanation for immediate/end feedback (not shipped as separate files).

The CLI and @lxpack/scorm embed all of this in the HTML config JSON. Exported ZIPs do not include assessments/ files, so answer keys are not fetchable as static assets.

Development

From the monorepo root:

pnpm --filter @lxpack/validators build
pnpm --filter @lxpack/validators test
pnpm --filter @lxpack/validators typecheck

Links

License

Apache-2.0