lens-content-processor 0.34.0 → 0.35.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 (63) hide show
  1. package/dist/authoring-markup.d.ts +36 -0
  2. package/dist/authoring-markup.js +103 -0
  3. package/dist/authoring-markup.js.map +1 -1
  4. package/dist/bundler/article.d.ts +69 -9
  5. package/dist/bundler/article.js +159 -58
  6. package/dist/bundler/article.js.map +1 -1
  7. package/dist/cli.d.ts +1 -0
  8. package/dist/cli.js +6 -2
  9. package/dist/cli.js.map +1 -1
  10. package/dist/content-schema.js +5 -0
  11. package/dist/content-schema.js.map +1 -1
  12. package/dist/flattener/index.js +381 -252
  13. package/dist/flattener/index.js.map +1 -1
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +13 -1
  16. package/dist/index.js.map +1 -1
  17. package/dist/parser/article.js +42 -32
  18. package/dist/parser/article.js.map +1 -1
  19. package/dist/parser/course.d.ts +0 -10
  20. package/dist/parser/course.js +19 -4
  21. package/dist/parser/course.js.map +1 -1
  22. package/dist/parser/learning-outcome.js +20 -4
  23. package/dist/parser/learning-outcome.js.map +1 -1
  24. package/dist/parser/lens.d.ts +1 -6
  25. package/dist/parser/lens.js +64 -18
  26. package/dist/parser/lens.js.map +1 -1
  27. package/dist/parser/module.js +28 -17
  28. package/dist/parser/module.js.map +1 -1
  29. package/dist/parser/sections.d.ts +2 -1
  30. package/dist/parser/sections.js +5 -3
  31. package/dist/parser/sections.js.map +1 -1
  32. package/dist/parser/survey.js +19 -4
  33. package/dist/parser/survey.js.map +1 -1
  34. package/dist/source-location.d.ts +2 -0
  35. package/dist/source-location.js +12 -0
  36. package/dist/source-location.js.map +1 -0
  37. package/dist/validator/article-review-provenance.d.ts +5 -0
  38. package/dist/validator/article-review-provenance.js +57 -0
  39. package/dist/validator/article-review-provenance.js.map +1 -0
  40. package/dist/validator/article-structure.d.ts +8 -0
  41. package/dist/validator/article-structure.js +660 -0
  42. package/dist/validator/article-structure.js.map +1 -0
  43. package/dist/validator/directives.js +20 -0
  44. package/dist/validator/directives.js.map +1 -1
  45. package/dist/validator/emphasis.js +104 -1
  46. package/dist/validator/emphasis.js.map +1 -1
  47. package/dist/validator/markdown-parse.d.ts +3 -0
  48. package/dist/validator/markdown-parse.js +22 -0
  49. package/dist/validator/markdown-parse.js.map +1 -0
  50. package/dist/validator/math.js +66 -9
  51. package/dist/validator/math.js.map +1 -1
  52. package/dist/validator/output-integrity.js +16 -16
  53. package/dist/validator/output-integrity.js.map +1 -1
  54. package/dist/validator/same-lens-links.d.ts +4 -0
  55. package/dist/validator/same-lens-links.js +297 -0
  56. package/dist/validator/same-lens-links.js.map +1 -0
  57. package/dist/validator/suppressions.d.ts +42 -0
  58. package/dist/validator/suppressions.js +97 -0
  59. package/dist/validator/suppressions.js.map +1 -0
  60. package/dist/validator/video-imports.d.ts +35 -0
  61. package/dist/validator/video-imports.js +183 -0
  62. package/dist/validator/video-imports.js.map +1 -0
  63. package/package.json +9 -1
@@ -0,0 +1,297 @@
1
+ import remarkDirective from "remark-directive";
2
+ import remarkGfm from "remark-gfm";
3
+ import remarkMath from "remark-math";
4
+ import remarkParse from "remark-parse";
5
+ import { unified } from "unified";
6
+ import { visit } from "unist-util-visit";
7
+ import { stripAuthoringMarkupForValidation } from "../authoring-markup.js";
8
+ function authoredText(segment) {
9
+ const values = [];
10
+ if ("content" in segment && typeof segment.content === "string")
11
+ values.push(segment.content);
12
+ if (segment.type === "roleplay") {
13
+ values.push(segment.aiInstructions);
14
+ if (segment.openingMessage)
15
+ values.push(segment.openingMessage);
16
+ if (segment.assessmentInstructions)
17
+ values.push(segment.assessmentInstructions);
18
+ }
19
+ if (segment.type === "question" && segment.assessmentInstructions)
20
+ values.push(segment.assessmentInstructions);
21
+ return values;
22
+ }
23
+ function lineAt(source, offset) {
24
+ return source.slice(0, offset).split("\n").length;
25
+ }
26
+ function sourceLocator(source) {
27
+ const cursors = new Map();
28
+ return (needle) => {
29
+ const from = cursors.get(needle) ?? 0;
30
+ let offset = source.indexOf(needle, from);
31
+ if (offset < 0)
32
+ offset = source.indexOf(needle);
33
+ if (offset < 0)
34
+ return 1;
35
+ cursors.set(needle, offset + needle.length);
36
+ return lineAt(source, offset);
37
+ };
38
+ }
39
+ function headingAnchor(value) {
40
+ return value.trim().toLocaleLowerCase().replace(/\s+/g, " ");
41
+ }
42
+ function maskCode(value) {
43
+ const chars = value.split("");
44
+ let fence;
45
+ let offset = 0;
46
+ for (const line of value.split("\n")) {
47
+ const marker = line.match(/^ {0,3}(`{3,}|~{3,})/);
48
+ const wasInFence = fence !== undefined;
49
+ const closesFence = Boolean(marker &&
50
+ fence &&
51
+ marker[1][0] === fence.char &&
52
+ marker[1].length >= fence.length &&
53
+ line.slice(marker[1].length).trim() === "");
54
+ for (let index = 0; index < line.length; index += 1) {
55
+ if (wasInFence || marker)
56
+ chars[offset + index] = " ";
57
+ }
58
+ if (closesFence)
59
+ fence = undefined;
60
+ else if (marker && !wasInFence)
61
+ fence = { char: marker[1][0], length: marker[1].length };
62
+ offset += line.length + 1;
63
+ }
64
+ return chars
65
+ .join("")
66
+ .replace(/(`+)(.*?)\1/g, (match) => " ".repeat(match.length));
67
+ }
68
+ function markerSource(source) {
69
+ const validationSource = stripAuthoringMarkupForValidation(source);
70
+ const chars = validationSource.split("");
71
+ const tree = unified()
72
+ .use(remarkParse)
73
+ .use(remarkGfm)
74
+ .use(remarkDirective)
75
+ .use(remarkMath)
76
+ .parse(validationSource);
77
+ visit(tree, (node) => {
78
+ if (!["code", "inlineCode", "math", "inlineMath"].includes(node.type))
79
+ return;
80
+ const start = node.position?.start.offset;
81
+ const end = node.position?.end.offset;
82
+ if (start === undefined || end === undefined)
83
+ return;
84
+ for (let index = start; index < end; index += 1) {
85
+ if (chars[index] !== "\n")
86
+ chars[index] = " ";
87
+ }
88
+ });
89
+ const standaloneAttachment = new Map();
90
+ const walk = (parent) => {
91
+ for (let index = 0; index < parent.children.length; index += 1) {
92
+ const node = parent.children[index];
93
+ if (node.type === "paragraph" &&
94
+ node.children.length === 1 &&
95
+ node.children[0].type === "text" &&
96
+ /^\^[^\s]+\s*$/.test(node.children[0].value)) {
97
+ standaloneAttachment.set(node.position?.start.line ?? 1, index > 0);
98
+ }
99
+ if ("children" in node && Array.isArray(node.children)) {
100
+ walk(node);
101
+ }
102
+ }
103
+ };
104
+ walk(tree);
105
+ return { masked: chars.join(""), standaloneAttachment };
106
+ }
107
+ function blockMarkerErrors(file, source) {
108
+ const errors = [];
109
+ const blockIds = new Map();
110
+ const { masked, standaloneAttachment } = markerSource(source);
111
+ const lines = masked.split("\n");
112
+ for (let index = 0; index < lines.length; index += 1) {
113
+ const match = lines[index].match(/(?:^|[ \t])\^([^\s]+)\s*$/);
114
+ if (!match)
115
+ continue;
116
+ const id = match[1];
117
+ const line = index + 1;
118
+ if (!/^[A-Za-z0-9-]+$/.test(id)) {
119
+ errors.push({
120
+ file,
121
+ line,
122
+ code: "lens.block-id-invalid",
123
+ severity: "error",
124
+ message: `Invalid same-Lens block ID '^${id}'`,
125
+ suggestion: "Use only letters, numbers, and dashes.",
126
+ });
127
+ continue;
128
+ }
129
+ blockIds.set(id, [...(blockIds.get(id) ?? []), line]);
130
+ const standalone = lines[index].trim() === `^${id}`;
131
+ if (standalone) {
132
+ const hasPriorContent = lines
133
+ .slice(0, index)
134
+ .some((candidate) => candidate.trim() !== "");
135
+ const blankBefore = index > 0 && lines[index - 1].trim() === "";
136
+ const blankAfter = index === lines.length - 1 || lines[index + 1].trim() === "";
137
+ if (!hasPriorContent) {
138
+ errors.push({
139
+ file,
140
+ line,
141
+ code: "lens.block-id-orphan",
142
+ severity: "error",
143
+ message: `Block marker '^${id}' is not attached to a content block`,
144
+ suggestion: "Place it after the block it labels.",
145
+ });
146
+ }
147
+ else if (!blankBefore || !blankAfter) {
148
+ errors.push({
149
+ file,
150
+ line,
151
+ code: "lens.block-id-position",
152
+ severity: "error",
153
+ message: `Structured block marker '^${id}' must have a blank line before and after it`,
154
+ suggestion: `Use a blank line, ^${id}, then another blank line.`,
155
+ });
156
+ }
157
+ else if (!standaloneAttachment.get(line)) {
158
+ errors.push({
159
+ file,
160
+ line,
161
+ code: "lens.block-id-orphan",
162
+ severity: "error",
163
+ message: `Block marker '^${id}' is not attached to a content block`,
164
+ suggestion: "Place it after the block it labels.",
165
+ });
166
+ }
167
+ }
168
+ else if (/^:{3,}\s+\^/.test(lines[index].trim())) {
169
+ errors.push({
170
+ file,
171
+ line,
172
+ code: "lens.block-id-position",
173
+ severity: "error",
174
+ message: `Block marker '^${id}' cannot share a directive closing fence`,
175
+ suggestion: `Close with :::, then put ^${id} on its own line with blank lines around it.`,
176
+ });
177
+ }
178
+ }
179
+ for (const [id, markerLines] of blockIds) {
180
+ if (markerLines.length > 1) {
181
+ errors.push({
182
+ file,
183
+ line: markerLines[1],
184
+ code: "lens.block-id-duplicate",
185
+ severity: "error",
186
+ message: `Duplicate same-Lens block ID '^${id}'`,
187
+ });
188
+ }
189
+ }
190
+ return { errors, blockIds };
191
+ }
192
+ /** Validate canonical same-Lens links against every authored text segment. */
193
+ export function validateSameLensLinks(lens, file, source, lineOffset = 0) {
194
+ const markerResult = blockMarkerErrors(file, source);
195
+ const errors = [...markerResult.errors];
196
+ const texts = lens.segments.flatMap(authoredText).map(maskCode);
197
+ const headings = new Map();
198
+ const locate = sourceLocator(source);
199
+ for (const text of texts) {
200
+ for (const match of text.matchAll(/^#{1,6}\s+(.+?)\s*#*\s*$/gm)) {
201
+ const name = headingAnchor(match[1].replace(/\s+\^[A-Za-z0-9-]+\s*$/, ""));
202
+ headings.set(name, [...(headings.get(name) ?? []), locate(match[0])]);
203
+ }
204
+ }
205
+ for (const text of texts) {
206
+ const malformedOffsets = new Set();
207
+ for (const match of text.matchAll(/\[\[#[^\]\n]*(?:$|\](?!\]))/gm)) {
208
+ malformedOffsets.add(match.index ?? 0);
209
+ errors.push({
210
+ file,
211
+ line: locate(match[0]),
212
+ code: "lens.wikilink-malformed",
213
+ severity: "error",
214
+ message: `Malformed same-Lens wikilink '${match[0].trim()}'`,
215
+ suggestion: "Close the link with ]] and use at most one |label.",
216
+ });
217
+ }
218
+ for (const match of text.matchAll(/\[\[#([^\]\n]*)\]\]/g)) {
219
+ if (malformedOffsets.has(match.index ?? 0))
220
+ continue;
221
+ const raw = match[1];
222
+ const [target, label, ...extra] = raw.split("|");
223
+ const line = locate(match[0]);
224
+ if (!target.trim() || extra.length || (label !== undefined && !label.trim())) {
225
+ errors.push({
226
+ file,
227
+ line,
228
+ code: "lens.wikilink-malformed",
229
+ severity: "error",
230
+ message: `Malformed same-Lens wikilink '${match[0]}'`,
231
+ suggestion: "Use [[#Heading text|label]] or [[#^block-id|label]].",
232
+ });
233
+ continue;
234
+ }
235
+ if (target.startsWith("^")) {
236
+ const id = target.slice(1).trim();
237
+ if (!/^[A-Za-z0-9-]+$/.test(id)) {
238
+ errors.push({
239
+ file,
240
+ line,
241
+ code: "lens.wikilink-malformed",
242
+ severity: "error",
243
+ message: `Invalid same-Lens block link '#^${id}'`,
244
+ suggestion: "Use letters, numbers, and dashes in block IDs.",
245
+ });
246
+ }
247
+ else if (!markerResult.blockIds.has(id)) {
248
+ errors.push({
249
+ file,
250
+ line,
251
+ code: "lens.target-missing",
252
+ severity: "error",
253
+ message: `Same-Lens block target '^${id}' does not exist`,
254
+ });
255
+ }
256
+ }
257
+ else {
258
+ const count = headings.get(headingAnchor(target))?.length ?? 0;
259
+ if (count === 0) {
260
+ errors.push({
261
+ file,
262
+ line,
263
+ code: "lens.target-missing",
264
+ severity: "error",
265
+ message: `Same-Lens heading target '${target}' does not exist`,
266
+ });
267
+ }
268
+ else if (count > 1) {
269
+ errors.push({
270
+ file,
271
+ line,
272
+ code: "lens.heading-ambiguous",
273
+ severity: "error",
274
+ message: `Same-Lens heading target '${target}' is ambiguous because the heading is duplicated`,
275
+ });
276
+ }
277
+ }
278
+ }
279
+ for (const match of text.matchAll(/\]\(anchor:(?:block|heading):[^)]+\)/g)) {
280
+ errors.push({
281
+ file,
282
+ line: locate(match[0]),
283
+ code: "lens.anchor-private",
284
+ severity: "error",
285
+ message: "The renderer-private anchor: scheme cannot be authored directly",
286
+ suggestion: "Use [[#^block-id|label]] or [[#Heading text|label]].",
287
+ });
288
+ }
289
+ }
290
+ return lineOffset === 0
291
+ ? errors
292
+ : errors.map((error) => ({
293
+ ...error,
294
+ line: error.line === undefined ? undefined : error.line + lineOffset,
295
+ }));
296
+ }
297
+ //# sourceMappingURL=same-lens-links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"same-lens-links.js","sourceRoot":"","sources":["../../src/validator/same-lens-links.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { ContentError } from "../index.js";
2
+ export interface ValidatorSuppressionDefinition {
3
+ code: string;
4
+ reason: string;
5
+ explanation: string;
6
+ }
7
+ export declare const VALIDATOR_SUPPRESSIONS: {
8
+ readonly imageLowResolutionAiSearchExhausted: {
9
+ readonly code: "article.image-low-resolution";
10
+ readonly reason: "AI-couldn't-find-higher-resolution-image";
11
+ readonly explanation: "An AI search could not find a higher-resolution image.";
12
+ };
13
+ readonly imageLowResolutionAiAndHumanSearchExhausted: {
14
+ readonly code: "article.image-low-resolution";
15
+ readonly reason: "AI-and-human-couldn't-find-higher-resolution-image";
16
+ readonly explanation: "Both AI and human searches could not find a higher-resolution image.";
17
+ };
18
+ readonly blockRepeatedDistinctStructuredEntries: {
19
+ readonly code: "article.block-repeated-nearby";
20
+ readonly reason: "intentional-repeat-in-distinct-structured-entries";
21
+ readonly explanation: "The same text intentionally applies to separate structured entries.";
22
+ };
23
+ readonly blockRepeatedAlternativeCitationFormats: {
24
+ readonly code: "article.block-repeated-nearby";
25
+ readonly reason: "source-provides-alternative-citation-formats";
26
+ readonly explanation: "The source intentionally repeats citation text in alternative formats.";
27
+ };
28
+ readonly externalSelfFragmentTargetsSourceOnlyContent: {
29
+ readonly code: "article.external-self-fragment";
30
+ readonly reason: "intentionally-targets-source-only-content";
31
+ readonly explanation: "The link intentionally targets source-page content that was not imported.";
32
+ };
33
+ };
34
+ export declare function formatValidatorSuppression(definition: ValidatorSuppressionDefinition): string;
35
+ export declare function validatorSuppressionsForCode(code: string): readonly ValidatorSuppressionDefinition[];
36
+ /**
37
+ * Apply allow-listed, next-line suppressions found in Obsidian comments.
38
+ *
39
+ * `source` must preserve the article body's line positions. Callers may mask
40
+ * code/math first so documentation examples are not interpreted as directives.
41
+ */
42
+ export declare function applyNextLineValidatorSuppressions(source: string, bodyStartLine: number, errors: ContentError[], file: string): ContentError[];
@@ -0,0 +1,97 @@
1
+ export const VALIDATOR_SUPPRESSIONS = {
2
+ imageLowResolutionAiSearchExhausted: {
3
+ code: "article.image-low-resolution",
4
+ reason: "AI-couldn't-find-higher-resolution-image",
5
+ explanation: "An AI search could not find a higher-resolution image.",
6
+ },
7
+ imageLowResolutionAiAndHumanSearchExhausted: {
8
+ code: "article.image-low-resolution",
9
+ reason: "AI-and-human-couldn't-find-higher-resolution-image",
10
+ explanation: "Both AI and human searches could not find a higher-resolution image.",
11
+ },
12
+ blockRepeatedDistinctStructuredEntries: {
13
+ code: "article.block-repeated-nearby",
14
+ reason: "intentional-repeat-in-distinct-structured-entries",
15
+ explanation: "The same text intentionally applies to separate structured entries.",
16
+ },
17
+ blockRepeatedAlternativeCitationFormats: {
18
+ code: "article.block-repeated-nearby",
19
+ reason: "source-provides-alternative-citation-formats",
20
+ explanation: "The source intentionally repeats citation text in alternative formats.",
21
+ },
22
+ externalSelfFragmentTargetsSourceOnlyContent: {
23
+ code: "article.external-self-fragment",
24
+ reason: "intentionally-targets-source-only-content",
25
+ explanation: "The link intentionally targets source-page content that was not imported.",
26
+ },
27
+ };
28
+ const suppressionDefinitions = Object.values(VALIDATOR_SUPPRESSIONS);
29
+ const suppressionsByCode = new Map();
30
+ for (const definition of suppressionDefinitions) {
31
+ const reasons = suppressionsByCode.get(definition.code) ?? new Map();
32
+ if (reasons.has(definition.reason)) {
33
+ throw new Error(`Duplicate validator suppression: ${definition.code}/${definition.reason}`);
34
+ }
35
+ reasons.set(definition.reason, definition);
36
+ suppressionsByCode.set(definition.code, reasons);
37
+ }
38
+ export function formatValidatorSuppression(definition) {
39
+ return `%% validator-ignore-next-line --code ${definition.code} --reason ${definition.reason} %%`;
40
+ }
41
+ export function validatorSuppressionsForCode(code) {
42
+ return [...(suppressionsByCode.get(code)?.values() ?? [])];
43
+ }
44
+ function suppressionIssue(file, bodyStartLine, relativeLine, code, message, suggestion) {
45
+ return {
46
+ file,
47
+ line: bodyStartLine + relativeLine - 1,
48
+ code,
49
+ severity: "error",
50
+ message,
51
+ suggestion,
52
+ };
53
+ }
54
+ /**
55
+ * Apply allow-listed, next-line suppressions found in Obsidian comments.
56
+ *
57
+ * `source` must preserve the article body's line positions. Callers may mask
58
+ * code/math first so documentation examples are not interpreted as directives.
59
+ */
60
+ export function applyNextLineValidatorSuppressions(source, bodyStartLine, errors, file) {
61
+ const remaining = [...errors];
62
+ const suppressionErrors = [];
63
+ const example = formatValidatorSuppression(suppressionDefinitions[0]);
64
+ source.split("\n").forEach((line, index) => {
65
+ if (!/^\s*%%\s*validator-ignore-next-line\b/.test(line))
66
+ return;
67
+ const markerLine = index + 1;
68
+ const match = line.match(/^\s*%%\s*validator-ignore-next-line\s+--code\s+([a-z0-9.-]+)\s+--reason\s+([A-Za-z0-9'-]+)\s*%%\s*$/);
69
+ if (!match) {
70
+ suppressionErrors.push(suppressionIssue(file, bodyStartLine, markerLine, "article.suppression-invalid", "Validator suppression is malformed", `Use the exact CLI-style form, for example: ${example}`));
71
+ return;
72
+ }
73
+ const [, code, reason] = match;
74
+ const allowedReasons = suppressionsByCode.get(code);
75
+ if (!allowedReasons) {
76
+ suppressionErrors.push(suppressionIssue(file, bodyStartLine, markerLine, "article.suppression-invalid", `Validation rule '${code}' cannot be suppressed`, "Only explicitly registered validation codes can be suppressed."));
77
+ return;
78
+ }
79
+ const definition = allowedReasons.get(reason);
80
+ if (!definition) {
81
+ const choices = [...allowedReasons.values()]
82
+ .map((entry) => `'${entry.reason}' (${entry.explanation})`)
83
+ .join(", ");
84
+ suppressionErrors.push(suppressionIssue(file, bodyStartLine, markerLine, "article.suppression-invalid", `Reason '${reason}' is not allowed for '${code}'`, `Allowed reason${allowedReasons.size === 1 ? "" : "s"}: ${choices}`));
85
+ return;
86
+ }
87
+ const targetLine = bodyStartLine + markerLine;
88
+ const errorIndex = remaining.findIndex((entry) => entry.code === code && entry.line === targetLine);
89
+ if (errorIndex === -1) {
90
+ suppressionErrors.push(suppressionIssue(file, bodyStartLine, markerLine, "article.suppression-unused", `Suppression for '${code}' does not match an error on the next line`, "Remove the stale suppression, or place it immediately before the affected content."));
91
+ return;
92
+ }
93
+ remaining.splice(errorIndex, 1);
94
+ });
95
+ return [...remaining, ...suppressionErrors];
96
+ }
97
+ //# sourceMappingURL=suppressions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suppressions.js","sourceRoot":"","sources":["../../src/validator/suppressions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { ContentError } from "../index.js";
2
+ export interface VideoImportSyntax {
3
+ /** Wikilink target, e.g. "../video_transcripts/name" */
4
+ target: string;
5
+ from?: string;
6
+ to?: string;
7
+ }
8
+ export type VideoImportParse = {
9
+ kind: "import";
10
+ import: VideoImportSyntax;
11
+ } | {
12
+ kind: "invalid";
13
+ reason: string;
14
+ suggestion: string;
15
+ } | {
16
+ kind: "none";
17
+ };
18
+ /**
19
+ * Classify a single line of article body text.
20
+ * - "import": a valid own-line ::video import
21
+ * - "invalid": an attempted import with broken syntax
22
+ * - "none": not a video import at all
23
+ */
24
+ export declare function parseVideoImportLine(line: string): VideoImportParse;
25
+ /**
26
+ * Parse-time validation of ::video imports in an article body.
27
+ * Resolution errors (missing transcript file, bad range) are reported later
28
+ * by the flattener, which has the file map.
29
+ */
30
+ export declare function validateVideoImports(body: string, file: string, bodyStartLine: number): ContentError[];
31
+ /**
32
+ * Lens text segments do not resolve ::video imports — catch attempts there
33
+ * with a pointer to the supported mechanisms.
34
+ */
35
+ export declare function validateNoVideoImportsInLensText(content: string, file: string, line: number): ContentError[];