lens-content-processor 0.2.1 → 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.
Files changed (75) hide show
  1. package/dist/bundler/video.js +4 -4
  2. package/dist/bundler/video.js.map +1 -1
  3. package/dist/cli.d.ts +1 -2
  4. package/dist/cli.js +11 -9
  5. package/dist/cli.js.map +1 -1
  6. package/dist/content-schema.d.ts +34 -0
  7. package/dist/content-schema.js +62 -0
  8. package/dist/content-schema.js.map +1 -0
  9. package/dist/flattener/index.d.ts +21 -1
  10. package/dist/flattener/index.js +316 -33
  11. package/dist/flattener/index.js.map +1 -1
  12. package/dist/fs/read-vault.d.ts +1 -4
  13. package/dist/fs/read-vault.js +4 -8
  14. package/dist/fs/read-vault.js.map +1 -1
  15. package/dist/index.d.ts +25 -2
  16. package/dist/index.js +231 -6
  17. package/dist/index.js.map +1 -1
  18. package/dist/parser/article.d.ts +16 -0
  19. package/dist/parser/article.js +70 -0
  20. package/dist/parser/article.js.map +1 -0
  21. package/dist/parser/course.d.ts +2 -2
  22. package/dist/parser/course.js +18 -35
  23. package/dist/parser/course.js.map +1 -1
  24. package/dist/parser/frontmatter.js +2 -0
  25. package/dist/parser/frontmatter.js.map +1 -1
  26. package/dist/parser/learning-outcome.d.ts +4 -2
  27. package/dist/parser/learning-outcome.js +65 -23
  28. package/dist/parser/learning-outcome.js.map +1 -1
  29. package/dist/parser/lens.d.ts +38 -1
  30. package/dist/parser/lens.js +173 -18
  31. package/dist/parser/lens.js.map +1 -1
  32. package/dist/parser/module.d.ts +11 -5
  33. package/dist/parser/module.js +172 -76
  34. package/dist/parser/module.js.map +1 -1
  35. package/dist/parser/sections.js +116 -13
  36. package/dist/parser/sections.js.map +1 -1
  37. package/dist/parser/video-transcript.d.ts +11 -0
  38. package/dist/parser/video-transcript.js +39 -0
  39. package/dist/parser/video-transcript.js.map +1 -0
  40. package/dist/parser/wikilink.d.ts +2 -0
  41. package/dist/parser/wikilink.js +88 -24
  42. package/dist/parser/wikilink.js.map +1 -1
  43. package/dist/utils/slug.d.ts +5 -0
  44. package/dist/utils/slug.js +16 -0
  45. package/dist/utils/slug.js.map +1 -0
  46. package/dist/validator/chat-precedence.d.ts +9 -0
  47. package/dist/validator/chat-precedence.js +32 -0
  48. package/dist/validator/chat-precedence.js.map +1 -0
  49. package/dist/validator/duplicates.d.ts +13 -0
  50. package/dist/validator/duplicates.js +27 -0
  51. package/dist/validator/duplicates.js.map +1 -0
  52. package/dist/validator/field-typos.d.ts +11 -0
  53. package/dist/validator/field-typos.js +36 -28
  54. package/dist/validator/field-typos.js.map +1 -1
  55. package/dist/validator/field-values.d.ts +14 -0
  56. package/dist/validator/field-values.js +61 -7
  57. package/dist/validator/field-values.js.map +1 -1
  58. package/dist/validator/output-integrity.d.ts +7 -0
  59. package/dist/validator/output-integrity.js +57 -0
  60. package/dist/validator/output-integrity.js.map +1 -0
  61. package/dist/validator/segment-fields.js +1 -15
  62. package/dist/validator/segment-fields.js.map +1 -1
  63. package/dist/validator/tier.d.ts +18 -0
  64. package/dist/validator/tier.js +74 -0
  65. package/dist/validator/tier.js.map +1 -0
  66. package/dist/validator/timestamps.d.ts +2 -0
  67. package/dist/validator/timestamps.js +91 -0
  68. package/dist/validator/timestamps.js.map +1 -0
  69. package/dist/validator/url-reachability.d.ts +8 -0
  70. package/dist/validator/url-reachability.js +60 -0
  71. package/dist/validator/url-reachability.js.map +1 -0
  72. package/dist/validator/validate-frontmatter.d.ts +9 -0
  73. package/dist/validator/validate-frontmatter.js +53 -0
  74. package/dist/validator/validate-frontmatter.js.map +1 -0
  75. package/package.json +5 -5
@@ -1,13 +1,100 @@
1
1
  // src/parser/wikilink.ts
2
2
  import { join, dirname, normalize } from 'path';
3
+ import { levenshtein } from '../validator/field-typos.js';
3
4
  // Matches [[path]], [[path|display]], ![[embed]], ![[embed|display]]
4
5
  const WIKILINK_PATTERN = /^!?\[\[([^\]|]+)(?:\|([^\]]+))?\]\]$/;
6
+ /**
7
+ * Check if a path contains a path traversal attack.
8
+ *
9
+ * We allow:
10
+ * - Single `../` at the start for legitimate relative references (e.g., ../Lenses/foo.md)
11
+ * - Paths with dots in filenames (e.g., file.name.with.dots.md)
12
+ * - Single dot directories (e.g., ./relative/path)
13
+ *
14
+ * We block:
15
+ * - Multiple consecutive `../` (e.g., ../../.. or ../../../etc/passwd)
16
+ * - Windows-style `..\\` (always suspicious in markdown)
17
+ * - `../` after a non-traversal path segment (e.g., articles/../../../secrets)
18
+ */
19
+ function containsPathTraversal(path) {
20
+ // Block any Windows-style path traversal (..\ is suspicious in markdown context)
21
+ if (path.includes('..\\')) {
22
+ return true;
23
+ }
24
+ // Block multiple consecutive ../ at the start (../../ or more)
25
+ if (/^(\.\.[/]){2,}/.test(path)) {
26
+ return true;
27
+ }
28
+ // Block ../ appearing after a non-traversal path segment
29
+ // e.g., "articles/../../../secrets" - the ../ after "articles/" is suspicious
30
+ // This catches cases like "foo/../bar/../../../etc"
31
+ if (/[^./][/]\.\./.test(path)) {
32
+ return true;
33
+ }
34
+ return false;
35
+ }
36
+ // Patterns for detecting malformed wikilinks
37
+ const MISSING_CLOSING_PATTERN = /^!?\[\[[^\]]+\](?!\])$/; // [[foo] but not [[foo]]
38
+ const MISSING_OPENING_PATTERN = /^(?<!!)\[(?!\[)[^\]]*\]\]$/; // [foo]] but not [[foo]]
39
+ const EMPTY_WIKILINK_PATTERN = /^!?\[\[\s*\]\]$/; // [[]] or ![[]] with optional whitespace
5
40
  export function parseWikilink(text) {
41
+ // Check for empty wikilink first (before general pattern match)
42
+ if (EMPTY_WIKILINK_PATTERN.test(text)) {
43
+ return {
44
+ path: '',
45
+ error: 'Empty wikilink',
46
+ isEmbed: text.startsWith('!'),
47
+ };
48
+ }
49
+ // Check for missing closing bracket: [[foo] (one closing bracket, not two)
50
+ if (MISSING_CLOSING_PATTERN.test(text)) {
51
+ return {
52
+ path: '',
53
+ error: 'Missing closing bracket ]]',
54
+ isEmbed: text.startsWith('!'),
55
+ };
56
+ }
57
+ // Check for missing opening bracket: [foo]] (one opening bracket, not two)
58
+ if (MISSING_OPENING_PATTERN.test(text)) {
59
+ return {
60
+ path: '',
61
+ error: 'Missing opening bracket [[',
62
+ };
63
+ }
6
64
  const match = text.match(WIKILINK_PATTERN);
7
65
  if (!match)
8
66
  return null;
67
+ const path = match[1].trim();
68
+ // Check for empty path after trimming (whitespace-only content)
69
+ if (!path) {
70
+ return {
71
+ path: '',
72
+ error: 'Empty wikilink',
73
+ isEmbed: text.startsWith('!'),
74
+ };
75
+ }
76
+ // Block clearly malicious path traversal (Windows-style, mid-path escapes)
77
+ if (path.includes('..\\') || /[^./][/]\.\./.test(path)) {
78
+ return {
79
+ path,
80
+ display: match[2]?.trim(),
81
+ isEmbed: text.startsWith('!'),
82
+ error: 'Path traversal not allowed',
83
+ };
84
+ }
85
+ // For multiple ../ at start: likely a mistake, suggest correction
86
+ if (/^(\.\.[/]){2,}/.test(path)) {
87
+ const corrected = path.replace(/^(\.\.[/])+/, '../');
88
+ return {
89
+ path,
90
+ display: match[2]?.trim(),
91
+ isEmbed: text.startsWith('!'),
92
+ error: `Path has too many '../' segments`,
93
+ correctedPath: corrected,
94
+ };
95
+ }
9
96
  return {
10
- path: match[1].trim(),
97
+ path,
11
98
  display: match[2]?.trim(),
12
99
  isEmbed: text.startsWith('!'),
13
100
  };
@@ -40,29 +127,6 @@ export function findFileWithExtension(path, files) {
40
127
  }
41
128
  return null;
42
129
  }
43
- /**
44
- * Calculate the Levenshtein (edit) distance between two strings.
45
- */
46
- function levenshtein(a, b) {
47
- const matrix = [];
48
- for (let i = 0; i <= b.length; i++) {
49
- matrix[i] = [i];
50
- }
51
- for (let j = 0; j <= a.length; j++) {
52
- matrix[0][j] = j;
53
- }
54
- for (let i = 1; i <= b.length; i++) {
55
- for (let j = 1; j <= a.length; j++) {
56
- if (b[i - 1] === a[j - 1]) {
57
- matrix[i][j] = matrix[i - 1][j - 1];
58
- }
59
- else {
60
- matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
61
- }
62
- }
63
- }
64
- return matrix[b.length][a.length];
65
- }
66
130
  /**
67
131
  * Extract the filename (without extension) from a path.
68
132
  */
@@ -1 +1 @@
1
- {"version":3,"file":"wikilink.js","sourceRoot":"","sources":["../../src/parser/wikilink.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"wikilink.js","sourceRoot":"","sources":["../../src/parser/wikilink.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Convert a filename (with optional path prefix and .md extension) to a URL slug.
3
+ * "Lenses/Four Background Claims.md" → "four-background-claims"
4
+ */
5
+ export declare function fileNameToSlug(fileName: string): string;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Convert a filename (with optional path prefix and .md extension) to a URL slug.
3
+ * "Lenses/Four Background Claims.md" → "four-background-claims"
4
+ */
5
+ export function fileNameToSlug(fileName) {
6
+ // Strip directory prefix — take only the final path segment
7
+ const base = fileName.split('/').pop() ?? fileName;
8
+ const slug = base
9
+ .replace(/\.md$/i, '') // strip .md
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9\s-]/g, '') // remove non-alphanumeric (keep spaces and hyphens)
12
+ .replace(/[\s-]+/g, '-') // spaces/hyphens → single hyphen
13
+ .replace(/^-+|-+$/g, ''); // trim leading/trailing hyphens
14
+ return slug || 'untitled'; // guard against empty result
15
+ }
16
+ //# sourceMappingURL=slug.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug.js","sourceRoot":"","sources":["../../src/utils/slug.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,4DAA4D;IAC5D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,QAAQ,CAAC;IACnD,MAAM,IAAI,GAAG,IAAI;SACd,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAQ,YAAY;SACzC,WAAW,EAAE;SACb,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,oDAAoD;SACjF,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAM,iCAAiC;SAC9D,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAK,gCAAgC;IAChE,OAAO,IAAI,IAAI,UAAU,CAAC,CAAM,6BAA6B;AAC/D,CAAC"}
@@ -0,0 +1,9 @@
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[];
@@ -0,0 +1,32 @@
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
@@ -0,0 +1 @@
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"}
@@ -0,0 +1,13 @@
1
+ import type { ContentError } from '../index.js';
2
+ export interface SlugEntry {
3
+ slug: string;
4
+ file: string;
5
+ }
6
+ /**
7
+ * Detect duplicate slugs across module entries.
8
+ * Returns an error for each occurrence after the first.
9
+ *
10
+ * @param entries - Array of slug entries to check for duplicates
11
+ * @returns Array of ContentError objects for duplicate slugs
12
+ */
13
+ export declare function detectDuplicateSlugs(entries: SlugEntry[]): ContentError[];
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Detect duplicate slugs across module entries.
3
+ * Returns an error for each occurrence after the first.
4
+ *
5
+ * @param entries - Array of slug entries to check for duplicates
6
+ * @returns Array of ContentError objects for duplicate slugs
7
+ */
8
+ export function detectDuplicateSlugs(entries) {
9
+ const errors = [];
10
+ const seenSlugs = new Map(); // slug -> first occurrence file
11
+ for (const entry of entries) {
12
+ const existing = seenSlugs.get(entry.slug);
13
+ if (existing) {
14
+ errors.push({
15
+ file: entry.file,
16
+ message: `Duplicate slug '${entry.slug}' - also defined in ${existing}`,
17
+ suggestion: `Each module must have a unique slug. The slug '${entry.slug}' is already used in ${existing}`,
18
+ severity: 'error',
19
+ });
20
+ }
21
+ else {
22
+ seenSlugs.set(entry.slug, entry.file);
23
+ }
24
+ }
25
+ return errors;
26
+ }
27
+ //# sourceMappingURL=duplicates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicates.js","sourceRoot":"","sources":["../../src/validator/duplicates.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAoB;IACvD,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,gCAAgC;IAE7E,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAE3C,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,mBAAmB,KAAK,CAAC,IAAI,uBAAuB,QAAQ,EAAE;gBACvE,UAAU,EAAE,kDAAkD,KAAK,CAAC,IAAI,wBAAwB,QAAQ,EAAE;gBAC1G,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,4 +1,10 @@
1
1
  import type { ContentError } from '../index.js';
2
+ /**
3
+ * Calculate the Levenshtein (edit) distance between two strings.
4
+ * This measures the minimum number of single-character edits
5
+ * (insertions, deletions, substitutions) needed to change one string into another.
6
+ */
7
+ export declare function levenshtein(a: string, b: string): number;
2
8
  /**
3
9
  * Detect likely typos in field names by comparing against known fields.
4
10
  *
@@ -8,3 +14,8 @@ import type { ContentError } from '../index.js';
8
14
  * @returns Array of warning ContentError objects for likely typos
9
15
  */
10
16
  export declare function detectFieldTypos(fields: Record<string, string>, file: string, line: number): ContentError[];
17
+ /**
18
+ * Detect likely typos in frontmatter field names by comparing against
19
+ * the valid fields for a specific file type.
20
+ */
21
+ export declare function detectFrontmatterTypos(frontmatter: Record<string, unknown>, validFields: string[], file: string): ContentError[];
@@ -1,34 +1,10 @@
1
- /**
2
- * Known valid field names across all content types.
3
- * Used to detect likely typos via Levenshtein distance.
4
- */
5
- const KNOWN_FIELDS = [
6
- // Common fields
7
- 'content',
8
- 'instructions',
9
- 'source',
10
- 'optional',
11
- 'from',
12
- 'to',
13
- 'id',
14
- 'slug',
15
- 'title',
16
- // Article metadata
17
- 'author',
18
- 'sourceUrl',
19
- // Video metadata
20
- 'channel',
21
- 'url',
22
- // Chat segment fields
23
- 'hidePreviousContentFromUser',
24
- 'hidePreviousContentFromTutor',
25
- ];
1
+ import { ALL_KNOWN_FIELDS } from '../content-schema.js';
26
2
  /**
27
3
  * Calculate the Levenshtein (edit) distance between two strings.
28
4
  * This measures the minimum number of single-character edits
29
5
  * (insertions, deletions, substitutions) needed to change one string into another.
30
6
  */
31
- function levenshtein(a, b) {
7
+ export function levenshtein(a, b) {
32
8
  const matrix = [];
33
9
  // Initialize first column (delete operations)
34
10
  for (let i = 0; i <= b.length; i++) {
@@ -66,13 +42,13 @@ export function detectFieldTypos(fields, file, line) {
66
42
  const warnings = [];
67
43
  for (const fieldName of Object.keys(fields)) {
68
44
  // Skip if it's a known valid field (case-sensitive match)
69
- if (KNOWN_FIELDS.includes(fieldName)) {
45
+ if (ALL_KNOWN_FIELDS.includes(fieldName)) {
70
46
  continue;
71
47
  }
72
48
  // Find the closest known field by Levenshtein distance
73
49
  let closest = '';
74
50
  let minDistance = Infinity;
75
- for (const known of KNOWN_FIELDS) {
51
+ for (const known of ALL_KNOWN_FIELDS) {
76
52
  const dist = levenshtein(fieldName.toLowerCase(), known.toLowerCase());
77
53
  if (dist < minDistance && dist <= 2) {
78
54
  // Only suggest if distance <= 2 (likely typo)
@@ -93,4 +69,36 @@ export function detectFieldTypos(fields, file, line) {
93
69
  }
94
70
  return warnings;
95
71
  }
72
+ /**
73
+ * Detect likely typos in frontmatter field names by comparing against
74
+ * the valid fields for a specific file type.
75
+ */
76
+ export function detectFrontmatterTypos(frontmatter, validFields, file) {
77
+ const warnings = [];
78
+ const validSet = new Set(validFields);
79
+ for (const fieldName of Object.keys(frontmatter)) {
80
+ if (validSet.has(fieldName))
81
+ continue;
82
+ // Find closest valid field by Levenshtein distance
83
+ let closest = '';
84
+ let minDistance = Infinity;
85
+ for (const valid of validFields) {
86
+ const dist = levenshtein(fieldName.toLowerCase(), valid.toLowerCase());
87
+ if (dist < minDistance && dist <= 2) {
88
+ minDistance = dist;
89
+ closest = valid;
90
+ }
91
+ }
92
+ if (closest) {
93
+ warnings.push({
94
+ file,
95
+ line: 2, // Frontmatter starts at line 2
96
+ message: `Unrecognized frontmatter field '${fieldName}'`,
97
+ suggestion: `Did you mean '${closest}'?`,
98
+ severity: 'warning',
99
+ });
100
+ }
101
+ }
102
+ return warnings;
103
+ }
96
104
  //# sourceMappingURL=field-typos.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"field-typos.js","sourceRoot":"","sources":["../../src/validator/field-typos.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"field-typos.js","sourceRoot":"","sources":["../../src/validator/field-typos.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,18 @@
1
1
  import type { ContentError } from '../index.js';
2
+ /**
3
+ * Validate slug format.
4
+ *
5
+ * Valid slugs must:
6
+ * - Contain only lowercase letters, numbers, and hyphens
7
+ * - Not start or end with a hyphen
8
+ * - Not contain spaces or special characters
9
+ *
10
+ * @param slug - The slug value to validate
11
+ * @param file - File path for error reporting
12
+ * @param line - Line number for error reporting
13
+ * @returns ContentError if invalid, null if valid
14
+ */
15
+ export declare function validateSlugFormat(slug: string, file: string, line: number): ContentError | null;
2
16
  /**
3
17
  * Validate field values, checking for appropriate types.
4
18
  * Currently validates that boolean fields contain 'true' or 'false'.
@@ -1,11 +1,65 @@
1
+ import { ALL_BOOLEAN_FIELDS } from '../content-schema.js';
1
2
  /**
2
- * Fields that should only contain boolean values ('true' or 'false').
3
+ * Valid slug pattern: lowercase letters, numbers, and hyphens only.
4
+ * Must not start or end with a hyphen.
3
5
  */
4
- const BOOLEAN_FIELDS = new Set([
5
- 'optional',
6
- 'hidePreviousContentFromUser',
7
- 'hidePreviousContentFromTutor',
8
- ]);
6
+ const VALID_SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
7
+ /**
8
+ * Validate slug format.
9
+ *
10
+ * Valid slugs must:
11
+ * - Contain only lowercase letters, numbers, and hyphens
12
+ * - Not start or end with a hyphen
13
+ * - Not contain spaces or special characters
14
+ *
15
+ * @param slug - The slug value to validate
16
+ * @param file - File path for error reporting
17
+ * @param line - Line number for error reporting
18
+ * @returns ContentError if invalid, null if valid
19
+ */
20
+ export function validateSlugFormat(slug, file, line) {
21
+ // Check for uppercase letters first (more specific message)
22
+ if (/[A-Z]/.test(slug)) {
23
+ return {
24
+ file,
25
+ line,
26
+ message: `Invalid slug format '${slug}': contains uppercase letters`,
27
+ suggestion: `Use lowercase only. Try '${slug.toLowerCase()}'`,
28
+ severity: 'error',
29
+ };
30
+ }
31
+ // Check for leading hyphen
32
+ if (slug.startsWith('-')) {
33
+ return {
34
+ file,
35
+ line,
36
+ message: `Invalid slug format '${slug}': cannot start with a hyphen`,
37
+ suggestion: 'Remove the leading hyphen',
38
+ severity: 'error',
39
+ };
40
+ }
41
+ // Check for trailing hyphen
42
+ if (slug.endsWith('-')) {
43
+ return {
44
+ file,
45
+ line,
46
+ message: `Invalid slug format '${slug}': cannot end with a hyphen`,
47
+ suggestion: 'Remove the trailing hyphen',
48
+ severity: 'error',
49
+ };
50
+ }
51
+ // Check general pattern (lowercase, numbers, hyphens only)
52
+ if (!VALID_SLUG_PATTERN.test(slug)) {
53
+ return {
54
+ file,
55
+ line,
56
+ message: `Invalid slug format '${slug}': must contain only lowercase letters, numbers, and hyphens`,
57
+ suggestion: 'Use only a-z, 0-9, and hyphens (-)',
58
+ severity: 'error',
59
+ };
60
+ }
61
+ return null;
62
+ }
9
63
  /**
10
64
  * Validate field values, checking for appropriate types.
11
65
  * Currently validates that boolean fields contain 'true' or 'false'.
@@ -19,7 +73,7 @@ export function validateFieldValues(fields, file, line) {
19
73
  const warnings = [];
20
74
  for (const [name, value] of Object.entries(fields)) {
21
75
  // Check if this is a boolean field
22
- if (BOOLEAN_FIELDS.has(name)) {
76
+ if (ALL_BOOLEAN_FIELDS.includes(name)) {
23
77
  const normalizedValue = value.toLowerCase();
24
78
  if (normalizedValue !== 'true' && normalizedValue !== 'false') {
25
79
  warnings.push({
@@ -1 +1 @@
1
- {"version":3,"file":"field-values.js","sourceRoot":"","sources":["../../src/validator/field-values.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,UAAU;IACV,6BAA6B;IAC7B,8BAA8B;CAC/B,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAA8B,EAC9B,IAAY,EACZ,IAAY;IAEZ,MAAM,QAAQ,GAAmB,EAAE,CAAC;IAEpC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,mCAAmC;QACnC,IAAI,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAC7B,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,eAAe,KAAK,MAAM,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;gBAC9D,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI;oBACJ,IAAI;oBACJ,OAAO,EAAE,UAAU,IAAI,4BAA4B,KAAK,GAAG;oBAC3D,UAAU,EAAE,4BAA4B;oBACxC,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"field-values.js","sourceRoot":"","sources":["../../src/validator/field-values.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE1D;;;GAGG;AACH,MAAM,kBAAkB,GAAG,0BAA0B,CAAC;AAEtD;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAChC,IAAY,EACZ,IAAY,EACZ,IAAY;IAEZ,4DAA4D;IAC5D,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,OAAO,EAAE,wBAAwB,IAAI,+BAA+B;YACpE,UAAU,EAAE,4BAA4B,IAAI,CAAC,WAAW,EAAE,GAAG;YAC7D,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC;IAED,2BAA2B;IAC3B,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACzB,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,OAAO,EAAE,wBAAwB,IAAI,+BAA+B;YACpE,UAAU,EAAE,2BAA2B;YACvC,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC;IAED,4BAA4B;IAC5B,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,OAAO,EAAE,wBAAwB,IAAI,6BAA6B;YAClE,UAAU,EAAE,4BAA4B;YACxC,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC;IAED,2DAA2D;IAC3D,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,OAAO,EAAE,wBAAwB,IAAI,8DAA8D;YACnG,UAAU,EAAE,oCAAoC;YAChD,QAAQ,EAAE,OAAO;SAClB,CAAC;IACJ,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAA8B,EAC9B,IAAY,EACZ,IAAY;IAEZ,MAAM,QAAQ,GAAmB,EAAE,CAAC;IAEpC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,mCAAmC;QACnC,IAAI,kBAAkB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,eAAe,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;YAC5C,IAAI,eAAe,KAAK,MAAM,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;gBAC9D,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI;oBACJ,IAAI;oBACJ,OAAO,EAAE,UAAU,IAAI,4BAA4B,KAAK,GAAG;oBAC3D,UAAU,EAAE,4BAA4B;oBACxC,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ContentError, FlattenedModule } from '../index.js';
2
+ /**
3
+ * Broad safety-net validation on the flattened output.
4
+ * Catches empty sections (no segments) and empty segments (no content)
5
+ * that specific validators might have missed.
6
+ */
7
+ export declare function validateOutputIntegrity(modules: FlattenedModule[], slugToPath?: Map<string, string>): ContentError[];
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Broad safety-net validation on the flattened output.
3
+ * Catches empty sections (no segments) and empty segments (no content)
4
+ * that specific validators might have missed.
5
+ */
6
+ export function validateOutputIntegrity(modules, slugToPath) {
7
+ const errors = [];
8
+ for (const module of modules) {
9
+ const file = slugToPath?.get(module.slug) ?? module.slug;
10
+ for (const section of module.sections) {
11
+ const sectionLabel = section.meta.title ?? section.type;
12
+ if (section.segments.length === 0) {
13
+ errors.push({
14
+ file,
15
+ message: `Section "${sectionLabel}" has no segments`,
16
+ severity: 'error',
17
+ });
18
+ continue;
19
+ }
20
+ for (let i = 0; i < section.segments.length; i++) {
21
+ const segment = section.segments[i];
22
+ switch (segment.type) {
23
+ case 'text':
24
+ if (!segment.content?.trim()) {
25
+ errors.push({
26
+ file,
27
+ message: `Empty text segment in "${sectionLabel}" (segment ${i + 1})`,
28
+ severity: 'error',
29
+ });
30
+ }
31
+ break;
32
+ case 'article-excerpt':
33
+ if (!segment.content?.trim()) {
34
+ errors.push({
35
+ file,
36
+ message: `Empty article-excerpt segment in "${sectionLabel}" (segment ${i + 1})`,
37
+ severity: 'error',
38
+ });
39
+ }
40
+ break;
41
+ case 'video-excerpt':
42
+ if (!segment.transcript?.trim()) {
43
+ errors.push({
44
+ file,
45
+ message: `Empty video-excerpt transcript in "${sectionLabel}" (segment ${i + 1})`,
46
+ severity: 'error',
47
+ });
48
+ }
49
+ break;
50
+ // chat segments have no required content body
51
+ }
52
+ }
53
+ }
54
+ }
55
+ return errors;
56
+ }
57
+ //# sourceMappingURL=output-integrity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-integrity.js","sourceRoot":"","sources":["../../src/validator/output-integrity.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAA0B,EAC1B,UAAgC;IAEhC,MAAM,MAAM,GAAmB,EAAE,CAAC;IAElC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC;QAEzD,KAAK,MAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACtC,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAExD,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAClC,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI;oBACJ,OAAO,EAAE,YAAY,YAAY,mBAAmB;oBACpD,QAAQ,EAAE,OAAO;iBAClB,CAAC,CAAC;gBACH,SAAS;YACX,CAAC;YAED,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,MAAM,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAEpC,QAAQ,OAAO,CAAC,IAAI,EAAE,CAAC;oBACrB,KAAK,MAAM;wBACT,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;4BAC7B,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,0BAA0B,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACrE,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,KAAK,iBAAiB;wBACpB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;4BAC7B,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,qCAAqC,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCAChF,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,KAAK,eAAe;wBAClB,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC;4BAChC,MAAM,CAAC,IAAI,CAAC;gCACV,IAAI;gCACJ,OAAO,EAAE,sCAAsC,YAAY,cAAc,CAAC,GAAG,CAAC,GAAG;gCACjF,QAAQ,EAAE,OAAO;6BAClB,CAAC,CAAC;wBACL,CAAC;wBACD,MAAM;oBACR,8CAA8C;gBAChD,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1,18 +1,4 @@
1
- /**
2
- * Define which fields are valid for each segment type.
3
- * This allows us to warn when a field appears in the wrong segment type.
4
- */
5
- const VALID_FIELDS_BY_SEGMENT_TYPE = {
6
- text: new Set(['content', 'optional']),
7
- chat: new Set([
8
- 'instructions',
9
- 'optional',
10
- 'hidePreviousContentFromUser',
11
- 'hidePreviousContentFromTutor',
12
- ]),
13
- 'article-excerpt': new Set(['from', 'to', 'optional']),
14
- 'video-excerpt': new Set(['from', 'to', 'optional']),
15
- };
1
+ import { VALID_FIELDS_BY_SEGMENT_TYPE } from '../content-schema.js';
16
2
  /**
17
3
  * Fields that are specific to excerpt segments (not valid in text/chat).
18
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"segment-fields.js","sourceRoot":"","sources":["../../src/validator/segment-fields.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,4BAA4B,GAAgC;IAChE,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;IACtC,IAAI,EAAE,IAAI,GAAG,CAAC;QACZ,cAAc;QACd,UAAU;QACV,6BAA6B;QAC7B,8BAA8B;KAC/B,CAAC;IACF,iBAAiB,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;IACtD,eAAe,EAAE,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;CACrD,CAAC;AAEF;;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,8DAA8D;oBACvF,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;YACL,CAAC;YACD,2DAA2D;YAC3D,mCAAmC;QACrC,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}