lens-content-processor 0.1.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/article.d.ts +41 -0
- package/dist/bundler/article.js +240 -0
- package/dist/bundler/article.js.map +1 -0
- package/dist/bundler/article.test.d.ts +1 -0
- package/dist/bundler/article.test.js +212 -0
- package/dist/bundler/article.test.js.map +1 -0
- package/dist/bundler/video.d.ts +50 -0
- package/dist/bundler/video.js +213 -0
- package/dist/bundler/video.js.map +1 -0
- package/dist/bundler/video.test.d.ts +1 -0
- package/dist/bundler/video.test.js +95 -0
- package/dist/bundler/video.test.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +80 -0
- package/dist/cli.js.map +1 -0
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +45 -0
- package/dist/cli.test.js.map +1 -0
- package/dist/flattener/index.d.ts +20 -0
- package/dist/flattener/index.js +707 -0
- package/dist/flattener/index.js.map +1 -0
- package/dist/flattener/index.test.d.ts +1 -0
- package/dist/flattener/index.test.js +547 -0
- package/dist/flattener/index.test.js.map +1 -0
- package/dist/fs/read-vault.d.ts +4 -0
- package/dist/fs/read-vault.js +28 -0
- package/dist/fs/read-vault.js.map +1 -0
- package/dist/fs/read-vault.test.d.ts +1 -0
- package/dist/fs/read-vault.test.js +37 -0
- package/dist/fs/read-vault.test.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +137 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/course.d.ts +17 -0
- package/dist/parser/course.js +145 -0
- package/dist/parser/course.js.map +1 -0
- package/dist/parser/course.test.d.ts +1 -0
- package/dist/parser/course.test.js +117 -0
- package/dist/parser/course.test.js.map +1 -0
- package/dist/parser/frontmatter.d.ts +8 -0
- package/dist/parser/frontmatter.js +58 -0
- package/dist/parser/frontmatter.js.map +1 -0
- package/dist/parser/frontmatter.test.d.ts +1 -0
- package/dist/parser/frontmatter.test.js +54 -0
- package/dist/parser/frontmatter.test.js.map +1 -0
- package/dist/parser/learning-outcome.d.ts +21 -0
- package/dist/parser/learning-outcome.js +116 -0
- package/dist/parser/learning-outcome.js.map +1 -0
- package/dist/parser/learning-outcome.test.d.ts +1 -0
- package/dist/parser/learning-outcome.test.js +75 -0
- package/dist/parser/learning-outcome.test.js.map +1 -0
- package/dist/parser/lens.d.ts +51 -0
- package/dist/parser/lens.js +328 -0
- package/dist/parser/lens.js.map +1 -0
- package/dist/parser/lens.test.d.ts +1 -0
- package/dist/parser/lens.test.js +453 -0
- package/dist/parser/lens.test.js.map +1 -0
- package/dist/parser/module.d.ts +21 -0
- package/dist/parser/module.js +128 -0
- package/dist/parser/module.js.map +1 -0
- package/dist/parser/module.test.d.ts +1 -0
- package/dist/parser/module.test.js +99 -0
- package/dist/parser/module.test.js.map +1 -0
- package/dist/parser/sections.d.ts +18 -0
- package/dist/parser/sections.js +128 -0
- package/dist/parser/sections.js.map +1 -0
- package/dist/parser/sections.test.d.ts +1 -0
- package/dist/parser/sections.test.js +297 -0
- package/dist/parser/sections.test.js.map +1 -0
- package/dist/parser/wikilink.d.ts +12 -0
- package/dist/parser/wikilink.js +35 -0
- package/dist/parser/wikilink.js.map +1 -0
- package/dist/parser/wikilink.test.d.ts +1 -0
- package/dist/parser/wikilink.test.js +76 -0
- package/dist/parser/wikilink.test.js.map +1 -0
- package/dist/validator/field-typos.d.ts +10 -0
- package/dist/validator/field-typos.js +96 -0
- package/dist/validator/field-typos.js.map +1 -0
- package/dist/validator/field-typos.test.d.ts +1 -0
- package/dist/validator/field-typos.test.js +41 -0
- package/dist/validator/field-typos.test.js.map +1 -0
- package/dist/validator/field-values.d.ts +11 -0
- package/dist/validator/field-values.js +37 -0
- package/dist/validator/field-values.js.map +1 -0
- package/dist/validator/field-values.test.d.ts +1 -0
- package/dist/validator/field-values.test.js +45 -0
- package/dist/validator/field-values.test.js.map +1 -0
- package/dist/validator/segment-fields.d.ts +12 -0
- package/dist/validator/segment-fields.js +55 -0
- package/dist/validator/segment-fields.js.map +1 -0
- package/dist/validator/segment-fields.test.d.ts +1 -0
- package/dist/validator/segment-fields.test.js +73 -0
- package/dist/validator/segment-fields.test.js.map +1 -0
- package/dist/validator/standalone.test.d.ts +1 -0
- package/dist/validator/standalone.test.js +190 -0
- package/dist/validator/standalone.test.js.map +1 -0
- package/dist/validator/uuid.d.ts +17 -0
- package/dist/validator/uuid.js +46 -0
- package/dist/validator/uuid.js.map +1 -0
- package/dist/validator/uuid.test.d.ts +1 -0
- package/dist/validator/uuid.test.js +229 -0
- package/dist/validator/uuid.test.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a timestamp string into seconds.
|
|
3
|
+
*
|
|
4
|
+
* Supports formats:
|
|
5
|
+
* - MM:SS (e.g., "1:30" -> 90)
|
|
6
|
+
* - H:MM:SS (e.g., "1:30:00" -> 5400)
|
|
7
|
+
* - M:SS.ms (e.g., "0:00.40" -> 0.4) - used in .timestamps.json files
|
|
8
|
+
*
|
|
9
|
+
* @param str - Timestamp string
|
|
10
|
+
* @returns Number of seconds, or null if invalid format
|
|
11
|
+
*/
|
|
12
|
+
export function parseTimestamp(str) {
|
|
13
|
+
// Handle M:SS.ms format (timestamps.json format)
|
|
14
|
+
const msMatch = str.match(/^(\d+):(\d+(?:\.\d+)?)$/);
|
|
15
|
+
if (msMatch) {
|
|
16
|
+
const minutes = parseInt(msMatch[1], 10);
|
|
17
|
+
const seconds = parseFloat(msMatch[2]);
|
|
18
|
+
return minutes * 60 + seconds;
|
|
19
|
+
}
|
|
20
|
+
// Match M:SS, MM:SS, H:MM:SS, HH:MM:SS patterns
|
|
21
|
+
const parts = str.split(':');
|
|
22
|
+
if (parts.length < 2 || parts.length > 3) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// Validate all parts are numeric
|
|
26
|
+
for (const part of parts) {
|
|
27
|
+
if (!/^\d+$/.test(part)) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (parts.length === 2) {
|
|
32
|
+
// MM:SS format
|
|
33
|
+
const minutes = parseInt(parts[0], 10);
|
|
34
|
+
const seconds = parseInt(parts[1], 10);
|
|
35
|
+
return minutes * 60 + seconds;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
// H:MM:SS format
|
|
39
|
+
const hours = parseInt(parts[0], 10);
|
|
40
|
+
const minutes = parseInt(parts[1], 10);
|
|
41
|
+
const seconds = parseInt(parts[2], 10);
|
|
42
|
+
return hours * 3600 + minutes * 60 + seconds;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Parse a transcript line to extract its timestamp.
|
|
47
|
+
* Expected format: "M:SS - text" or "H:MM:SS - text"
|
|
48
|
+
*
|
|
49
|
+
* @param line - A line from the transcript
|
|
50
|
+
* @returns The timestamp in seconds, or null if no timestamp found
|
|
51
|
+
*/
|
|
52
|
+
function parseTranscriptLineTimestamp(line) {
|
|
53
|
+
// Match timestamp at start of line: "0:00 - ", "1:30 - ", "1:30:00 - "
|
|
54
|
+
const match = line.match(/^(\d+:\d{2}(?::\d{2})?)\s*-/);
|
|
55
|
+
if (!match) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
return parseTimestamp(match[1]);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Extract words from timestamps data that fall within the requested time range.
|
|
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
|
|
67
|
+
*/
|
|
68
|
+
export function extractFromTimestamps(timestamps, fromSeconds, toSeconds) {
|
|
69
|
+
const wordsInRange = [];
|
|
70
|
+
for (const entry of timestamps) {
|
|
71
|
+
const entryTime = parseTimestamp(entry.start);
|
|
72
|
+
if (entryTime === null)
|
|
73
|
+
continue;
|
|
74
|
+
// Include word if its start time falls within the range
|
|
75
|
+
if (entryTime >= fromSeconds && entryTime <= toSeconds) {
|
|
76
|
+
wordsInRange.push(entry.text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return wordsInRange.join(' ');
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract transcript content between two timestamps.
|
|
83
|
+
*
|
|
84
|
+
* If timestamps data (from .timestamps.json) is provided, uses word-level
|
|
85
|
+
* extraction. Otherwise, falls back to inline timestamp markers in the
|
|
86
|
+
* transcript markdown.
|
|
87
|
+
*
|
|
88
|
+
* @param transcript - The full transcript content (markdown)
|
|
89
|
+
* @param fromTime - Start timestamp string (e.g., "1:30")
|
|
90
|
+
* @param toTime - End timestamp string (e.g., "5:45")
|
|
91
|
+
* @param file - Source file path for error reporting
|
|
92
|
+
* @param timestamps - Optional word-level timestamps from .timestamps.json
|
|
93
|
+
* @returns Extracted transcript with from/to as seconds, or error
|
|
94
|
+
*/
|
|
95
|
+
export function extractVideoExcerpt(transcript, fromTime, toTime, file, timestamps) {
|
|
96
|
+
// Parse the requested timestamps
|
|
97
|
+
const fromSeconds = parseTimestamp(fromTime);
|
|
98
|
+
if (fromSeconds === null) {
|
|
99
|
+
return {
|
|
100
|
+
error: {
|
|
101
|
+
file,
|
|
102
|
+
message: `Invalid start timestamp format: '${fromTime}'`,
|
|
103
|
+
suggestion: 'Use MM:SS (e.g., 1:30) or H:MM:SS (e.g., 1:30:00) format',
|
|
104
|
+
severity: 'error',
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const toSeconds = parseTimestamp(toTime);
|
|
109
|
+
if (toSeconds === null) {
|
|
110
|
+
return {
|
|
111
|
+
error: {
|
|
112
|
+
file,
|
|
113
|
+
message: `Invalid end timestamp format: '${toTime}'`,
|
|
114
|
+
suggestion: 'Use MM:SS (e.g., 1:30) or H:MM:SS (e.g., 1:30:00) format',
|
|
115
|
+
severity: 'error',
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// Validate that from is not after to
|
|
120
|
+
if (fromSeconds > toSeconds) {
|
|
121
|
+
return {
|
|
122
|
+
error: {
|
|
123
|
+
file,
|
|
124
|
+
message: `Start timestamp '${fromTime}' is after end timestamp '${toTime}'`,
|
|
125
|
+
suggestion: 'Ensure the from:: timestamp comes before the to:: timestamp',
|
|
126
|
+
severity: 'error',
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// If timestamps data is provided, use word-level extraction
|
|
131
|
+
if (timestamps && timestamps.length > 0) {
|
|
132
|
+
const extractedText = extractFromTimestamps(timestamps, fromSeconds, toSeconds);
|
|
133
|
+
return {
|
|
134
|
+
from: fromSeconds,
|
|
135
|
+
to: toSeconds,
|
|
136
|
+
transcript: extractedText,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Fall back to inline timestamp markers in the markdown
|
|
140
|
+
// Parse the transcript into lines with timestamps
|
|
141
|
+
const lines = transcript.split('\n');
|
|
142
|
+
const timestampedLines = [];
|
|
143
|
+
for (const line of lines) {
|
|
144
|
+
const ts = parseTranscriptLineTimestamp(line);
|
|
145
|
+
if (ts !== null) {
|
|
146
|
+
timestampedLines.push({ timestamp: ts, line });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Find lines that fall within the requested range
|
|
150
|
+
// Handle plain prose transcripts (no timestamps at all)
|
|
151
|
+
if (timestampedLines.length === 0) {
|
|
152
|
+
// If starting from 0:00, include entire transcript
|
|
153
|
+
if (fromSeconds === 0) {
|
|
154
|
+
return {
|
|
155
|
+
from: fromSeconds,
|
|
156
|
+
to: toSeconds,
|
|
157
|
+
transcript: transcript.trim(),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
// Can't extract from a non-zero timestamp in a plain prose transcript
|
|
161
|
+
return {
|
|
162
|
+
error: {
|
|
163
|
+
file,
|
|
164
|
+
message: `Start timestamp '${fromTime}' not found in transcript (transcript has no timestamp markers)`,
|
|
165
|
+
suggestion: 'This transcript has no timestamp markers. Use from:: 0:00 to include from the start, or add timestamps to the transcript.',
|
|
166
|
+
severity: 'error',
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// A line is included if its timestamp >= fromSeconds and < toSeconds
|
|
171
|
+
// Special case: fromSeconds === 0 means "start of video" - no need to find exact 0:00 marker
|
|
172
|
+
let foundFrom = fromSeconds === 0;
|
|
173
|
+
let foundTo = false;
|
|
174
|
+
const excerptLines = [];
|
|
175
|
+
for (const { timestamp, line } of timestampedLines) {
|
|
176
|
+
if (timestamp === fromSeconds) {
|
|
177
|
+
foundFrom = true;
|
|
178
|
+
}
|
|
179
|
+
if (timestamp === toSeconds) {
|
|
180
|
+
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
|
+
}
|
|
187
|
+
if (!foundFrom) {
|
|
188
|
+
return {
|
|
189
|
+
error: {
|
|
190
|
+
file,
|
|
191
|
+
message: `Start timestamp '${fromTime}' not found in transcript`,
|
|
192
|
+
suggestion: 'Check that the timestamp exists as a line marker in the transcript',
|
|
193
|
+
severity: 'error',
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (!foundTo) {
|
|
198
|
+
return {
|
|
199
|
+
error: {
|
|
200
|
+
file,
|
|
201
|
+
message: `End timestamp '${toTime}' not found in transcript`,
|
|
202
|
+
suggestion: 'Check that the timestamp exists as a line marker in the transcript',
|
|
203
|
+
severity: 'error',
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
from: fromSeconds,
|
|
209
|
+
to: toSeconds,
|
|
210
|
+
transcript: excerptLines.join('\n'),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=video.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/bundler/video.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// src/bundler/video.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { parseTimestamp, extractVideoExcerpt } from './video.js';
|
|
4
|
+
describe('parseTimestamp', () => {
|
|
5
|
+
it('converts MM:SS to seconds', () => {
|
|
6
|
+
expect(parseTimestamp('1:30')).toBe(90);
|
|
7
|
+
expect(parseTimestamp('5:45')).toBe(345);
|
|
8
|
+
expect(parseTimestamp('0:00')).toBe(0);
|
|
9
|
+
});
|
|
10
|
+
it('converts H:MM:SS to seconds', () => {
|
|
11
|
+
expect(parseTimestamp('1:30:00')).toBe(5400);
|
|
12
|
+
expect(parseTimestamp('2:15:30')).toBe(8130);
|
|
13
|
+
});
|
|
14
|
+
it('returns null for invalid format', () => {
|
|
15
|
+
expect(parseTimestamp('invalid')).toBeNull();
|
|
16
|
+
expect(parseTimestamp('abc:def')).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
describe('extractVideoExcerpt', () => {
|
|
20
|
+
it('extracts transcript between timestamps and returns seconds', () => {
|
|
21
|
+
const transcript = `0:00 - Welcome to this video.
|
|
22
|
+
0:30 - Today we'll discuss AI safety.
|
|
23
|
+
1:30 - The first key point is alignment.
|
|
24
|
+
2:00 - This means ensuring AI does what we want.
|
|
25
|
+
5:45 - Moving on to the next topic.
|
|
26
|
+
6:00 - Let's talk about interpretability.
|
|
27
|
+
`;
|
|
28
|
+
const result = extractVideoExcerpt(transcript, '1:30', // 90 seconds
|
|
29
|
+
'5:45', // 345 seconds
|
|
30
|
+
'video_transcripts/test.md');
|
|
31
|
+
expect(result.from).toBe(90); // Seconds as number
|
|
32
|
+
expect(result.to).toBe(345); // Seconds as number
|
|
33
|
+
expect(result.transcript).toContain('alignment');
|
|
34
|
+
expect(result.transcript).toContain('what we want');
|
|
35
|
+
expect(result.transcript).not.toContain('Welcome');
|
|
36
|
+
expect(result.transcript).not.toContain('interpretability');
|
|
37
|
+
});
|
|
38
|
+
it('returns error for invalid timestamp format', () => {
|
|
39
|
+
const transcript = '0:00 - Content here.';
|
|
40
|
+
const result = extractVideoExcerpt(transcript, 'invalid', '1:00', 'video_transcripts/test.md');
|
|
41
|
+
expect(result.error).toBeDefined();
|
|
42
|
+
expect(result.error?.message).toContain('timestamp');
|
|
43
|
+
expect(result.error?.suggestion).toContain('MM:SS');
|
|
44
|
+
});
|
|
45
|
+
it('returns error when timestamp not found in transcript', () => {
|
|
46
|
+
const transcript = '0:00 - Short video.\n0:30 - End.';
|
|
47
|
+
const result = extractVideoExcerpt(transcript, '5:00', '10:00', 'video_transcripts/test.md');
|
|
48
|
+
expect(result.error).toBeDefined();
|
|
49
|
+
expect(result.error?.message).toContain('not found');
|
|
50
|
+
});
|
|
51
|
+
it('returns error when from timestamp is after to timestamp', () => {
|
|
52
|
+
const transcript = `0:00 - Start.
|
|
53
|
+
3:00 - Middle.
|
|
54
|
+
5:00 - End.`;
|
|
55
|
+
const result = extractVideoExcerpt(transcript, '5:00', '3:00', 'video.md');
|
|
56
|
+
expect(result.error).toBeDefined();
|
|
57
|
+
expect(result.error?.message).toContain('after');
|
|
58
|
+
});
|
|
59
|
+
describe('from 0:00 (start of video)', () => {
|
|
60
|
+
it('treats 0:00 as start of video even when no 0:00 marker exists', () => {
|
|
61
|
+
// Transcript starts at 0:30, not 0:00
|
|
62
|
+
const transcript = `0:30 - First line of dialogue.
|
|
63
|
+
1:00 - Second line continues.
|
|
64
|
+
1:30 - Third line wraps up.
|
|
65
|
+
2:00 - End of section.`;
|
|
66
|
+
const result = extractVideoExcerpt(transcript, '0:00', '1:30', 'video.md');
|
|
67
|
+
// Should NOT error - 0:00 means "from the start"
|
|
68
|
+
expect(result.error).toBeUndefined();
|
|
69
|
+
expect(result.from).toBe(0);
|
|
70
|
+
expect(result.to).toBe(90);
|
|
71
|
+
// Should include lines from start up to (but not including) 1:30
|
|
72
|
+
expect(result.transcript).toContain('First line');
|
|
73
|
+
expect(result.transcript).toContain('Second line');
|
|
74
|
+
expect(result.transcript).not.toContain('Third line');
|
|
75
|
+
});
|
|
76
|
+
it('handles plain prose transcript with no timestamps at all', () => {
|
|
77
|
+
// This is common for transcripts that are just paragraphs of text
|
|
78
|
+
const transcript = `This is the first paragraph of the transcript. It contains some interesting content about AI safety.
|
|
79
|
+
|
|
80
|
+
This is the second paragraph. It continues discussing the topic.
|
|
81
|
+
|
|
82
|
+
This is the third paragraph. It concludes the main points.`;
|
|
83
|
+
const result = extractVideoExcerpt(transcript, '0:00', '5:00', 'video.md');
|
|
84
|
+
// Should NOT error - include entire content since no timestamps exist
|
|
85
|
+
expect(result.error).toBeUndefined();
|
|
86
|
+
expect(result.from).toBe(0);
|
|
87
|
+
expect(result.to).toBe(300);
|
|
88
|
+
// Should include entire transcript
|
|
89
|
+
expect(result.transcript).toContain('first paragraph');
|
|
90
|
+
expect(result.transcript).toContain('second paragraph');
|
|
91
|
+
expect(result.transcript).toContain('third paragraph');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=video.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video.test.js","sourceRoot":"","sources":["../../src/bundler/video.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { ProcessResult } from './index.js';
|
|
3
|
+
export interface CliOptions {
|
|
4
|
+
vaultPath: string | null;
|
|
5
|
+
outputPath: string | null;
|
|
6
|
+
includeWip: boolean;
|
|
7
|
+
stdin: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function parseArgs(argv: string[]): CliOptions;
|
|
10
|
+
export declare function run(options: CliOptions): Promise<ProcessResult>;
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// src/cli.ts
|
|
3
|
+
import { readVaultFiles } from './fs/read-vault.js';
|
|
4
|
+
import { processContent } from './index.js';
|
|
5
|
+
import { writeFile } from 'fs/promises';
|
|
6
|
+
export function parseArgs(argv) {
|
|
7
|
+
const args = argv.slice(2);
|
|
8
|
+
let vaultPath = null;
|
|
9
|
+
let outputPath = null;
|
|
10
|
+
let includeWip = false;
|
|
11
|
+
let stdin = false; // NEW
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
if (args[i] === '--output' || args[i] === '-o') {
|
|
14
|
+
outputPath = args[i + 1] || null;
|
|
15
|
+
i++; // skip next arg
|
|
16
|
+
}
|
|
17
|
+
else if (args[i] === '--include-wip') {
|
|
18
|
+
includeWip = true;
|
|
19
|
+
}
|
|
20
|
+
else if (args[i] === '--stdin') { // NEW
|
|
21
|
+
stdin = true;
|
|
22
|
+
}
|
|
23
|
+
else if (!args[i].startsWith('-')) {
|
|
24
|
+
vaultPath = args[i];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { vaultPath, outputPath, includeWip, stdin };
|
|
28
|
+
}
|
|
29
|
+
async function readStdin() {
|
|
30
|
+
const chunks = [];
|
|
31
|
+
for await (const chunk of process.stdin) {
|
|
32
|
+
chunks.push(chunk);
|
|
33
|
+
}
|
|
34
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
35
|
+
}
|
|
36
|
+
export async function run(options) {
|
|
37
|
+
let files;
|
|
38
|
+
if (options.stdin) {
|
|
39
|
+
// Read JSON from stdin: { "path": "content", ... }
|
|
40
|
+
const input = await readStdin();
|
|
41
|
+
const parsed = JSON.parse(input);
|
|
42
|
+
files = new Map(Object.entries(parsed));
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
if (!options.vaultPath) {
|
|
46
|
+
throw new Error('Vault path is required (or use --stdin)');
|
|
47
|
+
}
|
|
48
|
+
files = await readVaultFiles(options.vaultPath, { includeWip: options.includeWip });
|
|
49
|
+
}
|
|
50
|
+
return processContent(files);
|
|
51
|
+
}
|
|
52
|
+
async function main() {
|
|
53
|
+
const options = parseArgs(process.argv);
|
|
54
|
+
if (!options.vaultPath && !options.stdin) {
|
|
55
|
+
console.error('Usage: npx tsx src/cli.ts <vault-path> [--output <file>] [--include-wip]');
|
|
56
|
+
console.error(' npx tsx src/cli.ts --stdin [--output <file>]');
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const result = await run(options);
|
|
61
|
+
const json = JSON.stringify(result, null, 2);
|
|
62
|
+
if (options.outputPath) {
|
|
63
|
+
await writeFile(options.outputPath, json, 'utf-8');
|
|
64
|
+
console.error(`Written to ${options.outputPath}`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(json);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error('Error:', error instanceof Error ? error.message : error);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Run if executed directly (not imported as a module)
|
|
76
|
+
const isMainModule = process.argv[1]?.includes('cli.ts') || process.argv[1]?.includes('cli.js');
|
|
77
|
+
if (isMainModule) {
|
|
78
|
+
main();
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.test.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// src/cli.test.ts
|
|
2
|
+
import { describe, it, expect } from 'vitest';
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
|
+
import { readFile } from 'fs/promises';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
describe('CLI --stdin flag', () => {
|
|
7
|
+
it('produces same output as file-based input', async () => {
|
|
8
|
+
const fixturePath = join(__dirname, '../fixtures/valid/minimal-module/input');
|
|
9
|
+
const expectedPath = join(__dirname, '../fixtures/valid/minimal-module/expected.json');
|
|
10
|
+
// Read fixture files into a Map-like object
|
|
11
|
+
const { readVaultFiles } = await import('./fs/read-vault.js');
|
|
12
|
+
const files = await readVaultFiles(fixturePath);
|
|
13
|
+
const filesObject = {};
|
|
14
|
+
for (const [path, content] of files.entries()) {
|
|
15
|
+
filesObject[path] = content;
|
|
16
|
+
}
|
|
17
|
+
// Run CLI with --stdin
|
|
18
|
+
const result = await new Promise((resolve, reject) => {
|
|
19
|
+
const child = spawn('npx', ['tsx', 'src/cli.ts', '--stdin'], {
|
|
20
|
+
cwd: join(__dirname, '..'),
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
22
|
+
});
|
|
23
|
+
let stdout = '';
|
|
24
|
+
let stderr = '';
|
|
25
|
+
child.stdout.on('data', (data) => { stdout += data; });
|
|
26
|
+
child.stderr.on('data', (data) => { stderr += data; });
|
|
27
|
+
child.on('close', (code) => {
|
|
28
|
+
if (code !== 0) {
|
|
29
|
+
reject(new Error(`CLI exited with code ${code}: ${stderr}`));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
resolve(stdout);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
// Write JSON to stdin
|
|
36
|
+
child.stdin.write(JSON.stringify(filesObject));
|
|
37
|
+
child.stdin.end();
|
|
38
|
+
});
|
|
39
|
+
// Parse and compare (ignore whitespace differences)
|
|
40
|
+
const actual = JSON.parse(result);
|
|
41
|
+
const expected = JSON.parse(await readFile(expectedPath, 'utf-8'));
|
|
42
|
+
expect(actual).toEqual(expected);
|
|
43
|
+
}, 60000); // 60s timeout for subprocess (first run compiles)
|
|
44
|
+
});
|
|
45
|
+
//# sourceMappingURL=cli.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.js","sourceRoot":"","sources":["../src/cli.test.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,wCAAwC,CAAC,CAAC;QAC9E,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,gDAAgD,CAAC,CAAC;QAEvF,4CAA4C;QAC5C,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;QAChD,MAAM,WAAW,GAA2B,EAAE,CAAC;QAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;YAC9C,WAAW,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC;QAC9B,CAAC;QAED,uBAAuB;QACvB,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE;gBAC3D,GAAG,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC;gBAC1B,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;aAChC,CAAC,CAAC;YAEH,IAAI,MAAM,GAAG,EAAE,CAAC;YAChB,IAAI,MAAM,GAAG,EAAE,CAAC;YAEhB,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACvD,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,GAAG,MAAM,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAEvD,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACzB,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;oBACf,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC,CAAC;gBAC/D,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,MAAM,CAAC,CAAC;gBAClB,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,sBAAsB;YACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;YAC/C,KAAK,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;QAEH,oDAAoD;QACpD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;QAEnE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,kDAAkD;AAC/D,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FlattenedModule, ContentError } from '../index.js';
|
|
2
|
+
export interface FlattenModuleResult {
|
|
3
|
+
module: FlattenedModule | null;
|
|
4
|
+
errors: ContentError[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Flatten a module by resolving all references to Learning Outcomes, Lenses, and content.
|
|
8
|
+
*
|
|
9
|
+
* This function:
|
|
10
|
+
* 1. Parses the module file
|
|
11
|
+
* 2. For each Learning Outcome section, resolves the LO file
|
|
12
|
+
* 3. For each Lens in the LO, resolves the lens file
|
|
13
|
+
* 4. For each segment, extracts content from articles/videos as needed
|
|
14
|
+
*
|
|
15
|
+
* @param modulePath - Path to the module file within the files Map
|
|
16
|
+
* @param files - Map of all file paths to their content
|
|
17
|
+
* @param visitedPaths - Optional set of already-visited paths for cycle detection
|
|
18
|
+
* @returns Flattened module with resolved sections and segments, plus any errors
|
|
19
|
+
*/
|
|
20
|
+
export declare function flattenModule(modulePath: string, files: Map<string, string>, visitedPaths?: Set<string>): FlattenModuleResult;
|