Add getReactSource to extract React component source location
- Add bippy library for React fiber introspection - Create build-bippy.ts and build-selector-generator.ts using Bun.build - Use CDP Runtime.evaluate instead of addScriptTag to bypass CSP - Add getReactSource utility function exposed in MCP VMContext - Works on local React dev servers with JSX transform (not production builds)
This commit is contained in:
@@ -1,5 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
## 0.0.28
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- **Added `getReactSource` utility**: Extract React component source location (file, line, column) from DOM elements
|
||||
- Uses bippy library for React fiber introspection
|
||||
- Returns `{ fileName, lineNumber, columnNumber, componentName }` or `null`
|
||||
- Only works on local dev servers (Vite, Next.js, CRA) with JSX transform in development mode
|
||||
- **CSP bypass for script injection**: Changed `getLocatorStringForElement` and `getReactSource` to use CDP `Runtime.evaluate` instead of `addScriptTag`
|
||||
- Scripts now work on pages with strict Content Security Policy
|
||||
- **Switched to Bun.build**: Replaced esbuild and esm.sh downloads with Bun.build for bundling selector-generator and bippy
|
||||
- New `build-selector-generator.ts` and `build-bippy.ts` scripts
|
||||
|
||||
## 0.0.27
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "playwriter",
|
||||
"description": "",
|
||||
"version": "0.0.27",
|
||||
"version": "0.0.28",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": "https://github.com/remorses/playwriter",
|
||||
"scripts": {
|
||||
"build": "rm -rf dist *.tsbuildinfo && mkdir dist && cp src/resource.md src/prompt.md src/debugger-examples.ts dist/ && vite-node scripts/download-selector-generator.ts && tsc",
|
||||
"build": "rm -rf dist *.tsbuildinfo && mkdir dist && cp src/resource.md src/prompt.md src/debugger-examples.ts dist/ && bun scripts/build-selector-generator.ts && bun scripts/build-bippy.ts && tsc",
|
||||
"prepublishOnly": "pnpm build",
|
||||
"watch": "tsc -w",
|
||||
"typecheck": "tsc --noEmit",
|
||||
@@ -32,6 +32,7 @@
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/ws": "^8.18.1",
|
||||
"@vitest/ui": "^4.0.8",
|
||||
"bippy": "^0.5.27",
|
||||
"image-size": "^2.0.2",
|
||||
"mcp-extension": "workspace:*",
|
||||
"vite-node": "^5.0.0",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const distDir = path.join(__dirname, '..', 'dist')
|
||||
|
||||
const ENTRY_CODE = `
|
||||
import { getFiberFromHostInstance, getDisplayName, traverseFiber, isCompositeFiber, isHostFiber } from 'bippy'
|
||||
import { getSource, getOwnerStack, normalizeFileName, isSourceFile } from 'bippy/source'
|
||||
|
||||
globalThis.__bippy = {
|
||||
getFiberFromHostInstance,
|
||||
getDisplayName,
|
||||
traverseFiber,
|
||||
isCompositeFiber,
|
||||
isHostFiber,
|
||||
getSource,
|
||||
getOwnerStack,
|
||||
normalizeFileName,
|
||||
isSourceFile,
|
||||
}
|
||||
`
|
||||
|
||||
async function main() {
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true })
|
||||
}
|
||||
|
||||
const entryPath = path.join(distDir, '_bippy-entry.js')
|
||||
fs.writeFileSync(entryPath, ENTRY_CODE)
|
||||
|
||||
console.log('Bundling bippy...')
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: [entryPath],
|
||||
target: 'browser',
|
||||
format: 'iife',
|
||||
define: {
|
||||
'process.env.NODE_ENV': '"development"',
|
||||
},
|
||||
})
|
||||
|
||||
fs.unlinkSync(entryPath)
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Bundle errors:', result.logs)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const bundledCode = await result.outputs[0].text()
|
||||
const outputPath = path.join(distDir, 'bippy.js')
|
||||
fs.writeFileSync(outputPath, bundledCode)
|
||||
console.log(`Saved to ${outputPath} (${Math.round(bundledCode.length / 1024)}kb)`)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,46 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const distDir = path.join(__dirname, '..', 'dist')
|
||||
|
||||
const ENTRY_CODE = `
|
||||
import { createSelectorGenerator, toLocator } from '@mizchi/selector-generator'
|
||||
|
||||
globalThis.__selectorGenerator = { createSelectorGenerator, toLocator }
|
||||
`
|
||||
|
||||
async function main() {
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true })
|
||||
}
|
||||
|
||||
const entryPath = path.join(distDir, '_selector-generator-entry.js')
|
||||
fs.writeFileSync(entryPath, ENTRY_CODE)
|
||||
|
||||
console.log('Bundling selector-generator...')
|
||||
|
||||
const result = await Bun.build({
|
||||
entrypoints: [entryPath],
|
||||
target: 'browser',
|
||||
format: 'iife',
|
||||
})
|
||||
|
||||
fs.unlinkSync(entryPath)
|
||||
|
||||
if (!result.success) {
|
||||
console.error('Bundle errors:', result.logs)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const bundledCode = await result.outputs[0].text()
|
||||
const outputPath = path.join(distDir, 'selector-generator.js')
|
||||
fs.writeFileSync(outputPath, bundledCode)
|
||||
console.log(`Saved to ${outputPath} (${Math.round(bundledCode.length / 1024)}kb)`)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -1,55 +0,0 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const distDir = path.join(__dirname, '..', 'dist')
|
||||
|
||||
const SELECTOR_GENERATOR_URL = 'https://esm.sh/@mizchi/selector-generator@1.50.0-next/es2022/selector-generator.bundle.mjs'
|
||||
|
||||
async function main() {
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true })
|
||||
}
|
||||
|
||||
console.log('Downloading selector-generator from esm.sh...')
|
||||
const response = await fetch(SELECTOR_GENERATOR_URL)
|
||||
if (!response.ok) {
|
||||
throw new Error(`Failed to download: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
|
||||
let esmScript = await response.text()
|
||||
|
||||
const exportMatch = esmScript.match(/export\s*\{([^}]+)\}\s*;?\s*(\/\/.*)?$/)
|
||||
if (!exportMatch) {
|
||||
throw new Error('Could not find export statement in bundle')
|
||||
}
|
||||
|
||||
const exports = exportMatch[1].split(',').map((e) => {
|
||||
const parts = e.trim().split(/\s+as\s+/)
|
||||
return { local: parts[0].trim(), exported: (parts[1] || parts[0]).trim() }
|
||||
})
|
||||
|
||||
const createSelectorGenerator = exports.find((e) => e.exported === 'createSelectorGenerator')
|
||||
const toLocator = exports.find((e) => e.exported === 'toLocator')
|
||||
|
||||
if (!createSelectorGenerator || !toLocator) {
|
||||
throw new Error('Could not find createSelectorGenerator or toLocator exports')
|
||||
}
|
||||
|
||||
esmScript = esmScript.replace(/export\s*\{[^}]+\}\s*;?\s*(\/\/.*)?$/, '')
|
||||
|
||||
const wrappedScript = `(function() {
|
||||
${esmScript}
|
||||
globalThis.__selectorGenerator = { createSelectorGenerator: ${createSelectorGenerator.local}, toLocator: ${toLocator.local} };
|
||||
})();`
|
||||
|
||||
const outputPath = path.join(distDir, 'selector-generator.js')
|
||||
fs.writeFileSync(outputPath, wrappedScript)
|
||||
console.log(`Saved to ${outputPath}`)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -2809,4 +2809,84 @@ describe('CDP Session Tests', () => {
|
||||
await browser.close()
|
||||
await page.close()
|
||||
}, 60000)
|
||||
|
||||
it('should inject bippy and find React fiber with getReactSource', async () => {
|
||||
const browserContext = getBrowserContext()
|
||||
const serviceWorker = await getExtensionServiceWorker(browserContext)
|
||||
|
||||
const page = await browserContext.newPage()
|
||||
await page.setContent(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script>
|
||||
function MyComponent() {
|
||||
return React.createElement('button', { id: 'react-btn' }, 'Click me');
|
||||
}
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(React.createElement(MyComponent));
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`)
|
||||
await page.bringToFront()
|
||||
|
||||
await serviceWorker.evaluate(async () => {
|
||||
await globalThis.toggleExtensionForActiveTab()
|
||||
})
|
||||
await new Promise(r => setTimeout(r, 500))
|
||||
|
||||
const browser = await chromium.connectOverCDP(getCdpUrl({ port: TEST_PORT }))
|
||||
const pages = browser.contexts()[0].pages()
|
||||
const cdpPage = pages.find(p => p.url().startsWith('about:'))
|
||||
expect(cdpPage).toBeDefined()
|
||||
|
||||
const btn = cdpPage!.locator('#react-btn')
|
||||
const btnCount = await btn.count()
|
||||
expect(btnCount).toBe(1)
|
||||
|
||||
const hasBippyBefore = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
||||
expect(hasBippyBefore).toBe(false)
|
||||
|
||||
const wsUrl = getCdpUrl({ port: TEST_PORT })
|
||||
const cdpSession = await getCDPSessionForPage({ page: cdpPage!, wsUrl })
|
||||
|
||||
const { getReactSource } = await import('./react-source.js')
|
||||
const source = await getReactSource({ locator: btn, cdp: cdpSession })
|
||||
|
||||
const hasBippyAfter = await cdpPage!.evaluate(() => !!(globalThis as any).__bippy)
|
||||
expect(hasBippyAfter).toBe(true)
|
||||
|
||||
const hasFiber = await btn.evaluate((el) => {
|
||||
const bippy = (globalThis as any).__bippy
|
||||
const fiber = bippy.getFiberFromHostInstance(el)
|
||||
return !!fiber
|
||||
})
|
||||
expect(hasFiber).toBe(true)
|
||||
|
||||
const componentName = await btn.evaluate((el) => {
|
||||
const bippy = (globalThis as any).__bippy
|
||||
const fiber = bippy.getFiberFromHostInstance(el)
|
||||
let current = fiber
|
||||
while (current) {
|
||||
if (bippy.isCompositeFiber(current)) {
|
||||
return bippy.getDisplayName(current.type)
|
||||
}
|
||||
current = current.return
|
||||
}
|
||||
return null
|
||||
})
|
||||
expect(componentName).toBe('MyComponent')
|
||||
|
||||
console.log('Component name from fiber:', componentName)
|
||||
console.log('Source location (null for UMD React, works on local dev servers with JSX transform):', source)
|
||||
|
||||
await browser.close()
|
||||
await page.close()
|
||||
}, 60000)
|
||||
})
|
||||
|
||||
+11
-1
@@ -19,6 +19,7 @@ import { getCDPSessionForPage, CDPSession } from './cdp-session.js'
|
||||
import { Debugger } from './debugger.js'
|
||||
import { Editor } from './editor.js'
|
||||
import { getStylesForLocator, formatStylesAsText, type StylesResult } from './styles.js'
|
||||
import { getReactSource, type ReactSourceLocation } from './react-source.js'
|
||||
|
||||
class CodeExecutionTimeoutError extends Error {
|
||||
constructor(timeout: number) {
|
||||
@@ -80,6 +81,7 @@ interface VMContext {
|
||||
createEditor: (options: { cdp: CDPSession }) => Editor
|
||||
getStylesForLocator: (options: { locator: any }) => Promise<StylesResult>
|
||||
formatStylesAsText: (styles: StylesResult) => string
|
||||
getReactSource: (options: { locator: any }) => Promise<ReactSourceLocation | null>
|
||||
require: NodeRequire
|
||||
import: (specifier: string) => Promise<any>
|
||||
}
|
||||
@@ -641,7 +643,8 @@ server.tool(
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const scriptPath = path.join(currentDir, '..', 'dist', 'selector-generator.js')
|
||||
const scriptContent = fs.readFileSync(scriptPath, 'utf-8')
|
||||
await elementPage.addScriptTag({ content: scriptContent })
|
||||
const cdp = await getCDPSession({ page: elementPage })
|
||||
await cdp.send('Runtime.evaluate', { expression: scriptContent })
|
||||
}
|
||||
|
||||
return await element.evaluate((el: any) => {
|
||||
@@ -709,6 +712,11 @@ server.tool(
|
||||
return getStylesForLocator({ locator: options.locator, cdp })
|
||||
}
|
||||
|
||||
const getReactSourceFn = async (options: { locator: any }) => {
|
||||
const cdp = await getCDPSession({ page: options.locator.page() })
|
||||
return getReactSource({ locator: options.locator, cdp })
|
||||
}
|
||||
|
||||
let vmContextObj: VMContextWithGlobals = {
|
||||
page,
|
||||
context,
|
||||
@@ -724,6 +732,7 @@ server.tool(
|
||||
createEditor,
|
||||
getStylesForLocator: getStylesForLocatorFn,
|
||||
formatStylesAsText,
|
||||
getReactSource: getReactSourceFn,
|
||||
resetPlaywright: async () => {
|
||||
const { page: newPage, context: newContext } = await resetConnection()
|
||||
|
||||
@@ -742,6 +751,7 @@ server.tool(
|
||||
createEditor,
|
||||
getStylesForLocator: getStylesForLocatorFn,
|
||||
formatStylesAsText,
|
||||
getReactSource: getReactSourceFn,
|
||||
resetPlaywright: vmContextObj.resetPlaywright,
|
||||
require,
|
||||
// TODO --experimental-vm-modules is needed to make import work in vm
|
||||
|
||||
@@ -158,6 +158,19 @@ you have access to some functions in addition to playwright methods:
|
||||
console.log(formatStylesAsText(styles));
|
||||
```
|
||||
- `formatStylesAsText(styles)`: formats a `StylesResult` object as human-readable text. Use this to display styles in a readable format.
|
||||
- `getReactSource({ locator })`: gets the React component source location (file, line, column) for an element.
|
||||
- `locator`: a Playwright Locator or ElementHandle for the element to inspect
|
||||
- Returns: `{ fileName, lineNumber, columnNumber, componentName }` or `null` if not found
|
||||
- **Important**: Only works on **local dev servers** (localhost with Vite, Next.js, CRA in dev mode). Production builds strip source info. The JSX transform must have `development: true` to include `_debugSource`.
|
||||
- Example:
|
||||
```js
|
||||
const loc = page.locator('.my-component');
|
||||
const source = await getReactSource({ locator: loc });
|
||||
if (source) {
|
||||
console.log(`Component: ${source.componentName}`);
|
||||
console.log(`File: ${source.fileName}:${source.lineNumber}:${source.columnNumber}`);
|
||||
}
|
||||
```
|
||||
|
||||
example:
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import type { Page, Locator, ElementHandle } from 'playwright-core'
|
||||
import type { CDPSession } from './cdp-session.js'
|
||||
|
||||
export interface ReactSourceLocation {
|
||||
fileName: string | null
|
||||
lineNumber: number | null
|
||||
columnNumber: number | null
|
||||
componentName: string | null
|
||||
}
|
||||
|
||||
let bippyCode: string | null = null
|
||||
|
||||
function getBippyCode(): string {
|
||||
if (bippyCode) {
|
||||
return bippyCode
|
||||
}
|
||||
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||||
const bippyPath = path.join(currentDir, '..', 'dist', 'bippy.js')
|
||||
bippyCode = fs.readFileSync(bippyPath, 'utf-8')
|
||||
return bippyCode
|
||||
}
|
||||
|
||||
export async function getReactSource({
|
||||
locator,
|
||||
cdp,
|
||||
}: {
|
||||
locator: Locator | ElementHandle
|
||||
cdp: CDPSession
|
||||
}): Promise<ReactSourceLocation | null> {
|
||||
const page: Page = 'page' in locator && typeof locator.page === 'function' ? locator.page() : (locator as any)._page
|
||||
|
||||
if (!page) {
|
||||
throw new Error('Could not get page from locator')
|
||||
}
|
||||
|
||||
const hasBippy = await page.evaluate(() => !!(globalThis as any).__bippy)
|
||||
|
||||
if (!hasBippy) {
|
||||
const code = getBippyCode()
|
||||
await cdp.send('Runtime.evaluate', { expression: code })
|
||||
}
|
||||
|
||||
const result = await (locator as any).evaluate(async (el: any) => {
|
||||
const bippy = (globalThis as any).__bippy
|
||||
if (!bippy) {
|
||||
throw new Error('bippy not loaded')
|
||||
}
|
||||
|
||||
const fiber = bippy.getFiberFromHostInstance(el)
|
||||
if (!fiber) {
|
||||
return { _notFound: 'fiber' as const }
|
||||
}
|
||||
|
||||
const source = await bippy.getSource(fiber)
|
||||
if (source) {
|
||||
return {
|
||||
fileName: source.fileName ? bippy.normalizeFileName(source.fileName) : null,
|
||||
lineNumber: source.lineNumber ?? null,
|
||||
columnNumber: source.columnNumber ?? null,
|
||||
componentName: source.functionName ?? bippy.getDisplayName(fiber.type) ?? null,
|
||||
}
|
||||
}
|
||||
|
||||
const ownerStack = await bippy.getOwnerStack(fiber)
|
||||
for (const frame of ownerStack) {
|
||||
if (frame.fileName && bippy.isSourceFile(frame.fileName)) {
|
||||
return {
|
||||
fileName: bippy.normalizeFileName(frame.fileName),
|
||||
lineNumber: frame.lineNumber ?? null,
|
||||
columnNumber: frame.columnNumber ?? null,
|
||||
componentName: frame.functionName ?? null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { _notFound: 'source' as const }
|
||||
})
|
||||
|
||||
if (result && '_notFound' in result) {
|
||||
if (result._notFound === 'fiber') {
|
||||
console.warn('[getReactSource] no fiber found - is this a React element?')
|
||||
} else {
|
||||
console.warn('[getReactSource] no source location found - is this a React dev build?')
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
Generated
+48
-2
@@ -38,7 +38,7 @@ importers:
|
||||
version: link:../playwriter
|
||||
zustand:
|
||||
specifier: ^5.0.8
|
||||
version: 5.0.8
|
||||
version: 5.0.8(@types/react@19.2.7)(react@19.2.3)
|
||||
devDependencies:
|
||||
'@types/chrome':
|
||||
specifier: ^0.0.315
|
||||
@@ -124,6 +124,9 @@ importers:
|
||||
'@vitest/ui':
|
||||
specifier: ^4.0.8
|
||||
version: 4.0.8(vitest@4.0.8)
|
||||
bippy:
|
||||
specifier: ^0.5.27
|
||||
version: 0.5.27(@types/react@19.2.7)(react@19.2.3)
|
||||
image-size:
|
||||
specifier: ^2.0.2
|
||||
version: 2.0.2
|
||||
@@ -887,6 +890,14 @@ packages:
|
||||
'@types/node@24.10.1':
|
||||
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
|
||||
|
||||
'@types/react-reconciler@0.28.9':
|
||||
resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/react@19.2.7':
|
||||
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
|
||||
|
||||
'@types/retry@0.12.0':
|
||||
resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==}
|
||||
|
||||
@@ -1090,6 +1101,11 @@ packages:
|
||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
bippy@0.5.27:
|
||||
resolution: {integrity: sha512-LvGYTQm5cMqEhnUT2tr6mSE1mrhmX/xxu3jiypHAscThhxC+9SWdaQW8Nye5CT/sR79LObNJlQpOUgi4DopTMg==}
|
||||
peerDependencies:
|
||||
react: '>=17.0.1'
|
||||
|
||||
body-parser@2.2.0:
|
||||
resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -1228,6 +1244,9 @@ packages:
|
||||
resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
csstype@3.2.3:
|
||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||
|
||||
data-uri-to-buffer@4.0.1:
|
||||
resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==}
|
||||
engines: {node: '>= 12'}
|
||||
@@ -2220,6 +2239,10 @@ packages:
|
||||
resolution: {integrity: sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==}
|
||||
engines: {node: '>= 0.10'}
|
||||
|
||||
react@19.2.3:
|
||||
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
read-yaml-file@1.1.0:
|
||||
resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2727,6 +2750,7 @@ packages:
|
||||
whatwg-encoding@3.1.1:
|
||||
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
|
||||
engines: {node: '>=18'}
|
||||
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
|
||||
|
||||
whatwg-mimetype@4.0.0:
|
||||
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
|
||||
@@ -3622,6 +3646,14 @@ snapshots:
|
||||
dependencies:
|
||||
undici-types: 7.16.0
|
||||
|
||||
'@types/react-reconciler@0.28.9(@types/react@19.2.7)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.7
|
||||
|
||||
'@types/react@19.2.7':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/retry@0.12.0': {}
|
||||
|
||||
'@types/uuid@10.0.0': {}
|
||||
@@ -3818,6 +3850,13 @@ snapshots:
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
bippy@0.5.27(@types/react@19.2.7)(react@19.2.3):
|
||||
dependencies:
|
||||
'@types/react-reconciler': 0.28.9(@types/react@19.2.7)
|
||||
react: 19.2.3
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
body-parser@2.2.0:
|
||||
dependencies:
|
||||
bytes: 3.1.2
|
||||
@@ -3989,6 +4028,8 @@ snapshots:
|
||||
css-tree: 3.1.0
|
||||
optional: true
|
||||
|
||||
csstype@3.2.3: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
||||
data-uri-to-buffer@6.0.2:
|
||||
@@ -5060,6 +5101,8 @@ snapshots:
|
||||
iconv-lite: 0.7.0
|
||||
unpipe: 1.0.0
|
||||
|
||||
react@19.2.3: {}
|
||||
|
||||
read-yaml-file@1.1.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -5660,4 +5703,7 @@ snapshots:
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zustand@5.0.8: {}
|
||||
zustand@5.0.8(@types/react@19.2.7)(react@19.2.3):
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.7
|
||||
react: 19.2.3
|
||||
|
||||
Reference in New Issue
Block a user