@atlassian/chippy 0.0.0-stage → 0.0.1-beta.2
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/LICENSE +201 -0
- package/README.md +276 -2
- package/bin/chippy.mjs +92 -0
- package/bin/pack.mjs +204 -0
- package/bin/skill-dir.mjs +15 -0
- package/lib/avalanche.js +2601 -0
- package/lib/avalanche.js.map +7 -0
- package/lib/bootstrap.d.ts +1 -0
- package/lib/bootstrap.js +70 -0
- package/lib/bootstrap.js.map +7 -0
- package/lib/browser.js +15 -0
- package/lib/browser.js.map +7 -0
- package/lib/extension/avalanche.es.js +5535 -0
- package/lib/extension/avalanche.es.js.map +1 -0
- package/lib/extension/chippy-page-bridge.js +1953 -0
- package/lib/extension/inspect-react.js +216 -0
- package/lib/extension/layout-lab.js +1453 -0
- package/lib/extension/panel-dom.js +90 -0
- package/lib/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/LICENSE +201 -0
- package/lib/mcp-skill/README.md +148 -0
- package/lib/mcp-skill/SKILL.md +335 -0
- package/lib/mcp-skill/browser-scripts/playwright-probes.js +368 -0
- package/lib/mcp-skill/browser-scripts/playwright-session.js +114 -0
- package/lib/mcp-skill/extension/armed-reload-startup.js +83 -0
- package/lib/mcp-skill/extension/avalanche.es.js +5535 -0
- package/lib/mcp-skill/extension/avalanche.es.js.map +1 -0
- package/lib/mcp-skill/extension/avalanche.iife.js +5536 -0
- package/lib/mcp-skill/extension/avalanche.iife.js.map +1 -0
- package/lib/mcp-skill/extension/background.js +325 -0
- package/lib/mcp-skill/extension/chippy-page-bridge.js +1953 -0
- package/lib/mcp-skill/extension/content-script.js +190 -0
- package/lib/mcp-skill/extension/devtools-hook-shim.js +85 -0
- package/lib/mcp-skill/extension/early-bootstrap.js +49 -0
- package/lib/mcp-skill/extension/icon-128.png +0 -0
- package/lib/mcp-skill/extension/icon-16.png +0 -0
- package/lib/mcp-skill/extension/icon-32.png +0 -0
- package/lib/mcp-skill/extension/icon-48.png +0 -0
- package/lib/mcp-skill/extension/inspect-react.js +216 -0
- package/lib/mcp-skill/extension/layout-lab.js +1453 -0
- package/lib/mcp-skill/extension/logo.svg +14 -0
- package/lib/mcp-skill/extension/manifest.json +69 -0
- package/lib/mcp-skill/extension/panel-dom.js +90 -0
- package/lib/mcp-skill/extension/panel-settings.js +173 -0
- package/lib/mcp-skill/extension/popup.html +41 -0
- package/lib/mcp-skill/extension/popup.js +80 -0
- package/lib/mcp-skill/extension/probe-descriptor.js +241 -0
- package/lib/mcp-skill/extension/resolve-probes.js +342 -0
- package/lib/mcp-skill/extension/startup-handoff.js +30 -0
- package/lib/mcp-skill/package.json +1 -0
- package/lib/mcp-skill/references/README.md +56 -0
- package/lib/mcp-skill/references/active-stimulus.md +85 -0
- package/lib/mcp-skill/references/avalanche-detector.md +731 -0
- package/lib/mcp-skill/references/bdd-router.md +310 -0
- package/lib/mcp-skill/references/browser-runtime-api.md +193 -0
- package/lib/mcp-skill/references/command-output-formats.md +1085 -0
- package/lib/mcp-skill/references/command-surface.md +183 -0
- package/lib/mcp-skill/references/cynefin.md +99 -0
- package/lib/mcp-skill/references/debug-hook.md +161 -0
- package/lib/mcp-skill/references/fixing-performance.md +490 -0
- package/lib/mcp-skill/references/hook-metrics.md +199 -0
- package/lib/mcp-skill/references/hooks-inspection.md +61 -0
- package/lib/mcp-skill/references/jira-taxonomy.md +81 -0
- package/lib/mcp-skill/references/mcp.md +122 -0
- package/lib/mcp-skill/references/mode-a-injection.md +102 -0
- package/lib/mcp-skill/references/operating-model.md +128 -0
- package/lib/mcp-skill/references/playwright-cli-setup.md +105 -0
- package/lib/mcp-skill/references/playwright-probes.md +205 -0
- package/lib/mcp-skill/references/procedures.md +545 -0
- package/lib/mcp-skill/references/recipes.txt +114 -0
- package/lib/mcp-skill/references/stability-render.md +428 -0
- package/lib/mcp-skill/references/test-mode-playbook.md +194 -0
- package/lib/mcp-skill/references/troubleshooting.md +59 -0
- package/lib/mcp-skill/scripts/avalanche-analyze.cjs +495 -0
- package/lib/mcp-skill/scripts/build-avalanche-command.js +34 -0
- package/lib/mcp-skill/scripts/build-fiber-state-command.js +19 -0
- package/lib/mcp-skill/scripts/chippy-helper-server.js +1768 -0
- package/lib/mcp-skill/scripts/chippy-mcp.cjs +273 -0
- package/lib/mcp-skill/scripts/chippy.sh +1252 -0
- package/lib/mcp-skill/scripts/format-probe-result.js +34 -0
- package/lib/mcp-skill/scripts/helper-access.js +158 -0
- package/lib/mcp-skill/scripts/helper-proxy.js +87 -0
- package/lib/mcp-skill/scripts/page-socket-trust.js +81 -0
- package/lib/mcp-skill/scripts/probe-annotation-channel.js +116 -0
- package/lib/mcp-skill/scripts/search-recipes.js +248 -0
- package/lib/mcp-skill/snippets/README.md +89 -0
- package/lib/mcp-skill/snippets/count-context-consumers.js +40 -0
- package/lib/mcp-skill/snippets/find-context-providers.js +38 -0
- package/lib/mcp-skill/snippets/find-legacy-context.js +47 -0
- package/lib/mcp-skill/snippets/find-prop.js +83 -0
- package/lib/mcp-skill/snippets/hook-metrics-sample.js +69 -0
- package/lib/mcp-skill/snippets/hook-metrics.js +60 -0
- package/lib/mcp-skill/snippets/hook-tracker.js +198 -0
- package/lib/mcp-skill/snippets/stability-nudge.js +101 -0
- package/lib/package.json +1 -0
- package/lib/plugin.js +119 -0
- package/lib/plugin.js.map +7 -0
- package/lib/storybook/avalanche-panel-model.js +79 -0
- package/lib/storybook/brand.jsx +6 -0
- package/lib/storybook/connection-fallback.jsx +47 -0
- package/lib/storybook/constants.js +15 -0
- package/lib/storybook/context-card.jsx +171 -0
- package/lib/storybook/manager-owner.js +42 -0
- package/lib/storybook/manager.jsx +425 -0
- package/lib/storybook/preset.js +7 -0
- package/lib/storybook/preview.js +298 -0
- package/lib/storybook/probe-annotation-editor.jsx +52 -0
- package/lib/storybook/probe-intent.js +23 -0
- package/lib/storybook/setup.js +104 -0
- package/lib/types/avalanche/commit-metrics-adapter.d.ts +76 -0
- package/lib/types/avalanche/commit-stats-indices.d.ts +41 -0
- package/lib/types/avalanche/commit-surface-ownership.d.ts +9 -0
- package/lib/types/avalanche/detector.d.ts +183 -0
- package/lib/types/avalanche/interaction-capture.d.ts +25 -0
- package/lib/types/avalanche/internals.d.ts +189 -0
- package/lib/types/avalanche/report-serializer.d.ts +283 -0
- package/lib/types/avalanche/reporter.d.ts +610 -0
- package/lib/types/chippy-bridge/fiber-source.d.ts +48 -0
- package/lib/types/chippy-bridge/react-renderer.d.ts +74 -0
- package/lib/types/chippy-bridge/recorded-source.d.ts +44 -0
- package/lib/types/chippy-bridge/source-map.d.ts +30 -0
- package/lib/types/debug-render/core.d.ts +78 -0
- package/lib/types/layout-lab/fiber-boundaries.d.ts +51 -0
- package/lib/types/layout-lab/fiber-walk.d.ts +131 -0
- package/lib/types/layout-lab/marker-context.d.ts +94 -0
- package/lib/types/package/avalanche.d.ts +6 -0
- package/lib/types/package/browser.d.ts +10 -0
- package/lib/types/package/plugin.d.ts +15 -0
- package/lib/types/plugins/atl/commit-metrics-adapter.d.ts +65 -0
- package/lib/types/plugins/atl/metrics-slot.d.ts +37 -0
- package/lib/types/plugins/index.d.ts +49 -0
- package/lib/types/plugins/jira/context-provider.d.ts +36 -0
- package/package.json +106 -4
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// The content script owns extension UX/configuration and delegates MAIN-world
|
|
3
|
+
// execution to the background service worker. It cannot inspect React fibers
|
|
4
|
+
// directly, so it communicates through page events and runtime messages;
|
|
5
|
+
// keeping this split also avoids injecting inline code through page CSP.
|
|
6
|
+
(function chippyContentScript() {
|
|
7
|
+
|
|
8
|
+
const DEFAULT_HELPER = 'http://127.0.0.1:7823';
|
|
9
|
+
const BUNDLE_ATTR = 'data-chippy-bundle';
|
|
10
|
+
const LOG_PREFIX = '[chippy-ext]';
|
|
11
|
+
|
|
12
|
+
const pageStatus = {
|
|
13
|
+
status: 'booting',
|
|
14
|
+
detail: '',
|
|
15
|
+
updatedAt: new Date().toISOString(),
|
|
16
|
+
helperBaseUrl: DEFAULT_HELPER,
|
|
17
|
+
activated: false,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
function error(...args) { console.error(LOG_PREFIX, ...args); }
|
|
21
|
+
|
|
22
|
+
function getInjectionParent() {
|
|
23
|
+
return document.head || document.documentElement || document.body || null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function appendToDocument(node, label, onAppended = null, attempt = 0) {
|
|
27
|
+
const parent = getInjectionParent();
|
|
28
|
+
if (parent) {
|
|
29
|
+
parent.appendChild(node);
|
|
30
|
+
onAppended?.();
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (attempt >= 50) {
|
|
35
|
+
error('failed to append node; no injection parent', { label, readyState: document.readyState });
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
window.setTimeout(() => appendToDocument(node, label, onAppended, attempt + 1), 20);
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function normalizeBaseUrl(value) {
|
|
44
|
+
return String(value || DEFAULT_HELPER).replace(/\/+$/, '');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getOriginKey() {
|
|
48
|
+
try {
|
|
49
|
+
return window.location.origin || null;
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function setStatus(status, detail = '') {
|
|
56
|
+
pageStatus.status = status;
|
|
57
|
+
pageStatus.detail = detail || '';
|
|
58
|
+
pageStatus.updatedAt = new Date().toISOString();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writePageConfig(helperBaseUrl, activated) {
|
|
62
|
+
const normalizedBaseUrl = normalizeBaseUrl(helperBaseUrl);
|
|
63
|
+
pageStatus.helperBaseUrl = normalizedBaseUrl;
|
|
64
|
+
pageStatus.activated = Boolean(activated);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function injectBundle(helperBaseUrl) {
|
|
68
|
+
const base = normalizeBaseUrl(helperBaseUrl);
|
|
69
|
+
|
|
70
|
+
writePageConfig(base, true);
|
|
71
|
+
setStatus('injecting', 'background bundle');
|
|
72
|
+
|
|
73
|
+
chrome.runtime.sendMessage({ type: 'chippy-inject-bundle', helperBaseUrl: base }, (response) => {
|
|
74
|
+
if (chrome.runtime.lastError) {
|
|
75
|
+
error('injectBundle:background-error', { message: chrome.runtime.lastError.message });
|
|
76
|
+
setStatus('load-error', chrome.runtime.lastError.message);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!response?.ok) {
|
|
81
|
+
error('injectBundle:background-failed', response || null);
|
|
82
|
+
setStatus('load-error', response?.error || 'Background injection failed');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
setStatus('loaded', response.detail || 'background bundle injected');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function disableBundle() {
|
|
91
|
+
writePageConfig(DEFAULT_HELPER, false);
|
|
92
|
+
// Dispatch a custom event so the page bridge can pick up deactivation
|
|
93
|
+
// without inline script injection (CSP-safe).
|
|
94
|
+
try {
|
|
95
|
+
document.dispatchEvent(new CustomEvent('chippy-ext-command', {
|
|
96
|
+
detail: { type: 'deactivate' },
|
|
97
|
+
}));
|
|
98
|
+
} catch {}
|
|
99
|
+
document.querySelectorAll(`script[${BUNDLE_ATTR}]`).forEach((node) => node.remove());
|
|
100
|
+
setStatus('disabled', getOriginKey() || 'disabled');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function applyStoredConfig() {
|
|
104
|
+
const origin = getOriginKey();
|
|
105
|
+
if (!origin) {
|
|
106
|
+
setStatus('unsupported', 'No origin');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
chrome.storage.local.get({
|
|
111
|
+
helperBaseUrl: DEFAULT_HELPER,
|
|
112
|
+
enabledOrigins: {},
|
|
113
|
+
}, (stored) => {
|
|
114
|
+
const enabled = Boolean(stored.enabledOrigins?.[origin]);
|
|
115
|
+
if (!enabled) {
|
|
116
|
+
setStatus('disabled', origin);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
injectBundle(normalizeBaseUrl(stored.helperBaseUrl));
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
124
|
+
if (message.type === 'chippy-get-page-status') {
|
|
125
|
+
const bundle = document.querySelector(`script[${BUNDLE_ATTR}]`);
|
|
126
|
+
sendResponse({
|
|
127
|
+
ok: true,
|
|
128
|
+
result: {
|
|
129
|
+
origin: getOriginKey(),
|
|
130
|
+
status: pageStatus.status || 'unknown',
|
|
131
|
+
detail: pageStatus.detail || '',
|
|
132
|
+
updatedAt: pageStatus.updatedAt || null,
|
|
133
|
+
helperBaseUrl: pageStatus.helperBaseUrl || null,
|
|
134
|
+
activated: Boolean(pageStatus.activated),
|
|
135
|
+
bundlePresent: Boolean(bundle),
|
|
136
|
+
bundleSrc: bundle?.src || null,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (message.type === 'chippy-disable-now') {
|
|
143
|
+
disableBundle();
|
|
144
|
+
sendResponse({ ok: true });
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (message.type === 'chippy-update-helper-url') {
|
|
149
|
+
const enabled = Boolean(message.enabled);
|
|
150
|
+
const helperBaseUrl = normalizeBaseUrl(message.helperBaseUrl);
|
|
151
|
+
writePageConfig(helperBaseUrl, enabled);
|
|
152
|
+
if (enabled) {
|
|
153
|
+
try {
|
|
154
|
+
document.dispatchEvent(new CustomEvent('chippy-ext-command', {
|
|
155
|
+
detail: { type: 'apply-config', helperBaseUrl, activated: true },
|
|
156
|
+
}));
|
|
157
|
+
} catch {}
|
|
158
|
+
}
|
|
159
|
+
setStatus('configured', helperBaseUrl);
|
|
160
|
+
sendResponse({ ok: true });
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// The page bridge asks for early startup on the next navigation before an armed
|
|
166
|
+
// reload. Only the background can register the MAIN-world startup scripts. The
|
|
167
|
+
// background also re-runs this file, so the relay is installed once per document.
|
|
168
|
+
function relayArmedReload() {
|
|
169
|
+
const reply = (result) => {
|
|
170
|
+
try {
|
|
171
|
+
document.dispatchEvent(new CustomEvent('chippy-armed-reload-ready', { detail: JSON.stringify(result || {}) }));
|
|
172
|
+
} catch {}
|
|
173
|
+
};
|
|
174
|
+
try {
|
|
175
|
+
chrome.runtime.sendMessage({ type: 'chippy-register-armed-reload' }, (response) => {
|
|
176
|
+
if (chrome.runtime.lastError) reply({ ok: false, error: chrome.runtime.lastError.message });
|
|
177
|
+
else reply(response);
|
|
178
|
+
});
|
|
179
|
+
} catch (e) {
|
|
180
|
+
reply({ ok: false, error: e.message });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!globalThis.__chippyArmedReloadRelay) {
|
|
184
|
+
globalThis.__chippyArmedReloadRelay = true;
|
|
185
|
+
document.addEventListener('chippy-armed-reload-prepare', relayArmedReload);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
applyStoredConfig();
|
|
189
|
+
|
|
190
|
+
})();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
/**
|
|
3
|
+
* DevTools-hook shim.
|
|
4
|
+
*
|
|
5
|
+
* Must be installed BEFORE react-dom evaluates. React-DOM checks for
|
|
6
|
+
* window.__REACT_DEVTOOLS_GLOBAL_HOOK__
|
|
7
|
+
* at module evaluation time. If a hook with `inject(internals)` is present,
|
|
8
|
+
* react-dom registers itself into hook.renderers, which exposes the
|
|
9
|
+
* `currentDispatcherRef` that Avalanche debug-render needs to capture hook
|
|
10
|
+
* call stacks.
|
|
11
|
+
*
|
|
12
|
+
* Without this shim in place at evaluation time, hook.renderers stays empty,
|
|
13
|
+
* debug-render cannot patch the dispatcher, and `debugHook()` collapses to
|
|
14
|
+
* a flat fallback (no custom-hook ancestry, no folders).
|
|
15
|
+
*
|
|
16
|
+
* Idempotent: never overwrites an existing real hook (e.g. real React DevTools
|
|
17
|
+
* extension). Only installs an empty shim when nothing is there yet.
|
|
18
|
+
*
|
|
19
|
+
* Browser extension content-script and the local Playwright path both load
|
|
20
|
+
* this script BEFORE the page bootstraps React.
|
|
21
|
+
*/
|
|
22
|
+
(function chippyDevtoolsHookShim(global) {
|
|
23
|
+
try {
|
|
24
|
+
if (global.__REACT_DEVTOOLS_GLOBAL_HOOK__) return;
|
|
25
|
+
|
|
26
|
+
var renderers = new Map();
|
|
27
|
+
// Committed FiberRoots per renderer, tracked like React DevTools does so
|
|
28
|
+
// tree walks start from the live root instead of a container's stale
|
|
29
|
+
// HostRoot alternate.
|
|
30
|
+
var fiberRoots = new Map();
|
|
31
|
+
function rootsFor(id) {
|
|
32
|
+
var roots = fiberRoots.get(id);
|
|
33
|
+
if (!roots) { roots = new Set(); fiberRoots.set(id, roots); }
|
|
34
|
+
return roots;
|
|
35
|
+
}
|
|
36
|
+
var hook = {
|
|
37
|
+
__chippyShim: true,
|
|
38
|
+
supportsFiber: true,
|
|
39
|
+
renderers: renderers,
|
|
40
|
+
inject: function inject(renderer) {
|
|
41
|
+
var id = (renderers.size || 0) + 1;
|
|
42
|
+
renderers.set(id, renderer);
|
|
43
|
+
return id;
|
|
44
|
+
},
|
|
45
|
+
getFiberRoots: function getFiberRoots(id) { return rootsFor(id); },
|
|
46
|
+
onScheduleFiberRoot: function () {},
|
|
47
|
+
onCommitFiberRoot: function onCommitFiberRoot(id, root) {
|
|
48
|
+
if (!root || !root.current) return;
|
|
49
|
+
var roots = rootsFor(id);
|
|
50
|
+
var state = root.current.memoizedState;
|
|
51
|
+
var unmounting = !state || state.element == null;
|
|
52
|
+
if (unmounting) roots.delete(root);
|
|
53
|
+
else roots.add(root);
|
|
54
|
+
},
|
|
55
|
+
onCommitFiberUnmount: function () {},
|
|
56
|
+
onPostCommitFiberRoot: function () {},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
Object.defineProperty(global, '__REACT_DEVTOOLS_GLOBAL_HOOK__', {
|
|
60
|
+
configurable: true,
|
|
61
|
+
enumerable: true,
|
|
62
|
+
get: function () { return hook; },
|
|
63
|
+
set: function (next) {
|
|
64
|
+
// Real DevTools may try to install later. Accept it but preserve any
|
|
65
|
+
// renderers we already collected so debug-render can still find them.
|
|
66
|
+
if (next && typeof next === 'object') {
|
|
67
|
+
if (!next.renderers || (next.renderers.size === 0 && renderers.size > 0)) {
|
|
68
|
+
try { next.renderers = renderers; } catch (_) {}
|
|
69
|
+
}
|
|
70
|
+
hook = next;
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Cross-iframe note: if running in a top-frame, mirror so child iframes
|
|
76
|
+
// (Storybook iframe.html) can rely on it too. Safe no-op otherwise.
|
|
77
|
+
try {
|
|
78
|
+
if (global.top && global.top !== global && !global.top.__REACT_DEVTOOLS_GLOBAL_HOOK__) {
|
|
79
|
+
global.top.__REACT_DEVTOOLS_GLOBAL_HOOK__ = hook;
|
|
80
|
+
}
|
|
81
|
+
} catch (_) {}
|
|
82
|
+
} catch (_) {
|
|
83
|
+
// Never throw at page boot. A failed shim must degrade silently.
|
|
84
|
+
}
|
|
85
|
+
})(typeof globalThis !== 'undefined' ? globalThis : window);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Runs in MAIN before React evaluates. This phase exists only to establish
|
|
3
|
+
// timing-sensitive page contracts (startup config and the registered commit
|
|
4
|
+
// metrics adapter's pre-page hook). The actual Chippy bridge is injected
|
|
5
|
+
// later, after owner arbitration, so a user-controlled Storybook/import
|
|
6
|
+
// integration can win.
|
|
7
|
+
(function chippyEarlyBootstrap() {
|
|
8
|
+
if (globalThis.__CHIPPY_EARLY_BOOTSTRAP_INSTALLED__) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Without a config this run does nothing, so a later run that has one (the
|
|
13
|
+
// background's startup injection) must still be able to bootstrap.
|
|
14
|
+
const cfg = globalThis.__CHIPPY_STARTUP_CONFIG || null;
|
|
15
|
+
if (!cfg || !cfg.enabled) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
globalThis.__CHIPPY_EARLY_BOOTSTRAP_INSTALLED__ = true;
|
|
19
|
+
|
|
20
|
+
globalThis.__CHIPPY = globalThis.__CHIPPY || {};
|
|
21
|
+
|
|
22
|
+
globalThis.__CHIPPY.config = {
|
|
23
|
+
...(globalThis.__CHIPPY.config || {}),
|
|
24
|
+
helperBaseUrl: cfg.helperBaseUrl,
|
|
25
|
+
activated: true,
|
|
26
|
+
avalanche: cfg.avalanche ? {
|
|
27
|
+
autoStart: true,
|
|
28
|
+
max: (cfg.avalanche && cfg.avalanche.max) || 500,
|
|
29
|
+
options: (cfg.avalanche && cfg.avalanche.options) || {},
|
|
30
|
+
} : { autoStart: false, max: 500 },
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Run the registered commit metrics adapter's pre-page hook. It must run
|
|
34
|
+
// before the page's React evaluates (for example, the ATL adapter installs a
|
|
35
|
+
// configurable metrics-slot accessor). avalanche.iife.js loads before this
|
|
36
|
+
// script, so the registry is populated. A failure here is non-fatal:
|
|
37
|
+
// Avalanche falls back to DevTools integration.
|
|
38
|
+
try {
|
|
39
|
+
if (globalThis.__AVALANCHE__ && typeof globalThis.__AVALANCHE__.runPrePageHooks === 'function') {
|
|
40
|
+
globalThis.__AVALANCHE__.runPrePageHooks(cfg);
|
|
41
|
+
}
|
|
42
|
+
} catch (_e) {
|
|
43
|
+
// Non-fatal.
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (globalThis.__AVALANCHE__) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
(function installInspectReact(global) {
|
|
3
|
+
const chippy = global.__CHIPPY = global.__CHIPPY || {};
|
|
4
|
+
// Always reinstall — guards caused stale code to win after extension reload.
|
|
5
|
+
|
|
6
|
+
function getReactInternalKeys(node) {
|
|
7
|
+
if (!node) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return Object.getOwnPropertyNames(node).filter(
|
|
12
|
+
(key) =>
|
|
13
|
+
key.startsWith('__reactFiber$') ||
|
|
14
|
+
key.startsWith('__reactProps$') ||
|
|
15
|
+
key.startsWith('__reactContainer$'),
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function getFiberFromNode(node) {
|
|
20
|
+
if (!node) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const fiberKey = getReactInternalKeys(node).find((key) => key.startsWith('__reactFiber$'));
|
|
25
|
+
const containerKey = getReactInternalKeys(node).find((key) => key.startsWith('__reactContainer$'));
|
|
26
|
+
|
|
27
|
+
return node[fiberKey] || node[containerKey] || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getClosestFiber(node) {
|
|
31
|
+
let current = node;
|
|
32
|
+
|
|
33
|
+
while (current) {
|
|
34
|
+
const fiber = getFiberFromNode(current);
|
|
35
|
+
if (fiber) {
|
|
36
|
+
return fiber;
|
|
37
|
+
}
|
|
38
|
+
current = current.parentNode || current.host || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getDisplayName(type) {
|
|
45
|
+
if (!type) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
if (typeof type === 'string') {
|
|
49
|
+
return type;
|
|
50
|
+
}
|
|
51
|
+
return type.displayName || type.name || type.render?.displayName || type.render?.name || 'Anonymous';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeSource(source) {
|
|
55
|
+
if (!source) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
fileName: source.fileName || null,
|
|
61
|
+
lineNumber: source.lineNumber || null,
|
|
62
|
+
columnNumber: source.columnNumber || null,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sourceOf(fiber) {
|
|
67
|
+
const describeFiberSource = global.__AVALANCHE__?.layoutLab?.describeFiberSource;
|
|
68
|
+
return typeof describeFiberSource === 'function'
|
|
69
|
+
? describeFiberSource(fiber)
|
|
70
|
+
: normalizeSource(fiber?._debugSource || fiber?.type?._debugSource || null);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function describeFiber(fiber) {
|
|
74
|
+
if (!fiber) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
key: fiber.key ?? null,
|
|
80
|
+
tag: fiber.tag ?? null,
|
|
81
|
+
index: fiber.index ?? null,
|
|
82
|
+
elementType: getDisplayName(fiber.elementType),
|
|
83
|
+
type: getDisplayName(fiber.type),
|
|
84
|
+
stateNodeType:
|
|
85
|
+
fiber.stateNode && fiber.stateNode.nodeType === 1
|
|
86
|
+
? fiber.stateNode.tagName.toLowerCase()
|
|
87
|
+
: typeof fiber.stateNode,
|
|
88
|
+
debugSource: sourceOf(fiber),
|
|
89
|
+
memoizedPropsKeys: fiber.memoizedProps ? Object.keys(fiber.memoizedProps) : [],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function isCompositeFiber(fiber) {
|
|
94
|
+
if (!fiber) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const name = getDisplayName(fiber.type);
|
|
99
|
+
return Boolean(name && typeof fiber.type !== 'string');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getOwnerChain(fiber) {
|
|
103
|
+
const owners = [];
|
|
104
|
+
let current = fiber ? fiber._debugOwner : null;
|
|
105
|
+
|
|
106
|
+
while (current) {
|
|
107
|
+
owners.push(describeFiber(current));
|
|
108
|
+
current = current._debugOwner || null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return owners;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getReturnChain(fiber) {
|
|
115
|
+
const parents = [];
|
|
116
|
+
let current = fiber ? fiber.return : null;
|
|
117
|
+
|
|
118
|
+
while (current) {
|
|
119
|
+
parents.push(describeFiber(current));
|
|
120
|
+
current = current.return || null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return parents;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function getNearestCompositeFiber(fiber) {
|
|
127
|
+
let current = fiber;
|
|
128
|
+
|
|
129
|
+
while (current) {
|
|
130
|
+
if (isCompositeFiber(current)) {
|
|
131
|
+
return current;
|
|
132
|
+
}
|
|
133
|
+
current = current.return || null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
function getNearestTestIdParent(node) {
|
|
141
|
+
// Walk includes the node itself — if the probed element has data-testid, that wins.
|
|
142
|
+
let current = node || null;
|
|
143
|
+
while (current) {
|
|
144
|
+
const testId = current.dataset ? current.dataset.testid : null;
|
|
145
|
+
if (testId) {
|
|
146
|
+
const fiber = getFiberFromNode(current) || getClosestFiber(current);
|
|
147
|
+
const componentFiber = getNearestCompositeFiber(fiber);
|
|
148
|
+
return {
|
|
149
|
+
testId,
|
|
150
|
+
tagName: current.tagName ? current.tagName.toLowerCase() : null,
|
|
151
|
+
component: describeFiber(componentFiber),
|
|
152
|
+
fiber: describeFiber(fiber),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
current = current.parentElement || null;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function inspectNode(node) {
|
|
161
|
+
const fiber = getFiberFromNode(node) || getClosestFiber(node);
|
|
162
|
+
const componentFiber = getNearestCompositeFiber(fiber);
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
dom: node
|
|
166
|
+
? {
|
|
167
|
+
tagName: node.tagName ? node.tagName.toLowerCase() : null,
|
|
168
|
+
text: node.textContent?.trim() || '',
|
|
169
|
+
reactKeys: getReactInternalKeys(node),
|
|
170
|
+
}
|
|
171
|
+
: null,
|
|
172
|
+
fiber: describeFiber(fiber),
|
|
173
|
+
component: describeFiber(componentFiber),
|
|
174
|
+
owners: getOwnerChain(componentFiber || fiber),
|
|
175
|
+
returns: getReturnChain(componentFiber || fiber),
|
|
176
|
+
nearestTestIdParent: getNearestTestIdParent(node),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function findByText(text, root = document) {
|
|
181
|
+
return Array.from(root.querySelectorAll('*'))
|
|
182
|
+
.filter((node) => node.textContent?.trim() === text)
|
|
183
|
+
.map((node) => ({
|
|
184
|
+
node,
|
|
185
|
+
inspection: inspectNode(node),
|
|
186
|
+
}));
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function findBySelector(selector, root = document) {
|
|
190
|
+
return Array.from(root.querySelectorAll(selector)).map((node) => ({
|
|
191
|
+
node,
|
|
192
|
+
inspection: inspectNode(node),
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function inspectAtPoint(x, y) {
|
|
197
|
+
return document.elementsFromPoint(x, y).map((node) => ({
|
|
198
|
+
node,
|
|
199
|
+
inspection: inspectNode(node),
|
|
200
|
+
}));
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
chippy.inspectReact = {
|
|
204
|
+
getReactInternalKeys,
|
|
205
|
+
getFiberFromNode,
|
|
206
|
+
getClosestFiber,
|
|
207
|
+
getNearestCompositeFiber,
|
|
208
|
+
describeFiber,
|
|
209
|
+
getOwnerChain,
|
|
210
|
+
getReturnChain,
|
|
211
|
+
inspectNode,
|
|
212
|
+
inspectAtPoint,
|
|
213
|
+
findByText,
|
|
214
|
+
findBySelector,
|
|
215
|
+
};
|
|
216
|
+
})(window);
|