multiclone 0.0.0-stage → 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/README.md +89 -2
- package/agents/section-builder.md +37 -0
- package/commands/multiclone.md +20 -0
- package/dist/adapters/cli.js +120 -0
- package/dist/adapters/cli.js.map +1 -0
- package/dist/adapters/cliArgs.js +63 -0
- package/dist/adapters/cliArgs.js.map +1 -0
- package/dist/adapters/mcp.js +83 -0
- package/dist/adapters/mcp.js.map +1 -0
- package/dist/analyze/analyze.js +85 -0
- package/dist/analyze/analyze.js.map +1 -0
- package/dist/analyze/animations.js +340 -0
- package/dist/analyze/animations.js.map +1 -0
- package/dist/analyze/ast.js +182 -0
- package/dist/analyze/ast.js.map +1 -0
- package/dist/analyze/chunks.js +160 -0
- package/dist/analyze/chunks.js.map +1 -0
- package/dist/analyze/jobs/code.js +117 -0
- package/dist/analyze/jobs/code.js.map +1 -0
- package/dist/analyze/jobs/sections.js +356 -0
- package/dist/analyze/jobs/sections.js.map +1 -0
- package/dist/analyze/pretty.js +20 -0
- package/dist/analyze/pretty.js.map +1 -0
- package/dist/analyze/report.js +101 -0
- package/dist/analyze/report.js.map +1 -0
- package/dist/analyze/worker.js +71 -0
- package/dist/analyze/worker.js.map +1 -0
- package/dist/capture/capture.js +231 -0
- package/dist/capture/capture.js.map +1 -0
- package/dist/capture/collectors/a11y.js +92 -0
- package/dist/capture/collectors/a11y.js.map +1 -0
- package/dist/capture/collectors/animations.js +113 -0
- package/dist/capture/collectors/animations.js.map +1 -0
- package/dist/capture/collectors/breakpoints.js +31 -0
- package/dist/capture/collectors/breakpoints.js.map +1 -0
- package/dist/capture/collectors/consent.js +39 -0
- package/dist/capture/collectors/consent.js.map +1 -0
- package/dist/capture/collectors/coverage.js +119 -0
- package/dist/capture/collectors/coverage.js.map +1 -0
- package/dist/capture/collectors/dom.js +16 -0
- package/dist/capture/collectors/dom.js.map +1 -0
- package/dist/capture/collectors/fonts.js +154 -0
- package/dist/capture/collectors/fonts.js.map +1 -0
- package/dist/capture/collectors/frames.js +93 -0
- package/dist/capture/collectors/frames.js.map +1 -0
- package/dist/capture/collectors/index.js +50 -0
- package/dist/capture/collectors/index.js.map +1 -0
- package/dist/capture/collectors/interactions.js +208 -0
- package/dist/capture/collectors/interactions.js.map +1 -0
- package/dist/capture/collectors/intro.js +84 -0
- package/dist/capture/collectors/intro.js.map +1 -0
- package/dist/capture/collectors/listeners.js +84 -0
- package/dist/capture/collectors/listeners.js.map +1 -0
- package/dist/capture/collectors/motion.js +86 -0
- package/dist/capture/collectors/motion.js.map +1 -0
- package/dist/capture/collectors/network.js +288 -0
- package/dist/capture/collectors/network.js.map +1 -0
- package/dist/capture/collectors/probes.js +33 -0
- package/dist/capture/collectors/probes.js.map +1 -0
- package/dist/capture/collectors/runtime.js +38 -0
- package/dist/capture/collectors/runtime.js.map +1 -0
- package/dist/capture/collectors/scripts.js +98 -0
- package/dist/capture/collectors/scripts.js.map +1 -0
- package/dist/capture/collectors/sections.js +17 -0
- package/dist/capture/collectors/sections.js.map +1 -0
- package/dist/capture/collectors/shots.js +38 -0
- package/dist/capture/collectors/shots.js.map +1 -0
- package/dist/capture/collectors/snapshot.js +47 -0
- package/dist/capture/collectors/snapshot.js.map +1 -0
- package/dist/capture/collectors/sourcemaps.js +153 -0
- package/dist/capture/collectors/sourcemaps.js.map +1 -0
- package/dist/capture/collectors/storage.js +72 -0
- package/dist/capture/collectors/storage.js.map +1 -0
- package/dist/capture/context.js +35 -0
- package/dist/capture/context.js.map +1 -0
- package/dist/capture/hooks/candidates.js +104 -0
- package/dist/capture/hooks/candidates.js.map +1 -0
- package/dist/capture/hooks/consent.js +82 -0
- package/dist/capture/hooks/consent.js.map +1 -0
- package/dist/capture/hooks/recorders.js +507 -0
- package/dist/capture/hooks/recorders.js.map +1 -0
- package/dist/capture/hooks/sections.js +62 -0
- package/dist/capture/hooks/sections.js.map +1 -0
- package/dist/capture/hooks/styles.js +86 -0
- package/dist/capture/hooks/styles.js.map +1 -0
- package/dist/capture/probes.js +491 -0
- package/dist/capture/probes.js.map +1 -0
- package/dist/capture/report.js +115 -0
- package/dist/capture/report.js.map +1 -0
- package/dist/compare/compare.js +391 -0
- package/dist/compare/compare.js.map +1 -0
- package/dist/compare/image.js +105 -0
- package/dist/compare/image.js.map +1 -0
- package/dist/compare/probes.js +129 -0
- package/dist/compare/probes.js.map +1 -0
- package/dist/compare/report.js +99 -0
- package/dist/compare/report.js.map +1 -0
- package/dist/compare/session.js +133 -0
- package/dist/compare/session.js.map +1 -0
- package/dist/compare/shoot.js +44 -0
- package/dist/compare/shoot.js.map +1 -0
- package/dist/contracts/catalog.js +24 -0
- package/dist/contracts/catalog.js.map +1 -0
- package/dist/contracts/context.js +19 -0
- package/dist/contracts/context.js.map +1 -0
- package/dist/contracts/fields.js +16 -0
- package/dist/contracts/fields.js.map +1 -0
- package/dist/contracts/tools/analyze.js +38 -0
- package/dist/contracts/tools/analyze.js.map +1 -0
- package/dist/contracts/tools/capture.js +71 -0
- package/dist/contracts/tools/capture.js.map +1 -0
- package/dist/contracts/tools/compare.js +142 -0
- package/dist/contracts/tools/compare.js.map +1 -0
- package/dist/contracts/tools/doctor.js +23 -0
- package/dist/contracts/tools/doctor.js.map +1 -0
- package/dist/contracts/tools/inspect.js +41 -0
- package/dist/contracts/tools/inspect.js.map +1 -0
- package/dist/contracts/tools/method.js +56 -0
- package/dist/contracts/tools/method.js.map +1 -0
- package/dist/contracts/tools/mirror.js +53 -0
- package/dist/contracts/tools/mirror.js.map +1 -0
- package/dist/contracts/tools/project.js +61 -0
- package/dist/contracts/tools/project.js.map +1 -0
- package/dist/contracts/types.js +20 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/core/cdp.js +146 -0
- package/dist/core/cdp.js.map +1 -0
- package/dist/core/chrome.js +185 -0
- package/dist/core/chrome.js.map +1 -0
- package/dist/core/files.js +31 -0
- package/dist/core/files.js.map +1 -0
- package/dist/core/inpage.js +24 -0
- package/dist/core/inpage.js.map +1 -0
- package/dist/core/log.js +10 -0
- package/dist/core/log.js.map +1 -0
- package/dist/core/page.js +248 -0
- package/dist/core/page.js.map +1 -0
- package/dist/doctor.js +37 -0
- package/dist/doctor.js.map +1 -0
- package/dist/domain/breakpoints.js +47 -0
- package/dist/domain/breakpoints.js.map +1 -0
- package/dist/domain/budget.js +38 -0
- package/dist/domain/budget.js.map +1 -0
- package/dist/domain/compare.js +138 -0
- package/dist/domain/compare.js.map +1 -0
- package/dist/domain/coverage.js +96 -0
- package/dist/domain/coverage.js.map +1 -0
- package/dist/domain/motion.js +142 -0
- package/dist/domain/motion.js.map +1 -0
- package/dist/domain/paths.js +55 -0
- package/dist/domain/paths.js.map +1 -0
- package/dist/domain/result.js +38 -0
- package/dist/domain/result.js.map +1 -0
- package/dist/domain/routes.js +160 -0
- package/dist/domain/routes.js.map +1 -0
- package/dist/domain/sections.js +94 -0
- package/dist/domain/sections.js.map +1 -0
- package/dist/domain/snapshot.js +268 -0
- package/dist/domain/snapshot.js.map +1 -0
- package/dist/domain/sourcemap.js +95 -0
- package/dist/domain/sourcemap.js.map +1 -0
- package/dist/domain/stack.js +342 -0
- package/dist/domain/stack.js.map +1 -0
- package/dist/domain/tokens.js +183 -0
- package/dist/domain/tokens.js.map +1 -0
- package/dist/domain/types.js +2 -0
- package/dist/domain/types.js.map +1 -0
- package/dist/domain/viewports.js +45 -0
- package/dist/domain/viewports.js.map +1 -0
- package/dist/mirror/rewrite.js +56 -0
- package/dist/mirror/rewrite.js.map +1 -0
- package/dist/mirror/server.js +191 -0
- package/dist/mirror/server.js.map +1 -0
- package/dist/mirror/store.js +86 -0
- package/dist/mirror/store.js.map +1 -0
- package/dist/project/assets.js +48 -0
- package/dist/project/assets.js.map +1 -0
- package/dist/project/method.js +152 -0
- package/dist/project/method.js.map +1 -0
- package/dist/project/scaffold.js +140 -0
- package/dist/project/scaffold.js.map +1 -0
- package/dist/store/inspect.js +212 -0
- package/dist/store/inspect.js.map +1 -0
- package/dist/store/run.js +112 -0
- package/dist/store/run.js.map +1 -0
- package/dist/store/summary.js +69 -0
- package/dist/store/summary.js.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/package.json +66 -4
- package/skills/multiclone/SKILL.md +102 -0
- package/skills/multiclone/references/capture-format.md +73 -0
- package/skills/multiclone/references/fidelity.md +54 -0
- package/skills/multiclone/references/quality.md +47 -0
- package/skills/multiclone/references/stacks.md +156 -0
- package/templates/react-vite/.prettierignore +5 -0
- package/templates/react-vite/.prettierrc.json +6 -0
- package/templates/react-vite/eslint.config.js +38 -0
- package/templates/react-vite/gitignore +4 -0
- package/templates/react-vite/index.html +12 -0
- package/templates/react-vite/package.json +36 -0
- package/templates/react-vite/src/app/App.tsx +10 -0
- package/templates/react-vite/src/app/Page.tsx +4 -0
- package/templates/react-vite/src/components/README.md +7 -0
- package/templates/react-vite/src/content/README.md +2 -0
- package/templates/react-vite/src/hooks/useActiveSection.ts +37 -0
- package/templates/react-vite/src/hooks/useInView.ts +45 -0
- package/templates/react-vite/src/hooks/useMediaQuery.ts +15 -0
- package/templates/react-vite/src/lib/cn.ts +4 -0
- package/templates/react-vite/src/lib/math.ts +21 -0
- package/templates/react-vite/src/lib/mc.ts +13 -0
- package/templates/react-vite/src/lib/media.ts +4 -0
- package/templates/react-vite/src/main.tsx +13 -0
- package/templates/react-vite/src/providers/SmoothScroll.tsx +24 -0
- package/templates/react-vite/src/styles/base.css +42 -0
- package/templates/react-vite/src/styles/fonts.css +1 -0
- package/templates/react-vite/src/styles/index.css +3 -0
- package/templates/react-vite/src/styles/tokens.css +3 -0
- package/templates/react-vite/src/vite-env.d.ts +1 -0
- package/templates/react-vite/tsconfig.app.json +29 -0
- package/templates/react-vite/tsconfig.json +4 -0
- package/templates/react-vite/tsconfig.node.json +19 -0
- package/templates/react-vite/vite.config.ts +37 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { memberPath, parseCode, propertyName, textOf, walk } from './ast.js';
|
|
2
|
+
/** Known libraries: signature strings that survive minification. */
|
|
3
|
+
export const VENDOR_SIGNATURES = [
|
|
4
|
+
{ id: 'gsap', patterns: [/GreenSock|gsap\.config|_gsScope|"gsap"|\bGSCache\b/] },
|
|
5
|
+
{
|
|
6
|
+
id: 'gsap-scrolltrigger',
|
|
7
|
+
patterns: [/ScrollTrigger\.(refresh|create)|_scrollTriggerProxy|"ScrollTrigger"/],
|
|
8
|
+
},
|
|
9
|
+
{ id: 'lenis', patterns: [/lenis-smooth|lenis-stopped|"lenis"|class\s+Lenis\b|\blenis\.raf\b/i] },
|
|
10
|
+
{
|
|
11
|
+
id: 'locomotive-scroll',
|
|
12
|
+
patterns: [/data-scroll-container|locomotive-scroll|LocomotiveScroll/],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'three',
|
|
16
|
+
patterns: [
|
|
17
|
+
/THREE\.WebGLRenderer|WebGLRenderer\b.*precision|"three"|REVISION\s*=\s*"1\d\d"|__THREE__/,
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'react',
|
|
22
|
+
patterns: [
|
|
23
|
+
/react\.production\.min|Minified React error|__SECRET_INTERNALS_DO_NOT_USE|react-dom\.production/,
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'react-dom',
|
|
28
|
+
patterns: [/react-dom\.production|ReactDOMSharedInternals|unstable_batchedUpdates/],
|
|
29
|
+
},
|
|
30
|
+
{ id: 'next', patterns: [/__NEXT_DATA__|next\/dist|__next_f/] },
|
|
31
|
+
{ id: 'vue', patterns: [/__VUE__|Vue\.version|vue\.runtime|__vue_app__/] },
|
|
32
|
+
{ id: 'svelte', patterns: [/svelte\/internal|SvelteComponent/] },
|
|
33
|
+
{
|
|
34
|
+
id: 'motion',
|
|
35
|
+
patterns: [/framer-motion|motion\/react|MotionValue|useMotionValue|"motion\.dev"/],
|
|
36
|
+
},
|
|
37
|
+
{ id: 'lottie', patterns: [/lottie-web|bodymovin|LottiePlayer|dotlottie/i] },
|
|
38
|
+
{ id: 'rive', patterns: [/@rive-app|rive\.wasm|RiveCanvas/] },
|
|
39
|
+
{ id: 'swiper', patterns: [/swiper-wrapper|Swiper\.use|class\s+Swiper/] },
|
|
40
|
+
{ id: 'splide', patterns: [/splide__track|Splide\.defaults/] },
|
|
41
|
+
{ id: 'embla', patterns: [/embla-carousel|EmblaCarousel/] },
|
|
42
|
+
{ id: 'barba', patterns: [/barba\.init|@barba\/core/] },
|
|
43
|
+
{ id: 'swup', patterns: [/swup-progress|new Swup\b/] },
|
|
44
|
+
{ id: 'jquery', patterns: [/jquery:\s*"[\d.]+"|jQuery\.fn\.init|\$\.fn\.jquery/] },
|
|
45
|
+
{ id: 'lodash', patterns: [/lodash\.com|__lodash_hash_undefined__/] },
|
|
46
|
+
{ id: 'core-js', patterns: [/core-js\/modules|__core-js_shared__/] },
|
|
47
|
+
{ id: 'regenerator', patterns: [/regeneratorRuntime|Generator is already running/] },
|
|
48
|
+
{ id: 'anime', patterns: [/animejs|anime\.version/] },
|
|
49
|
+
{ id: 'pixi', patterns: [/PIXI\.|pixi\.js/] },
|
|
50
|
+
{ id: 'ogl', patterns: [/\bogl\b.*Renderer|class\s+Renderer\b.*gl\.getExtension/] },
|
|
51
|
+
{ id: 'spline', patterns: [/@splinetool|splinecode/] },
|
|
52
|
+
{ id: 'hls', patterns: [/hls\.js|Hls\.isSupported/] },
|
|
53
|
+
{
|
|
54
|
+
id: 'analytics',
|
|
55
|
+
patterns: [
|
|
56
|
+
/google-analytics|googletagmanager|gtag\(|fbq\(|_hsq|segment\.com\/analytics|posthog|hotjar|clarity\.ms|plausible|amplitude|mixpanel|datadog|sentry/i,
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
export function fingerprintVendor(code) {
|
|
61
|
+
const head = code.length > 400_000 ? `${code.slice(0, 200_000)}${code.slice(-200_000)}` : code;
|
|
62
|
+
for (const vendor of VENDOR_SIGNATURES) {
|
|
63
|
+
if (vendor.patterns.some((pattern) => pattern.test(head)))
|
|
64
|
+
return vendor.id;
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/** webpackChunk_*.push / (self.webpackChunk = …).push and turbopack-style registrations. */
|
|
69
|
+
function isChunkPush(node) {
|
|
70
|
+
if (node.callee.type !== 'MemberExpression' && node.callee.type !== 'OptionalMemberExpression')
|
|
71
|
+
return null;
|
|
72
|
+
const property = node.callee.property;
|
|
73
|
+
if (property.type === 'PrivateName' || propertyName(property, node.callee.computed) !== 'push')
|
|
74
|
+
return null;
|
|
75
|
+
let object = node.callee.object;
|
|
76
|
+
if (object.type === 'AssignmentExpression')
|
|
77
|
+
object = object.left;
|
|
78
|
+
if (object.type === 'LogicalExpression')
|
|
79
|
+
object = object.left;
|
|
80
|
+
const path = memberPath(object) ?? '';
|
|
81
|
+
if (!/webpackChunk|webpackJsonp|__LOADABLE_LOADED_CHUNKS__|TURBOPACK/.test(path))
|
|
82
|
+
return null;
|
|
83
|
+
const first = node.arguments[0];
|
|
84
|
+
if (first?.type !== 'ArrayExpression')
|
|
85
|
+
return null;
|
|
86
|
+
const table = first.elements[1];
|
|
87
|
+
if (table?.type === 'ObjectExpression' || table?.type === 'ArrayExpression')
|
|
88
|
+
return table;
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Splits a bundle into modules: webpack/rspack chunk tables, Vite chunks (one ES module per file,
|
|
93
|
+
* labelled), or the whole file. Each module gets a vendor label when a signature matches.
|
|
94
|
+
*/
|
|
95
|
+
export function splitBundle(code, file) {
|
|
96
|
+
const ast = parseCode(code, file);
|
|
97
|
+
if (!ast)
|
|
98
|
+
return [
|
|
99
|
+
{
|
|
100
|
+
key: file,
|
|
101
|
+
bundler: 'script',
|
|
102
|
+
start: 0,
|
|
103
|
+
end: code.length,
|
|
104
|
+
code,
|
|
105
|
+
vendor: fingerprintVendor(code),
|
|
106
|
+
},
|
|
107
|
+
];
|
|
108
|
+
const modules = [];
|
|
109
|
+
walk(ast, (node) => {
|
|
110
|
+
if (node.type !== 'CallExpression')
|
|
111
|
+
return true;
|
|
112
|
+
const table = isChunkPush(node);
|
|
113
|
+
if (!table)
|
|
114
|
+
return true;
|
|
115
|
+
const entries = table.type === 'ObjectExpression' ? table.properties : table.elements;
|
|
116
|
+
entries.forEach((entry, index) => {
|
|
117
|
+
if (!entry)
|
|
118
|
+
return;
|
|
119
|
+
let key = String(index);
|
|
120
|
+
let factory = entry;
|
|
121
|
+
if (entry.type === 'ObjectProperty') {
|
|
122
|
+
key = propertyName(entry.key, entry.computed) ?? textOf(code, entry.key);
|
|
123
|
+
factory = entry.value;
|
|
124
|
+
}
|
|
125
|
+
else if (entry.type === 'ObjectMethod') {
|
|
126
|
+
key = propertyName(entry.key, entry.computed) ?? textOf(code, entry.key);
|
|
127
|
+
}
|
|
128
|
+
else if (entry.type === 'SpreadElement')
|
|
129
|
+
return;
|
|
130
|
+
const body = textOf(code, factory);
|
|
131
|
+
modules.push({
|
|
132
|
+
key,
|
|
133
|
+
bundler: 'webpack',
|
|
134
|
+
start: factory.start ?? 0,
|
|
135
|
+
end: factory.end ?? 0,
|
|
136
|
+
code: body,
|
|
137
|
+
vendor: fingerprintVendor(body),
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
return false;
|
|
141
|
+
});
|
|
142
|
+
if (modules.length)
|
|
143
|
+
return modules;
|
|
144
|
+
const esm = ast.program.body.some((node) => node.type === 'ImportDeclaration' ||
|
|
145
|
+
node.type === 'ExportNamedDeclaration' ||
|
|
146
|
+
node.type === 'ExportDefaultDeclaration' ||
|
|
147
|
+
node.type === 'ExportAllDeclaration');
|
|
148
|
+
const vite = /__vite__mapDeps|import\.meta\.url|\/assets\/[\w-]+-[\w-]{8}\.js/.test(code);
|
|
149
|
+
return [
|
|
150
|
+
{
|
|
151
|
+
key: file,
|
|
152
|
+
bundler: esm || vite ? (vite ? 'vite' : 'esm') : 'script',
|
|
153
|
+
start: 0,
|
|
154
|
+
end: code.length,
|
|
155
|
+
code,
|
|
156
|
+
vendor: fingerprintVendor(code),
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks.js","sourceRoot":"","sources":["../../src/analyze/chunks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { writeBytes, writeJson } from '../../core/files.js';
|
|
3
|
+
import { extractAnimations } from '../animations.js';
|
|
4
|
+
import { splitBundle } from '../chunks.js';
|
|
5
|
+
import { prettify } from '../pretty.js';
|
|
6
|
+
const MAX_SCRIPT_BYTES = 12 * 1024 * 1024;
|
|
7
|
+
const PRETTY_BUDGET_MS = 90_000;
|
|
8
|
+
const PRETTY_MAX_BYTES = 1.5 * 1024 * 1024;
|
|
9
|
+
const safeName = (value) => value
|
|
10
|
+
.replace(/^https?:\/\//, '')
|
|
11
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '_')
|
|
12
|
+
.slice(-120) || 'module';
|
|
13
|
+
/**
|
|
14
|
+
* Animation extraction over original sources first, then app (non-vendor) modules of every
|
|
15
|
+
* captured script; app modules and stylesheets are written to code/app and prettified to
|
|
16
|
+
* code/pretty within a time budget. Writes animations-code.json and code/index.json.
|
|
17
|
+
*/
|
|
18
|
+
export async function runCodeJob(run, deadline) {
|
|
19
|
+
const findings = [];
|
|
20
|
+
const index = [];
|
|
21
|
+
const vendors = new Set();
|
|
22
|
+
let appModules = 0;
|
|
23
|
+
let vendorModules = 0;
|
|
24
|
+
const sources = await run.json('sources/index.json', { files: [] });
|
|
25
|
+
for (const source of sources.files) {
|
|
26
|
+
if (source.vendor || !/\.(m?[jt]sx?|vue|svelte|astro)$/.test(source.path))
|
|
27
|
+
continue;
|
|
28
|
+
const code = await run.text(source.path);
|
|
29
|
+
if (code === null)
|
|
30
|
+
continue;
|
|
31
|
+
findings.push(...extractAnimations(extractScript(code, source.path), source.path).findings);
|
|
32
|
+
}
|
|
33
|
+
const scripts = (await run.scripts()).filter((script) => script.file !== null && script.bytes <= MAX_SCRIPT_BYTES);
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
let prettyBudget = PRETTY_BUDGET_MS;
|
|
36
|
+
for (const script of scripts) {
|
|
37
|
+
if (script.file === null || seen.has(script.sha256))
|
|
38
|
+
continue;
|
|
39
|
+
seen.add(script.sha256);
|
|
40
|
+
const code = await run.text(script.file);
|
|
41
|
+
if (code === null)
|
|
42
|
+
continue;
|
|
43
|
+
for (const module of splitBundle(code, script.file)) {
|
|
44
|
+
if (module.vendor) {
|
|
45
|
+
vendors.add(module.vendor);
|
|
46
|
+
vendorModules++;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
appModules++;
|
|
50
|
+
const name = `${safeName(script.url || script.file)}${module.key === script.file ? '' : `.${safeName(module.key)}`}.js`;
|
|
51
|
+
const file = join('code', 'app', name);
|
|
52
|
+
await writeBytes(run.path(file), module.code);
|
|
53
|
+
findings.push(...extractAnimations(module.code, file).findings);
|
|
54
|
+
let pretty = null;
|
|
55
|
+
if (prettyBudget > 0 && Date.now() < deadline && module.code.length <= PRETTY_MAX_BYTES) {
|
|
56
|
+
const started = Date.now();
|
|
57
|
+
const formatted = await prettify(module.code, 'babel', Math.min(prettyBudget, 20_000));
|
|
58
|
+
prettyBudget -= Date.now() - started;
|
|
59
|
+
if (formatted !== null) {
|
|
60
|
+
pretty = join('code', 'pretty', name);
|
|
61
|
+
await writeBytes(run.path(pretty), formatted);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
index.push({
|
|
65
|
+
file,
|
|
66
|
+
from: script.file,
|
|
67
|
+
url: script.url,
|
|
68
|
+
module: module.key,
|
|
69
|
+
bundler: module.bundler,
|
|
70
|
+
vendor: null,
|
|
71
|
+
bytes: module.code.length,
|
|
72
|
+
pretty,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const entry of await run.network()) {
|
|
77
|
+
if (!entry.file || !entry.mimeType.includes('css') || entry.bytes > PRETTY_MAX_BYTES)
|
|
78
|
+
continue;
|
|
79
|
+
const css = await run.text(entry.file);
|
|
80
|
+
if (css === null)
|
|
81
|
+
continue;
|
|
82
|
+
const name = `${safeName(entry.url)}.css`;
|
|
83
|
+
let pretty = null;
|
|
84
|
+
if (prettyBudget > 0 && Date.now() < deadline) {
|
|
85
|
+
const started = Date.now();
|
|
86
|
+
const formatted = await prettify(css, 'css', Math.min(prettyBudget, 15_000));
|
|
87
|
+
prettyBudget -= Date.now() - started;
|
|
88
|
+
if (formatted !== null) {
|
|
89
|
+
pretty = join('code', 'pretty', name);
|
|
90
|
+
await writeBytes(run.path(pretty), formatted);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
index.push({
|
|
94
|
+
file: entry.file,
|
|
95
|
+
from: entry.file,
|
|
96
|
+
url: entry.url,
|
|
97
|
+
module: 'stylesheet',
|
|
98
|
+
bundler: 'css',
|
|
99
|
+
vendor: null,
|
|
100
|
+
bytes: entry.bytes,
|
|
101
|
+
pretty,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
findings.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column);
|
|
105
|
+
await writeJson(run.path('animations-code.json'), findings);
|
|
106
|
+
await writeJson(run.path('code/index.json'), { vendors: [...vendors].sort(), entries: index });
|
|
107
|
+
return { findings: findings.length, appModules, vendorModules, vendors: [...vendors].sort() };
|
|
108
|
+
}
|
|
109
|
+
/** Script part of single-file components; plain code passes through. */
|
|
110
|
+
function extractScript(code, path) {
|
|
111
|
+
if (!/\.(vue|svelte|astro)$/.test(path))
|
|
112
|
+
return code;
|
|
113
|
+
const blocks = [...code.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)].map((match) => match[1] ?? '');
|
|
114
|
+
const frontmatter = /^---\n([\s\S]*?)\n---/.exec(code)?.[1] ?? '';
|
|
115
|
+
return [frontmatter, ...blocks].join('\n');
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code.js","sourceRoot":"","sources":["../../../src/analyze/jobs/code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAyB,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAoBxC,MAAM,gBAAgB,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1C,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAChC,MAAM,gBAAgB,GAAG,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAU,EAAE,CACzC,KAAK;KACF,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;KAC3B,OAAO,CAAC,mBAAmB,EAAE,GAAG,CAAC;KACjC,KAAK,CAAC,CAAC,GAAG,CAAC,IAAI,QAAQ,CAAC;AAE7B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAc,EAAE,QAAgB;IAC/D,MAAM,QAAQ,GAAuB,EAAE,CAAC;IACxC,MAAM,KAAK,GAAqB,EAAE,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAC5B,oBAAoB,EACpB,EAAE,KAAK,EAAE,EAAE,EAAE,CACd,CAAC;IACF,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACnC,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,iCAAiC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YAAE,SAAS;QACpF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAC5B,QAAQ,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,MAAM,CAC1C,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,gBAAgB,CACrE,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,YAAY,GAAG,gBAAgB,CAAC;IACpC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;YAAE,SAAS;QAC9D,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAC5B,KAAK,MAAM,MAAM,IAAI,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpD,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC3B,aAAa,EAAE,CAAC;gBAChB,SAAS;YACX,CAAC;YACD,UAAU,EAAE,CAAC;YACb,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,GAAG,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,KAAK,CAAC;YACxH,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACvC,MAAM,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;YAC9C,QAAQ,CAAC,IAAI,CAAC,GAAG,iBAAiB,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;YAChE,IAAI,MAAM,GAAkB,IAAI,CAAC;YACjC,IAAI,YAAY,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,IAAI,gBAAgB,EAAE,CAAC;gBACxF,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;gBAC3B,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;gBACvF,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;gBACrC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;oBACvB,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;oBACtC,MAAM,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC;gBAChD,CAAC;YACH,CAAC;YACD,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI;gBACJ,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,GAAG,EAAE,MAAM,CAAC,GAAG;gBACf,MAAM,EAAE,MAAM,CAAC,GAAG;gBAClB,OAAO,EAAE,MAAM,CAAC,OAAO;gBACvB,MAAM,EAAE,IAAI;gBACZ,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM;gBACzB,MAAM;aACP,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,MAAM,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,gBAAgB;YAAE,SAAS;QAC/F,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACvC,IAAI,GAAG,KAAK,IAAI;YAAE,SAAS;QAC3B,MAAM,IAAI,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;QAC1C,IAAI,MAAM,GAAkB,IAAI,CAAC;QACjC,IAAI,YAAY,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;YAC7E,YAAY,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC;YACrC,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBACvB,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;gBACtC,MAAM,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC;YAChD,CAAC;QACH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,MAAM,EAAE,YAAY;YACpB,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,IAAI;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC;IAChG,MAAM,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,sBAAsB,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC5D,MAAM,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/F,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;AAChG,CAAC;AAED,wEAAwE;AACxE,SAAS,aAAa,CAAC,IAAY,EAAE,IAAY;IAC/C,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACrD,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,oCAAoC,CAAC,CAAC,CAAC,GAAG,CACzE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAC1B,CAAC;IACF,MAAM,WAAW,GAAG,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAClE,OAAO,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { decodePng, encodePng } from '../../compare/image.js';
|
|
4
|
+
import { writeBytes, writeJson } from '../../core/files.js';
|
|
5
|
+
import { pathWithin, matchSections, sectionFor, } from '../../domain/sections.js';
|
|
6
|
+
import { nodeStyles } from '../../domain/snapshot.js';
|
|
7
|
+
import { extractTokens } from '../../domain/tokens.js';
|
|
8
|
+
import { trackId } from '../../domain/motion.js';
|
|
9
|
+
import { PNG } from 'pngjs';
|
|
10
|
+
const MAX_TREE_NODES = 1500;
|
|
11
|
+
const MAX_CROP_HEIGHT = 4000;
|
|
12
|
+
/** Indented outline of a snapshot subtree with key styles — a compact DOM for the agent. */
|
|
13
|
+
function outline(snapshot, root, nodes) {
|
|
14
|
+
const children = new Map();
|
|
15
|
+
for (const node of nodes) {
|
|
16
|
+
const list = children.get(node.p) ?? [];
|
|
17
|
+
list.push(node);
|
|
18
|
+
children.set(node.p, list);
|
|
19
|
+
}
|
|
20
|
+
const lines = [];
|
|
21
|
+
const assets = new Set();
|
|
22
|
+
let count = 0;
|
|
23
|
+
const visit = (node, depth) => {
|
|
24
|
+
if (count >= MAX_TREE_NODES)
|
|
25
|
+
return;
|
|
26
|
+
if (node.t === 3) {
|
|
27
|
+
const text = node.text?.trim().replace(/\s+/g, ' ');
|
|
28
|
+
if (text) {
|
|
29
|
+
lines.push(`${' '.repeat(depth)}"${text.slice(0, 160)}"`);
|
|
30
|
+
count++;
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (node.t !== 1 && node.t !== 11) {
|
|
35
|
+
for (const child of children.get(node.i) ?? [])
|
|
36
|
+
visit(child, depth);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
count++;
|
|
40
|
+
const style = node.t === 1 ? nodeStyles(snapshot, node) : {};
|
|
41
|
+
if (style.display === 'none')
|
|
42
|
+
return;
|
|
43
|
+
const attrs = node.attrs ?? {};
|
|
44
|
+
for (const key of ['src', 'srcset', 'poster', 'href']) {
|
|
45
|
+
const value = attrs[key];
|
|
46
|
+
if (value && key !== 'href')
|
|
47
|
+
assets.add(value.split(/\s+/)[0] ?? value);
|
|
48
|
+
}
|
|
49
|
+
if (node.src)
|
|
50
|
+
assets.add(node.src);
|
|
51
|
+
if (style['background-image']?.includes('url(')) {
|
|
52
|
+
for (const match of style['background-image'].matchAll(/url\("?([^")]+)"?\)/g))
|
|
53
|
+
if (match[1])
|
|
54
|
+
assets.add(match[1]);
|
|
55
|
+
}
|
|
56
|
+
const name = node.pseudo
|
|
57
|
+
? `::${node.pseudo}`
|
|
58
|
+
: node.t === 11
|
|
59
|
+
? `#shadow-root(${node.shadow ?? 'open'})`
|
|
60
|
+
: node.name.toLowerCase();
|
|
61
|
+
const cls = attrs.class ? `.${attrs.class.trim().split(/\s+/).slice(0, 3).join('.')}` : '';
|
|
62
|
+
const id = attrs.id ? `#${attrs.id}` : '';
|
|
63
|
+
const box = node.box ? ` [${node.box.map((v) => Math.round(v)).join(',')}]` : '';
|
|
64
|
+
const key = [];
|
|
65
|
+
for (const prop of [
|
|
66
|
+
'display',
|
|
67
|
+
'position',
|
|
68
|
+
'font-size',
|
|
69
|
+
'font-weight',
|
|
70
|
+
'color',
|
|
71
|
+
'background-color',
|
|
72
|
+
'transform',
|
|
73
|
+
'opacity',
|
|
74
|
+
]) {
|
|
75
|
+
const value = style[prop];
|
|
76
|
+
if (!value || /^(static|block|inline|none|normal|1|rgba\(0, 0, 0, 0\))$/.test(value))
|
|
77
|
+
continue;
|
|
78
|
+
key.push(`${prop}:${value}`);
|
|
79
|
+
}
|
|
80
|
+
const extra = ['alt', 'role', 'aria-label', 'href']
|
|
81
|
+
.filter((k) => attrs[k])
|
|
82
|
+
.map((k) => `${k}="${(attrs[k] ?? '').slice(0, 80)}"`);
|
|
83
|
+
lines.push(`${' '.repeat(depth)}${name}${id}${cls}${box}${extra.length ? ` ${extra.join(' ')}` : ''}${key.length ? ` {${key.join('; ')}}` : ''}`);
|
|
84
|
+
for (const child of children.get(node.i) ?? [])
|
|
85
|
+
visit(child, depth + 1);
|
|
86
|
+
};
|
|
87
|
+
visit(root, 0);
|
|
88
|
+
if (count >= MAX_TREE_NODES)
|
|
89
|
+
lines.push(`… truncated at ${String(MAX_TREE_NODES)} nodes`);
|
|
90
|
+
return { text: lines.join('\n'), count, assets };
|
|
91
|
+
}
|
|
92
|
+
/** Nodes of the subtree rooted at the node with `path` (same document). */
|
|
93
|
+
function subtree(snapshot, path) {
|
|
94
|
+
const doc = snapshot.documents[0];
|
|
95
|
+
if (!doc)
|
|
96
|
+
return null;
|
|
97
|
+
const root = doc.nodes.find((node) => node.path === path);
|
|
98
|
+
if (!root)
|
|
99
|
+
return null;
|
|
100
|
+
const inside = new Set([root.i]);
|
|
101
|
+
const nodes = [root];
|
|
102
|
+
for (const node of doc.nodes) {
|
|
103
|
+
if (node.i <= root.i)
|
|
104
|
+
continue;
|
|
105
|
+
if (inside.has(node.p)) {
|
|
106
|
+
inside.add(node.i);
|
|
107
|
+
nodes.push(node);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return { root, nodes };
|
|
111
|
+
}
|
|
112
|
+
function crop(image, top, bottom) {
|
|
113
|
+
const y = Math.max(0, Math.min(image.height, Math.round(top)));
|
|
114
|
+
const height = Math.min(MAX_CROP_HEIGHT, Math.max(0, Math.min(image.height, Math.round(bottom)) - y));
|
|
115
|
+
if (height < 2)
|
|
116
|
+
return null;
|
|
117
|
+
const out = new PNG({ width: image.width, height });
|
|
118
|
+
PNG.bitblt(image, out, 0, y, image.width, height, 0, 0);
|
|
119
|
+
return encodePng(out);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Splits the page into sections matched across viewports and writes one self-contained bundle per
|
|
123
|
+
* section under sections/<nn>-<slug>/. Returns the summaries for the report and the store.
|
|
124
|
+
*/
|
|
125
|
+
export async function runSectionsJob(run, findings) {
|
|
126
|
+
const primary = run.viewports[0] ?? 'desktop';
|
|
127
|
+
const raw = new Map();
|
|
128
|
+
const heights = new Map();
|
|
129
|
+
for (const viewport of run.manifest.viewports) {
|
|
130
|
+
raw.set(viewport.viewport, await run.sections(viewport.viewport));
|
|
131
|
+
heights.set(viewport.viewport, viewport.pageHeight);
|
|
132
|
+
}
|
|
133
|
+
const matched = matchSections(raw, heights, primary);
|
|
134
|
+
const motion = await run.motion();
|
|
135
|
+
const animations = await run.animations();
|
|
136
|
+
const runtime = await run.runtime(primary);
|
|
137
|
+
const coverage = await run.json('motion-code/index.json', []);
|
|
138
|
+
const snapshots = new Map();
|
|
139
|
+
const images = new Map();
|
|
140
|
+
for (const viewport of run.viewports) {
|
|
141
|
+
const snapshot = await run.snapshot(viewport);
|
|
142
|
+
if (snapshot)
|
|
143
|
+
snapshots.set(viewport, snapshot);
|
|
144
|
+
try {
|
|
145
|
+
images.set(viewport, decodePng(await readFile(run.path(join('shots', `${viewport}-full.png`)))));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// No full-page shot.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const primarySnapshot = snapshots.get(primary);
|
|
152
|
+
const tokens = primarySnapshot ? extractTokens(primarySnapshot) : null;
|
|
153
|
+
const summaries = [];
|
|
154
|
+
const primarySections = matched
|
|
155
|
+
.map((section) => section.perViewport[primary])
|
|
156
|
+
.filter((s) => s !== undefined);
|
|
157
|
+
for (const section of matched) {
|
|
158
|
+
const dir = join('sections', section.id);
|
|
159
|
+
const counts = {
|
|
160
|
+
nodes: 0,
|
|
161
|
+
motion: 0,
|
|
162
|
+
animations: 0,
|
|
163
|
+
codeAnimations: 0,
|
|
164
|
+
interactions: 0,
|
|
165
|
+
assets: 0,
|
|
166
|
+
hotCode: 0,
|
|
167
|
+
};
|
|
168
|
+
const assets = new Set();
|
|
169
|
+
const viewports = {};
|
|
170
|
+
for (const [viewport, placement] of Object.entries(section.perViewport).sort(([a], [b]) => a.localeCompare(b))) {
|
|
171
|
+
viewports[viewport] = { top: placement.top, bottom: placement.bottom, path: placement.path };
|
|
172
|
+
const image = images.get(viewport);
|
|
173
|
+
const png = image ? crop(image, placement.top, placement.bottom) : null;
|
|
174
|
+
if (png)
|
|
175
|
+
await writeBytes(run.path(join(dir, `${viewport}.png`)), png);
|
|
176
|
+
const snapshot = snapshots.get(viewport);
|
|
177
|
+
const tree = snapshot ? subtree(snapshot, placement.path) : null;
|
|
178
|
+
if (snapshot && tree) {
|
|
179
|
+
const rendered = outline(snapshot, tree.root, tree.nodes);
|
|
180
|
+
for (const asset of rendered.assets)
|
|
181
|
+
assets.add(asset);
|
|
182
|
+
if (viewport === primary)
|
|
183
|
+
counts.nodes = rendered.count;
|
|
184
|
+
await writeBytes(run.path(join(dir, `dom-${viewport}.txt`)), rendered.text);
|
|
185
|
+
await writeJson(run.path(join(dir, `styles-${viewport}.json`)), sectionStyles(snapshot, tree.nodes));
|
|
186
|
+
const local = extractTokens({
|
|
187
|
+
...snapshot,
|
|
188
|
+
documents: [
|
|
189
|
+
{
|
|
190
|
+
...(snapshot.documents[0] ?? { url: '', frameId: '', contentHeight: 0 }),
|
|
191
|
+
nodes: tree.nodes,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
});
|
|
195
|
+
await writeJson(run.path(join(dir, `tokens-${viewport}.json`)), local);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const place = section.perViewport[primary];
|
|
199
|
+
const sectionMotion = place
|
|
200
|
+
? motion.filter((track) => pathWithin(track.path, place.path) ||
|
|
201
|
+
sectionFor(primarySections, track.top, track.top + track.height) === place)
|
|
202
|
+
: [];
|
|
203
|
+
counts.motion = sectionMotion.length;
|
|
204
|
+
if (sectionMotion.length)
|
|
205
|
+
await writeJson(run.path(join(dir, 'motion.json')), sectionMotion);
|
|
206
|
+
const sectionAnimations = place
|
|
207
|
+
? animations.filter((animation) => animation.viewport === primary &&
|
|
208
|
+
animation.target !== null &&
|
|
209
|
+
pathWithin(animation.target, place.path))
|
|
210
|
+
: [];
|
|
211
|
+
const runtimeHits = place ? runtimeForSection(runtime, place.path) : {};
|
|
212
|
+
counts.animations =
|
|
213
|
+
sectionAnimations.length +
|
|
214
|
+
Object.values(runtimeHits).reduce((sum, list) => sum + list.length, 0);
|
|
215
|
+
if (counts.animations)
|
|
216
|
+
await writeJson(run.path(join(dir, 'animations.json')), {
|
|
217
|
+
css: sectionAnimations,
|
|
218
|
+
runtime: runtimeHits,
|
|
219
|
+
});
|
|
220
|
+
const code = codeForSection(findings, section, place);
|
|
221
|
+
counts.codeAnimations = code.length;
|
|
222
|
+
if (code.length)
|
|
223
|
+
await writeJson(run.path(join(dir, 'animations-code.json')), code);
|
|
224
|
+
const interactions = place
|
|
225
|
+
? (await run.interactions(primary)).filter((item) => pathWithin(item.path, place.path))
|
|
226
|
+
: [];
|
|
227
|
+
counts.interactions = interactions.length;
|
|
228
|
+
if (interactions.length)
|
|
229
|
+
await writeJson(run.path(join(dir, 'interactions.json')), interactions.map((item) => ({ id: trackId(item.path), ...item })));
|
|
230
|
+
const hot = place
|
|
231
|
+
? coverage.filter((entry) => entry.target === place.path || pathWithin(entry.target, place.path))
|
|
232
|
+
: [];
|
|
233
|
+
counts.hotCode = hot.length;
|
|
234
|
+
if (hot.length)
|
|
235
|
+
await writeJson(run.path(join(dir, 'hot-code.json')), hot);
|
|
236
|
+
const assetList = await resolveAssets(run, [...assets]);
|
|
237
|
+
counts.assets = assetList.length;
|
|
238
|
+
if (assetList.length)
|
|
239
|
+
await writeJson(run.path(join(dir, 'assets.json')), assetList);
|
|
240
|
+
const summary = {
|
|
241
|
+
id: section.id,
|
|
242
|
+
label: section.label,
|
|
243
|
+
dir,
|
|
244
|
+
viewports,
|
|
245
|
+
counts,
|
|
246
|
+
};
|
|
247
|
+
await writeBytes(run.path(join(dir, 'section.md')), sectionBrief(summary, sectionMotion, sectionAnimations.length, code, interactions.length, hot, assetList));
|
|
248
|
+
summaries.push(summary);
|
|
249
|
+
}
|
|
250
|
+
await writeJson(run.path('sections/index.json'), summaries);
|
|
251
|
+
return { sections: summaries, tokens };
|
|
252
|
+
}
|
|
253
|
+
/** Distinct style declarations used in the section, with counts: the section's own CSS palette. */
|
|
254
|
+
function sectionStyles(snapshot, nodes) {
|
|
255
|
+
const out = {};
|
|
256
|
+
for (const node of nodes) {
|
|
257
|
+
if (node.t !== 1)
|
|
258
|
+
continue;
|
|
259
|
+
for (const [prop, value] of Object.entries(nodeStyles(snapshot, node))) {
|
|
260
|
+
if (!value || /^(none|normal|auto|0px|static|visible|rgba\(0, 0, 0, 0\))$/.test(value))
|
|
261
|
+
continue;
|
|
262
|
+
const bucket = (out[prop] ??= {});
|
|
263
|
+
bucket[value] = (bucket[value] ?? 0) + 1;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
function runtimeForSection(runtime, path) {
|
|
269
|
+
const out = {};
|
|
270
|
+
const mentions = (value) => JSON.stringify(value).includes(path);
|
|
271
|
+
for (const [bucket, value] of Object.entries(runtime)) {
|
|
272
|
+
if (!Array.isArray(value))
|
|
273
|
+
continue;
|
|
274
|
+
const hits = value.filter((entry) => mentions(entry));
|
|
275
|
+
if (hits.length)
|
|
276
|
+
out[bucket] = hits.slice(0, 100);
|
|
277
|
+
}
|
|
278
|
+
return out;
|
|
279
|
+
}
|
|
280
|
+
/** Code findings whose target selector matches something in the section (id/class/tag hints). */
|
|
281
|
+
function codeForSection(findings, section, place) {
|
|
282
|
+
if (!place)
|
|
283
|
+
return [];
|
|
284
|
+
const hints = new Set();
|
|
285
|
+
for (const part of place.path.split(/\s*>{1,3}\s*/)) {
|
|
286
|
+
const id = /^#([\w-]+)/.exec(part)?.[1];
|
|
287
|
+
if (id)
|
|
288
|
+
hints.add(id);
|
|
289
|
+
}
|
|
290
|
+
hints.add(section.label);
|
|
291
|
+
return findings.filter((finding) => {
|
|
292
|
+
const target = typeof finding.target === 'string' ? finding.target : '';
|
|
293
|
+
return [...hints].some((hint) => hint.length > 2 && target.includes(hint));
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
async function resolveAssets(run, urls) {
|
|
297
|
+
const network = await run.network();
|
|
298
|
+
const base = run.manifest.finalUrl;
|
|
299
|
+
const byUrl = new Map(network.map((entry) => [entry.url, entry]));
|
|
300
|
+
const out = [];
|
|
301
|
+
for (const raw of urls) {
|
|
302
|
+
let url;
|
|
303
|
+
try {
|
|
304
|
+
url = new URL(raw, base).href;
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (url.startsWith('data:'))
|
|
310
|
+
continue;
|
|
311
|
+
const entry = byUrl.get(url);
|
|
312
|
+
out.push({ url, file: entry?.file ?? null, mimeType: entry?.mimeType ?? '' });
|
|
313
|
+
}
|
|
314
|
+
return out.sort((a, b) => a.url.localeCompare(b.url));
|
|
315
|
+
}
|
|
316
|
+
function sectionBrief(summary, motion, cssAnimations, code, interactions, hot, assets) {
|
|
317
|
+
const lines = [`# Section ${summary.id}: ${summary.label}`, ''];
|
|
318
|
+
lines.push('Rebuild this section as one component. Wrap it in `<section data-mc-section="' +
|
|
319
|
+
summary.id +
|
|
320
|
+
'">` so compare can align it.', '');
|
|
321
|
+
lines.push('| Viewport | Top | Height | Root element |', '|---|---|---|---|');
|
|
322
|
+
for (const [viewport, place] of Object.entries(summary.viewports)) {
|
|
323
|
+
lines.push(`| ${viewport} | ${String(place.top)} | ${String(place.bottom - place.top)} | \`${place.path}\` |`);
|
|
324
|
+
}
|
|
325
|
+
lines.push('', '## Files', '');
|
|
326
|
+
for (const viewport of Object.keys(summary.viewports)) {
|
|
327
|
+
lines.push(`- \`${viewport}.png\`: how it looks; \`dom-${viewport}.txt\`: element outline with boxes and key styles; \`styles-${viewport}.json\` and \`tokens-${viewport}.json\`: its CSS values`);
|
|
328
|
+
}
|
|
329
|
+
if (motion.length) {
|
|
330
|
+
lines.push(`- \`motion.json\`: ${String(motion.length)} elements that change with scroll. Tag each rebuilt element with \`data-mc-track="<id>"\`.`);
|
|
331
|
+
}
|
|
332
|
+
if (cssAnimations)
|
|
333
|
+
lines.push(`- \`animations.json\`: CSS/WAAPI animations and runtime recordings for this section`);
|
|
334
|
+
if (code.length)
|
|
335
|
+
lines.push(`- \`animations-code.json\`: ${String(code.length)} animation calls from the site code that target this section`);
|
|
336
|
+
if (interactions)
|
|
337
|
+
lines.push(`- \`interactions.json\`: hover/focus/click states of ${String(interactions)} elements; tag each rebuilt element with \`data-mc-hover="<id>"\` so compare can check its hover`);
|
|
338
|
+
if (hot.length)
|
|
339
|
+
lines.push(`- \`hot-code.json\`: code that ran only while scrolling or hovering here (${hot.map((h) => `\`${h.file}\``).join(', ')})`);
|
|
340
|
+
if (assets.length)
|
|
341
|
+
lines.push(`- \`assets.json\`: ${String(assets.length)} images/videos used here`);
|
|
342
|
+
if (motion.length) {
|
|
343
|
+
lines.push('', '## Motion', '', '| Track | Element | Scroll range | Properties |', '|---|---|---|---|');
|
|
344
|
+
for (const track of motion.slice(0, 20)) {
|
|
345
|
+
lines.push(`| \`${track.id}\` | \`${track.path.slice(-60)}\` | ${String(track.range[0])}–${String(track.range[1])}${track.timeBased ? ' (loops)' : ''} | ${Object.keys(track.curves).join(', ')} |`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (code.length) {
|
|
349
|
+
lines.push('', '## Animation code', '');
|
|
350
|
+
for (const finding of code.slice(0, 15))
|
|
351
|
+
lines.push(`- \`${finding.call}\` at ${finding.file}:${String(finding.line)}: \`${finding.snippet.slice(0, 160)}\``);
|
|
352
|
+
}
|
|
353
|
+
lines.push('');
|
|
354
|
+
return lines.join('\n');
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=sections.js.map
|