diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 00000000..e5b6d8d6 --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 00000000..909247ba --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.5/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": false, + "fixed": [["react-scan", "@react-scan/extension"]], + "linked": [], + "access": "public", + "baseBranch": "main", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..e5cdb034 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,92 @@ +# Contributing to React Scan + +First off, thanks for taking the time to contribute! ❤️ + +## Table of Contents + +- [Contributing to React Scan](#contributing-to-react-scan) + - [Table of Contents](#table-of-contents) + - [Project Structure](#project-structure) + - [Development Setup](#development-setup) + - [Contributing Guidelines](#contributing-guidelines) + - [Commits](#commits) + - [Pull Request Process](#pull-request-process) + - [Development Workflow](#development-workflow) + - [Getting Help](#getting-help) + +## Project Structure + +This is a monorepo containing several packages: + +- `packages/scan` - Core React Scan package +- `packages/vite-plugin-react-scan` - Vite plugin for React Scan +- `packages/extension` - VS Code extension + +## Development Setup + +1. **Clone and Install** + ```bash + git clone https://github.com/aidenybai/react-scan.git + cd react-scan + pnpm install + ``` + +2. **Build all packages** + ```bash + pnpm build + ``` + +3. **Development Mode** + ```bash + # Run all packages in dev mode + pnpm dev + ``` + +## Contributing Guidelines + +### Commits + +We use conventional commits to ensure consistent commit messages: + +- `feat:` New features +- `fix:` Bug fixes +- `docs:` Documentation changes +- `chore:` Maintenance tasks +- `test:` Adding or updating tests +- `refactor:` Code changes that neither fix bugs nor add features + +Example: `fix(scan): fix a typo` + +### Pull Request Process + +1. Fork the repository +2. Create your feature branch (`git checkout -b feat/amazing-feature`) +3. Commit your changes using conventional commits +4. Push to your branch +5. Open a Pull Request +6. Ask for reviews (@pivanov, @RobPruzan are your friends in this journey) + +### Development Workflow + +1. **TypeScript** + - All code must be written in TypeScript + - Ensure strict type checking passes + - No `any` types unless absolutely necessary + +2. **Code Style** + - We use Biome for formatting and linting + - Run `pnpm format` to format code + - Run `pnpm lint` to check for issues + +3. **Documentation** + - Update relevant documentation + - Add JSDoc comments for public APIs + - Update README if needed + +## Getting Help +- Check existing issues +- Create a new issue + +
+ +⚛️ Happy coding! 🚀 diff --git a/package.json b/package.json index f41cfbe0..65d97834 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,14 @@ "pack:bump": "pnpm --filter scan pack:bump", "lint": "biome lint .", "format": "biome format . --write", - "check": "biome check . --write" + "check": "biome check . --write", + "changeset:add": "changeset add", + "bump": "changset add", + "changeset:publish": "changeset publish" }, "devDependencies": { "@biomejs/biome": "^1.9.4", + "@changesets/cli": "^2.27.12", "@types/node": "^22.10.2", "@vercel/style-guide": "^6.0.0", "autoprefixer": "^10.4.20", diff --git a/packages/extension/build/brave-extension-v1.0.4.zip b/packages/extension/build/brave-extension-v1.0.4.zip index 4a1babb8..bf43d92c 100644 Binary files a/packages/extension/build/brave-extension-v1.0.4.zip and b/packages/extension/build/brave-extension-v1.0.4.zip differ diff --git a/packages/extension/build/chrome-extension-v1.0.4.zip b/packages/extension/build/chrome-extension-v1.0.4.zip index 673c781c..f1ddf797 100644 Binary files a/packages/extension/build/chrome-extension-v1.0.4.zip and b/packages/extension/build/chrome-extension-v1.0.4.zip differ diff --git a/packages/extension/build/firefox-extension-v1.0.4.zip b/packages/extension/build/firefox-extension-v1.0.4.zip index 86facd0d..199bf8aa 100644 Binary files a/packages/extension/build/firefox-extension-v1.0.4.zip and b/packages/extension/build/firefox-extension-v1.0.4.zip differ diff --git a/packages/extension/vite.config.ts b/packages/extension/vite.config.ts index 992fc7ce..2143e220 100644 --- a/packages/extension/vite.config.ts +++ b/packages/extension/vite.config.ts @@ -69,8 +69,8 @@ export default defineConfig(({ mode }): UserConfig => { return { name: pkg.name, description: pkg.description, - version: pkg.version, ...manifest, + version: pkg.version, }; }; diff --git a/packages/scan/src/core/index.ts b/packages/scan/src/core/index.ts index 71a540a4..97303836 100644 --- a/packages/scan/src/core/index.ts +++ b/packages/scan/src/core/index.ts @@ -13,10 +13,10 @@ import type { RenderData } from 'src/core/utils'; import { initReactScanInstrumentation } from 'src/new-outlines'; import styles from '~web/assets/css/styles.css'; import { ICONS } from '~web/assets/svgs/svgs'; -import type { States } from '~web/components/inspector/utils'; import { createToolbar, scriptLevelToolbar } from '~web/toolbar'; import { readLocalStorage, saveLocalStorage } from '~web/utils/helpers'; import type { Outline } from '~web/utils/outline'; +import type { States } from '~web/views/inspector/utils'; import type { ChangeReason, Render, diff --git a/packages/scan/src/core/instrumentation.ts b/packages/scan/src/core/instrumentation.ts index 8399d166..7612fe5a 100644 --- a/packages/scan/src/core/instrumentation.ts +++ b/packages/scan/src/core/instrumentation.ts @@ -21,15 +21,15 @@ import { } from 'bippy'; import { isValidElement } from 'preact'; import { isEqual } from '~core/utils'; -import { - collectContextChanges, - collectPropsChanges, - collectStateChanges, -} from '~web/components/inspector/timeline/utils'; import { RENDER_PHASE_STRING_TO_ENUM, type RenderPhase, } from '~web/utils/outline'; +import { + collectContextChanges, + collectPropsChanges, + collectStateChanges, +} from '~web/views/inspector/timeline/utils'; import { type Change, type ContextChange, diff --git a/packages/scan/src/core/monitor/performance.ts b/packages/scan/src/core/monitor/performance.ts index 27803d35..a08b1a9e 100644 --- a/packages/scan/src/core/monitor/performance.ts +++ b/packages/scan/src/core/monitor/performance.ts @@ -1,5 +1,5 @@ import { type Fiber, getDisplayName } from 'bippy'; -import { getCompositeComponentFromElement } from '~web/components/inspector/utils'; +import { getCompositeComponentFromElement } from '~web/views/inspector/utils'; import { Store } from '..'; import type { PerformanceInteraction, diff --git a/packages/scan/src/new-outlines/index.ts b/packages/scan/src/new-outlines/index.ts index 6715f117..5d1aa18f 100644 --- a/packages/scan/src/new-outlines/index.ts +++ b/packages/scan/src/new-outlines/index.ts @@ -8,9 +8,9 @@ import { } from 'bippy'; import { ReactScanInternals, Store, ignoredProps } from '~core/index'; import { createInstrumentation } from '~core/instrumentation'; -import { inspectorUpdateSignal } from '~web/components/inspector/states'; import { readLocalStorage, removeLocalStorage } from '~web/utils/helpers'; import { log, logIntro } from '~web/utils/log'; +import { inspectorUpdateSignal } from '~web/views/inspector/states'; import { OUTLINE_ARRAY_SIZE, drawCanvas, diff --git a/packages/scan/src/web/assets/css/styles.tailwind.css b/packages/scan/src/web/assets/css/styles.tailwind.css index 6c086f2e..f18a290a 100644 --- a/packages/scan/src/web/assets/css/styles.tailwind.css +++ b/packages/scan/src/web/assets/css/styles.tailwind.css @@ -620,3 +620,52 @@ svg { @apply rounded-sm mx-[1px]; } } + +.react-scan-toolbar-notification { + @apply absolute inset-x-0; + @apply flex items-center gap-x-2; + @apply p-1 pl-2 text-[10px]; + @apply text-neutral-300; + @apply bg-black/90; + @apply transition-transform; + + &:before { + @apply content-['']; + @apply absolute inset-x-0; + @apply bg-black; + @apply h-2; + } + + &.position-top { + @apply top-full -translate-y-full; + @apply rounded-b-lg; + + &::before { + @apply top-0 -translate-y-full; + } + } + + &.position-bottom { + @apply bottom-full translate-y-full; + @apply rounded-t-lg; + + &::before { + @apply bottom-0 translate-y-full; + } + + } + + &.is-open { + @apply translate-y-0; + } +} + + +.react-scan-header-item { + @apply absolute inset-0 -translate-y-[200%]; + @apply transition-transform duration-300; + + &.is-visible { + @apply translate-y-0; + } +} diff --git a/packages/scan/src/web/assets/svgs/svgs.ts b/packages/scan/src/web/assets/svgs/svgs.ts index 58c64480..5cbb642d 100644 --- a/packages/scan/src/web/assets/svgs/svgs.ts +++ b/packages/scan/src/web/assets/svgs/svgs.ts @@ -98,5 +98,12 @@ export const ICONS = ` + + + + + + + `; diff --git a/packages/scan/src/web/components/widget/header.tsx b/packages/scan/src/web/components/widget/header.tsx deleted file mode 100644 index 8f7d1081..00000000 --- a/packages/scan/src/web/components/widget/header.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import type { Fiber } from 'bippy'; -import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; -import { Store } from '~core/index'; -import { signalIsSettingsOpen } from '~web/state'; -import { cn, getExtendedDisplayName } from '~web/utils/helpers'; -import { Icon } from '../icon'; -import { timelineState } from '../inspector/states'; -import { getOverrideMethods } from '../inspector/utils'; - -// const REPLAY_DELAY_MS = 300; - -export const BtnReplay = () => { - // const refTimeout = useRef(); - // const replayState = useRef({ - // isReplaying: false, - // toggleDisabled: (disabled: boolean, button: HTMLElement) => { - // button.classList[disabled ? 'add' : 'remove']('disabled'); - // }, - // }); - - const [canEdit, setCanEdit] = useState(false); - const isSettingsOpen = signalIsSettingsOpen.value; - - useEffect(() => { - const { overrideProps } = getOverrideMethods(); - const canEdit = !!overrideProps; - - requestAnimationFrame(() => { - setCanEdit(canEdit); - }); - }, []); - - // const handleReplay = (e: MouseEvent) => { - // e.stopPropagation(); - // const { overrideProps, overrideHookState } = getOverrideMethods(); - // const state = replayState.current; - // const button = e.currentTarget as HTMLElement; - - // const inspectState = Store.inspectState.value; - // if (state.isReplaying || inspectState.kind !== 'focused') return; - - // const { parentCompositeFiber } = getCompositeComponentFromElement( - // inspectState.focusedDomElement, - // ); - // if (!parentCompositeFiber || !overrideProps || !overrideHookState) return; - - // state.isReplaying = true; - // state.toggleDisabled(true, button); - - // void replayComponent(parentCompositeFiber) - // .catch(() => void 0) - // .finally(() => { - // clearTimeout(refTimeout.current); - // if (document.hidden) { - // state.isReplaying = false; - // state.toggleDisabled(false, button); - // } else { - // refTimeout.current = setTimeout(() => { - // state.isReplaying = false; - // state.toggleDisabled(false, button); - // }, REPLAY_DELAY_MS); - // } - // }); - // }; - - if (!canEdit) return null; - - return ( - - ); -}; -// const useSubscribeFocusedFiber = (onUpdate: () => void) => { -// // biome-ignore lint/correctness/useExhaustiveDependencies: no deps -// useEffect(() => { -// const subscribe = () => { -// if (Store.inspectState.value.kind !== 'focused') { -// return; -// } -// onUpdate(); -// }; - -// const unSubReportTime = Store.lastReportTime.subscribe(subscribe); -// const unSubState = Store.inspectState.subscribe(subscribe); -// return () => { -// unSubReportTime(); -// unSubState(); -// }; -// }, []); -// }; - -const HeaderInspect = () => { - const refReRenders = useRef(null); - const refTiming = useRef(null); - const isSettingsOpen = signalIsSettingsOpen.value; - const [currentFiber, setCurrentFiber] = useState(null); - - useEffect(() => { - const unSubState = Store.inspectState.subscribe((state) => { - if (state.kind !== 'focused') return; - - const fiber = state.fiber; - if (!fiber) return; - - setCurrentFiber(fiber); - }); - - return unSubState; - }, []); - - useEffect(() => { - const unSubTimeline = timelineState.subscribe((state) => { - if (Store.inspectState.value.kind !== 'focused') return; - if (!refReRenders.current || !refTiming.current) return; - - const { totalUpdates, currentIndex, updates, isVisible, windowOffset } = - state; - - const reRenders = Math.max(0, totalUpdates - 1); - const headerText = isVisible - ? `#${windowOffset + currentIndex} Re-render` - : `${reRenders} Re-renders`; - - let formattedTime: string | undefined; - if (reRenders > 0 && currentIndex >= 0 && currentIndex < updates.length) { - const time = updates[currentIndex]?.fiberInfo?.selfTime; - formattedTime = - time > 0 - ? time < 0.1 - Number.EPSILON - ? '< 0.1ms' - : `${Number(time.toFixed(1))}ms` - : undefined; - } - - refReRenders.current.dataset.text = `${headerText}${reRenders > 0 && formattedTime ? ' •' : ''}`; - if (formattedTime) { - refTiming.current.dataset.text = formattedTime; - } - }); - - return unSubTimeline; - }, []); - - const componentName = useMemo(() => { - if (!currentFiber) return null; - const { name, wrappers, wrapperTypes } = getExtendedDisplayName(currentFiber); - - const title = wrappers.length - ? `${wrappers.join('(')}(${name})${')'.repeat(wrappers.length)}` - : name ?? ''; - - const firstWrapperType = wrapperTypes[0]; - return ( - - {name ?? 'Unknown'} - - { - !!firstWrapperType && ( - <> - - {firstWrapperType.type} - - {firstWrapperType.compiler && ( - - )} - - ) - } - - { - wrapperTypes.length > 1 && ( - - ×{wrapperTypes.length - 1} - - ) - } - - {' • '} - - - ); - }, [currentFiber]); - - return ( -
- {componentName} -
- - -
-
- ); -}; - -const HeaderSettings = () => { - const isSettingsOpen = signalIsSettingsOpen.value; - return ( - - ); -}; - -export const Header = () => { - const handleClose = () => { - if (signalIsSettingsOpen.value) { - signalIsSettingsOpen.value = false; - return; - } - - Store.inspectState.value = { - kind: 'inspect-off', - }; - }; - - return ( -
-
- - -
- - {/* */} - {/* {Store.inspectState.value.kind !== 'inspect-off' && } */} - -
- ); -}; diff --git a/packages/scan/src/web/state.ts b/packages/scan/src/web/state.ts index 3605b119..72388d53 100644 --- a/packages/scan/src/web/state.ts +++ b/packages/scan/src/web/state.ts @@ -1,9 +1,4 @@ import { signal } from '@preact/signals'; -import type { - Corner, - WidgetConfig, - WidgetSettings, -} from './components/widget/types'; import { LOCALSTORAGE_KEY, MIN_CONTAINER_WIDTH, @@ -11,6 +6,7 @@ import { SAFE_AREA, } from './constants'; import { readLocalStorage, saveLocalStorage } from './utils/helpers'; +import type { Corner, WidgetConfig, WidgetSettings } from './widget/types'; export const signalIsSettingsOpen = signal(false); export const signalRefWidget = signal(null); @@ -83,3 +79,37 @@ export const updateDimensions = (): void => { }, }; }; + +export interface SlowDowns { + slowDowns: number; + hideNotification: boolean; +} + +export const signalSlowDowns = signal({ + slowDowns: 0, + hideNotification: false, +}); + +export type WidgetStates = + | { + view: 'none'; + } + | { + view: 'inspector'; + // extra params + } + | { + view: 'settings'; + // extra params + } + | { + view: 'slow-downs'; + // extra params + } + | { + view: 'summary'; + // extra params + }; +export const signalWidgetViews = signal({ + view: 'none', +}); diff --git a/packages/scan/src/web/toolbar.tsx b/packages/scan/src/web/toolbar.tsx index 84d0023d..b0cb229c 100644 --- a/packages/scan/src/web/toolbar.tsx +++ b/packages/scan/src/web/toolbar.tsx @@ -1,6 +1,6 @@ import { Component, render } from 'preact'; import { Icon } from './components/icon'; -import { Widget } from './components/widget'; +import { Widget } from './widget'; export let scriptLevelToolbar: HTMLDivElement | null = null diff --git a/packages/scan/src/web/utils/pin.ts b/packages/scan/src/web/utils/pin.ts index 53754442..46f0d279 100644 --- a/packages/scan/src/web/utils/pin.ts +++ b/packages/scan/src/web/utils/pin.ts @@ -1,6 +1,6 @@ import type { Fiber } from 'bippy'; import { Store } from '~core/index'; -import { findComponentDOMNode } from '~web/components/inspector/utils'; +import { findComponentDOMNode } from '~web/views/inspector/utils'; import { readLocalStorage } from './helpers'; export interface FiberMetadata { diff --git a/packages/scan/src/web/views/index.tsx b/packages/scan/src/web/views/index.tsx new file mode 100644 index 00000000..4fa38825 --- /dev/null +++ b/packages/scan/src/web/views/index.tsx @@ -0,0 +1,109 @@ +import type { ReactNode } from 'preact/compat'; +import { useEffect, useRef } from 'preact/hooks'; +import { Store } from '~core/index'; +import { signalWidgetViews } from '~web/state'; +import { cn, toggleMultipleClasses } from '~web/utils/helpers'; +import { Header } from '~web/widget/header'; +import { ViewInspector } from './inspector'; +import { ViewSlowDowns } from './slow-downs'; +import { Toolbar } from './toolbar'; + +export const Content = () => { + const refContainer = useRef(null); + + useEffect(() => { + const unsubscribeStoreInspectState = Store.inspectState.subscribe( + (state) => { + if (!refContainer.current) return; + if (state.kind === 'inspecting') { + toggleMultipleClasses(refContainer.current, [ + 'opacity-0', + 'duration-0', + 'delay-0', + ]); + } + }, + ); + + return unsubscribeStoreInspectState; + }, []); + + return ( +
+
+
+
+ + + + + + + + +
+
+ +
+ ); +}; + +interface ContentViewProps { + isOpen: boolean; + children: ReactNode; +} + +const ContentView = ({ isOpen, children }: ContentViewProps) => { + return ( +
+
+ {children} +
+
+ ); +}; diff --git a/packages/scan/src/web/components/widget/components-tree/breadcrumb.tsx b/packages/scan/src/web/views/inspector/components-tree/breadcrumb.tsx similarity index 97% rename from packages/scan/src/web/components/widget/components-tree/breadcrumb.tsx rename to packages/scan/src/web/views/inspector/components-tree/breadcrumb.tsx index fe54acda..92ca6f6c 100644 --- a/packages/scan/src/web/components/widget/components-tree/breadcrumb.tsx +++ b/packages/scan/src/web/views/inspector/components-tree/breadcrumb.tsx @@ -1,11 +1,9 @@ import { useEffect, useRef, useState } from 'preact/hooks'; import { Store } from '~core/index'; import { Icon } from '~web/components/icon'; -import { - getCompositeFiberFromElement, - getInspectableAncestors, -} from '~web/components/inspector/utils'; + import { cn } from '~web/utils/helpers'; +import { getCompositeFiberFromElement, getInspectableAncestors } from '../utils'; import { type TreeItem, signalSkipTreeUpdate } from './state'; export const Breadcrumb = ({ selectedElement }: { selectedElement: HTMLElement | null }) => { diff --git a/packages/scan/src/web/components/widget/components-tree/index.tsx b/packages/scan/src/web/views/inspector/components-tree/index.tsx similarity index 89% rename from packages/scan/src/web/components/widget/components-tree/index.tsx rename to packages/scan/src/web/views/inspector/components-tree/index.tsx index e40fc0b1..b8761153 100644 --- a/packages/scan/src/web/components/widget/components-tree/index.tsx +++ b/packages/scan/src/web/views/inspector/components-tree/index.tsx @@ -7,11 +7,6 @@ import { } from 'preact/hooks'; import { Store } from '~core/index'; import { Icon } from '~web/components/icon'; -import { inspectorUpdateSignal } from '~web/components/inspector/states'; -import { - type InspectableElement, - getInspectableElements, -} from '~web/components/inspector/utils'; import { LOCALSTORAGE_KEY, MIN_CONTAINER_WIDTH, @@ -25,7 +20,12 @@ import { saveLocalStorage, } from '~web/utils/helpers'; import { getFiberPath } from '~web/utils/pin'; -import { getCompositeComponentFromElement } from '../../inspector/utils'; +import { inspectorUpdateSignal } from '../states'; +import { + type InspectableElement, + getCompositeComponentFromElement, + getInspectableElements, +} from '../utils'; import { Breadcrumb } from './breadcrumb'; import { type FlattenedNode, @@ -78,7 +78,10 @@ const calculateIndentSize = (containerWidth: number, maxDepth: number) => { if (availableSpace < MIN_TOTAL_INDENT) return MIN_INDENT; // Otherwise, calculate based on available space - const targetTotalIndent = Math.min(availableSpace * 0.3, maxDepth * MAX_INDENT); + const targetTotalIndent = Math.min( + availableSpace * 0.3, + maxDepth * MAX_INDENT, + ); const baseIndent = targetTotalIndent / maxDepth; return Math.max(MIN_INDENT, Math.min(MAX_INDENT, baseIndent)); @@ -125,7 +128,10 @@ const isValidTypeSearch = (typeSearches: string[]) => { return true; }; -const matchesTypeSearch = (typeSearches: string[], wrapperTypes: Array<{ type: string }>) => { +const matchesTypeSearch = ( + typeSearches: string[], + wrapperTypes: Array<{ type: string }>, +) => { if (typeSearches.length === 0) return true; if (!wrapperTypes.length) return false; @@ -142,7 +148,10 @@ const matchesTypeSearch = (typeSearches: string[], wrapperTypes: Array<{ type: s return true; }; -const useNodeHighlighting = (node: FlattenedNode, searchValue: typeof searchState.value) => { +const useNodeHighlighting = ( + node: FlattenedNode, + searchValue: typeof searchState.value, +) => { return useMemo(() => { const { query, matches } = searchValue; const isMatch = matches.some((match) => match.nodeId === node.nodeId); @@ -152,7 +161,7 @@ const useNodeHighlighting = (node: FlattenedNode, searchValue: typeof searchStat if (!query || !isMatch) { return { highlightedText: {node.label}, - typeHighlight: false + typeHighlight: false, }; } @@ -217,7 +226,7 @@ const useNodeHighlighting = (node: FlattenedNode, searchValue: typeof searchStat return { highlightedText: textContent, - typeHighlight: matchesType && typeSearches.length > 0 + typeHighlight: matchesType && typeSearches.length > 0, }; }, [node.label, node.nodeId, node.fiber, searchValue]); }; @@ -238,13 +247,20 @@ const TreeNodeItem = ({ } }, [node.element, onElementClick]); - const handleToggle = useCallback(() => { - if (hasChildren) { - onToggle(node.nodeId); - } - }, [hasChildren, node.nodeId, onToggle]); + const handleToggle = useCallback( + (e: MouseEvent) => { + if (hasChildren) { + e.stopPropagation(); + onToggle(node.nodeId); + } + }, + [hasChildren, node.nodeId, onToggle], + ); - const { highlightedText, typeHighlight } = useNodeHighlighting(node, searchValue); + const { highlightedText, typeHighlight } = useNodeHighlighting( + node, + searchValue, + ); const componentTypes = useMemo(() => { if (!node.fiber) return null; @@ -292,7 +308,7 @@ const TreeNodeItem = ({ title={node.title} className={cn( 'flex items-center gap-x-1', - 'px-2', + 'pl-1 pr-2', 'w-full h-7', 'text-left', 'rounded', @@ -303,13 +319,13 @@ const TreeNodeItem = ({ - - - )} + - ) - : !!flattenedNodes.length && ( - - {flattenedNodes.length} - - ) - } + )} + + + ) : ( + !!flattenedNodes.length && ( + + {flattenedNodes.length} + + ) + )} diff --git a/packages/scan/src/web/components/widget/components-tree/state.ts b/packages/scan/src/web/views/inspector/components-tree/state.ts similarity index 100% rename from packages/scan/src/web/components/widget/components-tree/state.ts rename to packages/scan/src/web/views/inspector/components-tree/state.ts diff --git a/packages/scan/src/web/components/inspector/diff-value.tsx b/packages/scan/src/web/views/inspector/diff-value.tsx similarity index 97% rename from packages/scan/src/web/components/inspector/diff-value.tsx rename to packages/scan/src/web/views/inspector/diff-value.tsx index 2711f479..53a996a7 100644 --- a/packages/scan/src/web/components/inspector/diff-value.tsx +++ b/packages/scan/src/web/views/inspector/diff-value.tsx @@ -1,7 +1,7 @@ import { useMemo, useState } from 'preact/hooks'; +import { CopyToClipboard } from '~web/components/copy-to-clipboard'; +import { Icon } from '~web/components/icon'; import { cn } from '~web/utils/helpers'; -import { CopyToClipboard } from '../copy-to-clipboard'; -import { Icon } from '../icon'; import { formatForClipboard, formatValuePreview, safeGetValue } from './utils'; export const DiffValueView = ({ diff --git a/packages/scan/src/web/components/inspector/flash-overlay.ts b/packages/scan/src/web/views/inspector/flash-overlay.ts similarity index 100% rename from packages/scan/src/web/components/inspector/flash-overlay.ts rename to packages/scan/src/web/views/inspector/flash-overlay.ts diff --git a/packages/scan/src/web/views/inspector/header.tsx b/packages/scan/src/web/views/inspector/header.tsx new file mode 100644 index 00000000..3fede388 --- /dev/null +++ b/packages/scan/src/web/views/inspector/header.tsx @@ -0,0 +1,127 @@ +import type { Fiber } from 'bippy'; +import { useEffect, useMemo, useRef, useState } from 'preact/hooks'; +import { Store } from '~core/index'; +import { signalIsSettingsOpen } from '~web/state'; +import { cn, getExtendedDisplayName } from '~web/utils/helpers'; +import { timelineState } from './states'; + +export const HeaderInspect = () => { + const refReRenders = useRef(null); + const refTiming = useRef(null); + const isSettingsOpen = signalIsSettingsOpen.value; + const [currentFiber, setCurrentFiber] = useState(null); + + useEffect(() => { + const unSubState = Store.inspectState.subscribe((state) => { + if (state.kind !== 'focused') return; + + const fiber = state.fiber; + if (!fiber) return; + + setCurrentFiber(fiber); + }); + + return unSubState; + }, []); + + useEffect(() => { + const unSubTimeline = timelineState.subscribe((state) => { + if (Store.inspectState.value.kind !== 'focused') return; + if (!refReRenders.current || !refTiming.current) return; + + const { totalUpdates, currentIndex, updates, isVisible, windowOffset } = + state; + + const reRenders = Math.max(0, totalUpdates - 1); + const headerText = isVisible + ? `#${windowOffset + currentIndex} Re-render` + : `${reRenders} Re-renders`; + + let formattedTime: string | undefined; + if (reRenders > 0 && currentIndex >= 0 && currentIndex < updates.length) { + const time = updates[currentIndex]?.fiberInfo?.selfTime; + formattedTime = + time > 0 + ? time < 0.1 - Number.EPSILON + ? '< 0.1ms' + : `${Number(time.toFixed(1))}ms` + : undefined; + } + + refReRenders.current.dataset.text = `${headerText}${reRenders > 0 && formattedTime ? ' •' : ''}`; + if (formattedTime) { + refTiming.current.dataset.text = formattedTime; + } + }); + + return unSubTimeline; + }, []); + + const componentName = useMemo(() => { + if (!currentFiber) return null; + const { name, wrappers, wrapperTypes } = + getExtendedDisplayName(currentFiber); + + const title = wrappers.length + ? `${wrappers.join('(')}(${name})${')'.repeat(wrappers.length)}` + : (name ?? ''); + + const firstWrapperType = wrapperTypes[0]; + return ( + + {name ?? 'Unknown'} + + {!!firstWrapperType && ( + <> + + {firstWrapperType.type} + + {firstWrapperType.compiler && ( + + )} + + )} + + {wrapperTypes.length > 1 && ( + + ×{wrapperTypes.length - 1} + + )} + {' • '} + + ); + }, [currentFiber]); + + return ( +
+ {componentName} +
+ + +
+
+ ); +}; diff --git a/packages/scan/src/web/components/inspector/index.tsx b/packages/scan/src/web/views/inspector/index.tsx similarity index 93% rename from packages/scan/src/web/components/inspector/index.tsx rename to packages/scan/src/web/views/inspector/index.tsx index 909b64b4..925d07b4 100644 --- a/packages/scan/src/web/components/inspector/index.tsx +++ b/packages/scan/src/web/views/inspector/index.tsx @@ -2,11 +2,12 @@ import type { Fiber } from 'bippy'; import { Component } from 'preact'; import { useEffect, useRef } from 'preact/hooks'; import { Store } from '~core/index'; +import { Icon } from '~web/components/icon'; +import { StickySection } from '~web/components/sticky-section'; import { signalIsSettingsOpen } from '~web/state'; import { cn } from '~web/utils/helpers'; import { constant } from '~web/utils/preact/constant'; -import { Icon } from '../icon'; -import { StickySection } from '../sticky-section'; +import { ComponentsTree } from './components-tree'; import { flashManager } from './flash-overlay'; import { PropertySection } from './properties'; import { @@ -72,7 +73,7 @@ class InspectorErrorBoundary extends Component { } } -export const Inspector = constant(() => { +const Inspector = constant(() => { const refInspector = useRef(null); const refLastInspectedFiber = useRef(null); const isSettingsOpen = signalIsSettingsOpen.value; @@ -195,3 +196,14 @@ export const Inspector = constant(() => { ); }); + + +export const ViewInspector = constant(() => { + if (Store.inspectState.value.kind !== 'focused') return null; + return ( + + + + + ); +}); diff --git a/packages/scan/src/web/components/inspector/logging.ts b/packages/scan/src/web/views/inspector/logging.ts similarity index 100% rename from packages/scan/src/web/components/inspector/logging.ts rename to packages/scan/src/web/views/inspector/logging.ts diff --git a/packages/scan/src/web/components/inspector/overlay/index.tsx b/packages/scan/src/web/views/inspector/overlay/index.tsx similarity index 98% rename from packages/scan/src/web/components/inspector/overlay/index.tsx rename to packages/scan/src/web/views/inspector/overlay/index.tsx index 5c21354d..faa26da5 100644 --- a/packages/scan/src/web/components/inspector/overlay/index.tsx +++ b/packages/scan/src/web/views/inspector/overlay/index.tsx @@ -1,16 +1,17 @@ import { type Fiber, getDisplayName } from 'bippy'; import { useEffect, useRef } from 'preact/hooks'; import { ReactScanInternals, Store } from '~core/index'; + +import { signalIsSettingsOpen, signalWidgetViews } from '~web/state'; +import { cn, throttle } from '~web/utils/helpers'; +import { lerp } from '~web/utils/lerp'; import { type States, findComponentDOMNode, getAssociatedFiberRect, getCompositeComponentFromElement, nonVisualTags, -} from '~web/components/inspector/utils'; -import { signalIsSettingsOpen } from '~web/state'; -import { cn, throttle } from '~web/utils/helpers'; -import { lerp } from '~web/utils/lerp'; +} from '../utils'; type DrawKind = 'locked' | 'inspecting'; @@ -520,6 +521,10 @@ export const ScanOverlay = () => { return; } + signalWidgetViews.value = { + view: 'none', + }; + if (state.kind === 'focused' || state.kind === 'inspecting') { e.preventDefault(); e.stopPropagation(); @@ -583,6 +588,10 @@ export const ScanOverlay = () => { refLastHoveredElement.current = state.focusedDomElement; } + signalWidgetViews.value = { + view: 'inspector', + }; + drawHoverOverlay(state.focusedDomElement, canvas, ctx, 'locked'); unsubReport = Store.lastReportTime.subscribe(() => { diff --git a/packages/scan/src/web/components/inspector/properties.tsx b/packages/scan/src/web/views/inspector/properties.tsx similarity index 100% rename from packages/scan/src/web/components/inspector/properties.tsx rename to packages/scan/src/web/views/inspector/properties.tsx diff --git a/packages/scan/src/web/components/inspector/states.ts b/packages/scan/src/web/views/inspector/states.ts similarity index 100% rename from packages/scan/src/web/components/inspector/states.ts rename to packages/scan/src/web/views/inspector/states.ts diff --git a/packages/scan/src/web/components/inspector/timeline/index.tsx b/packages/scan/src/web/views/inspector/timeline/index.tsx similarity index 95% rename from packages/scan/src/web/components/inspector/timeline/index.tsx rename to packages/scan/src/web/views/inspector/timeline/index.tsx index 41662547..70a63498 100644 --- a/packages/scan/src/web/components/inspector/timeline/index.tsx +++ b/packages/scan/src/web/views/inspector/timeline/index.tsx @@ -2,8 +2,8 @@ import { isInstrumentationActive } from 'bippy'; import { memo } from 'preact/compat'; import { useCallback, useEffect, useMemo, useRef } from 'preact/hooks'; import { Icon } from '~web/components/icon'; +import { Slider } from '~web/components/slider'; import type { useMergedRefs } from '~web/hooks/use-merged-refs'; -import { Slider } from '../../slider'; import { timelineActions, timelineState, @@ -12,8 +12,8 @@ import { calculateSliderValues } from '../utils'; interface TimelineProps { refSticky?: - | ReturnType> - | ((node: HTMLElement | null) => void); + | ReturnType> + | ((node: HTMLElement | null) => void); } export const Timeline = memo(({ diff --git a/packages/scan/src/web/components/inspector/timeline/utils.ts b/packages/scan/src/web/views/inspector/timeline/utils.ts similarity index 100% rename from packages/scan/src/web/components/inspector/timeline/utils.ts rename to packages/scan/src/web/views/inspector/timeline/utils.ts diff --git a/packages/scan/src/web/components/inspector/utils.ts b/packages/scan/src/web/views/inspector/utils.ts similarity index 100% rename from packages/scan/src/web/components/inspector/utils.ts rename to packages/scan/src/web/views/inspector/utils.ts diff --git a/packages/scan/src/web/components/inspector/what-changed.tsx b/packages/scan/src/web/views/inspector/what-changed.tsx similarity index 95% rename from packages/scan/src/web/components/inspector/what-changed.tsx rename to packages/scan/src/web/views/inspector/what-changed.tsx index aaf8911a..075108aa 100644 --- a/packages/scan/src/web/components/inspector/what-changed.tsx +++ b/packages/scan/src/web/views/inspector/what-changed.tsx @@ -10,10 +10,10 @@ import { import { isEqual } from '~core/utils'; import { CopyToClipboard } from '~web/components/copy-to-clipboard'; import { Icon } from '~web/components/icon'; +import { StickySection } from '~web/components/sticky-section'; import type { useMergedRefs } from '~web/hooks/use-merged-refs'; import { cn } from '~web/utils/helpers'; import { throttle } from '~web/utils/helpers'; -import { StickySection } from '../sticky-section'; import { DiffValueView } from './diff-value'; import { type MinimalFiberInfo, timelineState } from './states'; import { Timeline } from './timeline'; @@ -625,7 +625,7 @@ const DiffChange = ({ }; title: string; renderName: (name: string) => ReactNode; - change: Change; + change: Change; expandedFns: Set; setExpandedFns: (updater: (prev: Set) => Set) => void; }) => { @@ -646,7 +646,7 @@ const DiffChange = ({ if (title === 'Props') { path = diffChange.path.length > 0 - ? `${renderName(String(change.name))}.${formatPath(diffChange.path)}` + ? `${renderName(String(change.name))}.${formatPath(diffChange.path)}` : undefined; } if (title === 'State' && diffChange.path.length > 0) { @@ -679,17 +679,17 @@ const DiffChange = ({ onClick={ isFunction ? () => { - const fnKey = `${formatPath(diffChange.path)}-prev`; - setExpandedFns((prev) => { - const next = new Set(prev); - if (next.has(fnKey)) { - next.delete(fnKey); - } else { - next.add(fnKey); - } - return next; - }); - } + const fnKey = `${formatPath(diffChange.path)}-prev`; + setExpandedFns((prev) => { + const next = new Set(prev); + if (next.has(fnKey)) { + next.delete(fnKey); + } else { + next.add(fnKey); + } + return next; + }); + } : undefined } > @@ -700,7 +700,7 @@ const DiffChange = ({ ) : isFunction ? (
- + {formatFunctionPreview( prevDiffValue as (...args: unknown[]) => unknown, expandedFns.has(`${formatPath(diffChange.path)}-prev`), @@ -709,7 +709,7 @@ const DiffChange = ({ {typeof prevDiffValue === 'function' && ( {({ ClipboardIcon }) => <>{ClipboardIcon}} @@ -758,17 +758,17 @@ const DiffChange = ({ onClick={ isFunction ? () => { - const fnKey = `${formatPath(diffChange.path)}-current`; - setExpandedFns((prev) => { - const next = new Set(prev); - if (next.has(fnKey)) { - next.delete(fnKey); - } else { - next.add(fnKey); - } - return next; - }); - } + const fnKey = `${formatPath(diffChange.path)}-current`; + setExpandedFns((prev) => { + const next = new Set(prev); + if (next.has(fnKey)) { + next.delete(fnKey); + } else { + next.add(fnKey); + } + return next; + }); + } : undefined } > @@ -788,7 +788,7 @@ const DiffChange = ({ {typeof currDiffValue === 'function' && ( {({ ClipboardIcon }) => <>{ClipboardIcon}} @@ -837,7 +837,7 @@ const ReferenceOnlyChange = ({ }: { prevValue: unknown; currValue: unknown; - entryKey: string | number; + entryKey: string | number; expandedFns: Set; setExpandedFns: (updater: (prev: Set) => Set) => void; }) => { @@ -902,10 +902,10 @@ const CountBadge = ({ isFunction, showWarning, }: { - count: number; - forceFlash: boolean; - isFunction: boolean; - showWarning: boolean; + count: number; + forceFlash: boolean; + isFunction: boolean; + showWarning: boolean; }) => { const refTimer = useRef(); const refIsFirstRender = useRef(true); diff --git a/packages/scan/src/web/views/settings/header.tsx b/packages/scan/src/web/views/settings/header.tsx new file mode 100644 index 00000000..7fd46660 --- /dev/null +++ b/packages/scan/src/web/views/settings/header.tsx @@ -0,0 +1,20 @@ +import { signalIsSettingsOpen } from "~web/state"; +import { cn } from "~web/utils/helpers"; + +export const HeaderSettings = () => { + const isSettingsOpen = signalIsSettingsOpen.value; + return ( + + ); +}; diff --git a/packages/scan/src/web/views/slow-downs/header.tsx b/packages/scan/src/web/views/slow-downs/header.tsx new file mode 100644 index 00000000..b710ac5d --- /dev/null +++ b/packages/scan/src/web/views/slow-downs/header.tsx @@ -0,0 +1,15 @@ +import { Icon } from "~web/components/icon" +import { cn } from "~web/utils/helpers" + +export const HeaderSlowDowns = () => { + return ( +
+ + Slow Downs +
+ ) +} diff --git a/packages/scan/src/web/views/slow-downs/index.tsx b/packages/scan/src/web/views/slow-downs/index.tsx new file mode 100644 index 00000000..a7978c77 --- /dev/null +++ b/packages/scan/src/web/views/slow-downs/index.tsx @@ -0,0 +1,7 @@ +export const ViewSlowDowns = () => { + return ( +
+ Slow Downs goes here +
+ ) +} diff --git a/packages/scan/src/web/views/slow-downs/toolbar-notification.tsx b/packages/scan/src/web/views/slow-downs/toolbar-notification.tsx new file mode 100644 index 00000000..cc595ed6 --- /dev/null +++ b/packages/scan/src/web/views/slow-downs/toolbar-notification.tsx @@ -0,0 +1,64 @@ +import { useCallback } from "preact/hooks"; +import { Store } from "~core/index"; +import { Icon } from "~web/components/icon"; +import { useDelayedValue } from "~web/hooks/use-mount-delay"; +import { signalSlowDowns, signalWidget, signalWidgetViews } from "~web/state"; +import { cn } from "~web/utils/helpers"; + +export const ToolbarNotification = () => { + const slowDowns = signalSlowDowns.value.slowDowns; + const hideNotification = signalSlowDowns.value.hideNotification; + const isMounted = useDelayedValue(slowDowns > 0 && !hideNotification, 0, 200); + const isOpen = useDelayedValue(isMounted && !hideNotification, 100, 100); + + // biome-ignore lint/suspicious/noExplicitAny: + (window as any).slowDowns = signalSlowDowns; + + const handleOpen = useCallback(() => { + Store.inspectState.value = { + kind: 'inspect-off', + }; + signalWidgetViews.value = { + view: 'slow-downs' + }; + }, []); + + const handleClose = useCallback((e: Event) => { + e.stopPropagation(); + signalSlowDowns.value = { + ...signalSlowDowns.value, + hideNotification: true, + }; + }, []); + + const corner = signalWidget.value.corner; + const isWidgetTopOfTheScreen = ['top-left', 'top-right'].includes(corner); + + if (!isMounted) return null; + + return ( + + + ); +}; diff --git a/packages/scan/src/web/components/widget/toolbar/index.tsx b/packages/scan/src/web/views/toolbar/index.tsx similarity index 99% rename from packages/scan/src/web/components/widget/toolbar/index.tsx rename to packages/scan/src/web/views/toolbar/index.tsx index b856cabe..1d22e3fb 100644 --- a/packages/scan/src/web/components/widget/toolbar/index.tsx +++ b/packages/scan/src/web/views/toolbar/index.tsx @@ -7,10 +7,10 @@ import { } from '~core/index'; import { Icon } from '~web/components/icon'; import { Toggle } from '~web/components/toggle'; -import FpsMeter from '~web/components/widget/fps-meter'; import { signalIsSettingsOpen } from '~web/state'; import { cn, readLocalStorage, saveLocalStorage } from '~web/utils/helpers'; import { constant } from '~web/utils/preact/constant'; +import FpsMeter from '~web/widget/fps-meter'; export const Toolbar = constant(() => { const refSettingsButton = useRef(null); diff --git a/packages/scan/src/web/components/widget/fps-meter.tsx b/packages/scan/src/web/widget/fps-meter.tsx similarity index 100% rename from packages/scan/src/web/components/widget/fps-meter.tsx rename to packages/scan/src/web/widget/fps-meter.tsx diff --git a/packages/scan/src/web/widget/header.tsx b/packages/scan/src/web/widget/header.tsx new file mode 100644 index 00000000..84f521cf --- /dev/null +++ b/packages/scan/src/web/widget/header.tsx @@ -0,0 +1,168 @@ +import { useEffect, useState } from 'preact/hooks'; +import { Store } from '~core/index'; +import { Icon } from '~web/components/icon'; +import { useDelayedValue } from '~web/hooks/use-mount-delay'; +import { signalWidgetViews } from '~web/state'; +import { cn } from '~web/utils/helpers'; +import { HeaderInspect } from '~web/views/inspector/header'; +import { getOverrideMethods } from '~web/views/inspector/utils'; +import { HeaderSettings } from '~web/views/settings/header'; +import { HeaderSlowDowns } from '~web/views/slow-downs/header'; + +// const REPLAY_DELAY_MS = 300; + +export const BtnReplay = () => { + // const refTimeout = useRef(); + // const replayState = useRef({ + // isReplaying: false, + // toggleDisabled: (disabled: boolean, button: HTMLElement) => { + // button.classList[disabled ? 'add' : 'remove']('disabled'); + // }, + // }); + + const [canEdit, setCanEdit] = useState(false); + + useEffect(() => { + const { overrideProps } = getOverrideMethods(); + const canEdit = !!overrideProps; + + requestAnimationFrame(() => { + setCanEdit(canEdit); + }); + }, []); + + // const handleReplay = (e: MouseEvent) => { + // e.stopPropagation(); + // const { overrideProps, overrideHookState } = getOverrideMethods(); + // const state = replayState.current; + // const button = e.currentTarget as HTMLElement; + + // const inspectState = Store.inspectState.value; + // if (state.isReplaying || inspectState.kind !== 'focused') return; + + // const { parentCompositeFiber } = getCompositeComponentFromElement( + // inspectState.focusedDomElement, + // ); + // if (!parentCompositeFiber || !overrideProps || !overrideHookState) return; + + // state.isReplaying = true; + // state.toggleDisabled(true, button); + + // void replayComponent(parentCompositeFiber) + // .catch(() => void 0) + // .finally(() => { + // clearTimeout(refTimeout.current); + // if (document.hidden) { + // state.isReplaying = false; + // state.toggleDisabled(false, button); + // } else { + // refTimeout.current = setTimeout(() => { + // state.isReplaying = false; + // state.toggleDisabled(false, button); + // }, REPLAY_DELAY_MS); + // } + // }); + // }; + + if (!canEdit) return null; + + return ( + + ); +}; + +// const useSubscribeFocusedFiber = (onUpdate: () => void) => { +// // biome-ignore lint/correctness/useExhaustiveDependencies: no deps +// useEffect(() => { +// const subscribe = () => { +// if (Store.inspectState.value.kind !== 'focused') { +// return; +// } +// onUpdate(); +// }; + +// const unSubReportTime = Store.lastReportTime.subscribe(subscribe); +// const unSubState = Store.inspectState.subscribe(subscribe); +// return () => { +// unSubReportTime(); +// unSubState(); +// }; +// }, []); +// }; + +export const Header = () => { + const isInitialView = useDelayedValue( + Store.inspectState.value.kind === 'focused', + 150, + 0, + ); + const handleClose = () => { + signalWidgetViews.value = { + view: 'none' + }; + Store.inspectState.value = { + kind: 'inspect-off', + }; + }; + + const isHeaderInspect = signalWidgetViews.value.view === 'inspector'; + const isHeaderSlowDowns = signalWidgetViews.value.view === 'slow-downs'; + const isHeaderSettings = signalWidgetViews.value.view === 'settings'; + + return ( +
+
+
+ +
+
+ +
+
+ +
+
+ + {/* {Store.inspectState.value.kind !== 'inspect-off' && } */} + +
+ ); +}; diff --git a/packages/scan/src/web/components/widget/helpers.ts b/packages/scan/src/web/widget/helpers.ts similarity index 99% rename from packages/scan/src/web/components/widget/helpers.ts rename to packages/scan/src/web/widget/helpers.ts index 88932f16..e4b28173 100644 --- a/packages/scan/src/web/components/widget/helpers.ts +++ b/packages/scan/src/web/widget/helpers.ts @@ -1,4 +1,4 @@ -import { MIN_SIZE, SAFE_AREA } from '../../constants'; +import { MIN_SIZE, SAFE_AREA } from '../constants'; import type { Corner, Position, ResizeHandleProps, Size } from './types'; class WindowDimensions { diff --git a/packages/scan/src/web/components/widget/index.tsx b/packages/scan/src/web/widget/index.tsx similarity index 53% rename from packages/scan/src/web/components/widget/index.tsx rename to packages/scan/src/web/widget/index.tsx index 5dc915c3..409a8001 100644 --- a/packages/scan/src/web/components/widget/index.tsx +++ b/packages/scan/src/web/widget/index.tsx @@ -1,34 +1,35 @@ import type { JSX } from 'preact'; import { useCallback, useEffect, useRef } from 'preact/hooks'; import { Store } from '~core/index'; -import { ScanOverlay } from '~web/components/inspector/overlay'; import { cn, saveLocalStorage, toggleMultipleClasses, } from '~web/utils/helpers'; -import { LOCALSTORAGE_KEY, MIN_SIZE, SAFE_AREA } from '../../constants'; +import { Content } from '~web/views'; +import { ScanOverlay } from '~web/views/inspector/overlay'; +import { ToolbarNotification } from '~web/views/slow-downs/toolbar-notification'; +import { LOCALSTORAGE_KEY, MIN_SIZE, SAFE_AREA } from '../constants'; import { defaultWidgetConfig, - signalIsSettingsOpen, signalRefWidget, + signalSlowDowns, signalWidget, + signalWidgetViews, updateDimensions, -} from '../../state'; -import { Inspector } from '../inspector'; -import { ComponentsTree } from './components-tree'; -import { Header } from './header'; +} from '../state'; import { calculateBoundedSize, calculatePosition, getBestCorner, } from './helpers'; import { ResizeHandle } from './resize-handle'; -import { Toolbar } from './toolbar'; export const Widget = () => { const refWidget = useRef(null); const refContent = useRef(null); + const refNotificationState = useRef(null); + const refShouldOpen = useRef(false); const refInitialMinimizedWidth = useRef(0); const refInitialMinimizedHeight = useRef(0); @@ -36,14 +37,11 @@ export const Widget = () => { const updateWidgetPosition = useCallback((shouldSave = true) => { if (!refWidget.current) return; - const inspectState = Store.inspectState.value; - const isInspectFocused = inspectState.kind === 'focused'; - const { corner } = signalWidget.value; let newWidth: number; let newHeight: number; - if (isInspectFocused || signalIsSettingsOpen.value) { + if (refShouldOpen.current) { const lastDims = signalWidget.value.lastDimensions; newWidth = calculateBoundedSize(lastDims.width, 0, true); newHeight = calculateBoundedSize(lastDims.height, 0, false); @@ -105,7 +103,7 @@ export const Widget = () => { signalWidget.value = { corner, dimensions: newDimensions, - lastDimensions: isInspectFocused + lastDimensions: refShouldOpen ? signalWidget.value.lastDimensions : newWidth > refInitialMinimizedWidth.current ? newDimensions @@ -123,140 +121,150 @@ export const Widget = () => { } updateDimensions(); - }, []); const handleDrag = useCallback((e: JSX.TargetedMouseEvent) => { - e.preventDefault(); + e.preventDefault(); - if (!refWidget.current || (e.target as HTMLElement).closest('button')) - return; + if (!refWidget.current || (e.target as HTMLElement).closest('button')) + return; - const container = refWidget.current; - const containerStyle = container.style; - const { dimensions } = signalWidget.value; + refNotificationState.current = signalSlowDowns.value.hideNotification; + signalSlowDowns.value = { + ...signalSlowDowns.value, + hideNotification: true, + }; + + const container = refWidget.current; + const containerStyle = container.style; + const { dimensions } = signalWidget.value; - const initialMouseX = e.clientX; - const initialMouseY = e.clientY; + const initialMouseX = e.clientX; + const initialMouseY = e.clientY; - const initialX = dimensions.position.x; - const initialY = dimensions.position.y; + const initialX = dimensions.position.x; + const initialY = dimensions.position.y; - let currentX = initialX; - let currentY = initialY; - let rafId: number | null = null; - let hasMoved = false; - let lastMouseX = initialMouseX; - let lastMouseY = initialMouseY; + let currentX = initialX; + let currentY = initialY; + let rafId: number | null = null; + let hasMoved = false; + let lastMouseX = initialMouseX; + let lastMouseY = initialMouseY; - const handleMouseMove = (e: globalThis.MouseEvent) => { - if (rafId) return; + const handleMouseMove = (e: globalThis.MouseEvent) => { + if (rafId) return; - hasMoved = true; - lastMouseX = e.clientX; - lastMouseY = e.clientY; + hasMoved = true; + lastMouseX = e.clientX; + lastMouseY = e.clientY; - rafId = requestAnimationFrame(() => { - const deltaX = lastMouseX - initialMouseX; - const deltaY = lastMouseY - initialMouseY; + rafId = requestAnimationFrame(() => { + const deltaX = lastMouseX - initialMouseX; + const deltaY = lastMouseY - initialMouseY; - currentX = Number(initialX) + deltaX; - currentY = Number(initialY) + deltaY; + currentX = Number(initialX) + deltaX; + currentY = Number(initialY) + deltaY; - containerStyle.transition = 'none'; - containerStyle.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; - rafId = null; + containerStyle.transition = 'none'; + containerStyle.transform = `translate3d(${currentX}px, ${currentY}px, 0)`; + rafId = null; + }); + }; + + const handleMouseUp = () => { + if (!container) return; + + if (rafId) { + cancelAnimationFrame(rafId); + rafId = null; + } + document.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseup', handleMouseUp); + + // Calculate total movement distance + const totalDeltaX = Math.abs(lastMouseX - initialMouseX); + const totalDeltaY = Math.abs(lastMouseY - initialMouseY); + const totalMovement = Math.sqrt(totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY); + + // Only consider it a move if we moved more than 60 pixels + if (!hasMoved || totalMovement < 60) return; + + const newCorner = getBestCorner( + lastMouseX, + lastMouseY, + initialMouseX, + initialMouseY, + Store.inspectState.value.kind === 'focused' ? 80 : 40, + ); + + if (newCorner === signalWidget.value.corner) { + containerStyle.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; + const currentPosition = signalWidget.value.dimensions.position; + requestAnimationFrame(() => { + containerStyle.transform = `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0)`; }); - }; - const handleMouseUp = () => { - if (!container) return; + if (refNotificationState.current !== null) { + signalSlowDowns.value = { + ...signalSlowDowns.value, + hideNotification: refNotificationState.current, + }; + } + return; + } + const snappedPosition = calculatePosition( + newCorner, + dimensions.width, + dimensions.height, + ); + + if (currentX === initialX && currentY === initialY) return; + + const onTransitionEnd = () => { + containerStyle.transition = 'none'; + updateDimensions(); + container.removeEventListener('transitionend', onTransitionEnd); if (rafId) { cancelAnimationFrame(rafId); rafId = null; } - document.removeEventListener('mousemove', handleMouseMove); - document.removeEventListener('mouseup', handleMouseUp); - - // Calculate total movement distance - const totalDeltaX = Math.abs(lastMouseX - initialMouseX); - const totalDeltaY = Math.abs(lastMouseY - initialMouseY); - const totalMovement = Math.sqrt(totalDeltaX * totalDeltaX + totalDeltaY * totalDeltaY); - - // Only consider it a move if we moved more than 60 pixels - if (!hasMoved || totalMovement < 60) return; - - const newCorner = getBestCorner( - lastMouseX, - lastMouseY, - initialMouseX, - initialMouseY, - Store.inspectState.value.kind === 'focused' ? 80 : 40, - ); - - if (newCorner === signalWidget.value.corner) { - containerStyle.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; - const currentPosition = signalWidget.value.dimensions.position; - requestAnimationFrame(() => { - containerStyle.transform = `translate3d(${currentPosition.x}px, ${currentPosition.y}px, 0)`; - }); - return; - } - - const snappedPosition = calculatePosition( - newCorner, - dimensions.width, - dimensions.height, - ); - - if (currentX === initialX && currentY === initialY) return; - - const onTransitionEnd = () => { - containerStyle.transition = 'none'; - updateDimensions(); - container.removeEventListener('transitionend', onTransitionEnd); - if (rafId) { - cancelAnimationFrame(rafId); - rafId = null; - } - }; - - container.addEventListener('transitionend', onTransitionEnd); - containerStyle.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; + }; - requestAnimationFrame(() => { - containerStyle.transform = `translate3d(${snappedPosition.x}px, ${snappedPosition.y}px, 0)`; - }); + container.addEventListener('transitionend', onTransitionEnd); + containerStyle.transition = 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'; - signalWidget.value = { - corner: newCorner, - dimensions: { - isFullWidth: dimensions.isFullWidth, - isFullHeight: dimensions.isFullHeight, - width: dimensions.width, - height: dimensions.height, - position: snappedPosition, - }, - lastDimensions: signalWidget.value.lastDimensions, - componentsTree: signalWidget.value.componentsTree, - }; + requestAnimationFrame(() => { + containerStyle.transform = `translate3d(${snappedPosition.x}px, ${snappedPosition.y}px, 0)`; + }); - saveLocalStorage(LOCALSTORAGE_KEY, { - corner: newCorner, - dimensions: signalWidget.value.dimensions, - lastDimensions: signalWidget.value.lastDimensions, - componentsTree: signalWidget.value.componentsTree, - }); + signalWidget.value = { + corner: newCorner, + dimensions: { + isFullWidth: dimensions.isFullWidth, + isFullHeight: dimensions.isFullHeight, + width: dimensions.width, + height: dimensions.height, + position: snappedPosition, + }, + lastDimensions: signalWidget.value.lastDimensions, + componentsTree: signalWidget.value.componentsTree, }; - document.addEventListener('mousemove', handleMouseMove, { - passive: true, + saveLocalStorage(LOCALSTORAGE_KEY, { + corner: newCorner, + dimensions: signalWidget.value.dimensions, + lastDimensions: signalWidget.value.lastDimensions, + componentsTree: signalWidget.value.componentsTree, }); - document.addEventListener('mouseup', handleMouseUp); - }, - [], - ); + }; + + document.addEventListener('mousemove', handleMouseMove, { + passive: true, + }); + document.addEventListener('mouseup', handleMouseUp); + }, []); // biome-ignore lint/correctness/useExhaustiveDependencies: no deps useEffect(() => { @@ -298,13 +306,16 @@ export const Widget = () => { }); }); - signalIsSettingsOpen.subscribe(() => { + const unsubscribeSignalWidgetViews = signalWidgetViews.subscribe((state) => { + refShouldOpen.current = state.view !== 'none'; updateWidgetPosition(); }); const unsubscribeStoreInspectState = Store.inspectState.subscribe((state) => { if (!refContent.current) return; + refShouldOpen.current = state.kind === 'focused'; + if (state.kind === 'inspecting') { toggleMultipleClasses(refContent.current, [ 'opacity-0', @@ -323,6 +334,7 @@ export const Widget = () => { return () => { window.removeEventListener('resize', handleWindowResize); + unsubscribeSignalWidgetViews(); unsubscribeStoreInspectState(); unsubscribeSignalWidget(); @@ -358,56 +370,8 @@ export const Widget = () => { -
-
-
-
- { - Store.inspectState.value.kind === 'focused' && ( - <> - - - - ) - } -
-
- -
+ +
); diff --git a/packages/scan/src/web/components/widget/resize-handle.tsx b/packages/scan/src/web/widget/resize-handle.tsx similarity index 90% rename from packages/scan/src/web/components/widget/resize-handle.tsx rename to packages/scan/src/web/widget/resize-handle.tsx index 5b572376..c30ba7ca 100644 --- a/packages/scan/src/web/components/widget/resize-handle.tsx +++ b/packages/scan/src/web/widget/resize-handle.tsx @@ -2,9 +2,9 @@ import type { JSX } from 'preact'; import { useCallback, useEffect, useRef } from 'preact/hooks'; import { Store } from '~core/index'; import { Icon } from '~web/components/icon'; +import { LOCALSTORAGE_KEY, MIN_CONTAINER_WIDTH, MIN_SIZE } from '~web/constants'; +import { signalRefWidget, signalSlowDowns, signalWidget, signalWidgetViews } from '~web/state'; import { cn, saveLocalStorage } from '~web/utils/helpers'; -import { LOCALSTORAGE_KEY, MIN_CONTAINER_WIDTH, MIN_SIZE } from '../../constants'; -import { signalRefWidget, signalWidget } from '../../state'; import { calculateNewSizeAndPosition, calculatePosition, @@ -27,9 +27,24 @@ export const ResizeHandle = ({ position }: ResizeHandleProps) => { const container = refContainer.current; if (!container) return; - const updateVisibility = (isFocused: boolean) => { + const checkForNotificationVisibility = () => { + container.classList.remove('pointer-events-none'); + if ( + signalWidgetViews.value.view !== 'slow-downs' && + (position === 'top' || position === 'bottom') + ) { + const slowDowns = signalSlowDowns.value; + if (slowDowns.slowDowns > 0 && !slowDowns.hideNotification) { + container.classList.add('pointer-events-none'); + } + } + }; + + const updateVisibility = () => { + const isFocused = Store.inspectState.value.kind === 'focused'; + const shouldShow = signalWidgetViews.value.view !== 'none'; const isVisible = - isFocused && + (isFocused || shouldShow) && getHandleVisibility( position, signalWidget.value.corner, @@ -49,6 +64,8 @@ export const ResizeHandle = ({ position }: ResizeHandleProps) => { }; const unsubscribeSignalWidget = signalWidget.subscribe((state) => { + checkForNotificationVisibility(); + if ( prevWidth.current !== null && prevHeight.current !== null && @@ -60,22 +77,26 @@ export const ResizeHandle = ({ position }: ResizeHandleProps) => { return; } - updateVisibility(Store.inspectState.value.kind === 'focused'); + updateVisibility(); prevWidth.current = state.dimensions.width; prevHeight.current = state.dimensions.height; prevCorner.current = state.corner; }); - const unsubscribeStoreInspectState = Store.inspectState.subscribe( - (state) => { - updateVisibility(state.kind === 'focused'); - }, - ); + const unsubscribeInspectState = Store.inspectState.subscribe(() => { + checkForNotificationVisibility(); + updateVisibility(); + }); + + const unsubscribeSignalSlowDowns = signalSlowDowns.subscribe(() => { + checkForNotificationVisibility(); + }); return () => { unsubscribeSignalWidget(); - unsubscribeStoreInspectState(); + unsubscribeInspectState(); + unsubscribeSignalSlowDowns(); prevWidth.current = null; prevHeight.current = null; prevCorner.current = null; diff --git a/packages/scan/src/web/components/widget/settings.tsx b/packages/scan/src/web/widget/settings.tsx similarity index 100% rename from packages/scan/src/web/components/widget/settings.tsx rename to packages/scan/src/web/widget/settings.tsx diff --git a/packages/scan/src/web/components/widget/types.ts b/packages/scan/src/web/widget/types.ts similarity index 100% rename from packages/scan/src/web/components/widget/types.ts rename to packages/scan/src/web/widget/types.ts diff --git a/packages/vite-plugin-react-scan/LICENSE b/packages/vite-plugin-react-scan/LICENSE new file mode 100644 index 00000000..c908db5a --- /dev/null +++ b/packages/vite-plugin-react-scan/LICENSE @@ -0,0 +1,7 @@ +Copyright 2024 Aiden Bai, Million Software, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/vite-plugin-react-scan/README.md b/packages/vite-plugin-react-scan/README.md new file mode 100644 index 00000000..cce2794c --- /dev/null +++ b/packages/vite-plugin-react-scan/README.md @@ -0,0 +1,76 @@ +# @react-scan/vite-plugin-react-scan + +A Vite plugin that integrates React Scan into your Vite application, automatically detecting performance issues in your React components. + +## Installation + +```bash +# npm +npm install -D @react-scan/vite-plugin-react-scan react-scan + +# pnpm +pnpm add -D @react-scan/vite-plugin-react-scan react-scan + +# yarn +yarn add -D @react-scan/vite-plugin-react-scan react-scan +``` + +## Usage + +Add the plugin to your `vite.config.ts`: + +```ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import reactScan from '@react-scan/vite-plugin-react-scan'; + +export default defineConfig({ + plugins: [ + react(), + reactScan({ + // options (optional) + }), + ], +}); +``` + +## Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `enable` | `boolean` | `process.env.NODE_ENV === 'development'` | Enable/disable scanning | +| `scanOptions` | `object` | `{ ... }` | Custom React Scan options | +| `autoDisplayNames` | `boolean` | `true` | Automatically add display names to React components | +| `debug` | `boolean` | `false` | Enable debug logging | + +## Example Configuration + +```ts +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import reactScan from '@react-scan/vite-plugin-react-scan'; + +export default defineConfig({ + plugins: [ + react(), + reactScan({ + enable: true, + autoDisplayNames: true, + scanOptions: {} // React Scan specific options + }), + ], +}); +``` + +## Development vs Production + +- In development: The plugin injects React Scan directly into your application for real-time analysis +- In production: The plugin can be disabled/enabled by default with specific options + +## Contributing + +Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details. + +## License + +React Scan Vite Plugin is [MIT-licensed](LICENSE) open-source software by Aiden Bai, [Million Software, Inc.](https://million.dev), and [contributors](https://github.com/aidenybai/react-scan/graphs/contributors): diff --git a/packages/vite-plugin-react-scan/package.json b/packages/vite-plugin-react-scan/package.json new file mode 100644 index 00000000..437422cb --- /dev/null +++ b/packages/vite-plugin-react-scan/package.json @@ -0,0 +1,73 @@ +{ + "name": "@react-scan/vite-plugin-react-scan", + "version": "0.1.0", + "description": "A Vite plugin for React Scan - detects performance issues in your React app.", + "type": "module", + "main": "dist/index.js", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "rm -rf dist && tsc", + "postbuild": "node ../../scripts/version-warning.mjs", + "lint": "biome lint .", + "format": "biome format . --write", + "check": "biome check . --write", + "prepublishOnly": "rm -rf dist && npm run build" + }, + "peerDependenciesMeta": { + "react-scan": { + "optional": false + }, + "vite": { + "optional": false + } + }, + "keywords": [ + "react", + "react-scan", + "react scan", + "render", + "performance", + "vite", + "vite plugin" + ], + "author": { + "name": "Team React Scan", + "url": "https://github.com/aidenybai/react-scan" + }, + "maintainers": [ + { + "name": "Pavel Ivanov", + "email": "pafelka@gmail.com", + "url": "https://github.com/pivanov" + } + ], + "license": "MIT", + "homepage": "https://github.com/aidenybai/react-scan", + "bugs": { + "url": "https://github.com/aidenybai/react-scan/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/aidenybai/react-scan.git" + }, + "peerDependencies": { + "react-scan": "^0.1.0", + "vite": "^2 || ^3 || ^4 || ^5 || ^6" + }, + "devDependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx": "^7.25.9", + "@biomejs/biome": "^1.9.4", + "@types/babel__core": "^7.20.5", + "@types/cheerio": "^0.22.35", + "@types/node": "^22.10.7", + "babel-plugin-add-react-displayname": "^0.0.5", + "cheerio": "^1.0.0", + "typescript": "latest", + "vite": "^6.0.7" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/vite-plugin-react-scan/src/global.d.ts b/packages/vite-plugin-react-scan/src/global.d.ts new file mode 100644 index 00000000..e5101386 --- /dev/null +++ b/packages/vite-plugin-react-scan/src/global.d.ts @@ -0,0 +1 @@ +declare module 'babel-plugin-add-react-displayname/index.js'; diff --git a/packages/vite-plugin-react-scan/src/index.ts b/packages/vite-plugin-react-scan/src/index.ts new file mode 100644 index 00000000..56e57b86 --- /dev/null +++ b/packages/vite-plugin-react-scan/src/index.ts @@ -0,0 +1,288 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { transformAsync } from '@babel/core'; +import babelPluginReactDisplayName from 'babel-plugin-add-react-displayname/index.js'; +import * as cheerio from 'cheerio'; +import type { Options } from 'react-scan'; +import type { Plugin, ResolvedConfig } from 'vite'; + +interface Logger { + debug: (...args: unknown[]) => void; + info: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; +} + +const createLogger = (prefix: string, debug = false): Logger => { + return { + debug: (...args: unknown[]) => + debug && process.stdout.write(`[${prefix}] ${args.join(' ')}\n`), + info: (...args: unknown[]) => + process.stdout.write(`[${prefix}] ${args.join(' ')}\n`), + warn: (...args: unknown[]) => + process.stderr.write(`[${prefix}] WARN: ${args.join(' ')}\n`), + error: (...args: unknown[]) => + process.stderr.write(`[${prefix}] ERROR: ${args.join(' ')}\n`), + }; +}; + +interface ReactScanPluginOptions { + /** + * Enable/disable scanning + * @default process.env.NODE_ENV === 'development' + */ + enable?: boolean; + + /** + * Custom React Scan options + */ + scanOptions?: Options; + + /** + * Enable debug logging + * @default false + */ + debug?: boolean; + + /** + * Automatically add display names to React components + * @default true + */ + autoDisplayNames?: boolean; +} + +const PLUGIN_NAME = 'vite-plugin-react-scan'; + +const DEFAULT_SCAN_OPTIONS: Partial = {}; + +const validateOptions = (options: ReactScanPluginOptions) => { + if (options.scanOptions && typeof options.scanOptions !== 'object') { + throw new Error('scanOptions must be an object'); + } + + if (options.enable !== undefined && typeof options.enable !== 'boolean') { + throw new Error('enable must be a boolean'); + } + + if (options.debug !== undefined && typeof options.debug !== 'boolean') { + throw new Error('debug must be a boolean'); + } + + if ( + options.autoDisplayNames !== undefined && + typeof options.autoDisplayNames !== 'boolean' + ) { + throw new Error('autoDisplayNames must be a boolean'); + } +}; + +const JSX_EXTENSIONS = ['.jsx', '.tsx'] as const; +const REACT_SCAN_IDENTIFIER = 'react-scan'; + +const isJsxFile = (id: string) => + JSX_EXTENSIONS.some((ext) => id.endsWith(ext)); + +const reactScanPlugin = (options: ReactScanPluginOptions = {}): Plugin => { + validateOptions(options); + const { + enable = process.env.NODE_ENV === 'development', + scanOptions = DEFAULT_SCAN_OPTIONS, + debug = false, + autoDisplayNames = true, + } = options; + + let config: ResolvedConfig; + let isProduction = false; + let scanFilePath = ''; + let assetsDir = ''; + + const log = createLogger(PLUGIN_NAME, debug); + + const generateScanScript = (options: Options = {}) => { + const hasOptions = Object.keys(options).length > 0; + + if (isProduction) { + // Create a proper JSON string for the options and wrap it in single quotes + const optionsJson = hasOptions ? `${JSON.stringify(options)}` : '{}'; + + return ` + + + `; + } + + // Development version remains the same + return ` + `; + }; + + return { + name: PLUGIN_NAME, + enforce: 'pre', + + transform: async (code, id) => { + if (!autoDisplayNames || !isJsxFile(id)) { + return null; + } + + try { + const result = await transformAsync(code, { + plugins: [ + [ + '@babel/plugin-transform-react-jsx', + { + runtime: 'automatic', + }, + ], + babelPluginReactDisplayName, + ], + filename: id, + }); + + if (!result?.code) { + log.warn(`No code generated for ${id}`); + return null; + } + + log.debug(`Successfully transformed ${id}`); + return { code: result.code, map: result.map }; + } catch (error) { + log.error(`Failed to transform ${id}:`, error); + return null; + } + }, + + configResolved(resolvedConfig) { + config = resolvedConfig; + isProduction = config.isProduction; + assetsDir = config.build?.assetsDir || 'assets'; + const base = config.base || '/'; + + // Ensure base path is properly formatted + scanFilePath = path.posix.join(base, assetsDir, 'auto.global.js'); + + log.debug('Plugin initialized with config:', { + mode: config.mode, + base, + enable, + isProduction, + assetsDir, + scanOptions, + scanFilePath, + }); + }, + + transformIndexHtml(html) { + if (!enable) { + log.debug('Plugin disabled'); + return html; + } + + try { + const $ = cheerio.load(html); + const scanScript = generateScanScript(scanOptions); + + // Remove any existing React Scan script to avoid duplicates + let removedCount = 0; + $('script').each((_index: number, element: cheerio.Element) => { + const content = $(element).html() || ''; + if (content.includes(REACT_SCAN_IDENTIFIER)) { + $(element).remove(); + removedCount++; + } + }); + + if (removedCount > 0) { + log.debug(`Removed ${removedCount} existing scan script(s)`); + } + + if (isProduction) { + // In production, insert at the beginning of head + $('head').prepend(scanScript); + log.debug( + 'Injected scan script at the beginning of head (production)', + ); + } else { + // In development, insert after Vite's client script + const viteClientScript = $('script[src="/@vite/client"]'); + if (viteClientScript.length) { + viteClientScript.after(scanScript); + log.debug('Injected scan script after Vite client (development)'); + } else { + $('head').append(scanScript); + log.debug('Injected scan script at end of head (development)'); + } + } + + return $.html(); + } catch (error) { + log.error('Failed to transform HTML:', error); + return html; + } + }, + + resolveId(id) { + if (!isProduction && id === `/@id/${REACT_SCAN_IDENTIFIER}`) { + log.debug('Resolving react-scan module'); + return REACT_SCAN_IDENTIFIER; + } + return null; + }, + + async generateBundle() { + if (isProduction && enable) { + log.debug('Production build started, processing react-scan'); + + try { + const nodeModulesPath = path.resolve('node_modules'); + const reactScanPath = path.join( + nodeModulesPath, + REACT_SCAN_IDENTIFIER, + 'dist', + 'auto.global.js', + ); + const content = await fs.promises.readFile(reactScanPath, 'utf-8'); + + // Let Vite handle the file placement in configured assets directory + const assetFileName = `${assetsDir}/auto.global.js`; + + // Emit the file to the build output + this.emitFile({ + type: 'asset', + fileName: assetFileName, + source: content, + }); + + // Store the full path for use in the script tag + scanFilePath = `/${assetFileName}`; + log.debug('Emitted react-scan as asset:', assetFileName); + } catch (error) { + log.error('Failed to process react-scan:', error); + } + } + }, + + buildEnd() { + if (isProduction) { + log.debug('Build completed'); + } + }, + }; +}; + +export default reactScanPlugin; diff --git a/packages/vite-plugin-react-scan/tsconfig.json b/packages/vite-plugin-react-scan/tsconfig.json new file mode 100644 index 00000000..3de9590a --- /dev/null +++ b/packages/vite-plugin-react-scan/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES6", + "module": "ES6", + "lib": [ + "es2016" + ], + "moduleResolution": "node", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "isolatedModules": true, + "resolveJsonModule": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 51ae63d9..a02a4519 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ importers: '@biomejs/biome': specifier: ^1.9.4 version: 1.9.4 + '@changesets/cli': + specifier: ^2.27.12 + version: 2.27.12 '@types/node': specifier: ^22.10.2 version: 22.10.2 @@ -65,14 +68,14 @@ importers: version: 0.7.5 vite-plugin-inspect: specifier: ^0.8.7 - version: 0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0)) + version: 0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0)) devDependencies: '@vitejs/plugin-react': specifier: ^4.3.4 - version: 4.3.4(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0)) + version: 4.3.4(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0)) vite: specifier: ^5.4.3 - version: 5.4.3(@types/node@22.10.2)(terser@5.36.0) + version: 5.4.3(@types/node@22.13.1)(terser@5.36.0) packages/extension: dependencies: @@ -106,7 +109,7 @@ importers: version: 0.10.0 '@vitejs/plugin-react': specifier: ^4.2.1 - version: 4.3.4(vite@6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1)) + version: 4.3.4(vite@6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1)) bestzip: specifier: ^2.2.1 version: 2.2.1 @@ -118,13 +121,13 @@ importers: version: 7.0.3 vite: specifier: ^6.0.7 - version: 6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) + version: 6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) vite-plugin-web-extension: specifier: ^4.4.3 - version: 4.4.3(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0) + version: 4.4.3(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0) vite-tsconfig-paths: specifier: ^5.1.4 - version: 5.1.4(typescript@5.7.3)(vite@6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1)) + version: 5.1.4(typescript@5.7.3)(vite@6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1)) webextension-polyfill: specifier: ^0.10.0 version: 0.10.0 @@ -152,13 +155,13 @@ importers: devDependencies: '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0)) + version: 4.3.1(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0)) vite: specifier: ^5.4.3 - version: 5.4.3(@types/node@22.10.2)(terser@5.36.0) + version: 5.4.3(@types/node@22.13.1)(terser@5.36.0) vite-plugin-inspect: specifier: ^0.8.7 - version: 0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0)) + version: 0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0)) packages/scan: dependencies: @@ -282,6 +285,43 @@ importers: specifier: ^1.0.0 version: 1.0.0(@types/node@20.17.10)(terser@5.36.0) + packages/vite-plugin-react-scan: + dependencies: + react-scan: + specifier: ^0.1.0 + version: 0.1.3(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(next@15.0.3(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-router-dom@6.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@6.28.0(react@19.0.0))(react@19.0.0)(rollup@4.28.0) + devDependencies: + '@babel/core': + specifier: ^7.26.0 + version: 7.26.0 + '@babel/plugin-transform-react-jsx': + specifier: ^7.25.9 + version: 7.25.9(@babel/core@7.26.0) + '@biomejs/biome': + specifier: ^1.9.4 + version: 1.9.4 + '@types/babel__core': + specifier: ^7.20.5 + version: 7.20.5 + '@types/cheerio': + specifier: ^0.22.35 + version: 0.22.35 + '@types/node': + specifier: ^22.10.7 + version: 22.13.1 + babel-plugin-add-react-displayname: + specifier: ^0.0.5 + version: 0.0.5 + cheerio: + specifier: ^1.0.0 + version: 1.0.0 + typescript: + specifier: latest + version: 5.7.3 + vite: + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) + packages/website: dependencies: '@vercel/analytics': @@ -358,6 +398,10 @@ packages: resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.25.9': resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} @@ -397,6 +441,12 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.25.9': resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} engines: {node: '>=6.9.0'} @@ -409,6 +459,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.25.9': + resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.24.7': resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} engines: {node: '>=6.9.0'} @@ -478,6 +534,61 @@ packages: cpu: [x64] os: [win32] + '@changesets/apply-release-plan@7.0.8': + resolution: {integrity: sha512-qjMUj4DYQ1Z6qHawsn7S71SujrExJ+nceyKKyI9iB+M5p9lCL55afuEd6uLBPRpLGWQwkwvWegDHtwHJb1UjpA==} + + '@changesets/assemble-release-plan@6.0.5': + resolution: {integrity: sha512-IgvBWLNKZd6k4t72MBTBK3nkygi0j3t3zdC1zrfusYo0KpdsvnDjrMM9vPnTCLCMlfNs55jRL4gIMybxa64FCQ==} + + '@changesets/changelog-git@0.2.0': + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} + + '@changesets/cli@2.27.12': + resolution: {integrity: sha512-9o3fOfHYOvBnyEn0mcahB7wzaA3P4bGJf8PNqGit5PKaMEFdsRixik+txkrJWd2VX+O6wRFXpxQL8j/1ANKE9g==} + hasBin: true + + '@changesets/config@3.0.5': + resolution: {integrity: sha512-QyXLSSd10GquX7hY0Mt4yQFMEeqnO5z/XLpbIr4PAkNNoQNKwDyiSrx4yd749WddusH1v3OSiA0NRAYmH/APpQ==} + + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} + + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} + + '@changesets/get-release-plan@4.0.6': + resolution: {integrity: sha512-FHRwBkY7Eili04Y5YMOZb0ezQzKikTka4wL753vfUA5COSebt7KThqiuCN9BewE4/qFGgF/5t3AuzXx1/UAY4w==} + + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} + + '@changesets/git@3.0.2': + resolution: {integrity: sha512-r1/Kju9Y8OxRRdvna+nxpQIsMsRQn9dhhAZt94FLDeu0Hij2hnOozW8iqnHBgvu+KdnJppCveQwK4odwfw/aWQ==} + + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} + + '@changesets/parse@0.4.0': + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} + + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} + + '@changesets/read@0.6.2': + resolution: {integrity: sha512-wjfQpJvryY3zD61p8jR87mJdyx2FIhEcdXhKUqkja87toMrP/3jtg/Yg29upN+N4Ckf525/uvV7a4tzBlpk6gg==} + + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} + + '@changesets/types@4.1.0': + resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} + + '@changesets/types@6.0.0': + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} + + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} + '@clack/core@0.3.5': resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==} @@ -1379,6 +1490,12 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@manypkg/find-root@1.1.0': + resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} + + '@manypkg/get-packages@1.1.3': + resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@microsoft/tsdoc-config@0.16.2': resolution: {integrity: sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==} @@ -1657,6 +1774,9 @@ packages: '@types/babel__traverse@7.20.6': resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} + '@types/cheerio@0.22.35': + resolution: {integrity: sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA==} + '@types/chrome@0.0.281': resolution: {integrity: sha512-MH+8FFrJ1POZwKnKRBa+jZcVp+yCFxes6PYKvgFd0qLTzoQe+TdejC3dkA8gSs8UGjFKrKzu4AkZypmswv0NOg==} @@ -1690,12 +1810,18 @@ packages: '@types/minimatch@3.0.5': resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} + '@types/node@12.20.55': + resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@20.17.10': resolution: {integrity: sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==} '@types/node@22.10.2': resolution: {integrity: sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==} + '@types/node@22.13.1': + resolution: {integrity: sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==} + '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1987,6 +2113,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2033,6 +2163,9 @@ packages: arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -2119,6 +2252,9 @@ packages: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} + babel-plugin-add-react-displayname@0.0.5: + resolution: {integrity: sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw==} + babel-plugin-react-compiler@19.0.0-beta-a7bf2bd-20241110: resolution: {integrity: sha512-WdxXtLxsV4gh/GlEK4fuFDGkcED0Wb9UJEBB6Uc1SFqRFEmJNFKboW+Z4NUS5gYrPImqrjh4IwHAmgS6ZBg4Cg==} @@ -2133,6 +2269,10 @@ packages: engines: {node: '>=10'} hasBin: true + better-path-resolve@1.0.0: + resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} + engines: {node: '>=4'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -2261,12 +2401,22 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + charenc@0.0.2: resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} check-error@1.0.3: resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2512,6 +2662,10 @@ packages: resolution: {integrity: sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==} engines: {node: '>= 0.6.0'} + detect-indent@6.1.0: + resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} + engines: {node: '>=8'} + detect-indent@7.0.1: resolution: {integrity: sha512-Mc7QhQ8s+cLrnUfU/Ji94vG/r8M26m8f++vyres4ZoojaRDpZ1eSIh/EpzLNwlWuvzSZ3UbDFspjFvTDXe6e/g==} engines: {node: '>=12.20'} @@ -2582,6 +2736,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + end-of-stream@1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} @@ -2589,6 +2746,10 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} + enquirer@2.4.1: + resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} + engines: {node: '>=8.6'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -2865,6 +3026,11 @@ packages: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + esquery@1.6.0: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} @@ -2899,6 +3065,13 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} + extendable-error@0.1.7: + resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2982,6 +3155,14 @@ packages: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + fs-extra@9.0.1: resolution: {integrity: sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==} engines: {node: '>=10'} @@ -3188,6 +3369,9 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -3195,6 +3379,9 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} + human-id@1.0.2: + resolution: {integrity: sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -3203,6 +3390,14 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -3417,6 +3612,10 @@ packages: resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} engines: {node: '>= 0.4'} + is-subdir@1.2.0: + resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} + engines: {node: '>=4'} + is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -3439,6 +3638,10 @@ packages: resolution: {integrity: sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==} engines: {node: '>= 0.4'} + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3491,6 +3694,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3535,6 +3742,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -3629,6 +3839,9 @@ packages: lodash.sortby@4.7.0: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.startcase@4.4.0: + resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} + lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} @@ -3915,10 +4128,21 @@ packages: resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} engines: {node: '>= 0.4.0'} + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + + outdent@0.5.0: + resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + p-cancelable@3.0.0: resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} engines: {node: '>=12.20'} + p-filter@2.1.0: + resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} + engines: {node: '>=8'} + p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -3939,6 +4163,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@2.1.0: + resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==} + engines: {node: '>=6'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -3950,6 +4178,9 @@ packages: resolution: {integrity: sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==} engines: {node: '>=14.16'} + package-manager-detector@0.2.8: + resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} + pako@1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} @@ -3969,6 +4200,15 @@ packages: resolution: {integrity: sha512-SgOTCX/EZXtZxBE5eJ97P4yGM5n37BwRU+YMsH4vNzFqJV/oWFXXCmwFlgWUM4PrakybVOueJJ6pwHqSVhTFDw==} engines: {node: '>=16'} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} + + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4044,6 +4284,10 @@ packages: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pirates@4.0.6: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} @@ -4175,6 +4419,11 @@ packages: prettier: optional: true + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + prettier@3.3.3: resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} engines: {node: '>=14'} @@ -4297,6 +4546,26 @@ packages: react-router-dom: optional: true + react-scan@0.1.3: + resolution: {integrity: sha512-mPhceIDUm6KugQPOF6PcQWBxo49/ZX7TnP5+/f/wbz7/36sM1A3ESAIAOXe+Leha30AJPriG1U4nABnEF4N8vQ==} + hasBin: true + peerDependencies: + '@remix-run/react': '>=1.0.0' + next: '>=13.0.0' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-router: ^5.0.0 || ^6.0.0 || ^7.0.0 + react-router-dom: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@remix-run/react': + optional: true + next: + optional: true + react-router: + optional: true + react-router-dom: + optional: true + react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -4328,6 +4597,10 @@ packages: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} + read-yaml-file@1.1.0: + resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} + engines: {node: '>=6'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4453,6 +4726,9 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sax@1.4.1: resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} @@ -4590,6 +4866,9 @@ packages: spawn-sync@1.0.15: resolution: {integrity: sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==} + spawndamnit@3.0.1: + resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -4605,6 +4884,9 @@ packages: split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} @@ -4755,6 +5037,10 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + term-size@2.2.1: + resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} + engines: {node: '>=8'} + terser@5.36.0: resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} @@ -4797,6 +5083,10 @@ packages: resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} engines: {node: '>=14.0.0'} + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -4955,6 +5245,10 @@ packages: undici-types@6.20.0: resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici@6.21.1: + resolution: {integrity: sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==} + engines: {node: '>=18.17'} + unicorn-magic@0.1.0: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} @@ -4963,6 +5257,10 @@ packages: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + universalify@1.0.0: resolution: {integrity: sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==} engines: {node: '>= 10.0.0'} @@ -5144,6 +5442,14 @@ packages: webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} @@ -5341,6 +5647,10 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 3.0.2 + '@babel/helper-annotate-as-pure@7.25.9': + dependencies: + '@babel/types': 7.26.0 + '@babel/helper-compilation-targets@7.25.9': dependencies: '@babel/compat-data': 7.26.2 @@ -5382,6 +5692,11 @@ snapshots: dependencies: '@babel/types': 7.26.0 + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': dependencies: '@babel/core': 7.26.0 @@ -5392,6 +5707,17 @@ snapshots: '@babel/core': 7.26.0 '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.0 + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.24.7': dependencies: regenerator-runtime: 0.14.1 @@ -5454,6 +5780,148 @@ snapshots: '@biomejs/cli-win32-x64@1.9.4': optional: true + '@changesets/apply-release-plan@7.0.8': + dependencies: + '@changesets/config': 3.0.5 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + detect-indent: 6.1.0 + fs-extra: 7.0.1 + lodash.startcase: 4.4.0 + outdent: 0.5.0 + prettier: 2.8.8 + resolve-from: 5.0.0 + semver: 7.6.3 + + '@changesets/assemble-release-plan@6.0.5': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + semver: 7.6.3 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/cli@2.27.12': + dependencies: + '@changesets/apply-release-plan': 7.0.8 + '@changesets/assemble-release-plan': 6.0.5 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.5 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.6 + '@changesets/git': 3.0.2 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.2 + '@manypkg/get-packages': 1.1.3 + ansi-colors: 4.1.3 + ci-info: 3.9.0 + enquirer: 2.4.1 + external-editor: 3.1.0 + fs-extra: 7.0.1 + mri: 1.2.0 + p-limit: 2.3.0 + package-manager-detector: 0.2.8 + picocolors: 1.1.1 + resolve-from: 5.0.0 + semver: 7.6.3 + spawndamnit: 3.0.1 + term-size: 2.2.1 + + '@changesets/config@3.0.5': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + micromatch: 4.0.8 + + '@changesets/errors@0.2.0': + dependencies: + extendable-error: 0.1.7 + + '@changesets/get-dependents-graph@2.1.2': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + picocolors: 1.1.1 + semver: 7.6.3 + + '@changesets/get-release-plan@4.0.6': + dependencies: + '@changesets/assemble-release-plan': 6.0.5 + '@changesets/config': 3.0.5 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.2 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/get-version-range-type@0.4.0': {} + + '@changesets/git@3.0.2': + dependencies: + '@changesets/errors': 0.2.0 + '@manypkg/get-packages': 1.1.3 + is-subdir: 1.2.0 + micromatch: 4.0.8 + spawndamnit: 3.0.1 + + '@changesets/logger@0.1.1': + dependencies: + picocolors: 1.1.1 + + '@changesets/parse@0.4.0': + dependencies: + '@changesets/types': 6.0.0 + js-yaml: 3.14.1 + + '@changesets/pre@2.0.1': + dependencies: + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + fs-extra: 7.0.1 + + '@changesets/read@0.6.2': + dependencies: + '@changesets/git': 3.0.2 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.1': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 + + '@changesets/types@4.1.0': {} + + '@changesets/types@6.0.0': {} + + '@changesets/write@0.3.2': + dependencies: + '@changesets/types': 6.0.0 + fs-extra: 7.0.1 + human-id: 1.0.2 + prettier: 2.8.8 + '@clack/core@0.3.5': dependencies: picocolors: 1.1.1 @@ -5995,6 +6463,22 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@manypkg/find-root@1.1.0': + dependencies: + '@babel/runtime': 7.24.7 + '@types/node': 12.20.55 + find-up: 4.1.0 + fs-extra: 8.1.0 + + '@manypkg/get-packages@1.1.3': + dependencies: + '@babel/runtime': 7.24.7 + '@changesets/types': 4.1.0 + '@manypkg/find-root': 1.1.0 + fs-extra: 8.1.0 + globby: 11.1.0 + read-yaml-file: 1.1.0 + '@microsoft/tsdoc-config@0.16.2': dependencies: '@microsoft/tsdoc': 0.14.2 @@ -6057,6 +6541,11 @@ snapshots: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + '@pivanov/utils@0.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + '@pkgjs/parseargs@0.11.0': optional: true @@ -6251,6 +6740,10 @@ snapshots: dependencies: '@babel/types': 7.26.0 + '@types/cheerio@0.22.35': + dependencies: + '@types/node': 22.13.1 + '@types/chrome@0.0.281': dependencies: '@types/filesystem': 0.0.36 @@ -6278,6 +6771,8 @@ snapshots: '@types/minimatch@3.0.5': {} + '@types/node@12.20.55': {} + '@types/node@20.17.10': dependencies: undici-types: 6.19.8 @@ -6286,6 +6781,10 @@ snapshots: dependencies: undici-types: 6.20.0 + '@types/node@22.13.1': + dependencies: + undici-types: 6.20.0 + '@types/normalize-package-data@2.4.4': {} '@types/prop-types@15.7.13': {} @@ -6614,36 +7113,36 @@ snapshots: - supports-color - vitest - '@vitejs/plugin-react@4.3.1(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0))': + '@vitejs/plugin-react@4.3.1(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.3(@types/node@22.10.2)(terser@5.36.0) + vite: 5.4.3(@types/node@22.13.1)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.4(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0))': + '@vitejs/plugin-react@4.3.4(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.4.3(@types/node@22.10.2)(terser@5.36.0) + vite: 5.4.3(@types/node@22.13.1)(terser@5.36.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1))': + '@vitejs/plugin-react@4.3.4(vite@6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1))': dependencies: '@babel/core': 7.26.0 '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) transitivePeerDependencies: - supports-color @@ -6707,6 +7206,8 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -6768,6 +7269,10 @@ snapshots: arg@5.0.2: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + argparse@2.0.1: {} aria-query@5.3.2: {} @@ -6871,6 +7376,8 @@ snapshots: axobject-query@4.1.0: {} + babel-plugin-add-react-displayname@0.0.5: {} + babel-plugin-react-compiler@19.0.0-beta-a7bf2bd-20241110: dependencies: '@babel/types': 7.26.0 @@ -6887,6 +7394,10 @@ snapshots: which: 2.0.2 yargs: 16.2.0 + better-path-resolve@1.0.0: + dependencies: + is-windows: 1.0.2 + binary-extensions@2.3.0: {} bippy@0.0.19: {} @@ -7033,12 +7544,37 @@ snapshots: chalk@5.3.0: {} + chardet@0.7.0: {} + charenc@0.0.2: {} check-error@1.0.3: dependencies: get-func-name: 2.0.2 + cheerio-select@2.1.0: + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 6.21.1 + whatwg-mimetype: 4.0.0 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -7053,7 +7589,7 @@ snapshots: chrome-launcher@1.1.0: dependencies: - '@types/node': 22.10.2 + '@types/node': 22.13.1 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.1 @@ -7281,6 +7817,8 @@ snapshots: dependency-graph@0.11.0: {} + detect-indent@6.1.0: {} + detect-indent@7.0.1: {} detect-libc@2.0.3: @@ -7343,6 +7881,11 @@ snapshots: emoji-regex@9.2.2: {} + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + end-of-stream@1.4.4: dependencies: once: 1.4.0 @@ -7352,6 +7895,11 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + enquirer@2.4.1: + dependencies: + ansi-colors: 4.1.3 + strip-ansi: 6.0.1 + entities@4.5.0: {} error-ex@1.3.2: @@ -7941,6 +8489,8 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 + esprima@4.0.1: {} + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -7985,6 +8535,14 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 + extendable-error@0.1.7: {} + + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + fast-deep-equal@3.1.3: {} fast-glob@3.3.1: @@ -8078,6 +8636,18 @@ snapshots: jsonfile: 6.1.0 universalify: 2.0.1 + fs-extra@7.0.1: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fs-extra@9.0.1: dependencies: at-least-node: 1.0.0 @@ -8309,6 +8879,13 @@ snapshots: domutils: 3.1.0 entities: 4.5.0 + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + http-cache-semantics@4.1.1: {} http2-wrapper@2.2.1: @@ -8316,10 +8893,20 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 + human-id@1.0.2: {} + human-signals@2.1.0: {} human-signals@5.0.0: {} + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore-walk@5.0.1: @@ -8493,6 +9080,10 @@ snapshots: dependencies: has-tostringtag: 1.0.2 + is-subdir@1.2.0: + dependencies: + better-path-resolve: 1.0.0 + is-symbol@1.0.4: dependencies: has-symbols: 1.0.3 @@ -8514,6 +9105,8 @@ snapshots: call-bind: 1.0.7 get-intrinsic: 1.2.4 + is-windows@1.0.2: {} + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 @@ -8558,6 +9151,11 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + js-yaml@4.1.0: dependencies: argparse: 2.0.1 @@ -8586,6 +9184,10 @@ snapshots: json5@2.2.3: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -8690,6 +9292,8 @@ snapshots: lodash.sortby@4.7.0: {} + lodash.startcase@4.4.0: {} + lodash.union@4.6.0: {} lodash.uniq@4.5.0: {} @@ -9058,8 +9662,16 @@ snapshots: os-shim@0.1.3: {} + os-tmpdir@1.0.2: {} + + outdent@0.5.0: {} + p-cancelable@3.0.0: {} + p-filter@2.1.0: + dependencies: + p-map: 2.1.0 + p-limit@2.3.0: dependencies: p-try: 2.2.0 @@ -9080,6 +9692,8 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@2.1.0: {} + p-try@2.2.0: {} package-json-from-dist@1.0.1: {} @@ -9091,6 +9705,8 @@ snapshots: registry-url: 6.0.1 semver: 7.6.3 + package-manager-detector@0.2.8: {} + pako@1.0.11: {} parent-module@1.0.1: @@ -9117,6 +9733,19 @@ snapshots: lines-and-columns: 2.0.4 type-fest: 3.13.1 + parse5-htmlparser2-tree-adapter@7.1.0: + dependencies: + domhandler: 5.0.3 + parse5: 7.2.1 + + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.2.1 + + parse5@7.2.1: + dependencies: + entities: 4.5.0 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -9164,6 +9793,8 @@ snapshots: pify@3.0.0: {} + pify@4.0.1: {} + pirates@4.0.6: {} pkg-types@1.2.1: @@ -9281,6 +9912,8 @@ snapshots: optionalDependencies: prettier: 3.3.3 + prettier@2.8.8: {} + prettier@3.3.3: {} pretty-format@29.7.0: @@ -9460,6 +10093,37 @@ snapshots: - rollup - supports-color + react-scan@0.1.3(@remix-run/react@2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3))(next@15.0.3(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react-router-dom@6.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-router@6.28.0(react@19.0.0))(react@19.0.0)(rollup@4.28.0): + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.2 + '@babel/types': 7.26.0 + '@clack/core': 0.3.5 + '@clack/prompts': 0.8.2 + '@pivanov/utils': 0.0.1(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@preact/signals': 1.3.1(preact@10.25.1) + '@rollup/pluginutils': 5.1.3(rollup@4.28.0) + '@types/node': 20.17.10 + bippy: 0.2.7 + esbuild: 0.24.2 + estree-walker: 3.0.3 + kleur: 4.1.5 + mri: 1.2.0 + playwright: 1.49.0 + preact: 10.25.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tsx: 4.0.0 + optionalDependencies: + '@remix-run/react': 2.15.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.3) + next: 15.0.3(@babel/core@7.26.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react-router: 6.28.0(react@19.0.0) + react-router-dom: 6.28.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + unplugin: 2.1.0 + transitivePeerDependencies: + - rollup + - supports-color + react@18.2.0: dependencies: loose-envify: 1.4.0 @@ -9493,6 +10157,13 @@ snapshots: parse-json: 5.2.0 type-fest: 0.6.0 + read-yaml-file@1.1.0: + dependencies: + graceful-fs: 4.2.11 + js-yaml: 3.14.1 + pify: 4.0.1 + strip-bom: 3.0.0 + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -9648,6 +10319,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.0 + safer-buffer@2.1.2: {} + sax@1.4.1: {} scheduler@0.23.2: @@ -9801,6 +10474,11 @@ snapshots: concat-stream: 1.6.2 os-shim: 0.1.3 + spawndamnit@3.0.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -9819,6 +10497,8 @@ snapshots: dependencies: through: 2.3.8 + sprintf-js@1.0.3: {} + stable-hash@0.0.4: {} stackback@0.0.2: {} @@ -10027,6 +10707,8 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + term-size@2.2.1: {} + terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 @@ -10063,6 +10745,10 @@ snapshots: tinyspy@2.2.1: {} + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + tmp@0.2.3: {} to-regex-range@5.0.1: @@ -10216,12 +10902,16 @@ snapshots: undici-types@6.20.0: {} + undici@6.21.1: {} + unicorn-magic@0.1.0: {} unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 + universalify@0.1.2: {} + universalify@1.0.0: {} universalify@2.0.1: {} @@ -10307,7 +10997,7 @@ snapshots: - terser optional: true - vite-plugin-inspect@0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.10.2)(terser@5.36.0)): + vite-plugin-inspect@0.8.7(rollup@4.28.0)(vite@5.4.3(@types/node@22.13.1)(terser@5.36.0)): dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.3(rollup@4.28.0) @@ -10318,12 +11008,12 @@ snapshots: perfect-debounce: 1.0.0 picocolors: 1.1.1 sirv: 2.0.4 - vite: 5.4.3(@types/node@22.10.2)(terser@5.36.0) + vite: 5.4.3(@types/node@22.13.1)(terser@5.36.0) transitivePeerDependencies: - rollup - supports-color - vite-plugin-web-extension@4.4.3(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0): + vite-plugin-web-extension@4.4.3(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0): dependencies: ajv: 8.17.1 async-lock: 1.4.1 @@ -10333,7 +11023,7 @@ snapshots: lodash.uniq: 4.5.0 lodash.uniqby: 4.7.0 md5: 2.3.0 - vite: 6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) + vite: 6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) web-ext-option-types: 8.3.1 web-ext-run: 0.2.2 webextension-polyfill: 0.10.0 @@ -10364,6 +11054,17 @@ snapshots: - supports-color - typescript + vite-tsconfig-paths@5.1.4(typescript@5.7.3)(vite@6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1)): + dependencies: + debug: 4.3.7 + globrex: 0.1.2 + tsconfck: 3.1.4(typescript@5.7.3) + optionalDependencies: + vite: 6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1) + transitivePeerDependencies: + - supports-color + - typescript + vite@5.4.3(@types/node@20.17.10)(terser@5.36.0): dependencies: esbuild: 0.21.5 @@ -10383,6 +11084,17 @@ snapshots: '@types/node': 22.10.2 fsevents: 2.3.3 terser: 5.36.0 + optional: true + + vite@5.4.3(@types/node@22.13.1)(terser@5.36.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.28.0 + optionalDependencies: + '@types/node': 22.13.1 + fsevents: 2.3.3 + terser: 5.36.0 vite@6.0.7(@types/node@22.10.2)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1): dependencies: @@ -10395,6 +11107,19 @@ snapshots: jiti: 1.21.6 terser: 5.36.0 yaml: 2.6.1 + optional: true + + vite@6.0.7(@types/node@22.13.1)(jiti@1.21.6)(terser@5.36.0)(yaml@2.6.1): + dependencies: + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.28.0 + optionalDependencies: + '@types/node': 22.13.1 + fsevents: 2.3.3 + jiti: 1.21.6 + terser: 5.36.0 + yaml: 2.6.1 vitest@1.0.0(@types/node@20.17.10)(terser@5.36.0): dependencies: @@ -10512,6 +11237,12 @@ snapshots: webpack-virtual-modules@0.6.2: optional: true + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + whatwg-url@7.1.0: dependencies: lodash.sortby: 4.7.0 diff --git a/scripts/version-warning.mjs b/scripts/version-warning.mjs index 3a396800..6e824660 100755 --- a/scripts/version-warning.mjs +++ b/scripts/version-warning.mjs @@ -6,6 +6,8 @@ import chalk from 'chalk'; const __dirname = dirname(fileURLToPath(import.meta.url)); +const pEnd = 38; + // Styling constants const styles = { title: '#7B65D0', @@ -90,10 +92,10 @@ const message = pkgInfo.versions .map(([pkg, version], index, array) => { const prevPkg = index > 0 ? array[index - 1][0] : ''; const needsSpace = prevPkg.startsWith('@') && pkg === 'react-scan'; - return `${needsSpace ? '\n' : ''}${styles.dim(pkg.padEnd(32))}${styles.version(`v${version}`)}`; + return `${needsSpace ? '\n' : ''}${styles.dim(pkg.padEnd(pEnd))}${styles.version(`v${version}`)}`; }) .join('\n')}` - : `${styles.text(MESSAGES.package.text)}\n\n${styles.dim(pkgInfo.name.padEnd(32))}${styles.version(`v${pkgInfo.version}`)}`; + : `${styles.text(MESSAGES.package.text)}\n\n${styles.dim(pkgInfo.name.padEnd(pEnd))}${styles.version(`v${pkgInfo.version}`)}`; // biome-ignore lint/suspicious/noConsole: Intended debug output console.log(