lens-content-processor 0.4.0 → 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.
Files changed (68) hide show
  1. package/dist/bundler/video.d.ts +12 -6
  2. package/dist/bundler/video.js +57 -34
  3. package/dist/bundler/video.js.map +1 -1
  4. package/dist/cli.d.ts +11 -2
  5. package/dist/cli.js +36 -19
  6. package/dist/cli.js.map +1 -1
  7. package/dist/content-schema.js +71 -16
  8. package/dist/content-schema.js.map +1 -1
  9. package/dist/dedupe-errors.d.ts +14 -0
  10. package/dist/dedupe-errors.js +33 -0
  11. package/dist/dedupe-errors.js.map +1 -0
  12. package/dist/flattener/index.d.ts +3 -28
  13. package/dist/flattener/index.js +507 -745
  14. package/dist/flattener/index.js.map +1 -1
  15. package/dist/flattener/resolve-text-links.d.ts +25 -0
  16. package/dist/flattener/resolve-text-links.js +248 -0
  17. package/dist/flattener/resolve-text-links.js.map +1 -0
  18. package/dist/flattener/test-helpers.d.ts +1 -1
  19. package/dist/flattener/test-helpers.js +1 -3
  20. package/dist/flattener/test-helpers.js.map +1 -1
  21. package/dist/index.d.ts +53 -21
  22. package/dist/index.js +285 -140
  23. package/dist/index.js.map +1 -1
  24. package/dist/parser/article.d.ts +2 -1
  25. package/dist/parser/article.js +75 -14
  26. package/dist/parser/article.js.map +1 -1
  27. package/dist/parser/course.d.ts +1 -1
  28. package/dist/parser/course.js +79 -22
  29. package/dist/parser/course.js.map +1 -1
  30. package/dist/parser/frontmatter.d.ts +1 -1
  31. package/dist/parser/frontmatter.js +38 -12
  32. package/dist/parser/frontmatter.js.map +1 -1
  33. package/dist/parser/learning-outcome.d.ts +5 -2
  34. package/dist/parser/learning-outcome.js +75 -37
  35. package/dist/parser/learning-outcome.js.map +1 -1
  36. package/dist/parser/lens.d.ts +40 -41
  37. package/dist/parser/lens.js +365 -349
  38. package/dist/parser/lens.js.map +1 -1
  39. package/dist/parser/module.d.ts +7 -15
  40. package/dist/parser/module.js +135 -171
  41. package/dist/parser/module.js.map +1 -1
  42. package/dist/parser/sections.d.ts +12 -1
  43. package/dist/parser/sections.js +74 -95
  44. package/dist/parser/sections.js.map +1 -1
  45. package/dist/parser/video-transcript.d.ts +2 -1
  46. package/dist/parser/video-transcript.js +8 -7
  47. package/dist/parser/video-transcript.js.map +1 -1
  48. package/dist/reference-graph.d.ts +8 -0
  49. package/dist/reference-graph.js +105 -0
  50. package/dist/reference-graph.js.map +1 -0
  51. package/dist/validator/card-module-membership.d.ts +2 -0
  52. package/dist/validator/card-module-membership.js +80 -0
  53. package/dist/validator/card-module-membership.js.map +1 -0
  54. package/dist/validator/output-integrity.js +5 -5
  55. package/dist/validator/output-integrity.js.map +1 -1
  56. package/dist/validator/segment-fields.d.ts +1 -1
  57. package/dist/validator/segment-fields.js +2 -2
  58. package/dist/validator/segment-fields.js.map +1 -1
  59. package/dist/validator/test-segments.d.ts +12 -0
  60. package/dist/validator/test-segments.js +40 -0
  61. package/dist/validator/test-segments.js.map +1 -0
  62. package/dist/validator/url-reachability.d.ts +2 -1
  63. package/dist/validator/url-reachability.js +40 -20
  64. package/dist/validator/url-reachability.js.map +1 -1
  65. package/package.json +1 -1
  66. package/dist/validator/chat-precedence.d.ts +0 -9
  67. package/dist/validator/chat-precedence.js +0 -32
  68. package/dist/validator/chat-precedence.js.map +0 -1
@@ -7,7 +7,7 @@ const EXCERPT_ONLY_FIELDS = new Set(['from', 'to']);
7
7
  * Validate that fields are appropriate for the segment type.
8
8
  * Warns about fields that belong to different segment types.
9
9
  *
10
- * @param segmentType - The type of segment (text, chat, article-excerpt, video-excerpt)
10
+ * @param segmentType - The type of segment (text, chat, article, video, question, roleplay)
11
11
  * @param fields - Object mapping field names to their values
12
12
  * @param file - File path for error reporting
13
13
  * @param line - Line number for error reporting
@@ -28,7 +28,7 @@ export function validateSegmentFields(segmentType, fields, file, line) {
28
28
  file,
29
29
  line,
30
30
  message: `Field '${fieldName}' is not valid in ${segmentType} segment`,
31
- suggestion: `'${fieldName}' is only valid in article-excerpt or video-excerpt segments`,
31
+ suggestion: `'${fieldName}' is only valid in article or video segments`,
32
32
  severity: 'warning',
33
33
  });
34
34
  }
@@ -1 +1 @@
1
- {"version":3,"file":"segment-fields.js","sourceRoot":"","sources":["../../src/validator/segment-fields.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAEpE;;GAEG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,WAAmB,EACnB,MAA8B,EAC9B,IAAY,EACZ,IAAY;IAEZ,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,WAAW,GAAG,4BAA4B,CAAC,WAAW,CAAC,CAAC;IAE9D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,sDAAsD;QACtD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,oEAAoE;YACpE,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACvC,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI;oBACJ,IAAI;oBACJ,OAAO,EAAE,UAAU,SAAS,qBAAqB,WAAW,UAAU;oBACtE,UAAU,EAAE,IAAI,SAAS,8DAA8D;oBACvF,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,2DAA2D;YAC3D,mCAAmC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"segment-fields.js","sourceRoot":"","sources":["../../src/validator/segment-fields.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAEpE;;GAEG;AACH,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;AAEpD;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB,CACnC,WAAmB,EACnB,MAA8B,EAC9B,IAAY,EACZ,IAAY;IAEZ,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,MAAM,WAAW,GAAG,4BAA4B,CAAC,WAAW,CAAC,CAAC;IAE9D,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,sDAAsD;QACtD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,oEAAoE;YACpE,IAAI,mBAAmB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC;gBACvC,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI;oBACJ,IAAI;oBACJ,OAAO,EAAE,UAAU,SAAS,qBAAqB,WAAW,UAAU;oBACtE,UAAU,EAAE,IAAI,SAAS,8CAA8C;oBACvE,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,2DAA2D;YAC3D,mCAAmC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ContentError } from "../index.js";
2
+ import type { ParsedSection } from "../parser/sections.js";
3
+ /**
4
+ * Validate that an LO Test section contains only assessable segment types.
5
+ * Flags non-assessable segments (text/article/video/chat) as errors, since
6
+ * they are silently discarded by every consumer.
7
+ *
8
+ * @param segments - The raw segments parsed from the Test section body
9
+ * @param file - File path for error reporting
10
+ * @returns Array of error ContentError objects for disallowed segments
11
+ */
12
+ export declare function validateTestSegments(segments: ParsedSection[], file: string): ContentError[];
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Segment types that an LO `## Test:` section may contain.
3
+ *
4
+ * A Test is an assessment: only `question` and `roleplay` segments are rendered
5
+ * to the student (web_frontend TestSection.tsx) and scored by the grader
6
+ * (core/assessment.py) / tutor (core/modules/tutor_scenario.py). Any other
7
+ * segment type (text, article, video, chat) placed in a Test is silently
8
+ * dropped everywhere — never shown and never sent to the model — so it is
9
+ * almost always an authoring mistake (e.g. intro text that belongs inside the
10
+ * question's content::).
11
+ */
12
+ const ASSESSABLE_TEST_SEGMENT_TYPES = new Set(["question", "roleplay"]);
13
+ /**
14
+ * Validate that an LO Test section contains only assessable segment types.
15
+ * Flags non-assessable segments (text/article/video/chat) as errors, since
16
+ * they are silently discarded by every consumer.
17
+ *
18
+ * @param segments - The raw segments parsed from the Test section body
19
+ * @param file - File path for error reporting
20
+ * @returns Array of error ContentError objects for disallowed segments
21
+ */
22
+ export function validateTestSegments(segments, file) {
23
+ const errors = [];
24
+ for (const segment of segments) {
25
+ if (ASSESSABLE_TEST_SEGMENT_TYPES.has(segment.type))
26
+ continue;
27
+ const suggestion = segment.type === "text"
28
+ ? "Move this text into the following Question's content:: field — a Test only renders question/roleplay segments, so a standalone Text is never shown to students or sent to the grader."
29
+ : `A Test section only renders question/roleplay segments. Remove this '${segment.type}' segment, or move it into a Lens.`;
30
+ errors.push({
31
+ file,
32
+ line: segment.line,
33
+ message: `Test section contains a '${segment.type}' segment, which is silently dropped (not rendered to students, not sent to the grader or tutor). A Test may only contain question or roleplay segments.`,
34
+ suggestion,
35
+ severity: "error",
36
+ });
37
+ }
38
+ return errors;
39
+ }
40
+ //# sourceMappingURL=test-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-segments.js","sourceRoot":"","sources":["../../src/validator/test-segments.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,MAAM,6BAA6B,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,UAAU,oBAAoB,CAClC,QAAyB,EACzB,IAAY;IAEZ,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,6BAA6B,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC;YAAE,SAAS;QAE9D,MAAM,UAAU,GACd,OAAO,CAAC,IAAI,KAAK,MAAM;YACrB,CAAC,CAAC,uLAAuL;YACzL,CAAC,CAAC,wEAAwE,OAAO,CAAC,IAAI,oCAAoC,CAAC;QAE/H,MAAM,CAAC,IAAI,CAAC;YACV,IAAI;YACJ,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,OAAO,EAAE,4BAA4B,OAAO,CAAC,IAAI,0JAA0J;YAC3M,UAAU;YACV,QAAQ,EAAE,OAAO;SAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,8 +1,9 @@
1
- import type { ContentError } from '../index.js';
1
+ import type { ContentError } from "../index.js";
2
2
  export interface UrlToValidate {
3
3
  url: string;
4
4
  file: string;
5
5
  line: number;
6
6
  label: string;
7
+ allowUnreachable?: boolean;
7
8
  }
8
9
  export declare function validateUrls(urls: UrlToValidate[]): Promise<ContentError[]>;
@@ -4,43 +4,63 @@ const TIMEOUT_MS = 15_000;
4
4
  const REACHABLE_STATUSES = new Set([429]);
5
5
  // Status codes that definitively mean the resource doesn't exist
6
6
  const NOT_FOUND_STATUSES = new Set([404, 410]);
7
- async function fetchWithTimeout(url, method, signal) {
8
- return fetch(url, { method, signal });
7
+ // Shown on every "could not confirm reachable" warning, pointing at the opt-out.
8
+ const OVERRIDE_HINT = "If you have verified this URL works in a browser, add 'allowUnreachableUrl: true' to the frontmatter to suppress this warning.";
9
+ // Each call gets its own timeout so HEAD and a retried GET don't share one budget.
10
+ async function fetchWithTimeout(url, method) {
11
+ const controller = new AbortController();
12
+ const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
13
+ try {
14
+ return await fetch(url, { method, signal: controller.signal });
15
+ }
16
+ finally {
17
+ clearTimeout(timeout);
18
+ }
9
19
  }
10
20
  async function checkUrl(entry) {
11
21
  try {
12
- const controller = new AbortController();
13
- const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
14
- let response = await fetchWithTimeout(entry.url, 'HEAD', controller.signal);
15
- // Some servers reject HEAD retry with GET
16
- if (response.status === 405) {
17
- response = await fetchWithTimeout(entry.url, 'GET', controller.signal);
22
+ let response = await fetchWithTimeout(entry.url, "HEAD");
23
+ // A HEAD that isn't a definitive answer (not OK, not a known 404/410 or
24
+ // rate-limit) may just mean the server rejects the HEAD method: some do so
25
+ // with 405, others unconventionally with 403. Retry with GET to get the true
26
+ // status; this also surfaces a real 404/410 that a HEAD rejection could mask.
27
+ if (!response.ok &&
28
+ !NOT_FOUND_STATUSES.has(response.status) &&
29
+ !REACHABLE_STATUSES.has(response.status)) {
30
+ response = await fetchWithTimeout(entry.url, "GET");
18
31
  }
19
- clearTimeout(timeout);
20
32
  if (!response.ok && !REACHABLE_STATUSES.has(response.status)) {
33
+ // 404/410 are definitively dead: an error, never overridable. Any other
34
+ // status means we could not confirm reachability (a 403 bot/WAF block, a
35
+ // 5xx, etc.); that warning can be vouched for by hand via allowUnreachable.
36
+ const isNotFound = NOT_FOUND_STATUSES.has(response.status);
37
+ if (!isNotFound && entry.allowUnreachable)
38
+ return null;
21
39
  return {
22
40
  file: entry.file,
23
41
  line: entry.line,
24
42
  message: `${entry.label} returned HTTP ${response.status}: ${entry.url}`,
25
- severity: NOT_FOUND_STATUSES.has(response.status) ? 'error' : 'warning',
43
+ severity: isNotFound ? "error" : "warning",
44
+ ...(isNotFound ? {} : { suggestion: OVERRIDE_HINT }),
26
45
  };
27
46
  }
28
47
  return null;
29
48
  }
30
49
  catch (error) {
31
- if (error instanceof DOMException && error.name === 'AbortError') {
32
- return {
33
- file: entry.file,
34
- line: entry.line,
35
- message: `${entry.label} timed out: ${entry.url}`,
36
- severity: 'warning',
37
- };
38
- }
50
+ // A timeout or network failure is "could not confirm" too (our own network, a
51
+ // transient DNS hiccup, or a flaky host), not a definitive 404/410, so it is
52
+ // overridable as well.
53
+ if (entry.allowUnreachable)
54
+ return null;
55
+ const aborted = error instanceof DOMException && error.name === "AbortError";
39
56
  return {
40
57
  file: entry.file,
41
58
  line: entry.line,
42
- message: `${entry.label} unreachable: ${entry.url}`,
43
- severity: 'warning',
59
+ message: aborted
60
+ ? `${entry.label} timed out: ${entry.url}`
61
+ : `${entry.label} unreachable: ${entry.url}`,
62
+ suggestion: OVERRIDE_HINT,
63
+ severity: "warning",
44
64
  };
45
65
  }
46
66
  }
@@ -1 +1 @@
1
- {"version":3,"file":"url-reachability.js","sourceRoot":"","sources":["../../src/validator/url-reachability.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"url-reachability.js","sourceRoot":"","sources":["../../src/validator/url-reachability.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lens-content-processor",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Parser and validator for Lens educational content (Markdown modules, lenses, learning outcomes)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1,9 +0,0 @@
1
- import type { ContentError } from '../index.js';
2
- /**
3
- * Validate that every Chat segment is immediately preceded by a Text segment.
4
- * Students need instructions (Text) before every interactive discussion (Chat).
5
- */
6
- export declare function validateChatPrecedence(segments: {
7
- type: string;
8
- line: number;
9
- }[], file: string): ContentError[];
@@ -1,32 +0,0 @@
1
- /**
2
- * Validate that every Chat segment is immediately preceded by a Text segment.
3
- * Students need instructions (Text) before every interactive discussion (Chat).
4
- */
5
- export function validateChatPrecedence(segments, file) {
6
- const errors = [];
7
- for (let i = 0; i < segments.length; i++) {
8
- if (segments[i].type !== 'chat')
9
- continue;
10
- const prev = segments[i - 1];
11
- if (!prev) {
12
- errors.push({
13
- file,
14
- line: segments[i].line,
15
- message: "'#### Chat' must be immediately preceded by a '#### Text' segment, but it is the first segment in the section",
16
- suggestion: "Add a '#### Text' segment with content:: before this '#### Chat'",
17
- severity: 'error',
18
- });
19
- }
20
- else if (prev.type !== 'text') {
21
- errors.push({
22
- file,
23
- line: segments[i].line,
24
- message: `'#### Chat' must be immediately preceded by a '#### Text' segment, but found '#### ${prev.type}' instead`,
25
- suggestion: "Add a '#### Text' segment with content:: before this '#### Chat'",
26
- severity: 'error',
27
- });
28
- }
29
- }
30
- return errors;
31
- }
32
- //# sourceMappingURL=chat-precedence.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"chat-precedence.js","sourceRoot":"","sources":["../../src/validator/chat-precedence.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAA0C,EAC1C,IAAY;IAEZ,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM;YAAE,SAAS;QAE1C,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE7B,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI;gBACJ,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBACtB,OAAO,EAAE,+GAA+G;gBACxH,UAAU,EAAE,kEAAkE;gBAC9E,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI;gBACJ,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI;gBACtB,OAAO,EAAE,sFAAsF,IAAI,CAAC,IAAI,WAAW;gBACnH,UAAU,EAAE,kEAAkE;gBAC9E,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}