webview-preview 1.1.1 → 1.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/AGENTS.md +25 -1
- package/README.md +113 -4
- package/bin/webview-preview.mjs +20 -7
- package/package.json +1 -1
- package/src/core/annotate-report.mjs +66 -0
- package/src/core/annotate-settings.mjs +30 -0
- package/src/core/server.mjs +12 -2
- package/src/core/source-map.mjs +140 -0
- package/src/core/steps.mjs +16 -1
- package/src/engines/playwright.mjs +70 -3
- package/src/engines/webkitgtk.mjs +3 -1
- package/src/engines/webkitgtk.py +115 -16
- package/src/index.d.ts +50 -0
- package/src/index.mjs +48 -4
- package/src/page/annotate/dom.mjs +44 -0
- package/src/page/annotate/format.mjs +21 -0
- package/src/page/annotate/icons.mjs +12 -0
- package/src/page/annotate/index.mjs +50 -0
- package/src/page/annotate/keys.mjs +33 -0
- package/src/page/annotate/markup.mjs +37 -0
- package/src/page/annotate/panel.mjs +743 -0
- package/src/page/annotate/react.mjs +55 -0
- package/src/page/annotate/selector.mjs +130 -0
- package/src/page/annotate/styles.mjs +79 -0
- package/src/page/annotate/vue.mjs +53 -0
- package/src/snapshots/clean.mjs +2 -2
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function pageReact() {
|
|
2
|
+
const COMPONENT_TAGS = new Set([0, 1, 11, 14, 15]);
|
|
3
|
+
const LIBRARY_URL = /\/node_modules\/|\/@vite\/|\/@react-refresh|\/@id\//;
|
|
4
|
+
const FRAME = /^\s*(?:at\s+)?(?:(.*?)@|(.*?)\s+\()?((?:https?|file):\/\/.+?):(\d+):(\d+)\)?\s*$/;
|
|
5
|
+
|
|
6
|
+
const fiberOf = element => {
|
|
7
|
+
const key = Object.keys(element).find(name => name.startsWith("__reactFiber$"));
|
|
8
|
+
return key ? element[key] : null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const typeName = type => {
|
|
12
|
+
if (!type || typeof type === "string") return null;
|
|
13
|
+
return type.displayName || type.name || typeName(type.render) || typeName(type.type) || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const appFrame = fiber => {
|
|
17
|
+
if (fiber?._debugSource?.fileName) {
|
|
18
|
+
const { fileName, lineNumber, columnNumber } = fiber._debugSource;
|
|
19
|
+
return { file: fileName, line: lineNumber, column: columnNumber ?? 1 }
|
|
20
|
+
}
|
|
21
|
+
for (const line of String(fiber?._debugStack?.stack ?? "").split("\n")) {
|
|
22
|
+
const match = FRAME.exec(line);
|
|
23
|
+
if (!match) continue;
|
|
24
|
+
const url = match[3];
|
|
25
|
+
const isProject = url.startsWith("file:") || url.startsWith(location.origin);
|
|
26
|
+
if (!isProject || LIBRARY_URL.test(url)) continue;
|
|
27
|
+
return { url, line: Number(match[4]), column: Number(match[5]), owner: match[1] || match[2] || null }
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const componentOf = element => {
|
|
33
|
+
const fiber = fiberOf(element);
|
|
34
|
+
if (!fiber) return null;
|
|
35
|
+
const chain = [];
|
|
36
|
+
for (let node = fiber.return; node; node = node.return) {
|
|
37
|
+
if (!COMPONENT_TAGS.has(node.tag)) continue;
|
|
38
|
+
const name = typeName(node.type);
|
|
39
|
+
const usedAt = appFrame(node);
|
|
40
|
+
if (!name || !usedAt) continue;
|
|
41
|
+
if (chain[0]?.name === name) continue;
|
|
42
|
+
chain.unshift({ name, usedAt });
|
|
43
|
+
}
|
|
44
|
+
const own = appFrame(fiber);
|
|
45
|
+
const nearest = chain.at(-1);
|
|
46
|
+
return {
|
|
47
|
+
name: nearest?.name ?? null,
|
|
48
|
+
framework: "react",
|
|
49
|
+
chain: chain.slice(-8),
|
|
50
|
+
source: own ? { ...own, confidence: "exact" } : nearest ? { ...nearest.usedAt, confidence: "nearest" } : null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { componentOf }
|
|
55
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
export function pageSelectors(extraPrivate = []) {
|
|
2
|
+
const PRIVATE = [
|
|
3
|
+
"input[type=password]", "[autocomplete^=cc-]", "[autocomplete=one-time-code]", "[autocomplete=current-password]",
|
|
4
|
+
"[autocomplete=new-password]", "[data-private]", "[data-webview-preview-private]", ...extraPrivate
|
|
5
|
+
].join(", ");
|
|
6
|
+
const HIDDEN = "•••";
|
|
7
|
+
const normalize = value => (value ?? "").replace(/\s+/g, " ").trim();
|
|
8
|
+
const isPrivate = element => {
|
|
9
|
+
try {
|
|
10
|
+
return !!element.closest(PRIVATE);
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const privateElements = () => {
|
|
16
|
+
try {
|
|
17
|
+
return [...document.querySelectorAll(PRIVATE)];
|
|
18
|
+
} catch {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const textOf = element => {
|
|
23
|
+
if (isPrivate(element)) return HIDDEN;
|
|
24
|
+
let inside = [];
|
|
25
|
+
try {
|
|
26
|
+
inside = element.querySelectorAll(PRIVATE);
|
|
27
|
+
} catch {}
|
|
28
|
+
if (inside.length === 0) return normalize(element.innerText ?? element.textContent);
|
|
29
|
+
const copy = element.cloneNode(true);
|
|
30
|
+
for (const node of copy.querySelectorAll(PRIVATE)) node.replaceWith(` ${HIDDEN} `);
|
|
31
|
+
return normalize(copy.textContent);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const stableClass = className => {
|
|
35
|
+
const module = /^_?([a-zA-Z][\w-]*?)_[a-zA-Z0-9]{5,}(?:_\d+)?$/.exec(className);
|
|
36
|
+
if (module) return module[1];
|
|
37
|
+
return /[0-9a-f]{6,}/i.test(className) ? null : className;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const describe = element => {
|
|
41
|
+
const tag = element.tagName.toLowerCase();
|
|
42
|
+
const label = ((isPrivate(element) ? null : element.getAttribute("aria-label")) || textOf(element)).slice(0, 32);
|
|
43
|
+
const className = [...element.classList].map(stableClass).find(Boolean);
|
|
44
|
+
return `${tag}${className ? "." + className : ""}${label ? ` "${label}"` : ""}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const quote = value => `"${String(value).replace(/["\\]/g, "\\$&")}"`;
|
|
48
|
+
const isUnique = selector => {
|
|
49
|
+
try {
|
|
50
|
+
return document.querySelectorAll(selector).length === 1;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const ownSelector = element => {
|
|
57
|
+
const tag = element.tagName.toLowerCase();
|
|
58
|
+
for (const attribute of ["data-test", "data-testid"]) {
|
|
59
|
+
if (!element.hasAttribute(attribute)) continue;
|
|
60
|
+
const selector = `[${attribute}=${quote(element.getAttribute(attribute))}]`;
|
|
61
|
+
if (isUnique(selector)) return selector;
|
|
62
|
+
}
|
|
63
|
+
if (element.id && !/\d{3,}|^[:_-]|^r[0-9a-z]{1,4}$/i.test(element.id)) {
|
|
64
|
+
const selector = `#${CSS.escape(element.id)}`;
|
|
65
|
+
if (isUnique(selector)) return selector;
|
|
66
|
+
}
|
|
67
|
+
const label = isPrivate(element) ? null : element.getAttribute("aria-label");
|
|
68
|
+
if (label && label.length <= 60) {
|
|
69
|
+
const selector = `${tag}[aria-label=${quote(label)}]`;
|
|
70
|
+
if (isUnique(selector)) return selector;
|
|
71
|
+
}
|
|
72
|
+
for (const className of element.classList) {
|
|
73
|
+
const stable = stableClass(className);
|
|
74
|
+
if (!stable) continue;
|
|
75
|
+
const selector = stable === className ? `.${CSS.escape(className)}` : `[class*=${quote(stable)}]`;
|
|
76
|
+
if (isUnique(selector)) return selector;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const buildSelector = element => {
|
|
82
|
+
const own = ownSelector(element);
|
|
83
|
+
if (own) return { selector: own, selectorQuality: "stable" }
|
|
84
|
+
const parts = [];
|
|
85
|
+
for (let node = element; node && node.nodeType === 1; node = node.parentElement) {
|
|
86
|
+
const anchor = node === element ? null : ownSelector(node);
|
|
87
|
+
if (anchor) {
|
|
88
|
+
parts.unshift(anchor);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
if (node === document.body || node === document.documentElement) {
|
|
92
|
+
parts.unshift(node.tagName.toLowerCase());
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
if (!node.parentElement) {
|
|
96
|
+
parts.unshift(node.tagName.toLowerCase());
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
const index = [...node.parentElement.children].indexOf(node) + 1;
|
|
100
|
+
parts.unshift(`${node.tagName.toLowerCase()}:nth-child(${index})`);
|
|
101
|
+
}
|
|
102
|
+
return { selector: parts.join(" > "), selectorQuality: "positional" }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const shortName = element => {
|
|
106
|
+
const className = [...element.classList].map(stableClass).find(Boolean);
|
|
107
|
+
return `${element.tagName.toLowerCase()}${className ? "." + className : ""}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const lookOf = element => {
|
|
111
|
+
const style = getComputedStyle(element);
|
|
112
|
+
const sides = name => {
|
|
113
|
+
const values = ["top", "right", "bottom", "left"].map(side => style.getPropertyValue(`${name}-${side}${name === "border" ? "-width" : ""}`));
|
|
114
|
+
return values.every(value => value === values[0]) ? values[0] : values.join(" ");
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
box: { margin: sides("margin"), padding: sides("padding"), border: sides("border"), borderRadius: style.borderRadius },
|
|
118
|
+
style: {
|
|
119
|
+
display: style.display,
|
|
120
|
+
font: `${style.fontSize}/${style.lineHeight} ${style.fontWeight} ${style.fontFamily.split(",")[0].replace(/["']/g, "").trim()}`,
|
|
121
|
+
color: style.color,
|
|
122
|
+
background: style.backgroundColor,
|
|
123
|
+
opacity: style.opacity,
|
|
124
|
+
gap: style.gap
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { normalize, textOf, describe, shortName, buildSelector, lookOf, isPrivate, privateElements }
|
|
130
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export const STYLES = `
|
|
2
|
+
:host { all: initial }
|
|
3
|
+
* { box-sizing: border-box }
|
|
4
|
+
.layer, .dock, .tip { font: 13px/1.4 system-ui, -apple-system, "Segoe UI", sans-serif; color: #ffffff; user-select: none; -webkit-user-select: none }
|
|
5
|
+
.layer { position: fixed; inset: 0; pointer-events: none }
|
|
6
|
+
.outline { position: fixed; border: 2px solid #0a77ff; border-radius: 4px; background: rgba(10, 119, 255, 0.08); display: none }
|
|
7
|
+
.outline-label { position: absolute; left: -2px; bottom: calc(100% + 4px); white-space: nowrap; padding: 2px 8px; border-radius: 6px; background: #0a77ff; color: #fff; font-size: 12px; font-weight: 600 }
|
|
8
|
+
.outline-label.below { bottom: auto; top: calc(100% + 4px) }
|
|
9
|
+
.mask { position: fixed; background: repeating-linear-gradient(45deg, #1b1b1b 0 6px, #262626 6px 12px); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 4px }
|
|
10
|
+
.privacy { margin: 0; color: #939393; font-size: 12px }
|
|
11
|
+
.picked { position: fixed; border: 1px dashed rgba(10, 119, 255, 0.9); border-radius: 4px }
|
|
12
|
+
.marker { position: fixed; min-width: 22px; height: 22px; padding: 0 6px; border-radius: 11px; background: #0a77ff; color: #fff; font-size: 12px; font-weight: 700; display: flex; align-items: center; justify-content: center; pointer-events: auto; cursor: pointer; box-shadow: 0 0 0 2px #0a0a0a, 0 6px 16px rgba(0, 0, 0, 0.4) }
|
|
13
|
+
.marker.active { background: #e0a43a; color: #0a0a0a }
|
|
14
|
+
.card { background: #111111; border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 12px; box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4) }
|
|
15
|
+
.dock { position: fixed; left: 12px; top: 12px; pointer-events: none }
|
|
16
|
+
.dock > .card { pointer-events: auto }
|
|
17
|
+
.dock .sheet { position: absolute; left: calc(100% + 6px); top: 0 }
|
|
18
|
+
.dock.flip .sheet { left: auto; right: calc(100% + 6px) }
|
|
19
|
+
.dock.raise .sheet { top: auto; bottom: 0 }
|
|
20
|
+
.toolbar { display: flex; flex-direction: column; padding: 4px }
|
|
21
|
+
.grip { height: 14px; display: flex; align-items: center; justify-content: center; color: #4a4a4a; cursor: grab; touch-action: none }
|
|
22
|
+
.grip svg { width: 16px; height: 16px }
|
|
23
|
+
.grip:active { cursor: grabbing }
|
|
24
|
+
.collapse { display: grid; grid-template-rows: 1fr; opacity: 1; transition: grid-template-rows 0.2s ease-out, opacity 0.2s ease-out }
|
|
25
|
+
.collapse > * { overflow: hidden; min-height: 0 }
|
|
26
|
+
.collapsed .collapse { grid-template-rows: 0fr; opacity: 0 }
|
|
27
|
+
.tools { display: flex; flex-direction: column }
|
|
28
|
+
.divider { height: 1px; margin: 4px 6px; background: rgba(255, 255, 255, 0.08) }
|
|
29
|
+
button { font: inherit; color: inherit }
|
|
30
|
+
.tool { position: relative; width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; color: #939393; background: none; border: none; border-radius: 8px; cursor: pointer; transition: color 0.2s ease-out, background-color 0.2s ease-out }
|
|
31
|
+
.tool:hover { color: #ffffff }
|
|
32
|
+
.tool[aria-pressed="true"] { color: #ffffff; background: rgba(255, 255, 255, 0.08) }
|
|
33
|
+
.tool.accent { color: #0a77ff }
|
|
34
|
+
.tool.accent:hover { color: #3d95ff }
|
|
35
|
+
.tool[aria-disabled="true"] { opacity: 0.4; cursor: not-allowed }
|
|
36
|
+
.tool[aria-disabled="true"]:hover { color: #939393 }
|
|
37
|
+
.tool:focus-visible, .button:focus-visible, .kbd:focus-visible, textarea:focus-visible { outline: none; box-shadow: 0 0 0 2px #0a0a0a, 0 0 0 4px #0a77ff }
|
|
38
|
+
.tip { position: fixed; z-index: 2; padding: 4px 8px; border-radius: 8px; background: #111111; border: 1px solid rgba(255, 255, 255, 0.05); box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4); white-space: nowrap; font-size: 12px; pointer-events: none; display: none }
|
|
39
|
+
.tip kbd { margin-left: 8px; color: #939393; font: 11px ui-monospace, monospace }
|
|
40
|
+
.sheet { width: 300px; max-width: calc(100vw - 72px); max-height: calc(100vh - 16px); overflow: auto; padding: 12px; display: flex; flex-direction: column; gap: 10px; transition: opacity 0.2s ease-out, transform 0.2s ease-out }
|
|
41
|
+
.collapsed .sheet { opacity: 0; transform: translateX(-6px); pointer-events: none }
|
|
42
|
+
.flip.collapsed .sheet { transform: translateX(6px) }
|
|
43
|
+
.flip .toggle svg { transform: scaleX(-1) }
|
|
44
|
+
.sheet[hidden] { display: none }
|
|
45
|
+
.sheet-head { display: flex; align-items: baseline; gap: 6px }
|
|
46
|
+
.sheet-head b { font-size: 14px }
|
|
47
|
+
.sheet-head span { color: #939393; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
48
|
+
.badge { position: absolute; top: 3px; right: 3px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: #0a77ff; color: #fff; font-size: 10px; font-weight: 700; line-height: 16px; text-align: center }
|
|
49
|
+
.badge:empty { display: none }
|
|
50
|
+
.notes { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; max-height: 320px; overflow: auto }
|
|
51
|
+
.note-body { display: flex; flex-direction: column; gap: 8px; padding: 4px 4px 8px 38px }
|
|
52
|
+
.notes:empty::before { content: "No notes yet — click an element on the page."; color: #939393 }
|
|
53
|
+
.note { display: flex; gap: 8px; align-items: center; padding: 6px 8px; border-radius: 8px; cursor: pointer; transition: background-color 0.2s ease-out }
|
|
54
|
+
.note:hover, .note.active { background: rgba(255, 255, 255, 0.06) }
|
|
55
|
+
.note b { min-width: 22px; height: 22px; border-radius: 11px; background: #0a77ff; color: #fff; font-size: 12px; display: flex; align-items: center; justify-content: center }
|
|
56
|
+
.note.active b { background: #e0a43a; color: #0a0a0a }
|
|
57
|
+
.note span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
58
|
+
.note span.empty { color: #939393 }
|
|
59
|
+
label { display: flex; flex-direction: column; gap: 6px; color: #939393 }
|
|
60
|
+
textarea { font: inherit; color: #ffffff; background: #0a0a0a; border: 1px solid rgba(255, 255, 255, 0.08); border-radius: 8px; padding: 8px 10px; resize: vertical; min-height: 44px; user-select: text; -webkit-user-select: text; transition: border-color 0.2s ease-out }
|
|
61
|
+
textarea:hover { border-color: rgba(255, 255, 255, 0.16) }
|
|
62
|
+
.hint { margin: 0; color: #939393; font-size: 12px }
|
|
63
|
+
.hint kbd, .rows kbd { font: 11px ui-monospace, monospace; color: #ffffff }
|
|
64
|
+
.rows { display: flex; flex-direction: column; gap: 2px }
|
|
65
|
+
.row { display: flex; align-items: center; gap: 8px; padding: 4px 0 }
|
|
66
|
+
.row span { flex: 1 }
|
|
67
|
+
.kbd { min-width: 64px; padding: 4px 8px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.08); background: #0a0a0a; color: #ffffff; font: 12px ui-monospace, monospace; cursor: pointer }
|
|
68
|
+
.kbd:hover { border-color: rgba(255, 255, 255, 0.16) }
|
|
69
|
+
.kbd.listening { border-color: #0a77ff; color: #0a77ff }
|
|
70
|
+
.actions { display: flex; gap: 6px; justify-content: flex-end }
|
|
71
|
+
.button { padding: 6px 12px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.04); cursor: pointer; transition: border-color 0.2s ease-out, color 0.2s ease-out }
|
|
72
|
+
.button:hover { border-color: rgba(255, 255, 255, 0.16) }
|
|
73
|
+
.button.danger:hover { border-color: #d9534f; color: #ff8a86 }
|
|
74
|
+
.elements { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; color: #939393 }
|
|
75
|
+
.elements li { display: flex; gap: 6px; align-items: center }
|
|
76
|
+
.elements li span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
|
|
77
|
+
.elements .tool { width: 24px; height: 24px }
|
|
78
|
+
@media (prefers-reduced-motion: reduce) { .collapse, .sheet, .tool, .note, textarea, .button { transition: none } }
|
|
79
|
+
`;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function pageVue() {
|
|
2
|
+
const fileOf = type => (type && typeof type === "object" ? type.__file ?? null : null);
|
|
3
|
+
const nameOf = type => {
|
|
4
|
+
if (!type || typeof type !== "object") return null;
|
|
5
|
+
const file = fileOf(type);
|
|
6
|
+
return type.name || type.__name || (file ? file.split(/[\\/]/).pop().replace(/\.vue$/, "") : null);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const vue3 = element => {
|
|
10
|
+
for (let node = element; node; node = node.parentElement) {
|
|
11
|
+
if (node.__vueParentComponent) return node.__vueParentComponent;
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const vue2 = element => {
|
|
17
|
+
for (let node = element; node; node = node.parentElement) {
|
|
18
|
+
if (node.__vue__) return node.__vue__;
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const chainOf = (instance, next, typeOf) => {
|
|
24
|
+
const chain = [];
|
|
25
|
+
for (let node = instance; node; node = next(node)) {
|
|
26
|
+
const type = typeOf(node);
|
|
27
|
+
const name = nameOf(type);
|
|
28
|
+
if (!name) continue;
|
|
29
|
+
const parentFile = fileOf(typeOf(next(node)));
|
|
30
|
+
chain.unshift({ name, usedAt: parentFile ? { file: parentFile, line: null, column: null, how: "vue-file" } : null });
|
|
31
|
+
}
|
|
32
|
+
return chain;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const componentOf = element => {
|
|
36
|
+
const modern = vue3(element);
|
|
37
|
+
const legacy = modern ? null : vue2(element);
|
|
38
|
+
if (!modern && !legacy) return null;
|
|
39
|
+
const typeOf = modern ? node => node?.type : node => node?.$options;
|
|
40
|
+
const next = modern ? node => node.parent : node => node.$parent;
|
|
41
|
+
const instance = modern ?? legacy;
|
|
42
|
+
const chain = chainOf(instance, next, typeOf);
|
|
43
|
+
const ownFile = fileOf(typeOf(instance));
|
|
44
|
+
return {
|
|
45
|
+
name: nameOf(typeOf(instance)),
|
|
46
|
+
framework: "vue",
|
|
47
|
+
chain: chain.slice(-8),
|
|
48
|
+
source: ownFile ? { file: ownFile, line: null, column: null, how: "vue-file", confidence: "file" } : null
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { componentOf }
|
|
53
|
+
}
|
package/src/snapshots/clean.mjs
CHANGED
|
@@ -28,9 +28,9 @@ export const cleanSnapshots = async (options = {}) => {
|
|
|
28
28
|
const maxAge = options.olderThan ? parseDuration(options.olderThan) : 0;
|
|
29
29
|
const now = Date.now();
|
|
30
30
|
const removed = readdirSync(out)
|
|
31
|
-
.filter(name => name.toLowerCase().endsWith(".png"))
|
|
31
|
+
.filter(name => name.toLowerCase().endsWith(".png") || name === "report.html" || /annotate-[\w.-]+\.(json|md)$/.test(name))
|
|
32
32
|
.map(name => path.join(out, name))
|
|
33
|
-
.filter(file => statSync(file).isFile() && now - statSync(file).mtimeMs >= maxAge);
|
|
33
|
+
.filter(file => statSync(file).isFile() && (maxAge === 0 || now - statSync(file).mtimeMs >= maxAge));
|
|
34
34
|
if (!options.dryRun) for (const file of removed) rmSync(file);
|
|
35
35
|
return { removed, out }
|
|
36
36
|
}
|