@memberjunction/video-studio-render-worker 0.0.1-seed.1 → 6.2.0-edge.7

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 (59) hide show
  1. package/LICENSE +183 -0
  2. package/README.md +139 -4
  3. package/assets/fonts/Inter-OFL.txt +93 -0
  4. package/assets/fonts/Inter-Regular.ttf +0 -0
  5. package/assets/fonts/JetBrainsMono-OFL.txt +93 -0
  6. package/assets/fonts/JetBrainsMono-Regular.ttf +0 -0
  7. package/dist/backend.d.ts +201 -0
  8. package/dist/backend.d.ts.map +1 -0
  9. package/dist/backend.js +205 -0
  10. package/dist/backend.js.map +1 -0
  11. package/dist/bundle.d.ts +112 -0
  12. package/dist/bundle.d.ts.map +1 -0
  13. package/dist/bundle.js +433 -0
  14. package/dist/bundle.js.map +1 -0
  15. package/dist/checks/dom-rules.d.ts +57 -0
  16. package/dist/checks/dom-rules.d.ts.map +1 -0
  17. package/dist/checks/dom-rules.js +205 -0
  18. package/dist/checks/dom-rules.js.map +1 -0
  19. package/dist/checks/hyperframes-check.d.ts +37 -0
  20. package/dist/checks/hyperframes-check.d.ts.map +1 -0
  21. package/dist/checks/hyperframes-check.js +161 -0
  22. package/dist/checks/hyperframes-check.js.map +1 -0
  23. package/dist/checks/static-checks.d.ts +46 -0
  24. package/dist/checks/static-checks.d.ts.map +1 -0
  25. package/dist/checks/static-checks.js +681 -0
  26. package/dist/checks/static-checks.js.map +1 -0
  27. package/dist/hyperframes-backend.d.ts +53 -0
  28. package/dist/hyperframes-backend.d.ts.map +1 -0
  29. package/dist/hyperframes-backend.js +733 -0
  30. package/dist/hyperframes-backend.js.map +1 -0
  31. package/dist/index.d.ts +20 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +20 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/jobs.d.ts +88 -0
  36. package/dist/jobs.d.ts.map +1 -0
  37. package/dist/jobs.js +650 -0
  38. package/dist/jobs.js.map +1 -0
  39. package/dist/lint-gate.d.ts +21 -0
  40. package/dist/lint-gate.d.ts.map +1 -0
  41. package/dist/lint-gate.js +528 -0
  42. package/dist/lint-gate.js.map +1 -0
  43. package/dist/main.d.ts +47 -0
  44. package/dist/main.d.ts.map +1 -0
  45. package/dist/main.js +195 -0
  46. package/dist/main.js.map +1 -0
  47. package/dist/one-shot.d.ts +37 -0
  48. package/dist/one-shot.d.ts.map +1 -0
  49. package/dist/one-shot.js +228 -0
  50. package/dist/one-shot.js.map +1 -0
  51. package/dist/preamble.d.ts +69 -0
  52. package/dist/preamble.d.ts.map +1 -0
  53. package/dist/preamble.js +331 -0
  54. package/dist/preamble.js.map +1 -0
  55. package/dist/server.d.ts +75 -0
  56. package/dist/server.d.ts.map +1 -0
  57. package/dist/server.js +526 -0
  58. package/dist/server.js.map +1 -0
  59. package/package.json +43 -5
@@ -0,0 +1,681 @@
1
+ import * as path from 'node:path';
2
+ import { ExtractNumericLiteralTokens, } from '@memberjunction/video-studio-core';
3
+ /** Issue code emitted when a relative asset reference is missing from `Bundle.Files` and `Bundle.Assets`. */
4
+ export const MJ_MISSING_ASSET_CODE = 'MJ_MISSING_ASSET';
5
+ /** Issue code emitted when a composition script calls a non-deterministic time or timer API. */
6
+ export const MJ_NONDETERMINISTIC_API_CODE = 'MJ_NONDETERMINISTIC_API';
7
+ /** Issue code emitted when authored HTML contains an unbound numeric literal outside `data-mj-bound`. */
8
+ export const MJ_UNBOUND_LITERAL_SOURCE_CODE = 'MJ_UNBOUND_LITERAL_SOURCE';
9
+ /**
10
+ * `@hyperframes/lint` rule codes that already flag `Date.now(`, `new Date()`, `performance.now(`,
11
+ * and `requestAnimationFrame(` inside HTML inline `<script>` blocks (verified in Step 1c).
12
+ */
13
+ export const HYPERFRAMES_LINT_NONDETERMINISTIC_CODES = [
14
+ 'non_deterministic_code',
15
+ 'requestanimationframe_in_composition',
16
+ ];
17
+ const HTML_ASSET_ATTR_REGEX = /\b(src|href|poster|data|xlink:href|background)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>"']+))/gi;
18
+ const HTML_SRCSET_ATTR_REGEX = /\bsrcset\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>"']+))/gi;
19
+ const HTML_STYLE_ATTR_REGEX = /\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)')/gi;
20
+ const CSS_URL_REGEX = /\burl\(\s*(?:"([^"]*)"|'([^']*)'|([^)\s"']+))\s*\)/gi;
21
+ const CSS_IMPORT_REGEX = /@import\s+(?:url\(\s*)?(?:"([^"]*)"|'([^']*)'|([^)\s"';]+))/gi;
22
+ const JS_IMPORT_FROM_REGEX = /\b(?:import|export)\b[\s\S]*?\bfrom\s*(?:"([^"]*)"|'([^']*)')/g;
23
+ const JS_SIDE_EFFECT_IMPORT_REGEX = /\bimport\s*(?:"([^"]*)"|'([^']*)')/g;
24
+ const JS_DYNAMIC_IMPORT_REGEX = /\bimport\s*\(\s*(?:"([^"]*)"|'([^']*)'|`([^`$]*)`)\s*\)/g;
25
+ const NONDETERMINISTIC_API_RULES = [
26
+ { Regex: /\bDate\s*\.\s*now\s*\(/g, Label: 'Date.now()', CoveredByHyperFramesHtmlLint: true },
27
+ { Regex: /\bnew\s+Date\s*\(\s*\)/g, Label: 'new Date()', CoveredByHyperFramesHtmlLint: true },
28
+ { Regex: /\bperformance\s*\.\s*now\s*\(/g, Label: 'performance.now()', CoveredByHyperFramesHtmlLint: true },
29
+ {
30
+ Regex: /\brequestAnimationFrame\s*\(/g,
31
+ Label: 'requestAnimationFrame()',
32
+ CoveredByHyperFramesHtmlLint: true,
33
+ },
34
+ { Regex: /\bsetTimeout\s*\(/g, Label: 'setTimeout()', CoveredByHyperFramesHtmlLint: false },
35
+ { Regex: /\bsetInterval\s*\(/g, Label: 'setInterval()', CoveredByHyperFramesHtmlLint: false },
36
+ ];
37
+ const VOID_HTML_TAGS = new Set([
38
+ 'area',
39
+ 'base',
40
+ 'br',
41
+ 'col',
42
+ 'embed',
43
+ 'hr',
44
+ 'img',
45
+ 'input',
46
+ 'link',
47
+ 'meta',
48
+ 'param',
49
+ 'source',
50
+ 'track',
51
+ 'wbr',
52
+ ]);
53
+ /**
54
+ * Runs pure static checks over authored files in `bundle`:
55
+ * - `MJ_MISSING_ASSET` (`MissingAsset`, Error)
56
+ * - `MJ_NONDETERMINISTIC_API` (`Lint`, Error)
57
+ * - `MJ_UNBOUND_LITERAL_SOURCE` (`DataLiteral`, Error)
58
+ */
59
+ export function RunStaticChecks(bundle, options) {
60
+ const resolvedOptions = Array.isArray(options)
61
+ ? { AllowedDataLiterals: options }
62
+ : (options ?? {});
63
+ const knownPaths = buildKnownBundlePathSet(bundle);
64
+ const allowedLiterals = buildAllowedLiteralSet(resolvedOptions.AllowedDataLiterals);
65
+ const hfReportedLocations = buildHyperFramesReportedSet(resolvedOptions.ExistingLintIssues);
66
+ const skipHfHtml = resolvedOptions.SkipHyperFramesHtmlDuplicates ?? true;
67
+ const issues = [];
68
+ for (const file of bundle.Files) {
69
+ const lower = file.Path.toLowerCase();
70
+ if (lower.endsWith('.html') || lower.endsWith('.htm') || lower.endsWith('.svg')) {
71
+ issues.push(...scanHtmlStaticIssues(file, knownPaths, allowedLiterals, hfReportedLocations, skipHfHtml));
72
+ }
73
+ else if (lower.endsWith('.css')) {
74
+ issues.push(...scanCssMissingAssets(file.Content, file.Path, 1, knownPaths));
75
+ }
76
+ else if (lower.endsWith('.js') || lower.endsWith('.mjs')) {
77
+ issues.push(...scanJsMissingAssets(file.Content, file.Path, 1, knownPaths));
78
+ issues.push(...scanJsNondeterministicApis(file.Content, file.Path, 1, false, hfReportedLocations, false));
79
+ }
80
+ }
81
+ return issues;
82
+ }
83
+ function buildKnownBundlePathSet(bundle) {
84
+ const known = new Set();
85
+ for (const file of bundle.Files) {
86
+ known.add(normalizeBundlePath(file.Path));
87
+ }
88
+ for (const asset of bundle.Assets) {
89
+ known.add(normalizeBundlePath(asset.RelativePath));
90
+ }
91
+ return known;
92
+ }
93
+ function buildAllowedLiteralSet(allowed) {
94
+ const set = new Set();
95
+ if (!allowed) {
96
+ return set;
97
+ }
98
+ for (const item of allowed) {
99
+ if (typeof item === 'string' && item.trim().length > 0) {
100
+ set.add(item.trim());
101
+ }
102
+ }
103
+ return set;
104
+ }
105
+ function buildHyperFramesReportedSet(existing) {
106
+ const set = new Set();
107
+ if (!existing) {
108
+ return set;
109
+ }
110
+ const hfCodes = new Set(HYPERFRAMES_LINT_NONDETERMINISTIC_CODES);
111
+ for (const issue of existing) {
112
+ if (hfCodes.has(issue.Code)) {
113
+ set.add(`${issue.File ?? ''}:${issue.Line ?? 0}`);
114
+ }
115
+ }
116
+ return set;
117
+ }
118
+ function scanHtmlStaticIssues(file, knownPaths, allowedLiterals, hfReportedLocations, skipHfHtml) {
119
+ const issues = [];
120
+ const html = file.Content;
121
+ const htmlWithoutScriptsOrStyles = blankOutHtmlTagBlocks(blankOutHtmlTagBlocks(html, 'script'), 'style');
122
+ for (const match of htmlWithoutScriptsOrStyles.matchAll(HTML_ASSET_ATTR_REGEX)) {
123
+ const attrName = (match[1] ?? '').toLowerCase();
124
+ const rawUrl = (match[2] ?? match[3] ?? match[4] ?? '').trim();
125
+ if (attrName === 'href' && isAnchorTagAt(htmlWithoutScriptsOrStyles, match.index ?? 0)) {
126
+ continue;
127
+ }
128
+ const line = computeLineNumber(htmlWithoutScriptsOrStyles, match.index ?? 0);
129
+ const missing = checkRelativeBundleAsset(rawUrl, file.Path, line, knownPaths);
130
+ if (missing) {
131
+ issues.push(missing);
132
+ }
133
+ }
134
+ for (const match of htmlWithoutScriptsOrStyles.matchAll(HTML_SRCSET_ATTR_REGEX)) {
135
+ const rawSrcset = (match[1] ?? match[2] ?? match[3] ?? '').trim();
136
+ const line = computeLineNumber(htmlWithoutScriptsOrStyles, match.index ?? 0);
137
+ for (const candidate of rawSrcset.split(',')) {
138
+ const urlPart = candidate.trim().split(/\s+/)[0] ?? '';
139
+ const missing = checkRelativeBundleAsset(urlPart, file.Path, line, knownPaths);
140
+ if (missing) {
141
+ issues.push(missing);
142
+ }
143
+ }
144
+ }
145
+ for (const match of htmlWithoutScriptsOrStyles.matchAll(HTML_STYLE_ATTR_REGEX)) {
146
+ const styleContent = match[1] ?? match[2] ?? '';
147
+ const line = computeLineNumber(htmlWithoutScriptsOrStyles, match.index ?? 0);
148
+ issues.push(...scanCssMissingAssets(styleContent, file.Path, line, knownPaths));
149
+ }
150
+ for (const styleBlock of findHtmlTagBlocks(html, 'style')) {
151
+ const line = computeLineNumber(html, styleBlock.fullStart);
152
+ issues.push(...scanCssMissingAssets(styleBlock.body, file.Path, line, knownPaths));
153
+ }
154
+ const isHfScannedHtml = isHyperFramesLintedHtmlPath(file.Path);
155
+ for (const scriptBlock of findHtmlTagBlocks(html, 'script')) {
156
+ const baseLine = computeLineNumber(html, scriptBlock.bodyStart);
157
+ issues.push(...scanJsMissingAssets(scriptBlock.body, file.Path, baseLine, knownPaths));
158
+ issues.push(...scanJsNondeterministicApis(scriptBlock.body, file.Path, baseLine, isHfScannedHtml, hfReportedLocations, skipHfHtml));
159
+ }
160
+ issues.push(...scanHtmlUnboundLiterals(html, file.Path, allowedLiterals));
161
+ return issues;
162
+ }
163
+ function isAnchorTagAt(html, attrMatchIndex) {
164
+ const before = html.slice(0, attrMatchIndex);
165
+ const lastOpenBracket = before.lastIndexOf('<');
166
+ if (lastOpenBracket < 0) {
167
+ return false;
168
+ }
169
+ const tagSlice = before.slice(lastOpenBracket);
170
+ return /^<\s*a\b/i.test(tagSlice);
171
+ }
172
+ function scanCssMissingAssets(css, filePath, baseLine, knownPaths) {
173
+ const issues = [];
174
+ for (const match of css.matchAll(CSS_IMPORT_REGEX)) {
175
+ const rawUrl = (match[1] ?? match[2] ?? match[3] ?? '').trim();
176
+ const line = baseLine + computeLineNumber(css, match.index ?? 0) - 1;
177
+ const missing = checkRelativeBundleAsset(rawUrl, filePath, line, knownPaths);
178
+ if (missing) {
179
+ issues.push(missing);
180
+ }
181
+ }
182
+ for (const match of css.matchAll(CSS_URL_REGEX)) {
183
+ const rawUrl = (match[1] ?? match[2] ?? match[3] ?? '').trim();
184
+ const line = baseLine + computeLineNumber(css, match.index ?? 0) - 1;
185
+ const missing = checkRelativeBundleAsset(rawUrl, filePath, line, knownPaths);
186
+ if (missing) {
187
+ issues.push(missing);
188
+ }
189
+ }
190
+ return issues;
191
+ }
192
+ function scanJsMissingAssets(js, filePath, baseLine, knownPaths) {
193
+ const issues = [];
194
+ for (const regex of [JS_IMPORT_FROM_REGEX, JS_SIDE_EFFECT_IMPORT_REGEX, JS_DYNAMIC_IMPORT_REGEX]) {
195
+ for (const match of js.matchAll(regex)) {
196
+ const specifier = (match[1] ?? match[2] ?? match[3] ?? '').trim();
197
+ const line = baseLine + computeLineNumber(js, match.index ?? 0) - 1;
198
+ const missing = checkRelativeBundleAsset(specifier, filePath, line, knownPaths);
199
+ if (missing) {
200
+ issues.push(missing);
201
+ }
202
+ }
203
+ }
204
+ return issues;
205
+ }
206
+ function scanJsNondeterministicApis(js, filePath, baseLine, isHfScannedHtml, hfReportedLocations, skipHfHtml) {
207
+ const issues = [];
208
+ const strippedJs = stripJsCommentsPreserveNewlines(js);
209
+ for (const rule of NONDETERMINISTIC_API_RULES) {
210
+ if (isHfScannedHtml && skipHfHtml && rule.CoveredByHyperFramesHtmlLint) {
211
+ continue;
212
+ }
213
+ for (const match of strippedJs.matchAll(rule.Regex)) {
214
+ const line = baseLine + computeLineNumber(strippedJs, match.index ?? 0) - 1;
215
+ if (isHfScannedHtml &&
216
+ rule.CoveredByHyperFramesHtmlLint &&
217
+ hfReportedLocations.has(`${filePath}:${line}`)) {
218
+ continue;
219
+ }
220
+ issues.push({
221
+ Code: MJ_NONDETERMINISTIC_API_CODE,
222
+ Category: 'Lint',
223
+ Severity: 'Error',
224
+ Message: `Non-deterministic API "${rule.Label}" is forbidden in composition scripts; use MJVideo.OnFrame(t) and MJVideo.Random() / MJVideo.Noise().`,
225
+ File: filePath,
226
+ Line: line,
227
+ });
228
+ }
229
+ }
230
+ return issues;
231
+ }
232
+ function checkRelativeBundleAsset(rawUrl, fromFilePath, line, knownPaths) {
233
+ const trimmed = rawUrl.trim();
234
+ if (!trimmed || trimmed.startsWith('#')) {
235
+ return null;
236
+ }
237
+ if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(trimmed) || trimmed.startsWith('//')) {
238
+ return null;
239
+ }
240
+ const cleanRef = (trimmed.split(/[?#]/)[0] ?? '').replace(/\\/g, '/').trim();
241
+ if (!cleanRef) {
242
+ return null;
243
+ }
244
+ const resolved = resolveBundleRelativeReference(fromFilePath, cleanRef);
245
+ if (!resolved) {
246
+ return null;
247
+ }
248
+ if (!knownPaths.has(normalizeBundlePath(resolved))) {
249
+ return {
250
+ Code: MJ_MISSING_ASSET_CODE,
251
+ Category: 'MissingAsset',
252
+ Severity: 'Error',
253
+ Message: `Referenced bundle path "${resolved}" (from "${trimmed}") does not exist in Bundle.Files or Bundle.Assets.`,
254
+ File: fromFilePath,
255
+ Line: line,
256
+ };
257
+ }
258
+ return null;
259
+ }
260
+ function resolveBundleRelativeReference(fromFilePath, cleanRef) {
261
+ const dirNormalized = path.posix.dirname(fromFilePath.replace(/\\/g, '/'));
262
+ const stack = cleanRef.startsWith('/') || dirNormalized === '.' || dirNormalized === ''
263
+ ? []
264
+ : dirNormalized.split('/').filter((seg) => seg.length > 0 && seg !== '.');
265
+ const segments = (cleanRef.startsWith('/') ? cleanRef.slice(1) : cleanRef).split('/');
266
+ for (const seg of segments) {
267
+ if (seg === '' || seg === '.') {
268
+ continue;
269
+ }
270
+ if (seg === '..') {
271
+ if (stack.length === 0) {
272
+ return null;
273
+ }
274
+ stack.pop();
275
+ }
276
+ else {
277
+ stack.push(seg);
278
+ }
279
+ }
280
+ return stack.length > 0 ? stack.join('/') : null;
281
+ }
282
+ function isAsciiAlphaChar(ch) {
283
+ if (ch === undefined) {
284
+ return false;
285
+ }
286
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
287
+ }
288
+ function isTagNameContinuationChar(ch) {
289
+ if (ch === undefined) {
290
+ return false;
291
+ }
292
+ return (isAsciiAlphaChar(ch) ||
293
+ (ch >= '0' && ch <= '9') ||
294
+ ch === ':' ||
295
+ ch === '-');
296
+ }
297
+ function isWordBoundaryChar(ch) {
298
+ if (ch === undefined) {
299
+ return false;
300
+ }
301
+ return (isAsciiAlphaChar(ch) ||
302
+ (ch >= '0' && ch <= '9') ||
303
+ ch === '_');
304
+ }
305
+ function toAsciiLowerCase(s) {
306
+ return s.replace(/[A-Z]/g, (c) => c.toLowerCase());
307
+ }
308
+ function isHtmlCloseTagDelimiter(ch) {
309
+ return (ch === ' ' ||
310
+ ch === '\t' ||
311
+ ch === '\n' ||
312
+ ch === '\f' ||
313
+ ch === '\r' ||
314
+ ch === '/' ||
315
+ ch === '>');
316
+ }
317
+ function findClosingTagEnd(html, lowerHtml, tagLower, fromIndex) {
318
+ const needle = `</${tagLower}`;
319
+ let pos = fromIndex;
320
+ while (pos < html.length) {
321
+ const idx = lowerHtml.indexOf(needle, pos);
322
+ if (idx === -1) {
323
+ return -1;
324
+ }
325
+ const afterName = idx + needle.length;
326
+ if (afterName < html.length && isHtmlCloseTagDelimiter(html[afterName])) {
327
+ const gtIdx = html.indexOf('>', afterName);
328
+ if (gtIdx === -1) {
329
+ return -1;
330
+ }
331
+ return gtIdx + 1;
332
+ }
333
+ pos = afterName;
334
+ }
335
+ return -1;
336
+ }
337
+ function findHtmlTagBlocks(html, tagLower) {
338
+ const blocks = [];
339
+ const lowerHtml = toAsciiLowerCase(html);
340
+ const openNeedle = `<${tagLower}`;
341
+ const closeNeedle = `</${tagLower}`;
342
+ let pos = 0;
343
+ while (pos < html.length) {
344
+ const openIdx = lowerHtml.indexOf(openNeedle, pos);
345
+ if (openIdx === -1) {
346
+ break;
347
+ }
348
+ const afterOpenName = openIdx + openNeedle.length;
349
+ if (afterOpenName >= html.length || isWordBoundaryChar(html[afterOpenName])) {
350
+ pos = afterOpenName;
351
+ continue;
352
+ }
353
+ const openGt = html.indexOf('>', afterOpenName);
354
+ if (openGt === -1) {
355
+ break;
356
+ }
357
+ const bodyStart = openGt + 1;
358
+ let closeSearch = bodyStart;
359
+ let matchedClose = false;
360
+ while (closeSearch < html.length) {
361
+ const closeIdx = lowerHtml.indexOf(closeNeedle, closeSearch);
362
+ if (closeIdx === -1) {
363
+ return blocks;
364
+ }
365
+ const afterCloseName = closeIdx + closeNeedle.length;
366
+ if (afterCloseName >= html.length || !isHtmlCloseTagDelimiter(html[afterCloseName])) {
367
+ closeSearch = afterCloseName;
368
+ continue;
369
+ }
370
+ const closeGt = html.indexOf('>', afterCloseName);
371
+ if (closeGt === -1) {
372
+ return blocks;
373
+ }
374
+ blocks.push({
375
+ fullStart: openIdx,
376
+ fullEnd: closeGt + 1,
377
+ bodyStart,
378
+ body: html.slice(bodyStart, closeIdx),
379
+ });
380
+ pos = closeGt + 1;
381
+ matchedClose = true;
382
+ break;
383
+ }
384
+ if (!matchedClose) {
385
+ break;
386
+ }
387
+ }
388
+ return blocks;
389
+ }
390
+ function blankOutHtmlTagBlocks(html, tagLower) {
391
+ const blocks = findHtmlTagBlocks(html, tagLower);
392
+ if (blocks.length === 0) {
393
+ return html;
394
+ }
395
+ let out = '';
396
+ let cursor = 0;
397
+ for (const block of blocks) {
398
+ out += html.slice(cursor, block.fullStart);
399
+ for (let i = block.fullStart; i < block.fullEnd; i++) {
400
+ out += html.charCodeAt(i) === 10 ? '\n' : ' ';
401
+ }
402
+ cursor = block.fullEnd;
403
+ }
404
+ out += html.slice(cursor);
405
+ return out;
406
+ }
407
+ function scanHtmlTokens(html) {
408
+ const tokens = [];
409
+ const lowerHtml = toAsciiLowerCase(html);
410
+ let scanPos = 0;
411
+ let noMoreCommentClose = false;
412
+ let noMoreBangGt = false;
413
+ let noMoreScriptClose = false;
414
+ let noMoreStyleClose = false;
415
+ while (scanPos < html.length) {
416
+ const openIdx = html.indexOf('<', scanPos);
417
+ if (openIdx === -1) {
418
+ break;
419
+ }
420
+ if (html.startsWith('<!--', openIdx) && !noMoreCommentClose) {
421
+ const commentClose = html.indexOf('-->', openIdx + 4);
422
+ if (commentClose !== -1) {
423
+ const matchEnd = commentClose + 3;
424
+ tokens.push({
425
+ matchStart: openIdx,
426
+ matchEnd,
427
+ fullToken: html.slice(openIdx, matchEnd),
428
+ tagName: '',
429
+ attrText: '',
430
+ });
431
+ scanPos = matchEnd;
432
+ continue;
433
+ }
434
+ noMoreCommentClose = true;
435
+ }
436
+ if (lowerHtml.startsWith('<script', openIdx) && !isWordBoundaryChar(html[openIdx + 7])) {
437
+ const gtIdx = html.indexOf('>', openIdx + 7);
438
+ if (gtIdx !== -1 && !noMoreScriptClose) {
439
+ const closeEnd = findClosingTagEnd(html, lowerHtml, 'script', gtIdx + 1);
440
+ if (closeEnd !== -1) {
441
+ tokens.push({
442
+ matchStart: openIdx,
443
+ matchEnd: closeEnd,
444
+ fullToken: html.slice(openIdx, closeEnd),
445
+ tagName: '',
446
+ attrText: '',
447
+ });
448
+ scanPos = closeEnd;
449
+ continue;
450
+ }
451
+ noMoreScriptClose = true;
452
+ }
453
+ }
454
+ if (lowerHtml.startsWith('<style', openIdx) && !isWordBoundaryChar(html[openIdx + 6])) {
455
+ const gtIdx = html.indexOf('>', openIdx + 6);
456
+ if (gtIdx !== -1 && !noMoreStyleClose) {
457
+ const closeEnd = findClosingTagEnd(html, lowerHtml, 'style', gtIdx + 1);
458
+ if (closeEnd !== -1) {
459
+ tokens.push({
460
+ matchStart: openIdx,
461
+ matchEnd: closeEnd,
462
+ fullToken: html.slice(openIdx, closeEnd),
463
+ tagName: '',
464
+ attrText: '',
465
+ });
466
+ scanPos = closeEnd;
467
+ continue;
468
+ }
469
+ noMoreStyleClose = true;
470
+ }
471
+ }
472
+ if (html.startsWith('<!', openIdx)) {
473
+ if (!noMoreBangGt) {
474
+ const gtIdx = html.indexOf('>', openIdx + 2);
475
+ if (gtIdx !== -1) {
476
+ const matchEnd = gtIdx + 1;
477
+ tokens.push({
478
+ matchStart: openIdx,
479
+ matchEnd,
480
+ fullToken: html.slice(openIdx, matchEnd),
481
+ tagName: '',
482
+ attrText: '',
483
+ });
484
+ scanPos = matchEnd;
485
+ continue;
486
+ }
487
+ noMoreBangGt = true;
488
+ }
489
+ scanPos = openIdx + 1;
490
+ continue;
491
+ }
492
+ const isClosing = html[openIdx + 1] === '/';
493
+ const nameStart = isClosing ? openIdx + 2 : openIdx + 1;
494
+ if (!isAsciiAlphaChar(html[nameStart])) {
495
+ scanPos = openIdx + 1;
496
+ continue;
497
+ }
498
+ let maxNameEnd = nameStart + 1;
499
+ while (maxNameEnd < html.length && isTagNameContinuationChar(html[maxNameEnd])) {
500
+ maxNameEnd++;
501
+ }
502
+ let nameEnd = maxNameEnd;
503
+ while (nameEnd > nameStart &&
504
+ isWordBoundaryChar(html[nameEnd - 1]) === isWordBoundaryChar(html[nameEnd])) {
505
+ nameEnd--;
506
+ }
507
+ if (nameEnd === nameStart) {
508
+ scanPos = openIdx + 1;
509
+ continue;
510
+ }
511
+ const gtIdx = html.indexOf('>', nameEnd);
512
+ if (gtIdx === -1) {
513
+ break;
514
+ }
515
+ const rawTagName = lowerHtml.slice(nameStart, nameEnd);
516
+ const attrText = html.slice(nameEnd, gtIdx);
517
+ const openTagEnd = gtIdx + 1;
518
+ tokens.push({
519
+ matchStart: openIdx,
520
+ matchEnd: openTagEnd,
521
+ fullToken: html.slice(openIdx, openTagEnd),
522
+ tagName: rawTagName,
523
+ attrText,
524
+ });
525
+ scanPos = openTagEnd;
526
+ }
527
+ return tokens;
528
+ }
529
+ function scanHtmlUnboundLiterals(html, filePath, allowedLiterals) {
530
+ const issues = [];
531
+ const stack = [];
532
+ let cursor = 0;
533
+ for (const token of scanHtmlTokens(html)) {
534
+ const { matchStart, matchEnd, fullToken, tagName, attrText } = token;
535
+ if (matchStart > cursor) {
536
+ const textChunk = html.slice(cursor, matchStart);
537
+ const isCurrentlyBound = stack.length > 0 && stack[stack.length - 1].IsBound;
538
+ if (!isCurrentlyBound) {
539
+ collectTextLiteralIssues(textChunk, html, cursor, filePath, allowedLiterals, issues);
540
+ }
541
+ }
542
+ if (tagName) {
543
+ const isClosing = fullToken.startsWith('</');
544
+ const isSelfClosing = /\/>\s*$/.test(fullToken) || VOID_HTML_TAGS.has(tagName);
545
+ if (isClosing) {
546
+ for (let i = stack.length - 1; i >= 0; i--) {
547
+ if (stack[i].Tag === tagName) {
548
+ stack.length = i;
549
+ break;
550
+ }
551
+ }
552
+ }
553
+ else if (!isSelfClosing) {
554
+ const parentBound = stack.length > 0 && stack[stack.length - 1].IsBound;
555
+ const selfBound = hasDataMjBoundAttribute(attrText);
556
+ stack.push({ Tag: tagName, IsBound: parentBound || selfBound });
557
+ }
558
+ }
559
+ cursor = matchEnd;
560
+ }
561
+ if (cursor < html.length) {
562
+ const trailing = html.slice(cursor);
563
+ const isCurrentlyBound = stack.length > 0 && stack[stack.length - 1].IsBound;
564
+ if (!isCurrentlyBound) {
565
+ collectTextLiteralIssues(trailing, html, cursor, filePath, allowedLiterals, issues);
566
+ }
567
+ }
568
+ return issues;
569
+ }
570
+ function collectTextLiteralIssues(rawText, fullHtml, textStartOffset, filePath, allowedLiterals, outIssues) {
571
+ const decoded = decodeHtmlEntities(rawText);
572
+ if (!decoded.trim()) {
573
+ return;
574
+ }
575
+ const tokens = ExtractNumericLiteralTokens(decoded);
576
+ if (tokens.length === 0) {
577
+ return;
578
+ }
579
+ const line = computeLineNumber(fullHtml, textStartOffset);
580
+ for (const token of tokens) {
581
+ if (isLiteralAllowed(token, allowedLiterals)) {
582
+ continue;
583
+ }
584
+ outIssues.push({
585
+ Code: MJ_UNBOUND_LITERAL_SOURCE_CODE,
586
+ Category: 'DataLiteral',
587
+ Severity: 'Error',
588
+ Message: `Unbound numeric literal "${token}" in authored HTML text; bind via MJVideo.Data / data-mj-bound or add to AllowedDataLiterals.`,
589
+ File: filePath,
590
+ Line: line,
591
+ });
592
+ }
593
+ }
594
+ /**
595
+ * Checks whether an extracted numeric literal token matches `allowedLiterals`.
596
+ */
597
+ export function IsAllowedNumericLiteral(token, allowedLiterals) {
598
+ return isLiteralAllowed(token, allowedLiterals);
599
+ }
600
+ const ALLOWED_LITERAL_PUNCTUATION = new Set(Array.from('(),.;:!?'));
601
+ function stripAllowedLiteralPunctuation(value) {
602
+ let start = 0;
603
+ let end = value.length;
604
+ while (start < end && ALLOWED_LITERAL_PUNCTUATION.has(value[start])) {
605
+ start++;
606
+ }
607
+ while (end > start && ALLOWED_LITERAL_PUNCTUATION.has(value[end - 1])) {
608
+ end--;
609
+ }
610
+ return value.slice(start, end);
611
+ }
612
+ function isLiteralAllowed(token, allowedLiterals) {
613
+ const trimmed = token.trim();
614
+ if (allowedLiterals.has(trimmed)) {
615
+ return true;
616
+ }
617
+ const stripped = stripAllowedLiteralPunctuation(trimmed);
618
+ if (stripped.length > 0 && allowedLiterals.has(stripped)) {
619
+ return true;
620
+ }
621
+ const withoutLeadingPlus = stripped.replace(/^\+/, '');
622
+ return withoutLeadingPlus.length > 0 && allowedLiterals.has(withoutLeadingPlus);
623
+ }
624
+ function hasDataMjBoundAttribute(attrText) {
625
+ const match = /\bdata-mj-bound(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>"']+)))?/i.exec(attrText);
626
+ if (!match) {
627
+ return false;
628
+ }
629
+ const val = (match[1] ?? match[2] ?? match[3] ?? '').trim().toLowerCase();
630
+ return val !== 'false' && val !== '0';
631
+ }
632
+ function decodeHtmlEntities(text) {
633
+ return text
634
+ .replace(/&nbsp;/gi, ' ')
635
+ .replace(/&lt;/gi, '<')
636
+ .replace(/&gt;/gi, '>')
637
+ .replace(/&quot;/gi, '"')
638
+ .replace(/&#39;/gi, "'")
639
+ .replace(/&minus;|&#8722;|&#x2212;/gi, '−')
640
+ .replace(/&amp;/gi, '&');
641
+ }
642
+ function isHyperFramesLintedHtmlPath(filePath) {
643
+ const norm = normalizeBundlePath(filePath);
644
+ return norm === 'index.html' || /^compositions\/.+\.html$/.test(norm);
645
+ }
646
+ function normalizeBundlePath(relPath) {
647
+ return relPath.replace(/\\/g, '/').replace(/^\/+/, '').toLowerCase();
648
+ }
649
+ function stripJsCommentsPreserveNewlines(js) {
650
+ let withoutBlockComments = '';
651
+ let cursor = 0;
652
+ while (cursor < js.length) {
653
+ const startIdx = js.indexOf('/*', cursor);
654
+ if (startIdx === -1) {
655
+ withoutBlockComments += js.slice(cursor);
656
+ break;
657
+ }
658
+ const endIdx = js.indexOf('*/', startIdx + 2);
659
+ if (endIdx === -1) {
660
+ withoutBlockComments += js.slice(cursor);
661
+ break;
662
+ }
663
+ withoutBlockComments += js.slice(cursor, startIdx);
664
+ const commentEnd = endIdx + 2;
665
+ for (let i = startIdx; i < commentEnd; i++) {
666
+ withoutBlockComments += js.charCodeAt(i) === 10 ? '\n' : ' ';
667
+ }
668
+ cursor = commentEnd;
669
+ }
670
+ return withoutBlockComments.replace(/\/\/[^\n]*/g, '');
671
+ }
672
+ function computeLineNumber(content, charIndex) {
673
+ let line = 1;
674
+ for (let i = 0; i < charIndex && i < content.length; i++) {
675
+ if (content.charCodeAt(i) === 10) {
676
+ line++;
677
+ }
678
+ }
679
+ return line;
680
+ }
681
+ //# sourceMappingURL=static-checks.js.map