multiclone 0.0.0-stage → 0.2.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 +78 -2
- package/agents/section-builder.md +37 -0
- package/commands/multiclone.md +15 -0
- package/dist/adapters/cli.js +129 -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 +340 -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 +121 -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 +61 -0
- package/dist/capture/collectors/index.js.map +1 -0
- package/dist/capture/collectors/interactions.js +207 -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 +91 -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 +34 -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 +58 -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 +40 -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 +139 -0
- package/dist/compare/session.js.map +1 -0
- package/dist/compare/shoot.js +62 -0
- package/dist/compare/shoot.js.map +1 -0
- package/dist/contracts/catalog.js +29 -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 +87 -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 +60 -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/tools/session.js +195 -0
- package/dist/contracts/tools/session.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 +269 -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 +69 -0
- package/dist/project/assets.js.map +1 -0
- package/dist/project/draft.js +860 -0
- package/dist/project/draft.js.map +1 -0
- package/dist/project/method.js +154 -0
- package/dist/project/method.js.map +1 -0
- package/dist/project/scaffold.js +142 -0
- package/dist/project/scaffold.js.map +1 -0
- package/dist/session/discover.js +53 -0
- package/dist/session/discover.js.map +1 -0
- package/dist/session/proc.js +113 -0
- package/dist/session/proc.js.map +1 -0
- package/dist/session/progress.js +83 -0
- package/dist/session/progress.js.map +1 -0
- package/dist/session/session.js +450 -0
- package/dist/session/session.js.map +1 -0
- package/dist/session/state.js +47 -0
- package/dist/session/state.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/install.sh +29 -0
- package/package.json +67 -4
- package/skills/multiclone/SKILL.md +123 -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 +15 -0
- package/templates/react-vite/src/app/Page.tsx +11 -0
- package/templates/react-vite/src/app/routes.ts +7 -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/draft.css +1 -0
- package/templates/react-vite/src/styles/fonts.css +1 -0
- package/templates/react-vite/src/styles/index.css +4 -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
|