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.
- package/dist/bundler/video.d.ts +12 -6
- package/dist/bundler/video.js +57 -34
- package/dist/bundler/video.js.map +1 -1
- package/dist/cli.d.ts +11 -2
- package/dist/cli.js +36 -19
- package/dist/cli.js.map +1 -1
- package/dist/content-schema.js +71 -16
- package/dist/content-schema.js.map +1 -1
- package/dist/dedupe-errors.d.ts +14 -0
- package/dist/dedupe-errors.js +33 -0
- package/dist/dedupe-errors.js.map +1 -0
- package/dist/flattener/index.d.ts +3 -28
- package/dist/flattener/index.js +507 -745
- package/dist/flattener/index.js.map +1 -1
- package/dist/flattener/resolve-text-links.d.ts +25 -0
- package/dist/flattener/resolve-text-links.js +248 -0
- package/dist/flattener/resolve-text-links.js.map +1 -0
- package/dist/flattener/test-helpers.d.ts +1 -1
- package/dist/flattener/test-helpers.js +1 -3
- package/dist/flattener/test-helpers.js.map +1 -1
- package/dist/index.d.ts +53 -21
- package/dist/index.js +285 -140
- package/dist/index.js.map +1 -1
- package/dist/parser/article.d.ts +2 -1
- package/dist/parser/article.js +75 -14
- package/dist/parser/article.js.map +1 -1
- package/dist/parser/course.d.ts +1 -1
- package/dist/parser/course.js +79 -22
- package/dist/parser/course.js.map +1 -1
- package/dist/parser/frontmatter.d.ts +1 -1
- package/dist/parser/frontmatter.js +38 -12
- package/dist/parser/frontmatter.js.map +1 -1
- package/dist/parser/learning-outcome.d.ts +5 -2
- package/dist/parser/learning-outcome.js +75 -37
- package/dist/parser/learning-outcome.js.map +1 -1
- package/dist/parser/lens.d.ts +40 -41
- package/dist/parser/lens.js +365 -349
- package/dist/parser/lens.js.map +1 -1
- package/dist/parser/module.d.ts +7 -15
- package/dist/parser/module.js +135 -171
- package/dist/parser/module.js.map +1 -1
- package/dist/parser/sections.d.ts +12 -1
- package/dist/parser/sections.js +74 -95
- package/dist/parser/sections.js.map +1 -1
- package/dist/parser/video-transcript.d.ts +2 -1
- package/dist/parser/video-transcript.js +8 -7
- package/dist/parser/video-transcript.js.map +1 -1
- package/dist/reference-graph.d.ts +8 -0
- package/dist/reference-graph.js +105 -0
- package/dist/reference-graph.js.map +1 -0
- package/dist/validator/card-module-membership.d.ts +2 -0
- package/dist/validator/card-module-membership.js +80 -0
- package/dist/validator/card-module-membership.js.map +1 -0
- package/dist/validator/output-integrity.js +5 -5
- package/dist/validator/output-integrity.js.map +1 -1
- package/dist/validator/segment-fields.d.ts +1 -1
- package/dist/validator/segment-fields.js +2 -2
- package/dist/validator/segment-fields.js.map +1 -1
- package/dist/validator/test-segments.d.ts +12 -0
- package/dist/validator/test-segments.js +40 -0
- package/dist/validator/test-segments.js.map +1 -0
- package/dist/validator/url-reachability.d.ts +2 -1
- package/dist/validator/url-reachability.js +40 -20
- package/dist/validator/url-reachability.js.map +1 -1
- package/package.json +1 -1
- package/dist/validator/chat-precedence.d.ts +0 -9
- package/dist/validator/chat-precedence.js +0 -32
- package/dist/validator/chat-precedence.js.map +0 -1
package/dist/bundler/video.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { ContentError } from '../index.js';
|
|
2
|
+
export interface TranscriptSentence {
|
|
3
|
+
text: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
}
|
|
2
7
|
export interface VideoExcerptResult {
|
|
3
8
|
from?: number;
|
|
4
9
|
to?: number | null;
|
|
5
|
-
transcript?:
|
|
10
|
+
transcript?: TranscriptSentence[];
|
|
6
11
|
error?: ContentError;
|
|
7
12
|
}
|
|
8
13
|
/**
|
|
@@ -25,14 +30,15 @@ export interface TimestampEntry {
|
|
|
25
30
|
*/
|
|
26
31
|
export declare function parseTimestamp(str: string): number | null;
|
|
27
32
|
/**
|
|
28
|
-
* Extract words from timestamps data that fall within the requested time range
|
|
33
|
+
* Extract words from timestamps data that fall within the requested time range,
|
|
34
|
+
* grouped into sentences by punctuation.
|
|
29
35
|
*
|
|
30
36
|
* @param timestamps - Array of word-level timestamp entries
|
|
31
37
|
* @param fromSeconds - Start time in seconds
|
|
32
|
-
* @param toSeconds - End time in seconds
|
|
33
|
-
* @returns
|
|
38
|
+
* @param toSeconds - End time in seconds (use Infinity for open-ended)
|
|
39
|
+
* @returns Sentences with start/end times in seconds
|
|
34
40
|
*/
|
|
35
|
-
export declare function extractFromTimestamps(timestamps: TimestampEntry[], fromSeconds: number, toSeconds: number):
|
|
41
|
+
export declare function extractFromTimestamps(timestamps: TimestampEntry[], fromSeconds: number, toSeconds: number): TranscriptSentence[];
|
|
36
42
|
/**
|
|
37
43
|
* Extract transcript content between two timestamps.
|
|
38
44
|
*
|
|
@@ -47,4 +53,4 @@ export declare function extractFromTimestamps(timestamps: TimestampEntry[], from
|
|
|
47
53
|
* @param timestamps - Optional word-level timestamps from .timestamps.json
|
|
48
54
|
* @returns Extracted transcript with from/to as seconds, or error
|
|
49
55
|
*/
|
|
50
|
-
export declare function extractVideoExcerpt(transcript: string, fromTime: string, toTime: string, file: string, timestamps?: TimestampEntry[]): VideoExcerptResult;
|
|
56
|
+
export declare function extractVideoExcerpt(transcript: string, fromTime: string, toTime: string | undefined, file: string, timestamps?: TimestampEntry[]): VideoExcerptResult;
|
package/dist/bundler/video.js
CHANGED
|
@@ -58,25 +58,46 @@ function parseTranscriptLineTimestamp(line) {
|
|
|
58
58
|
return parseTimestamp(match[1]);
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* @param timestamps - Array of word-level timestamp entries
|
|
64
|
-
* @param fromSeconds - Start time in seconds
|
|
65
|
-
* @param toSeconds - End time in seconds
|
|
66
|
-
* @returns Words joined with spaces
|
|
61
|
+
* Group word-level timestamp entries into sentences by detecting sentence-ending punctuation.
|
|
62
|
+
* The end of each sentence is the start of the next sentence (or toSeconds for the last).
|
|
67
63
|
*/
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
64
|
+
function groupIntoSentences(entries, toSeconds) {
|
|
65
|
+
const sentences = [];
|
|
66
|
+
const words = [];
|
|
67
|
+
let sentenceStart = null;
|
|
68
|
+
for (let i = 0; i < entries.length; i++) {
|
|
69
|
+
const entryTime = parseTimestamp(entries[i].start);
|
|
72
70
|
if (entryTime === null)
|
|
73
71
|
continue;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
if (sentenceStart === null)
|
|
73
|
+
sentenceStart = entryTime;
|
|
74
|
+
words.push(entries[i].text);
|
|
75
|
+
if (/[.?!…]$/.test(entries[i].text.trim())) {
|
|
76
|
+
sentences.push({ text: words.join(' '), start: sentenceStart, end: entryTime });
|
|
77
|
+
words.length = 0;
|
|
78
|
+
sentenceStart = null;
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
|
-
|
|
81
|
+
if (words.length > 0 && sentenceStart !== null) {
|
|
82
|
+
sentences.push({ text: words.join(' '), start: sentenceStart, end: toSeconds });
|
|
83
|
+
}
|
|
84
|
+
return sentences;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Extract words from timestamps data that fall within the requested time range,
|
|
88
|
+
* grouped into sentences by punctuation.
|
|
89
|
+
*
|
|
90
|
+
* @param timestamps - Array of word-level timestamp entries
|
|
91
|
+
* @param fromSeconds - Start time in seconds
|
|
92
|
+
* @param toSeconds - End time in seconds (use Infinity for open-ended)
|
|
93
|
+
* @returns Sentences with start/end times in seconds
|
|
94
|
+
*/
|
|
95
|
+
export function extractFromTimestamps(timestamps, fromSeconds, toSeconds) {
|
|
96
|
+
const inRange = timestamps.filter(entry => {
|
|
97
|
+
const t = parseTimestamp(entry.start);
|
|
98
|
+
return t !== null && t >= fromSeconds && t <= toSeconds;
|
|
99
|
+
});
|
|
100
|
+
return groupIntoSentences(inRange, toSeconds);
|
|
80
101
|
}
|
|
81
102
|
/**
|
|
82
103
|
* Extract transcript content between two timestamps.
|
|
@@ -105,8 +126,9 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
105
126
|
},
|
|
106
127
|
};
|
|
107
128
|
}
|
|
108
|
-
|
|
109
|
-
|
|
129
|
+
// toTime is optional — undefined means "full video from fromSeconds"
|
|
130
|
+
const toSeconds = toTime !== undefined ? parseTimestamp(toTime) : null;
|
|
131
|
+
if (toTime !== undefined && toSeconds === null) {
|
|
110
132
|
return {
|
|
111
133
|
error: {
|
|
112
134
|
file,
|
|
@@ -116,8 +138,8 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
116
138
|
},
|
|
117
139
|
};
|
|
118
140
|
}
|
|
119
|
-
// Validate that from is not after to
|
|
120
|
-
if (fromSeconds > toSeconds) {
|
|
141
|
+
// Validate that from is not after to (only when to is specified)
|
|
142
|
+
if (toSeconds !== null && fromSeconds > toSeconds) {
|
|
121
143
|
return {
|
|
122
144
|
error: {
|
|
123
145
|
file,
|
|
@@ -129,7 +151,8 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
129
151
|
}
|
|
130
152
|
// If timestamps data is provided, use word-level extraction
|
|
131
153
|
if (timestamps && timestamps.length > 0) {
|
|
132
|
-
const
|
|
154
|
+
const effectiveTo = toSeconds ?? Infinity;
|
|
155
|
+
const extractedText = extractFromTimestamps(timestamps, fromSeconds, effectiveTo);
|
|
133
156
|
return {
|
|
134
157
|
from: fromSeconds,
|
|
135
158
|
to: toSeconds,
|
|
@@ -149,12 +172,12 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
149
172
|
// Find lines that fall within the requested range
|
|
150
173
|
// Handle plain prose transcripts (no timestamps at all)
|
|
151
174
|
if (timestampedLines.length === 0) {
|
|
152
|
-
// If starting from 0:00, include entire transcript
|
|
175
|
+
// If starting from 0:00, include entire transcript as a single sentence
|
|
153
176
|
if (fromSeconds === 0) {
|
|
154
177
|
return {
|
|
155
178
|
from: fromSeconds,
|
|
156
179
|
to: toSeconds,
|
|
157
|
-
transcript: transcript.trim(),
|
|
180
|
+
transcript: [{ text: transcript.trim(), start: fromSeconds, end: toSeconds ?? Infinity }],
|
|
158
181
|
};
|
|
159
182
|
}
|
|
160
183
|
// Can't extract from a non-zero timestamp in a plain prose transcript
|
|
@@ -167,22 +190,16 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
167
190
|
},
|
|
168
191
|
};
|
|
169
192
|
}
|
|
170
|
-
// A line is included if its timestamp >= fromSeconds and < toSeconds
|
|
193
|
+
// A line is included if its timestamp >= fromSeconds and < toSeconds (or all remaining if no to)
|
|
171
194
|
// Special case: fromSeconds === 0 means "start of video" - no need to find exact 0:00 marker
|
|
195
|
+
const effectiveTo = toSeconds ?? Infinity;
|
|
172
196
|
let foundFrom = fromSeconds === 0;
|
|
173
|
-
let foundTo =
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
if (timestamp === fromSeconds) {
|
|
197
|
+
let foundTo = toSeconds === null; // no to:: means we want everything — no need to find marker
|
|
198
|
+
for (const { timestamp } of timestampedLines) {
|
|
199
|
+
if (timestamp === fromSeconds)
|
|
177
200
|
foundFrom = true;
|
|
178
|
-
|
|
179
|
-
if (timestamp === toSeconds) {
|
|
201
|
+
if (toSeconds !== null && timestamp === toSeconds)
|
|
180
202
|
foundTo = true;
|
|
181
|
-
}
|
|
182
|
-
// Include lines from fromSeconds up to (but not including) toSeconds
|
|
183
|
-
if (timestamp >= fromSeconds && timestamp < toSeconds) {
|
|
184
|
-
excerptLines.push(line);
|
|
185
|
-
}
|
|
186
203
|
}
|
|
187
204
|
if (!foundFrom) {
|
|
188
205
|
return {
|
|
@@ -204,10 +221,16 @@ export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestam
|
|
|
204
221
|
},
|
|
205
222
|
};
|
|
206
223
|
}
|
|
224
|
+
const inRange = timestampedLines.filter(({ timestamp }) => timestamp >= fromSeconds && timestamp < effectiveTo);
|
|
225
|
+
const excerptSentences = inRange.map(({ timestamp, line }) => ({
|
|
226
|
+
text: line.replace(/^\d+:\d+(?::\d+)?\s*-\s*/, '').trim(),
|
|
227
|
+
start: timestamp,
|
|
228
|
+
end: timestamp,
|
|
229
|
+
}));
|
|
207
230
|
return {
|
|
208
231
|
from: fromSeconds,
|
|
209
232
|
to: toSeconds,
|
|
210
|
-
transcript:
|
|
233
|
+
transcript: excerptSentences,
|
|
211
234
|
};
|
|
212
235
|
}
|
|
213
236
|
//# sourceMappingURL=video.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type ProcessResult } from
|
|
2
|
+
import { type ProcessResult } from "./index.js";
|
|
3
3
|
export interface CliOptions {
|
|
4
4
|
vaultPath: string | null;
|
|
5
5
|
outputPath: string | null;
|
|
6
6
|
stdin: boolean;
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* The CLI's JSON output: the core ProcessResult plus a course-reachability map
|
|
10
|
+
* for the validator page. fileToCourses is a CLI-boundary concern (not part of
|
|
11
|
+
* core processing / the Python-parity output), so it lives here, not on
|
|
12
|
+
* ProcessResult.
|
|
13
|
+
*/
|
|
14
|
+
export type CliResult = ProcessResult & {
|
|
15
|
+
fileToCourses: Record<string, string[]>;
|
|
16
|
+
};
|
|
8
17
|
export declare function parseArgs(argv: string[]): CliOptions;
|
|
9
|
-
export declare function run(options: CliOptions): Promise<
|
|
18
|
+
export declare function run(options: CliOptions): Promise<CliResult>;
|
package/dist/cli.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// src/cli.ts
|
|
3
|
-
import { readVaultFiles } from
|
|
4
|
-
import { processContent } from
|
|
5
|
-
import { validateUrls } from
|
|
6
|
-
import {
|
|
3
|
+
import { readVaultFiles } from "./fs/read-vault.js";
|
|
4
|
+
import { processContent } from "./index.js";
|
|
5
|
+
import { validateUrls } from "./validator/url-reachability.js";
|
|
6
|
+
import { buildFileToCourses } from "./reference-graph.js";
|
|
7
|
+
import { dedupeErrors } from "./dedupe-errors.js";
|
|
8
|
+
import { writeFile } from "fs/promises";
|
|
7
9
|
export function parseArgs(argv) {
|
|
8
10
|
const args = argv.slice(2);
|
|
9
11
|
let vaultPath = null;
|
|
10
12
|
let outputPath = null;
|
|
11
13
|
let stdin = false;
|
|
12
14
|
for (let i = 0; i < args.length; i++) {
|
|
13
|
-
if (args[i] ===
|
|
15
|
+
if (args[i] === "--output" || args[i] === "-o") {
|
|
14
16
|
outputPath = args[i + 1] || null;
|
|
15
17
|
i++; // skip next arg
|
|
16
18
|
}
|
|
17
|
-
else if (args[i] ===
|
|
19
|
+
else if (args[i] === "--stdin") {
|
|
18
20
|
stdin = true;
|
|
19
21
|
}
|
|
20
|
-
else if (!args[i].startsWith(
|
|
22
|
+
else if (!args[i].startsWith("-")) {
|
|
21
23
|
vaultPath = args[i];
|
|
22
24
|
}
|
|
23
25
|
}
|
|
@@ -28,7 +30,7 @@ async function readStdin() {
|
|
|
28
30
|
for await (const chunk of process.stdin) {
|
|
29
31
|
chunks.push(chunk);
|
|
30
32
|
}
|
|
31
|
-
return Buffer.concat(chunks).toString(
|
|
33
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
32
34
|
}
|
|
33
35
|
export async function run(options) {
|
|
34
36
|
let files;
|
|
@@ -40,17 +42,30 @@ export async function run(options) {
|
|
|
40
42
|
}
|
|
41
43
|
else {
|
|
42
44
|
if (!options.vaultPath) {
|
|
43
|
-
throw new Error(
|
|
45
|
+
throw new Error("Vault path is required (or use --stdin)");
|
|
44
46
|
}
|
|
45
47
|
files = await readVaultFiles(options.vaultPath);
|
|
46
48
|
}
|
|
47
|
-
|
|
49
|
+
const result = processContent(files);
|
|
50
|
+
// Course-reachability map for the validator page. Computed here, not in
|
|
51
|
+
// processContent, so it stays out of the Python-parity / fixture output.
|
|
52
|
+
// Degradable (per AGENTS.md): this feeds only the validator page's course
|
|
53
|
+
// filter, so a failure here must never break content processing for the
|
|
54
|
+
// whole platform — fall back to an empty map and carry on.
|
|
55
|
+
let fileToCourses = {};
|
|
56
|
+
try {
|
|
57
|
+
fileToCourses = buildFileToCourses(files);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("buildFileToCourses failed; serving validation without the course filter:", error instanceof Error ? error.message : error);
|
|
61
|
+
}
|
|
62
|
+
return { ...result, fileToCourses };
|
|
48
63
|
}
|
|
49
64
|
async function main() {
|
|
50
65
|
const options = parseArgs(process.argv);
|
|
51
66
|
if (!options.vaultPath && !options.stdin) {
|
|
52
|
-
console.error(
|
|
53
|
-
console.error(
|
|
67
|
+
console.error("Usage: npx tsx src/cli.ts <vault-path> [--output <file>]");
|
|
68
|
+
console.error(" npx tsx src/cli.ts --stdin [--output <file>]");
|
|
54
69
|
process.exit(1);
|
|
55
70
|
}
|
|
56
71
|
try {
|
|
@@ -58,11 +73,13 @@ async function main() {
|
|
|
58
73
|
// Async URL reachability validation
|
|
59
74
|
if (result.urlsToValidate.length > 0) {
|
|
60
75
|
const urlWarnings = await validateUrls(result.urlsToValidate);
|
|
61
|
-
|
|
76
|
+
// Re-dedupe: urlWarnings are appended after processContent already
|
|
77
|
+
// deduped, and can themselves repeat (same unreachable URL twice).
|
|
78
|
+
result.errors = dedupeErrors([...result.errors, ...urlWarnings]);
|
|
62
79
|
}
|
|
63
80
|
const json = JSON.stringify(result, null, 2);
|
|
64
81
|
if (options.outputPath) {
|
|
65
|
-
await writeFile(options.outputPath, json,
|
|
82
|
+
await writeFile(options.outputPath, json, "utf-8");
|
|
66
83
|
console.error(`Written to ${options.outputPath}`);
|
|
67
84
|
}
|
|
68
85
|
else {
|
|
@@ -70,16 +87,16 @@ async function main() {
|
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
catch (error) {
|
|
73
|
-
console.error(
|
|
90
|
+
console.error("Error:", error instanceof Error ? error.message : error);
|
|
74
91
|
process.exit(1);
|
|
75
92
|
}
|
|
76
93
|
}
|
|
77
94
|
// Run if executed directly (not imported as a module)
|
|
78
95
|
// Check for cli.ts, cli.js, or lens-content-processor (when run as npx binary)
|
|
79
|
-
const scriptName = process.argv[1] ||
|
|
80
|
-
const isMainModule = scriptName.includes(
|
|
81
|
-
scriptName.includes(
|
|
82
|
-
scriptName.endsWith(
|
|
96
|
+
const scriptName = process.argv[1] || "";
|
|
97
|
+
const isMainModule = scriptName.includes("cli.ts") ||
|
|
98
|
+
scriptName.includes("cli.js") ||
|
|
99
|
+
scriptName.endsWith("lens-content-processor");
|
|
83
100
|
if (isMainModule) {
|
|
84
101
|
main();
|
|
85
102
|
}
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,aAAa;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAsB,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/content-schema.js
CHANGED
|
@@ -1,36 +1,89 @@
|
|
|
1
1
|
// content-schema.ts — Single source of truth for content type and segment type field definitions.
|
|
2
2
|
function contentSchema(required, optional) {
|
|
3
|
-
return {
|
|
3
|
+
return {
|
|
4
|
+
requiredFields: required,
|
|
5
|
+
optionalFields: optional,
|
|
6
|
+
allFields: [...required, ...optional],
|
|
7
|
+
};
|
|
4
8
|
}
|
|
5
9
|
function segmentSchema(required, optional, booleanFields) {
|
|
6
|
-
return {
|
|
10
|
+
return {
|
|
11
|
+
requiredFields: required,
|
|
12
|
+
optionalFields: optional,
|
|
13
|
+
allFields: [...required, ...optional],
|
|
14
|
+
booleanFields,
|
|
15
|
+
};
|
|
7
16
|
}
|
|
8
17
|
export const CONTENT_SCHEMAS = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
module: contentSchema(["slug", "title"], ["contentId", "id", "discussion", "tags"]),
|
|
19
|
+
course: contentSchema(["slug", "title"], [
|
|
20
|
+
"id",
|
|
21
|
+
"slug-aliases",
|
|
22
|
+
"tags",
|
|
23
|
+
"partner-name",
|
|
24
|
+
"partner-logo",
|
|
25
|
+
"partner-logo-small",
|
|
26
|
+
"partner-url",
|
|
27
|
+
]),
|
|
28
|
+
lens: contentSchema(["id"], ["title", "tldr", "summary_for_tutor", "tags"]),
|
|
29
|
+
"learning-outcome": contentSchema(["id"], ["discussion", "learning-outcome", "tags"]),
|
|
30
|
+
article: contentSchema(["title", "author", "source_url", "published"], [
|
|
31
|
+
"created",
|
|
32
|
+
"description",
|
|
33
|
+
"tags",
|
|
34
|
+
"url",
|
|
35
|
+
"allowAuthorInTitle",
|
|
36
|
+
"allowUnreachableUrl",
|
|
37
|
+
]),
|
|
38
|
+
"video-transcript": contentSchema(["title", "channel", "url"], ["tags", "allowUnreachableUrl"]),
|
|
15
39
|
};
|
|
16
40
|
export const SEGMENT_SCHEMAS = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
41
|
+
text: segmentSchema(["content"], ["optional"], ["optional"]),
|
|
42
|
+
// Note: chat and question take min-messages instead of optional —
|
|
43
|
+
// min-messages:: 0 is the "not required" spelling at segment level
|
|
44
|
+
// (optional:: true remains a lens-level concept)
|
|
45
|
+
chat: segmentSchema(["instructions"], [
|
|
46
|
+
"min-messages",
|
|
47
|
+
"hidePreviousContentFromUser",
|
|
48
|
+
"hidePreviousContentFromTutor",
|
|
49
|
+
], ["hidePreviousContentFromUser", "hidePreviousContentFromTutor"]),
|
|
50
|
+
article: segmentSchema([], ["source", "from", "to", "optional"], ["optional"]),
|
|
51
|
+
video: segmentSchema([], ["source", "from", "to", "optional"], ["optional"]),
|
|
52
|
+
question: segmentSchema(["content"], [
|
|
53
|
+
"assessment-instructions",
|
|
54
|
+
"max-time",
|
|
55
|
+
"max-chars",
|
|
56
|
+
"min-messages",
|
|
57
|
+
"enforce-voice",
|
|
58
|
+
"feedback",
|
|
59
|
+
], ["enforce-voice", "feedback"]),
|
|
60
|
+
roleplay: segmentSchema(["id", "content", "ai-instructions"], [
|
|
61
|
+
"opening-message",
|
|
62
|
+
"assessment-instructions",
|
|
63
|
+
"user-customizable",
|
|
64
|
+
"optional",
|
|
65
|
+
"feedback",
|
|
66
|
+
], ["user-customizable", "optional", "feedback"]),
|
|
23
67
|
};
|
|
24
68
|
/**
|
|
25
69
|
* Valid fields per segment type, derived from SEGMENT_SCHEMAS.
|
|
26
70
|
* Used by segment-fields.ts to check for misplaced fields.
|
|
27
71
|
*/
|
|
28
|
-
export const VALID_FIELDS_BY_SEGMENT_TYPE = Object.fromEntries(Object.entries(SEGMENT_SCHEMAS).map(([type, schema]) => [
|
|
72
|
+
export const VALID_FIELDS_BY_SEGMENT_TYPE = Object.fromEntries(Object.entries(SEGMENT_SCHEMAS).map(([type, schema]) => [
|
|
73
|
+
type,
|
|
74
|
+
new Set(schema.allFields),
|
|
75
|
+
]));
|
|
29
76
|
/**
|
|
30
77
|
* Section-level fields used in body sections (not frontmatter, not segments).
|
|
31
78
|
* These are fields like source:: and learningOutcomeId:: used in LO/lens sections.
|
|
32
79
|
*/
|
|
33
|
-
const SECTION_LEVEL_FIELDS = [
|
|
80
|
+
const SECTION_LEVEL_FIELDS = [
|
|
81
|
+
"source",
|
|
82
|
+
"learningOutcomeId",
|
|
83
|
+
"sourceUrl",
|
|
84
|
+
"hide",
|
|
85
|
+
];
|
|
86
|
+
const SECTION_LEVEL_BOOLEAN_FIELDS = ["hide"];
|
|
34
87
|
/**
|
|
35
88
|
* All known field names across all content types, derived from schemas.
|
|
36
89
|
* Used by typo detection to suggest corrections for unrecognized fields.
|
|
@@ -58,6 +111,8 @@ export const ALL_BOOLEAN_FIELDS = (() => {
|
|
|
58
111
|
for (const field of schema.booleanFields)
|
|
59
112
|
fields.add(field);
|
|
60
113
|
}
|
|
114
|
+
for (const field of SECTION_LEVEL_BOOLEAN_FIELDS)
|
|
115
|
+
fields.add(field);
|
|
61
116
|
return [...fields];
|
|
62
117
|
})();
|
|
63
118
|
//# sourceMappingURL=content-schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAsBlG,SAAS,aAAa,
|
|
1
|
+
{"version":3,"file":"content-schema.js","sourceRoot":"","sources":["../src/content-schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ContentError } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* Collapse duplicate errors that point at the same problem.
|
|
4
|
+
*
|
|
5
|
+
* A single content file is validated more than once per run: a learning
|
|
6
|
+
* outcome is parsed standalone AND again every time a module that references
|
|
7
|
+
* it is flattened (the flattener re-parses referenced LOs). Each pass runs the
|
|
8
|
+
* same parse code on the same content, so an identical error is emitted once
|
|
9
|
+
* per pass — e.g. one mistake in an LO referenced by one module surfaces twice.
|
|
10
|
+
*
|
|
11
|
+
* Authors should see each problem once. Dedupe on (file, line, severity,
|
|
12
|
+
* message), keeping the first occurrence so report order is stable.
|
|
13
|
+
*/
|
|
14
|
+
export declare function dedupeErrors(errors: ContentError[]): ContentError[];
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapse duplicate errors that point at the same problem.
|
|
3
|
+
*
|
|
4
|
+
* A single content file is validated more than once per run: a learning
|
|
5
|
+
* outcome is parsed standalone AND again every time a module that references
|
|
6
|
+
* it is flattened (the flattener re-parses referenced LOs). Each pass runs the
|
|
7
|
+
* same parse code on the same content, so an identical error is emitted once
|
|
8
|
+
* per pass — e.g. one mistake in an LO referenced by one module surfaces twice.
|
|
9
|
+
*
|
|
10
|
+
* Authors should see each problem once. Dedupe on (file, line, severity,
|
|
11
|
+
* message), keeping the first occurrence so report order is stable.
|
|
12
|
+
*/
|
|
13
|
+
export function dedupeErrors(errors) {
|
|
14
|
+
const seen = new Set();
|
|
15
|
+
const deduped = [];
|
|
16
|
+
for (const error of errors) {
|
|
17
|
+
// \x1f (unit separator) joins the fields: file paths and messages can
|
|
18
|
+
// themselves contain spaces, so a printable separator could let two
|
|
19
|
+
// distinct errors collapse to the same key.
|
|
20
|
+
const key = [
|
|
21
|
+
error.file,
|
|
22
|
+
error.line ?? "",
|
|
23
|
+
error.severity,
|
|
24
|
+
error.message,
|
|
25
|
+
].join("\x1f");
|
|
26
|
+
if (seen.has(key))
|
|
27
|
+
continue;
|
|
28
|
+
seen.add(key);
|
|
29
|
+
deduped.push(error);
|
|
30
|
+
}
|
|
31
|
+
return deduped;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=dedupe-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dedupe-errors.js","sourceRoot":"","sources":["../src/dedupe-errors.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY,CAAC,MAAsB;IACjD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,sEAAsE;QACtE,oEAAoE;QACpE,4CAA4C;QAC5C,MAAM,GAAG,GAAG;YACV,KAAK,CAAC,IAAI;YACV,KAAK,CAAC,IAAI,IAAI,EAAE;YAChB,KAAK,CAAC,QAAQ;YACd,KAAK,CAAC,OAAO;SACd,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACf,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { FlattenedModule, Section, ContentError } from
|
|
2
|
-
import type { ContentTier } from
|
|
3
|
-
import { type ParsedLens } from
|
|
1
|
+
import type { FlattenedModule, Section, ContentError } from "../index.js";
|
|
2
|
+
import type { ContentTier } from "../validator/tier.js";
|
|
3
|
+
import { type ParsedLens } from "../parser/lens.js";
|
|
4
4
|
export interface FlattenModuleResult {
|
|
5
5
|
module: FlattenedModule | null;
|
|
6
6
|
modules: FlattenedModule[];
|
|
@@ -25,35 +25,10 @@ interface VirtualModuleGroup {
|
|
|
25
25
|
export declare function splitAtBoundaries(items: FlatItem[], parentSlug: string, parentTitle: string): VirtualModuleGroup[];
|
|
26
26
|
/**
|
|
27
27
|
* Flatten a module by resolving all references to Learning Outcomes, Lenses, and content.
|
|
28
|
-
*
|
|
29
|
-
* This function:
|
|
30
|
-
* 1. Parses the module file
|
|
31
|
-
* 2. For each Learning Outcome section, resolves the LO file
|
|
32
|
-
* 3. For each Lens in the LO, resolves the lens file
|
|
33
|
-
* 4. For each segment, extracts content from articles/videos as needed
|
|
34
|
-
*
|
|
35
|
-
* @param modulePath - Path to the module file within the files Map
|
|
36
|
-
* @param files - Map of all file paths to their content
|
|
37
|
-
* @param visitedPaths - Optional set of already-visited paths for cycle detection
|
|
38
|
-
* @returns Flattened module with resolved sections and segments, plus any errors
|
|
39
28
|
*/
|
|
40
29
|
export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>, tierMap?: Map<string, ContentTier>): FlattenModuleResult;
|
|
41
30
|
/**
|
|
42
31
|
* Flatten a single Lens file into a FlattenedModule.
|
|
43
|
-
*
|
|
44
|
-
* This wraps a standalone Lens as a single-section module so it can be
|
|
45
|
-
* rendered by the frontend using the existing Module.tsx component.
|
|
46
|
-
*
|
|
47
|
-
* The resulting module has:
|
|
48
|
-
* - slug: 'lens/' + fileNameToSlug(lensPath)
|
|
49
|
-
* - title: extracted from source metadata or lens header
|
|
50
|
-
* - sections: exactly one section (lens-article, lens-video, or page)
|
|
51
|
-
*
|
|
52
|
-
* @param lensPath - Path to the lens file within the files Map
|
|
53
|
-
* @param files - Map of all file paths to their content
|
|
54
|
-
* @param tierMap - Optional tier map for filtering ignored content
|
|
55
|
-
* @param preParsedLens - Optional pre-parsed lens to avoid re-parsing
|
|
56
|
-
* @returns Flattened module with a single section, plus any errors
|
|
57
32
|
*/
|
|
58
33
|
export declare function flattenLens(lensPath: string, files: Map<string, string>, tierMap?: Map<string, ContentTier>, preParsedLens?: ParsedLens): FlattenModuleResult;
|
|
59
34
|
export {};
|