From 8cbb82d734bb3330698ceba62a81b42824e0fee5 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 14:32:37 -0700 Subject: [PATCH 01/11] docs: extend the docs --- README.md | 248 ++++++++++-------- README2.md | 146 ----------- CONTRIBUTING.md => docs/CONTRIBUTING.md | 13 +- docs/assets/demo.md | 35 --- docs/assets/experimental.md | 58 ---- docs/assets/manual-installation-vite.md | 53 ---- docs/demo.md | 65 +++++ docs/experimental.md | 200 ++++++++++++++ docs/installation-next.md | 67 +++++ docs/installation-vite.md | 37 +++ docs/{assets => }/internal.md | 19 +- docs/usage-examples.md | 0 packages/cli/bin.js | 3 +- .../eslint-plugin-react-zero-ui/package.json | 12 +- 14 files changed, 533 insertions(+), 423 deletions(-) delete mode 100644 README2.md rename CONTRIBUTING.md => docs/CONTRIBUTING.md (84%) delete mode 100644 docs/assets/demo.md delete mode 100644 docs/assets/experimental.md delete mode 100644 docs/assets/manual-installation-vite.md create mode 100644 docs/demo.md create mode 100644 docs/experimental.md create mode 100644 docs/installation-next.md create mode 100644 docs/installation-vite.md rename docs/{assets => }/internal.md (75%) create mode 100644 docs/usage-examples.md diff --git a/README.md b/README.md index ce5194c..2d9260b 100644 --- a/README.md +++ b/README.md @@ -1,186 +1,216 @@ -# React Zero‑UI (Beta) + + +![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) -**Instant UI state updates. ZERO React re‑renders. Near ZERO runtime. <500 bytes** +

-Pre‑render your UI once, flip a `data-*` attribute to update — that's it. +

+ Frame 342 -npm version npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) +

---- + +
+ The fastest possible UI updates in React. Period. -## 📚 Quick Links +Zero runtime, zero React re-renders, and the simplest developer experience ever. Say goodbye to context and prop-drilling. -- [⚡️ Quick Start](#️-quick-start) -- [🏄 Usage](#-usage) -- [🧬 How it works](#-how-it-works) -- [✅ Features](#-features) -- [🏗 Best Practices](#-best-practices) +bundle size npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) ---- -## 🚀 Live Demo +[📖 See the proof](/docs/demo) [🚀 Quick Start](#-quick-start) [📚 API Reference](#-api-reference) [🤝 Contributing](#-contributing) -| Example | Link | What it shows | Link to Code | -| --- | --- | --- | --- | -| Interactive menu with render tracker | Main Demo↗ | Compare Zero‑UI vs. React side‑by‑side while toggling a menu. | Github | -| React benchmark (10 000 nested nodes) | React 10k↗ | How long the traditional React render path takes. | Github | -| Zero‑UI benchmark (10 000 nested nodes) | Zero‑UI 10k↗ | Identical DOM, but powered by Zero‑UI's `data-*` switch. | Github | +
--- -## 🧐 Why Zero‑UI? +## 🔥 Core Concept: *"Pre-Rendering"* + +Why re-render UI if all states are known at build time? React Zero-UI **pre-renders** UI states once ( at no runtime cost ), and flips `data-*` attribute to update - that's it. + +**Example:** + +```tsx +const [, setTheme] = useUI("theme", "dark"); + +// Flip theme to "light" +setTheme("light"); // data-theme="light" on body +``` + +**Tailwind usage:** Anywhere in your app + +```html +
Fast & Reactive
+``` -Every `setState` in React triggers the full VDOM → Diff → Reconciliation → Paint pipeline. For _pure UI state_ (themes, menus, toggles) that work is wasted. +--- -**Zero‑UI introduces "_PRE‑rendering_":** +## 🚀 How it Works (Build-Time Magic) -1. Tailwind variants for every state are **generated at build‑time**. -2. The app **pre‑renders once**. -3. Runtime state changes only **flip a `data-*` attribute on ``**. +React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -Result → **5-10× faster visual updates** with **ZERO additional bundle cost**. +* `useUI` and `useScopedUI` hook usage. +* Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). +* Tailwind variant classes (e.g. `theme-dark:bg-black`). -### 📊 Micro‑benchmarks (Apple M1) +**This generates:** -| Nodes updated | React state | Zero‑UI | Speed‑up | -| ------------- | ----------- | ------- | -------- | -| 10,000 | \~50 ms | \~5 ms | **10×** | -| 25,000 | \~180 ms | \~15 ms | **12×** | -| 50,000 | \~300 ms | \~20 ms | **15×** | +* Optimal CSS with global or scoped variant selectors. +* Initial data-attributes injected onto the body (zero FOUC). +* UI state with ease, no prop-drilling. +* **Zero runtime overhead in production**. -Re‑run these numbers yourself via the links above. --- -## ⚡️ Quick Start +## 🚀 Quick Start +Zero-UI CLI -> **Prerequisite:** Tailwind CSS v4 must already be initialized in your project. +**Pre-requisites:** Vite or Next.js (App Router) ```bash -# Inside an existing *Next.js (App Router)* or *Vite* repo npx create-zero-ui ``` -That's it — the CLI patch‑installs the required Babel & PostCSS plugins and updates `configs` for you. +> For manual configuration, see [Next JS Installation](/docs/installation-next.md) +> [Vite Installation](/docs/installation-vite.md) -### Manual Install +**That's it.** Start your app and see the magic. -```bash -npm install @react-zero-ui/core -``` +--- -Then follow **Setup →** for your bundler. +## 📚 API Reference ---- +**The Basics:** -## 🔧 Setup +```tsx +const [, ] = useUI(, ); +``` -### Vite +- `stateKey` ➡️ becomes `data-{stateKey}` on ``. +- `defaultValue` ➡️ SSR, prevents FOUC. +- `staleValue` ➡️ For scoped UI, set the data-* to the `staleValue` to prevent FOUC. +- **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). -```js -// vite.config.* -import { zeroUIPlugin } from '@react-zero-ui/core/vite'; -export default { - // ❗️Remove the default `tailwindcss()` plugin — Zero‑UI extends it internally - plugins: [zeroUIPlugin()], -}; -``` -### Next.js (App Router) -1. **Spread `bodyAttributes` on ``** in your root layout. - ```tsx - // app/layout.tsx - import { bodyAttributes } from '@zero-ui/attributes'; - // or: import { bodyAttributes } from '../.zero-ui/attributes'; - export default function RootLayout({ children }) { - return ( - - {children} - - ); - } - ``` +### 🔨 `useUI` Hook (Global UI State) -2. **Add the PostCSS plugin (must come _before_ Tailwind).** +Simple hook mirroring React's `useState`: - ```js - // postcss.config.js - module.exports = { plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} } }; - ``` +```tsx +import { useUI } from '@react-zero-ui/core'; + +const [theme, setTheme] = useUI("theme", "dark"); +``` + +**Features:** +* Flips global `data-theme` attribute on ``. +* Zero React re-renders. +* Global UI state available anywhere in your app through tailwind variants. --- -## 🏄 Usage +### 🎯 `useScopedUI` Hook (Scoped UI State) + +Control UI states at the element-level: + +```diff ++ import { useScopedUI } from '@react-zero-ui/core'; + +const [theme, setTheme] = useScopedUI("theme", "dark"); + +// ❗️Flips data-* on the specific ref element ++
+ Scoped UI Here +
+``` -![react zero ui usage explained](docs/assets/useui-explained.webp) +**Features:** +* Data-* flips on specific target element. +* Generates scoped CSS selectors only applying within the target element. +* No FOUC, no re-renders. --- -## 🛠 API +### 🌈 CSS Variables Support -### `useUI(key, defaultValue)` +Sometimes CSS variables are more efficient. React Zero-UI makes it trivial by passing the `CssVar` option: -```ts -const [staleValue, setValue] = useUI<'open' | 'closed'>('sidebar', 'closed'); +```tsx +useUI(, , CssVar); // ❗️Pass CssVar to either hook to use CSS variables ``` +automatically adds `--` to the cssVariable -- `key` → becomes `data-{key}` on ``. -- `defaultValue` → SSR, prevents FOUC. -- **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). - -### Tailwind variants +**Global CSS Variable:** +```diff ++ import { CssVar } from '@react-zero-ui/core'; +``` -```jsx -
+```tsx +const [blur, setBlur] = useUI("blur", "0px", CssVar); +setBlur("5px"); // body { --blur: 5px } ``` -Any `data-{key}="{value}"` pair becomes a variant: `{key}-{value}:`. +**Scoped CSS Variable:** +```tsx +const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); ---- +
+ Scoped blur effect. +
+``` -## 🧬 How it works -1. **`useUI`** → writes to `data-*` attributes on ``. -2. **Babel plugin** → scans code, finds every `key/value`, injects them into **PostCSS**. -3. **PostCSS plugin** → generates static Tailwind classes **at build‑time**. -4. **Runtime** → changing state only touches the attribute — no VDOM, no reconciliation, ZERO re‑renders. --- -## ✅ Features +## 🧪 Experimental Features + +### SSR-safe `zeroOnClick` + +Enable client-side interactivity **without leaving server components**. +Just 300 bytes of runtime overhead. -- **Zero React re‑renders** for UI‑only state. -- **Global setters** — call from any component or util. -- **Tiny**: < 391 Byte gzipped runtime. -- **SSR‑friendly** (Next.js & Vite SSR). -- **Use from anywhere** — Consume with tailwind variants from anywhere. +See [experimental](./docs/assets/experimental.md) for more details. --- -## 🏗 Best Practices +## 📦 Summary of Benefits -1. **Global UI state only** → themes, layout toggles, feature flags. -2. **Business logic stays in React** → fetching, data mutation, etc. -3. **Kebab‑case keys** → e.g. `sidebar-open`. -4. **Provide defaults** to avoid Flash‑Of‑Unstyled‑Content. -5. **Avoid** for per-component logic or data. +* **🚀 Zero React re-renders:** Pure CSS-driven UI state. +* **⚡️ Pre-rendered UI:** All states injected at build-time and only loaded when needed. +* **📦 Tiny footprint:** <350 bytes, zero runtime overhead for CSS states. +* **💫 Amazing DX:** Simple hooks, auto-generated Tailwind variants. +* **⚙️ Highly optimized AST resolver:** Fast, cached build process. + +React Zero-UI delivers the fastest, simplest, most performant way to handle global and scoped UI state in modern React applications. Say goodbye to re-renders and prop-drilling. + +--- --- ## 🤝 Contributing -PRs & issues welcome! Please read the [Contributing Guide](CONTRIBUTING.md). +We welcome contributions from the community! Whether it's bug fixes, feature requests, documentation improvements, or performance optimizations - every contribution helps make React Zero-UI better. ---- +**Get involved:** +- 🐛 Found a bug? [Open an issue](https://github.com/react-zero-ui/core/issues) +- 💡 Have an idea? [Start a discussion](https://github.com/react-zero-ui/core/discussions) +- 🔧 Want to contribute code? Check out our [**Contributing Guide**](/docs/CONTRIBUTING.md) -## 📜 License +> **First time contributor?** We have good first issues labeled `good-first-issue` to help you get started! -[MIT](LICENSE) © Austin Serb +-- ---- +
+ +Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) -Built with ❤️ for the React community. If Zero‑UI makes your app feel ZERO fast, please ⭐️ the repo! +
\ No newline at end of file diff --git a/README2.md b/README2.md deleted file mode 100644 index 69d25b6..0000000 --- a/README2.md +++ /dev/null @@ -1,146 +0,0 @@ - -## ⚡️ React Zero-UI -*The ZERO re-render UI state library* - -**The fastest possible UI updates in React. Period.** -Zero runtime, zero React re-renders, and the simplest developer experience ever. - -See the proof in [here](/docs/demo) - -npm version npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) - ---- - - -## 🔥 Core Concept: *"Pre-Rendering"* - -Why re-render UI if all states are known at build time? React Zero-UI **pre-renders** UI states once, and flips `data-*` attribute to update - that's it. - -**Example:** - -```tsx -const [, setTheme] = useUI("theme", "dark"); - -// Flip theme to "light" -setTheme("light"); // data-theme="light" on body -``` - -Tailwind usage: - -```html -
Fast & Reactive
-``` - ---- - -## 🚀 How it Works (Build-Time Magic) - -React Zero-UI uses a hyper-optimized AST resolver in development/build-time that scans your codebase for: - -* `useUI` and `useScopedUI` hook usage. -* Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). -* Tailwind variant classes (e.g., `theme-dark:bg-black`). - -This generates: - -* Optimal CSS with scoped variant selectors. -* Initial data-attributes injected onto the body (zero FOUC). -* **Zero runtime overhead in production**. - ---- - -## ⚙️ Installation (Zero-UI CLI) - -pre-requisites: -- Tailwind CSS v4 must already be initialized in your project. -- Vite or Next.js (App Router) - - -```bash -npx create-zero-ui -``` -> for manual configuration, see [manual installation.](https://github.com/react-zero-ui/core) - ---- - -## 🔨 API: `useUI` Hook (Global UI state) - -Simple hook mirroring React's `useState`: - -```tsx -import { useUI } from '@react-zero-ui/core'; - -const [theme, setTheme] = useUI("theme", "dark"); -``` - -* Flips global `data-theme` attribute on ``. -* Zero React re-renders. -* Initial state pre-rendered at build time (no FOUC). - ---- - -## 🎯 API: `useScopedUI` Hook (Scoped UI state) - -Control UI states at the element-level: - -```tsx -import { useScopedUI } from '@react-zero-ui/core'; - -const [theme, setTheme] = useScopedUI("theme", "dark"); - -// Flips data-theme attribute on the specific ref element -
- Scoped UI Here -
-``` - -* Data attribute flips on specific target element. -* Generates scoped CSS selectors only applying within the target element. - ---- - -## 🌈 API: CSS Variables Support - -Sometimes CSS variables are more efficient. React Zero-UI makes it trivial using the `CssVar` option: - -```tsx -import { useUI, CssVar } from '@react-zero-ui/core'; - -const [blur, setBlur] = useUI("blur", "0px", CssVar); - -// Flips CSS variable --blur on body -setBlur("5px"); // body { --blur: 5px } -``` - -**Scoped CSS Variable Example:** - -```tsx -const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); - -
- Scoped blur effect. -
-``` - ---- - -## 🧪 Experimental: SSR-safe `zeroOnClick` - -Enable client-side interactivity **without leaving server components**. -Just 300 bytes of runtime overhead. - -See [experimental](./docs/assets/experimental.md) for more details. - -## 📦 Summary of Benefits - -* **Zero React re-renders:** Pure CSS-driven UI state. -* **Pre-rendered UI:** All states injected at build-time. and only loaded when needed. -* **Tiny footprint:** <350 bytes runtime, zero overhead for CSS states. -* **SSR-safe interaction:** Static server components, fully interactive. -* **Amazing DX:** Simple hooks, auto-generated Tailwind variants. -* **Highly optimized AST resolver:** Fast, cached build process. - -React Zero-UI delivers the fastest, simplest, most performant way to handle global and scoped UI state in modern React applications. - - -Made with ❤️ for the React community by [@austinserb](https://github.com/austin1serb) \ No newline at end of file diff --git a/CONTRIBUTING.md b/docs/CONTRIBUTING.md similarity index 84% rename from CONTRIBUTING.md rename to docs/CONTRIBUTING.md index 33f2ce3..2a71571 100644 --- a/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -66,18 +66,19 @@ Use the templates. ## 🧪 Test Commands ```bash -pnpm test:unit # Core logic tests -pnpm test:cli # CLI creation flow -pnpm test:vite # E2E tests on Vite fixture -pnpm test:next # E2E tests on Next.js fixture -pnpm test # Runs all of the above +pnpm test:unit # Core logic tests +pnpm test:integration # Core logic tests +pnpm test:cli # CLI creation flow +pnpm test:vite # E2E tests on Vite fixture +pnpm test:next # E2E tests on Next.js fixture +pnpm test # Runs all of the above ``` --- ## 🤝 Code of Conduct -Keep it respectful. Push ideas hard, not people. +Keep it respectful, accessible. Push ideas hard, not people. --- diff --git a/docs/assets/demo.md b/docs/assets/demo.md deleted file mode 100644 index dff8337..0000000 --- a/docs/assets/demo.md +++ /dev/null @@ -1,35 +0,0 @@ -## 🚀 Demo + Benchmarks - -| Example | Link | What it shows | Link to Code | -| -- | -- | -- | -- | -| Interactive menu with render tracker | [Main Demo](https://zero-ui.dev/) | Compare Zero‑UI vs. React side‑by‑side while toggling a menu. | [Github](https://zero-ui.dev/react) | -| React benchmark (10 000 nested nodes) | [React 10k](https://zero-ui.dev/react) | How long the traditional React render path takes. | [Github](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/react) | -| Zero‑UI benchmark (10 000 nested nodes) | [Zero‑UI 10k](https://zero-ui.dev/zero-ui) | Identical DOM, but powered by Zero‑UI's `data-*` switch. | [Github](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/zero-ui) | - -source code for the demo: [Zero Rerender Demo](/examples/demo/) - ---- - -## 🧐 Why Zero‑UI? - -Every `setState` in React triggers the full VDOM → Diff → Reconciliation → Paint pipeline. For _pure UI state_ (themes, menus, toggles) that work is wasted. - -**Zero‑UI introduces "_PRE‑rendering_":** - -1. Tailwind variants for every state are **generated at build‑time**. -2. The app **pre‑renders once**. -3. Runtime state changes only **flip a `data-*`**. - -Result → **5-10× faster visual updates** with **ZERO additional bundle cost**. - -### 📊 Micro‑benchmarks (Apple M1) - -| Nodes updated | React state | Zero‑UI | Speed‑up | -| ------------- | ----------- | ------- | -------- | -| 10,000 | \~50 ms | \~5 ms | **10×** | -| 25,000 | \~180 ms | \~15 ms | **12×** | -| 50,000 | \~300 ms | \~20 ms | **15×** | - -Re‑run these numbers yourself via the links above with chrome dev tools. - ---- \ No newline at end of file diff --git a/docs/assets/experimental.md b/docs/assets/experimental.md deleted file mode 100644 index 9d2af2b..0000000 --- a/docs/assets/experimental.md +++ /dev/null @@ -1,58 +0,0 @@ - -## 🧪 Experimental: SSR-safe `zeroOnClick` - -Enable client-side interactivity **without leaving server components**. -Just 300 bytes of runtime overhead. - -### ⚙️ Installation (Zero-UI Experimental) - -```bash -npm install @react-zero-ui/core@0.3.1-beta.2 -``` - -Initialize once in your root: - -```tsx -"use client" - -/* ① import the generated defaults */ -import { variantKeyMap } from "../.zero-ui/attributes" -/* ② activate the runtime shipped in the package */ -import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime" - -activateZeroUiRuntime(variantKeyMap) - -export const ZeroUiRuntime = () => null // this component just runs the side effect - -``` - -### Usage - -```tsx -import { zeroSSR } from "@react-zero-ui/core/experimental" - -
- Click me to cycle themes! -
-``` - -Usage is the same as the `useUI` hooks: -```html -
- Interactive Server Component! -
-``` - -**Scoped Version:** - -```tsx -import { zeroScopedOnClick } from '@react-zero-ui/experimental'; - -
// set the scope with a data-attribute that matches the key - -
-``` - -see the source code for the `zeroSSR` object, see [experimental](/packages/core/src/experimental) \ No newline at end of file diff --git a/docs/assets/manual-installation-vite.md b/docs/assets/manual-installation-vite.md deleted file mode 100644 index ad25c9d..0000000 --- a/docs/assets/manual-installation-vite.md +++ /dev/null @@ -1,53 +0,0 @@ - -### Manual Install Vite - -```bash -npm install @react-zero-ui/core @tailwindcss/postcss -``` - ---- - -## 🔧 Setup - -### Vite - -```js -// vite.config.* -import zeroUI from "@react-zero-ui/core/vite"; -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -export default defineConfig({ - // ❗️Remove the default `tailwindcss()` plugin — Zero‑UI extends it internally - plugins: [zeroUI(), react()] -}); -``` -### Next.js (App Router) - -2. **Add the PostCSS plugin (must come _before_ Tailwind).** - - ```js - // postcss.config.js - module.exports = { plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} } }; - ``` - ---- - - - - -1. **Spread `bodyAttributes` on ``** in your root layout. - - ```tsx - // app/layout.tsx - import { bodyAttributes } from './.zero-ui/attributes'; - // or: import { bodyAttributes } from '../.zero-ui/attributes'; - - export default function RootLayout({ children }) { - return ( - - {children} - - ); - } - ``` \ No newline at end of file diff --git a/docs/demo.md b/docs/demo.md new file mode 100644 index 0000000..05346af --- /dev/null +++ b/docs/demo.md @@ -0,0 +1,65 @@ +# 🚀 Demo + Benchmarks + +
+ +**See Zero-UI in action** with interactive demos and performance comparisons. + +Experience the difference between React re-renders and Zero-UI's instant updates. + +[🎮 **Live Demo**](https://zero-ui.dev/) | [⚛️ **React Version**](https://zero-ui.dev/react) | [⚡️ **Zero-UI Version**](https://zero-ui.dev/zero-ui) + +
+ +--- + +## 🎯 Interactive Examples + +| Demo | Description | Live Link | Source Code | +|------|-------------|-----------|-------------| +| **🎛️ Interactive Menu** | Side-by-side comparison with render tracker | [Main Demo](https://zero-ui.dev/) | [GitHub](https://zero-ui.dev/react) | +| **⚛️ React Benchmark** | Traditional React render path (10k nodes) | [React 10k](https://zero-ui.dev/react) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/react) | +| **⚡️ Zero-UI Benchmark** | Identical DOM with `data-*` switching (10k nodes) | [Zero-UI 10k](https://zero-ui.dev/zero-ui) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/zero-ui) | + +> **📁 Full Demo Source:** [Zero Rerender Demo](/examples/demo/) + +--- + +## 🧐 Why Zero-UI? + +Every `setState` in React triggers the full **VDOM → Diff → Reconciliation → Paint** pipeline. For _pure UI state_ (themes, menus, toggles) **that work is wasted**. + +### 🔄 Zero-UI's "PRE-rendering" Approach: + +1. **🏗️ Build-time:** Tailwind variants generated for every state +2. **🎨 Pre-render:** App renders once with all possible states +3. **⚡️ Runtime:** State changes only flip a `data-*` attribute + +**Result:** **5-10× faster visual updates** with **ZERO additional bundle cost**. + +--- + +## 📊 Performance Benchmarks + +
+ +*Tested on Apple M1 - Chrome DevTools Performance Tab* + +
+ +| **Nodes Updated** | **React State** | **Zero-UI** | **Speed Improvement** | +|:-----------------:|:---------------:|:-----------:|:--------------------:| +| 10,000 | ~50 ms | ~5 ms | **🚀 10× faster** | +| 25,000 | ~180 ms | ~15 ms | **🚀 12× faster** | +| 50,000 | ~300 ms | ~20 ms | **🚀 15× faster** | + +> **🔬 Try it yourself:** Re-run these benchmarks using the demo links above with Chrome DevTools. + +--- + +
+ +### Ready to get started? + +[**🚀 Get Started**](https://github.com/react-zero-ui/core/#-quick-start) and never re-render again. + +
\ No newline at end of file diff --git a/docs/experimental.md b/docs/experimental.md new file mode 100644 index 0000000..dc99c17 --- /dev/null +++ b/docs/experimental.md @@ -0,0 +1,200 @@ + +
+

🧪 Experimental Runtime (Zero-UI)

+ +**SSR-safe runtime logic** for handling interactivity in React server components without using + +```diff +- use client +``` + +
+
+Designed to be tiny(~300 bytes), deterministic, and fully compatible with + +React Zero-UI's pre-rendered data-attribute model. + +
+ +--- + +### ❓ Why This Approach? + +**The Problem:** A single `onClick` event forces your entire component tree to become client-rendered. In Next.js, this means shipping extra JavaScript, losing SSR benefits, and adding hydration overhead—all for basic interactivity. + +**The Solution:** This design creates the perfect bridge between **static HTML** and **interactive UX**, while maintaining: +- Server-rendered performance +- Zero JavaScript bundle overhead +- Instant visual feedback + +*Why sacrifice server-side rendering for a simple click handler when 300 bytes of runtime can handle all the clicks in your app?* + +--- + +## 📦 Core Functionality + +### `activateZeroUiRuntime()` + +The core runtime entrypoint that enables client-side interactivity in server components: + +**How it works:** + +1. **🎯 Single Global Listener** - Registers one click event listener on `document` +2. **👂 Smart Detection** - Listens for clicks on elements with `data-ui` attributes +3. **🔍 Directive Parsing** - Interprets `data-ui` directives in this format. +```diff ++ data-ui="global:key(val1,val2,...)" → flips data-key on document.body ++ data-ui="scoped:key(val1,val2,...)" → flips data-key on closest ancestor/self +``` +4. **🔄 Round-Robin Cycling** - Cycles through values in sequence +5. **⚡️ Instant DOM Updates** - Updates DOM immediately for Tailwind responsiveness + +> **Note:** Guards against duplicate initialization using `window.__zero` flag. + +--- + +## 🛠️ Helper Functions + +### `zeroSSR.onClick()` & `scopedZeroSSR.onClick()` + +Utility functions that generate valid `data-ui` attributes for JSX/TSX: + +**Global Example:** +```tsx +zeroSSR.onClick("theme", ["dark", "light"]) +// Returns: { 'data-ui': 'global:theme(dark,light)' } +``` + +**Scoped Example:** +```tsx +scopedZeroSSR.onClick("modal", ["open", "closed"]) +// Returns: { 'data-ui': 'scoped:modal(open,closed)' } +``` + +**Development Validation:** +- ✅ Ensures keys are kebab-case +- ✅ Validates at least one value is provided + +--- + +## 🚀 Installation & Setup + +### Step 1: Install Package + +```bash +npm install @react-zero-ui/core@0.3.1-beta.2 +``` + +### Step 2: Generate Variants + +Run your development server to generate the required variant map: + +```bash +npm run dev +``` + +This creates `.zero-ui/attributes.ts` containing the variant map needed for runtime activation. + +### Step 3: Create `` Component + +```tsx +"use client"; + +import { variantKeyMap } from "path/to/.zero-ui/attributes"; +import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime"; + +activateZeroUiRuntime(variantKeyMap); + +export const InitZeroUI = () => null; +``` + +### Step 4: Add to Root Layout + +```tsx +import { InitZeroUI } from "path/to/InitZeroUI"; + +export default function RootLayout({ children }) { + return ( + + + + {children} + + + ); +} +``` + +--- + +## 💡 Usage Examples + +### Global Theme Toggle + +```tsx +import { zeroSSR } from "@react-zero-ui/core/experimental"; + +
+ Click me to cycle themes! +
+``` + +**Pair with Tailwind variants:** + +```html +
+ Interactive Server Component! +
+``` + +### Scoped Modal Toggle + +```tsx +import { scopedZeroSSR } from "@react-zero-ui/experimental"; + +// ❗️ Scopes based on matching data-* attribute (e.g. data-modal) +
+ +
+``` + +--- + +## 🧠 Design Philosophy + +### Core Principles + +- **🚫 No React State** - Zero re-renders involved +- **🎯 Pure DOM Mutations** - Works entirely via `data-*` attribute changes +- **🔧 Server Component Compatible** - Full compatibility with all server components +- **⚡️ Tailwind-First** - Designed for conditional CSS classes + + +--- + +## 📋 Summary + +| Feature | Description | +|---------|-------------| +| **`activateZeroUiRuntime()`** | Enables click handling on static components via `data-ui` | +| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props | +| **Runtime Overhead** | ~300 bytes total | +| **React Re-renders** | Zero | +| **Server Component Support** | ✅ Full compatibility | + +> **Source Code:** See [experimental](/packages/core/src/experimental) for implementation details. + +--- + +
+ +**The bridge between static HTML and interactive UX** + +*No state. No runtime overhead. Works in server components. ZERO re-renders.* + +[**🚀 Get Started in less than 5 minutes**](/#-quick-start) + +
+ diff --git a/docs/installation-next.md b/docs/installation-next.md new file mode 100644 index 0000000..d00eb57 --- /dev/null +++ b/docs/installation-next.md @@ -0,0 +1,67 @@ +### Next.js (App Router) Setup + +1. **Install the dependencies** +```bash +npm install @react-zero-ui/core +``` + +```bash +npm install @tailwindcss/postcss +``` + +--- + + +2. **Add the PostCSS plugin (must come _before_ Tailwind).** + +```js + // postcss.config.* ESM Syntax + const config = { + // ❗️ Zero-UI must come before Tailwind + plugins: ["@react-zero-ui/core/postcss", "@tailwindcss/postcss"]} + export default config; +``` + + ```js + // postcss.config.* Common Module Syntax + module.exports = { + // ❗️ Zero-UI must come before Tailwind + plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} } }; + ``` + +--- + +3. **Start the App** + +```bash +npm run dev +``` +> Zero-UI will generate a .zero-ui folder in your project root. and generate the attributes.ts and type definitions for it. + + +--- + +4. **Preventing FOUC (Flash Of Unstyled Content)** + +Spread `bodyAttributes` on `` in your root layout. + +```tsx + // app/layout.tsx +import { bodyAttributes } from './.zero-ui/attributes'; + +export default function RootLayout({ children }) { + return ( + + // ❗️ Spread the bodyAttributes on the body tag + {children} + + ); + } +``` + +**Thats it.** +Zero-UI will now add used data-* attributes to the body tag and the CSS will be injected and transformed by tailwind. + +** 🧪 Checkout our Experimental SSR Safe OnClick Handler ** + +[**🚀 Zero UI OnClick**](/docs/experimental.md) \ No newline at end of file diff --git a/docs/installation-vite.md b/docs/installation-vite.md new file mode 100644 index 0000000..a07641c --- /dev/null +++ b/docs/installation-vite.md @@ -0,0 +1,37 @@ +### Vite Setup + +1. **Install the dependencies** +```bash +npm install @react-zero-ui/core +``` +```bash +npm install @tailwindcss/postcss +``` + +--- + +## 🔧 Setup + +### Vite + +2. **Add the plugin to your vite.config.ts** + +```js +// vite.config.* +import zeroUI from "@react-zero-ui/core/vite"; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindCss from '@tailwindcss/postcss'; + +export default defineConfig({ + // ❗️Remove the default `tailwindcss()` plugin — and pass it into the `zeroUI` plugin + plugins: [zeroUI({tailwind: tailwindCss}), react()] +}); +``` +**Thats it.** + +The plugin will add the data-* attributes to the body tag (no FOUC) and the CSS will be injected and transformed by tailwind. + + + + diff --git a/docs/assets/internal.md b/docs/internal.md similarity index 75% rename from docs/assets/internal.md rename to docs/internal.md index b44cbf3..4cd1a62 100644 --- a/docs/assets/internal.md +++ b/docs/internal.md @@ -47,6 +47,7 @@ source files ─► ├─► Map> key: string; // 'stateKey' values: string[]; // ['light', 'dark', …] (unique & sorted) initialValue: string; // from 2nd arg of useUI() + scope: 'global' | 'scoped'; }; ``` @@ -65,10 +66,10 @@ source files ─► ├─► Map> ## 2. Pipeline overview (AST + global token scan) -| Stage | Scope & algorithm | Output | -| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | -| **A - collectUseUIHooks** | Single AST traversal per file.
• Validate `useUI()` shapes.
• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).
• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set` | -| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).
Matches tokens for **every stateKey discovered in Stage A**. | `Map>` | +| Stage | Scope & algorithm | Output | +| --- | --- | --- | +| **A - collectUseUIHooks** | Single AST traversal per file.
• Validate `useUI()` shapes.
• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).
• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set` | +| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).
Matches tokens for **every stateKey discovered in Stage A**. | `Map>` | The pipeline now ensures tokens are captured globally—regardless of hook declarations in each file. @@ -111,12 +112,12 @@ Everything funnels through **`literalFromNode`**. Think of it as a deterministic ### 3.2 Resolvers -| Helper | Purpose | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. | +| Helper | Purpose | +| --- | --- | +| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. | | **`resolveLocalConstIdentifier`** | Maps an `Identifier` → its `const` initializer _iff_ initializer is a local static string/template. Imported bindings rejected explicitly. | -| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. | -| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. | +| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. | +| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. | Resolvers throw contextual errors via **`throwCodeFrame`** (`@babel/code-frame`). diff --git a/docs/usage-examples.md b/docs/usage-examples.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/cli/bin.js b/packages/cli/bin.js index 2b3f121..1240451 100755 --- a/packages/cli/bin.js +++ b/packages/cli/bin.js @@ -18,8 +18,7 @@ if (!existsSync(resolve(target, 'package.json'))) exec('init', ['-y']); exec(pm === 'yarn' ? 'add' : 'install', ['@react-zero-ui/core']); /* 3️⃣ dev deps */ -// TODO figure out if we can do it without tailwindcss and only the postcss plugin -exec(pm === 'yarn' ? 'add' : 'install', ['postcss', 'tailwindcss', '@tailwindcss/postcss', '--save-dev']); +exec(pm === 'yarn' ? 'add' : 'install', ['@tailwindcss/postcss', '--save-dev']); /* 4️⃣ handoff */ // eslint-disable-next-line import/no-unresolved diff --git a/packages/eslint-plugin-react-zero-ui/package.json b/packages/eslint-plugin-react-zero-ui/package.json index e972381..a07b22d 100644 --- a/packages/eslint-plugin-react-zero-ui/package.json +++ b/packages/eslint-plugin-react-zero-ui/package.json @@ -22,11 +22,13 @@ "prepare": "pnpm run build" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.21.0", - "eslint": "^7.7.0" + "@typescript-eslint/parser": "^8.39.0", + "eslint": "^9.0.0" }, "devDependencies": { - "@typescript-eslint/utils": "^8.38.0", - "typescript": "^5.4.3" + "@typescript-eslint/utils": "^8.39.0" + }, + "dependencies": { + "typescript": "^5.9.2" } -} +} \ No newline at end of file From 77740e47aeb1e7f6f05a3d7bf64f8effd2c409e6 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 16:49:47 -0700 Subject: [PATCH 02/11] update resolver to resolve complex template literals --- package.json | 5 +- packages/cli/package.json | 7 +- .../core/__tests__/fixtures/next/app/page.tsx | 5 +- .../core/__tests__/fixtures/vite/index.html | 26 +- .../fixtures/vite/src/ChildComponent.tsx | 4 +- .../__tests__/fixtures/vite/tsconfig.json | 17 +- .../__tests__/fixtures/vite/vite.config.ts | 3 +- packages/core/package.json | 18 +- packages/core/src/cli/postInstall.ts | 7 + packages/core/src/postcss/ast-parsing.test.ts | 23 + packages/core/src/postcss/ast-parsing.ts | 16 +- packages/core/src/postcss/index.cts | 7 +- packages/core/src/postcss/resolvers.test.ts | 518 ++++++++---- packages/core/src/postcss/resolvers.ts | 187 ++++- packages/core/src/postcss/utilities.ts | 9 +- packages/core/src/postcss/vite.ts | 6 +- pnpm-lock.yaml | 771 ++---------------- 17 files changed, 694 insertions(+), 935 deletions(-) diff --git a/package.json b/package.json index 534c6c0..28ec8e0 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "preinstall": "npx only-allow pnpm", "reset": "git clean -fdx && pnpm install --frozen-lockfile && pnpm prepack:core && pnpm i-tarball", "bootstrap": "pnpm install --frozen-lockfile && pnpm build && pnpm prepack:core && pnpm i-tarball", + "dev": "pnpm install && pnpm build && pnpm prepack:core && pnpm i-tarball", "build": "cd packages/core && pnpm build", "test": "cd packages/core && pnpm test:all && pnpm smoke", "prepack:core": "pnpm -F @react-zero-ui/core pack --pack-destination ./dist", @@ -43,8 +44,6 @@ "devDependencies": { "@eslint/js": "^9.32.0", "@types/node": "^24.1.0", - "@typescript-eslint/eslint-plugin": "^8.38.0", - "@typescript-eslint/parser": "^8.38.0", "esbuild": "^0.25.8", "eslint": "^9.32.0", "eslint-plugin-import": "^2.32.0", @@ -54,4 +53,4 @@ "tsx": "^4.20.3", "typescript": "^5.9.2" } -} +} \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 98d1ad2..0159cba 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -16,9 +16,6 @@ "LICENSE" ], "dependencies": { - "@react-zero-ui/core": "^0.2.2", - "postcss": "^8.4.27", - "tailwindcss": "^4.0.0", - "@tailwindcss/postcss": "^4.1.8" + "@react-zero-ui/core": "^0.3.1" } -} +} \ No newline at end of file diff --git a/packages/core/__tests__/fixtures/next/app/page.tsx b/packages/core/__tests__/fixtures/next/app/page.tsx index 49e658a..0f686dc 100644 --- a/packages/core/__tests__/fixtures/next/app/page.tsx +++ b/packages/core/__tests__/fixtures/next/app/page.tsx @@ -5,10 +5,10 @@ import FAQ from './FAQ'; import { ChildComponent } from './ChildComponent'; import { ChildWithoutSetter } from './ChildWithoutSetter'; import CssVarDemo from './CssVarDemo'; - + import { zeroSSR } from '@react-zero-ui/core/experimental'; + import ZeroUiRuntime from './zero-runtime'; - export default function Page() { const [scope, setScope] = useScopedUI<'off' | 'on'>('scope', 'off'); @@ -25,7 +25,6 @@ export default function Page() { const [, setToggleFunction] = useUI<'white' | 'black'>('toggle-function', 'white'); const [, setGlobal] = useUI<'0px' | '4px'>('blur-global', '0px', cssVar); - const toggleFunction = () => { setToggleFunction((prev) => (prev === 'white' ? 'black' : 'white')); }; diff --git a/packages/core/__tests__/fixtures/vite/index.html b/packages/core/__tests__/fixtures/vite/index.html index 6828453..b7292f8 100644 --- a/packages/core/__tests__/fixtures/vite/index.html +++ b/packages/core/__tests__/fixtures/vite/index.html @@ -1,17 +1,15 @@ - - - - Vite + React + TS - - -
- - - + + + + Vite + React + TS + + + +
+ + + + \ No newline at end of file diff --git a/packages/core/__tests__/fixtures/vite/src/ChildComponent.tsx b/packages/core/__tests__/fixtures/vite/src/ChildComponent.tsx index 00d9d46..2e87ce0 100644 --- a/packages/core/__tests__/fixtures/vite/src/ChildComponent.tsx +++ b/packages/core/__tests__/fixtures/vite/src/ChildComponent.tsx @@ -1,6 +1,6 @@ -import { type UISetterFn } from '@react-zero-ui/core'; +import { type GlobalSetterFn } from '@react-zero-ui/core'; -export function ChildComponent({ setIsOpen }: { setIsOpen: UISetterFn<'open' | 'closed'> }) { +export function ChildComponent({ setIsOpen }: { setIsOpen: GlobalSetterFn<'open' | 'closed'> }) { return (
", "license": "MIT", "repository": { @@ -86,9 +72,7 @@ }, "peerDependencies": { "@tailwindcss/postcss": "^4.1.10", - "react": ">=16.8.0", - "tailwindcss": "^4.1.10", - "postcss": "^8.5.5" + "react": ">=16.8.0" }, "dependencies": { "@babel/code-frame": "^7.27.1", diff --git a/packages/core/src/cli/postInstall.ts b/packages/core/src/cli/postInstall.ts index d8aeef3..34cadbe 100644 --- a/packages/core/src/cli/postInstall.ts +++ b/packages/core/src/cli/postInstall.ts @@ -4,6 +4,13 @@ import { generateAttributesFile, patchTsConfig, patchPostcssConfig, patchViteCon import { processVariants } from '../postcss/ast-parsing.js'; export async function runZeroUiInit() { + // 0️⃣ Check peer dependency + try { + require.resolve('@tailwindcss/postcss'); + } catch { + console.error('\n[Zero-UI] ❌ Missing peer dependency "@tailwindcss/postcss".\n' + 'Run: npm install @tailwindcss/postcss\n'); + process.exit(1); + } try { console.log('[Zero-UI] Initializing...'); diff --git a/packages/core/src/postcss/ast-parsing.test.ts b/packages/core/src/postcss/ast-parsing.test.ts index ce8eeb7..90f9eef 100644 --- a/packages/core/src/postcss/ast-parsing.test.ts +++ b/packages/core/src/postcss/ast-parsing.test.ts @@ -101,3 +101,26 @@ test('collectUseUIHooks handles + both hooks', async () => { ] ); }); + +test('collectUseUIHooks NumericLiterals bound to const identifiers', async () => { + const code = ` + import { useUI, useScopedUI } from '@react-zero-ui/core'; + const T = "true"; const M = { "true": "dark", "false": "light" }; const x = M[T] + export function Comp() { + const [, setTheme] = useUI<'theme', 'dark'>('theme', x); + const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); + return
; + } + `; + const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); + const hooks = collectUseUIHooks(ast, code); + + assert.equal(hooks.length, 2); + assert.deepEqual( + hooks.map((h) => [h.stateKey, h.scope]), + [ + ['theme', 'global'], + ['accordion', 'scoped'], + ] + ); +}); diff --git a/packages/core/src/postcss/ast-parsing.ts b/packages/core/src/postcss/ast-parsing.ts index cce68e4..fcb5f51 100644 --- a/packages/core/src/postcss/ast-parsing.ts +++ b/packages/core/src/postcss/ast-parsing.ts @@ -41,8 +41,10 @@ export interface HookMeta { * reduced to a space-free string. */ const ALL_HOOK_NAMES = new Set([CONFIG.HOOK_NAME, CONFIG.LOCAL_HOOK_NAME]); +type HookName = typeof CONFIG.HOOK_NAME | typeof CONFIG.LOCAL_HOOK_NAME; const OBJ_NAMES = new Set([CONFIG.SSR_HOOK_NAME, CONFIG.SSR_HOOK_NAME_SCOPED]); +type LocalHookName = typeof CONFIG.SSR_HOOK_NAME | typeof CONFIG.SSR_HOOK_NAME_SCOPED; export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { const hooks: HookMeta[] = []; @@ -51,7 +53,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { const optsBase = { throwOnFail: true, source: sourceCode } as ResolveOpts; - function lit(node: t.Expression, p: NodePath, hook: ResolveOpts['hook']): string | null { + function resolveLiteralMemoized(node: t.Expression, p: NodePath, hook: ResolveOpts['hook']): string | null { if (memo.has(node)) return memo.get(node)!; // clone instead of mutate @@ -75,7 +77,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { if (!t.isArrayPattern(id) || !t.isCallExpression(init)) return; // b) callee must be one of our hook names - if (!(t.isIdentifier(init.callee) && ALL_HOOK_NAMES.has(init.callee.name as any))) return; + if (!(t.isIdentifier(init.callee) && ALL_HOOK_NAMES.has(init.callee.name as HookName))) return; if (id.elements.length !== 2) { throwCodeFrame(path, path.opts?.filename, sourceCode, `[Zero-UI] useUI() must destructure two values: [value, setterFn].`); @@ -90,7 +92,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { const [keyArg, initialArg] = init.arguments; // resolve state key with new helpers - const stateKey = lit(keyArg as t.Expression, path as NodePath, 'stateKey'); + const stateKey = resolveLiteralMemoized(keyArg as t.Expression, path as NodePath, 'stateKey'); if (stateKey === null) { throwCodeFrame( @@ -103,7 +105,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { } // resolve initial value with helpers - const initialValue = lit(initialArg as t.Expression, path as NodePath, 'initialValue'); + const initialValue = resolveLiteralMemoized(initialArg as t.Expression, path as NodePath, 'initialValue'); if (initialValue === null) { throwCodeFrame( @@ -134,7 +136,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { if ( !t.isMemberExpression(callee) || !t.isIdentifier(callee.object) || - !OBJ_NAMES.has(callee.object.name as any) || + !OBJ_NAMES.has(callee.object.name as LocalHookName) || !t.isIdentifier(callee.property, { name: 'onClick' }) ) return; @@ -144,7 +146,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { const [keyArg, arrArg] = path.node.arguments; /* --- resolve key ------------------------------------------------ */ - const stateKey = lit(keyArg as t.Expression, path, 'stateKey'); + const stateKey = resolveLiteralMemoized(keyArg as t.Expression, path, 'stateKey'); if (stateKey === null) { throwCodeFrame(keyArg, path.opts?.filename, sourceCode, `[Zero-UI] zeroSSR.onClick("key"): key must be a fully-static string.`); } @@ -160,7 +162,7 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { } const values: string[] = []; for (const el of arrArg.elements) { - const v = lit(el as t.Expression, path, 'initialValue'); + const v = resolveLiteralMemoized(el as t.Expression, path, 'initialValue'); if (v === null) { throwCodeFrame(el!, path.opts?.filename, sourceCode, `[Zero-UI] zeroSSR.onClick("${stateKey}",[string]): array values must be static strings.`); } diff --git a/packages/core/src/postcss/index.cts b/packages/core/src/postcss/index.cts index 72ccd88..7636921 100644 --- a/packages/core/src/postcss/index.cts +++ b/packages/core/src/postcss/index.cts @@ -4,14 +4,15 @@ */ import { buildCss, generateAttributesFile, isZeroUiInitialized } from './helpers'; import { runZeroUiInit } from '../cli/postInstall.js'; -import type { PluginCreator, Root, Result } from 'postcss'; import { processVariants } from './ast-parsing'; import { CONFIG } from '../config'; -import { formatError, registerDeps } from './utilities.js'; +import { formatError, registerDeps, Result } from './utilities.js'; + +type Root = { prepend: (css: string) => void }; const zeroUIPlugin = CONFIG.PLUGIN_NAME; -const plugin: PluginCreator = () => { +const plugin = () => { return { postcssPlugin: zeroUIPlugin, async Once(root: Root, { result }: { result: Result }) { diff --git a/packages/core/src/postcss/resolvers.test.ts b/packages/core/src/postcss/resolvers.test.ts index 2019c37..82dcbdd 100644 --- a/packages/core/src/postcss/resolvers.test.ts +++ b/packages/core/src/postcss/resolvers.test.ts @@ -9,15 +9,18 @@ import traverse from './traverse.cjs'; /* Test Coverage: -1. literalFromNode (8 tests) -- String literals -- Template literals without expressions -- Binary string concatenation with + -- Logical OR expressions (||) -- Nullish coalescing (??) -- Identifiers bound to const -- Template literals with expressions -- Non-literal expressions (returns null) +1. literalFromNode + + LogicalExpressions + ConditionalExpression + BinaryExpression + UnaryExpression + StringLiteral + TemplateLiteral + Identifier + MemberExpression + UnaryExpression + 2. resolveLocalConstIdentifier (6 tests) - Const string literals - Const template literals @@ -40,6 +43,8 @@ Test Coverage: - Optional member expressions (THEMES?.dark) - Computed property with const variable - TypeScript as const assertions + + */ // Helper to find a specific expression node @@ -74,6 +79,145 @@ test('literalFromNode should resolve string literals', async () => { }); }); +test('literalFromNode should resolve UnaryExpression that results in a string', async () => { + await runTest({}, async () => { + const code = `const x = typeof "hello";`; + const found = findExpression( + code, + (n) => t.isUnaryExpression(n) && n.operator === 'typeof' && t.isStringLiteral(n.argument) && n.argument.value === 'hello' + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, 'string'); + }); +}); + +test('literalFromNode should resolve UnaryExpression coerced in TemplateLiteral', async () => { + await runTest({}, async () => { + const code = 'const x = `${!true}`;'; + const found = findExpression( + code, + (n) => + t.isTemplateLiteral(n) && + n.expressions.length === 1 && + t.isUnaryExpression(n.expressions[0]) && + n.expressions[0].operator === '!' && + t.isBooleanLiteral(n.expressions[0].argument) && + n.expressions[0].argument.value === true + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, 'false'); + }); +}); + +test('literalFromNode should resolve !false inside TemplateLiteral', async () => { + await runTest({}, async () => { + const code = 'const x = `flag is: ${!false}`;'; + const found = findExpression( + code, + (n) => + t.isTemplateLiteral(n) && + n.expressions.length === 1 && + t.isUnaryExpression(n.expressions[0]) && + n.expressions[0].operator === '!' && + t.isBooleanLiteral(n.expressions[0].argument) && + n.expressions[0].argument.value === false + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, 'flag is: true'); + }); +}); + +test('literalFromNode should resolve multiple UnaryExpressions inside TemplateLiteral', async () => { + await runTest({}, async () => { + const code = 'const x = `${+true}${-2}${typeof null}`;'; + const found = findExpression( + code, + (n) => + t.isTemplateLiteral(n) && + n.expressions.length === 3 && + t.isUnaryExpression(n.expressions[0]) && + t.isUnaryExpression(n.expressions[1]) && + t.isUnaryExpression(n.expressions[2]) + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, '1-2object'); + }); +}); + +test('literalFromNode should resolve typeof null inside TemplateLiteral', async () => { + await runTest({}, async () => { + const code = 'const x = `type: ${typeof null}`;'; + const found = findExpression( + code, + (n) => t.isTemplateLiteral(n) && n.expressions.length === 1 && t.isUnaryExpression(n.expressions[0]) && n.expressions[0].operator === 'typeof' + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, 'type: object'); + }); +}); + +test('literalFromNode should resolve !someConst with local const', async () => { + await runTest({}, async () => { + const code = ` + const someConst = true; + const x = \`prefix-\${!someConst}\`; + `; + const found = findExpression( + code, + (n) => + t.isTemplateLiteral(n) && + n.expressions.length === 1 && + t.isUnaryExpression(n.expressions[0]) && + t.isIdentifier(n.expressions[0].argument) && + n.expressions[0].operator === '!' + ); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + assert.strictEqual(result, 'prefix-false'); + }); +}); + +test('literalFromNode should resolve UnaryExpressions to strings', async () => { + const cases = [ + { description: 'typeof "hello"', code: `const x = typeof "hello";`, expected: 'string' }, + { description: '+42 coerced to string', code: 'const x = `${+42}`;', expected: '42' }, + { description: '-5 coerced to string', code: 'const x = `${-5}`;', expected: '-5' }, + { description: '!false coerced to string', code: 'const x = `${!false}`;', expected: 'true' }, + { description: 'void 0 coerced to string', code: 'const x = `${void 0}`;', expected: 'undefined' }, + ]; + + await runTest({}, async () => { + for (const testCase of cases) { + const { code, expected, description } = testCase; + + const found = findExpression(code, (n) => t.isTemplateLiteral(n) || (t.isUnaryExpression(n) && ['typeof', '+', '-', '!', 'void'].includes(n.operator))); + assert(found, `Expected expression for: ${description}`); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node as t.Expression, found.path, opts); + + assert.strictEqual(result, expected, `Failed: ${description}`); + } + }); +}); + test('literalFromNode should resolve template literals with no expressions', async () => { await runTest({}, async () => { const code = 'const x = `hello world`;'; @@ -86,6 +230,56 @@ test('literalFromNode should resolve template literals with no expressions', asy }); }); +test('literalFromNode should return null or throw on invalid UnaryExpressions', async () => { + const cases = [ + { description: 'typeof function call (dynamic)', code: `const x = typeof getValue();`, shouldThrow: false }, + { description: 'typeof runtime identifier (undeclared)', code: `const x = typeof runtimeValue;`, shouldThrow: false }, + { description: '+imported identifier (illegal)', code: `import { value } from './lib.js'; const x = \`\${+value}\`;`, shouldThrow: true }, + { description: 'Unary ! with non-const identifier', code: `let flag = true; const x = \`\${!flag}\`;`, shouldThrow: false }, + { description: 'typeof Symbol() (non-serializable)', code: `const x = \`\${typeof Symbol()}\`;`, shouldThrow: false }, + ]; + + await runTest({}, async () => { + for (const testCase of cases) { + const { code, description, shouldThrow } = testCase; + const found = findExpression(code, (n) => t.isTemplateLiteral(n) || (t.isUnaryExpression(n) && ['typeof', '+', '-', '!', 'void'].includes(n.operator))); + assert(found, `Expected expression for: ${description}`); + + const opts: ResolveOpts = { throwOnFail: shouldThrow, source: code }; + + let didThrow = false; + let result: string | null = null; + + try { + result = literalFromNode(found.node as t.Expression, found.path, opts); + } catch (e) { + didThrow = true; + } + + if (shouldThrow) { + assert(didThrow, `Expected to throw for: ${description}`); + } else { + assert.strictEqual(result, null, `Expected null for: ${description}`); + } + } + }); +}); + +// Conditional Expression +test('literalFromNode should handle ConditionalExpression', async () => { + await runTest({}, async () => { + const code = ` + const x = true ? "isTrue" : "isFalse"; + `; + const found = findExpression(code, (n) => t.isConditionalExpression(n)); + assert(found); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node as t.ConditionalExpression, found.path, opts); + assert.strictEqual(result, 'isTrue'); + }); +}); + test('literalFromNode should resolve binary string concatenation', async () => { await runTest({}, async () => { const code = `const x = "hello" + " " + "world";`; @@ -98,18 +292,31 @@ test('literalFromNode should resolve binary string concatenation', async () => { }); }); -test('literalFromNode should resolve logical OR expressions', async () => { +test('literalFromNode should resolve various LogicalExpressions to strings', async () => { + const cases = [ + { description: 'OR: undefined || "default"', code: `const undef=undefined; const x = undef || "default";`, expected: 'default' }, + { description: 'OR: "primary" || "fallback"', code: `const x = "primary" || "fallback";`, expected: 'primary' }, + { description: 'OR: null || undefined || "final"', code: `const n=null; const undef=undefined; const x = n || undef || "final";`, expected: 'final' }, + { description: 'OR: null || fallback identifier', code: `const fallback = "default"; const x = null || fallback;`, expected: 'default' }, + { description: 'OR: null || fallback identifier', code: `const fallback = "default"; const x = null || fallback;`, expected: 'default' }, + { description: 'AND: "truthy" && "final"', code: `const x = "truthy" && "final";`, expected: 'final' }, + { description: 'AND: null && "never hit"', code: `const x = null && "never hit";`, expected: null }, + { description: 'Nullish: null ?? "default"', code: `const x = null ?? "default";`, expected: 'default' }, + { description: 'Nullish: "set" ?? "default"', code: `const x = "set" ?? "default";`, expected: 'set' }, + ]; + await runTest({}, async () => { - const code = ` - const fallback = "default"; - const x = undefined || fallback; - `; - const found = findExpression(code, (n) => t.isLogicalExpression(n) && n.operator === '||'); - assert(found); + for (const testCase of cases) { + const { code, expected, description } = testCase; - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = literalFromNode(found.node, found.path, opts); - assert.strictEqual(result, 'default'); + const found = findExpression(code, (n) => t.isLogicalExpression(n)); + assert(found, `Expected to find LogicalExpression in: ${description}`); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node, found.path, opts); + + assert.strictEqual(result, expected, `Failed: ${description}`); + } }); }); @@ -171,6 +378,32 @@ test('literalFromNode should return null for non-literal expressions', async () }); }); +test('literalFromNode should resolve ArrayExpression values via static index access', async () => { + const cases = [ + { description: 'array access with numeric index', code: `const COLORS = ["red", "green", "blue"]; const x = COLORS[1];`, expected: 'green' }, + { description: 'array access with numeric index', code: `const idx = 0; const COLORS = ["red", "green", "blue"]; const x = COLORS[idx];`, expected: 'red' }, + // { description: 'nested object in array access', code: `const THEMES = [{ name: "light" }, { name: "dark" }]; const x = THEMES[1].name;`, expected: 'dark' }, + // { description: 'template literal inside array', code: 'const VALUES = [`va${"lue"}`]; const x = VALUES[0];', expected: 'value' }, + // { description: 'computed numeric index from const', code: `const IDX = 2; const LIST = ["a", "b", 'c']; const x = LIST[IDX];`, expected: 'c' }, + // { description: 'array inside object', code: `const DATA = { values: ["a", "b", "c"] }; const x = DATA.values[0];`, expected: 'a' }, + ]; + + await runTest({}, async () => { + for (const testCase of cases) { + const { code, description, expected } = testCase; + + const found = findExpression(code, (n) => t.isMemberExpression(n) || t.isOptionalMemberExpression(n)); + assert(found, `Expected MemberExpression for: ${description}`); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + const result = literalFromNode(found.node as t.Expression, found.path, opts); + console.log('result: ', result); + + assert.strictEqual(result, expected, `Failed: ${description}`); + } + }); +}); + // Tests for resolveLocalConstIdentifier test('resolveLocalConstIdentifier should resolve const string literals', async () => { await runTest({}, async () => { @@ -214,7 +447,7 @@ test('resolveLocalConstIdentifier should return null for non-identifier', async }); }); -test('resolveLocalConstIdentifier should return null for let/var variables', async () => { +test('resolveLocalConstIdentifier should throw for let/var variables', async () => { await runTest({}, async () => { const code = ` let THEME = "dark"; @@ -224,8 +457,9 @@ test('resolveLocalConstIdentifier should return null for let/var variables', asy assert(found); const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveLocalConstIdentifier(found.node, found.path, opts); - assert.strictEqual(result, null); + assert.throws(() => { + resolveLocalConstIdentifier(found.node, found.path, opts); + }, /Only top-level `const` variables are allowed./); }); }); @@ -320,146 +554,140 @@ test('resolveTemplateLiteral should throw for dynamic expressions', async () => }); // Tests for resolveMemberExpression -test('resolveMemberExpression should resolve simple object property', async () => { - await runTest({}, async () => { - const code = ` - const THEMES = { dark: "dark-theme", light: "light-theme" }; - const x = THEMES.dark; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'dark-theme'); - }); -}); - -test('resolveMemberExpression should resolve computed property access', async () => { - await runTest({}, async () => { - const code = ` - const THEMES = { dark: "dark-theme", light: "light-theme" }; - const x = THEMES["dark"]; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n) && n.computed); - assert(found); - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'dark-theme'); - }); -}); +test('resolveMemberExpression should handle valid and invalid member expressions', async () => { + const cases = [ + { + description: 'simple object property', + code: `const THEMES = { dark: "dark-theme", light: "light-theme" }; const x = THEMES.dark;`, + expected: 'dark-theme', + }, + { + description: 'computed property access', + code: `const THEMES = { dark: "dark-theme", light: "light-theme" }; const x = THEMES["dark"];`, + expected: 'dark-theme', + }, + { + description: 'nested object property', + code: `const THEMES = { brand: { primary: "blue", secondary: "green" } }; const x = THEMES.brand.primary;`, + expected: 'blue', + }, + { description: 'array access by index', code: `const COLORS = ["red", "green", "blue"]; const x = COLORS[1];`, expected: 'green' }, + { description: 'optional member expression', code: `const THEMES = { dark: "dark-theme" }; const x = THEMES?.dark;`, expected: 'dark-theme' }, + { + description: 'computed property using const identifier', + code: `const KEY = "dark"; const THEMES = { dark: "dark-theme" }; const x = THEMES[KEY];`, + expected: 'dark-theme', + }, + { description: 'TypeScript const assertion', code: `const THEMES = { dark: "dark-theme" } as const; const x = THEMES.dark;`, expected: 'dark-theme' }, + { + description: 'nonexistent property should throw', + code: `const THEMES = { dark: "dark-theme" }; const x = THEMES.nonexistent;`, + shouldThrow: /cannot be resolved at build-time/, + }, + { description: 'imported object should throw', code: `import { THEMES } from './constants'; const x = THEMES.dark;`, shouldThrow: /Imports Not Allowed/ }, + ]; -test('resolveMemberExpression should resolve nested object properties', async () => { await runTest({}, async () => { - const code = ` - const THEMES = { - brand: { - primary: "blue", - secondary: "green" - } - }; - const x = THEMES.brand.primary; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n) && t.isMemberExpression(n.object)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'blue'); + for (const testCase of cases) { + const { description, code, expected, shouldThrow } = testCase; + + const found = findExpression(code, (n) => t.isMemberExpression(n) || t.isOptionalMemberExpression(n)); + assert(found, `Expected member expression for: ${description}`); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + + if (shouldThrow) { + assert.throws( + () => { + resolveMemberExpression(found.node as any, found.path, literalFromNode, opts); + }, + shouldThrow, + `Expected error for: ${description}` + ); + } else { + const result = resolveMemberExpression(found.node as any, found.path, literalFromNode, opts); + assert.strictEqual(result, expected, `Failed: ${description}`); + } + } }); }); -test('resolveMemberExpression should resolve array access', async () => { - await runTest({}, async () => { - const code = ` - const COLORS = ["red", "green", "blue"]; - const x = COLORS[1]; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n) && n.computed); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'green'); - }); -}); +test('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { + const cases = [ + { description: 'const binding to numeric literal', code: `const IDX = 2; const x = IDX;`, expected: '2' }, + { description: 'numeric const as object key', code: `const idx = 1; const M = { "1": "yes", 2: "no" }; const x = M[idx];`, expected: 'yes' }, + { + description: 'rejected let-bound numeric literal', + code: `let IDX = 0; const LIST = ["a", "b"]; const x = LIST[IDX];`, + shouldThrow: /Only top-level `const` variables are allowed/, + }, + ]; -test('resolveMemberExpression should throw for non-existent properties', async () => { await runTest({}, async () => { - const code = ` - const THEMES = { dark: "dark-theme" }; - const x = THEMES.nonexistent; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - assert.throws(() => { - resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - }, /cannot be resolved at build-time/); + for (const testCase of cases) { + const { code, expected, shouldThrow, description } = testCase; + + const found = findExpression(code, (n) => t.isIdentifier(n) || t.isMemberExpression(n) || t.isTemplateLiteral(n)); + assert(found, `Expected expression for: ${description}`); + + const opts: ResolveOpts = { throwOnFail: true, source: code }; + + if (shouldThrow) { + assert.throws( + () => { + literalFromNode(found.node as t.Expression, found.path, opts); + }, + shouldThrow, + `Expected failure: ${description}` + ); + } else { + const result = literalFromNode(found.node as t.Expression, found.path, opts); + assert.strictEqual(result, expected, `Failed: ${description}`); + } + } }); }); -test('resolveMemberExpression should throw for imported objects', async () => { - await runTest({}, async () => { - const code = ` - import { THEMES } from './constants'; - const x = THEMES.dark; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - assert.throws(() => { - resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - }, /Imports Not Allowed/); - }); -}); +test('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { + const cases = [ + { + description: 'object access with boolean identifier', + code: `const T = "true"; const M = { "true": "yes", "false": "no" }; const x = M[T];`, + expected: 'yes', + }, + // { description: 'boolean literal as key', code: `const x = { true: 'yes' }[true];`, expected: 'yes' }, + { description: 'boolean const used as key', code: `const FLAG = "false"; const x = { true: 'yes', false: 'no' }[FLAG];`, expected: 'no' }, + ]; -test('resolveMemberExpression should handle optional member expressions', async () => { await runTest({}, async () => { - const code = ` - const THEMES = { dark: "dark-theme" }; - const x = THEMES?.dark; - `; - const found = findExpression(code, (n) => t.isOptionalMemberExpression(n)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - // Cast to MemberExpression since the function handles both - const result = resolveMemberExpression(found.node as any, found.path, literalFromNode, opts); - assert.strictEqual(result, 'dark-theme'); + for (const testCase of cases) { + const { code, expected, description } = testCase; + const found = findExpression(code, (n) => t.isIdentifier(n) || t.isMemberExpression(n) || t.isTemplateLiteral(n)); + assert(found, `Expected expression for: ${description}`); + const opts: ResolveOpts = { throwOnFail: true, source: code }; + + const result = literalFromNode(found.node as t.Expression, found.path, opts); + assert.strictEqual(result, expected, `Failed: ${description}`); + } }); }); -test('resolveMemberExpression should handle computed property with const variable', async () => { - await runTest({}, async () => { - const code = ` - const KEY = "dark"; - const THEMES = { dark: "dark-theme" }; - const x = THEMES[KEY]; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n) && n.computed); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'dark-theme'); - }); -}); +test('literalFromNode should resolve SequenceExpression values', async () => { + const cases = [ + { description: 'sequence returns last string literal', code: `const doSomething = () => {}; const x = (doSomething(), "hello");`, expected: 'hello' }, + { description: 'sequence returns last numeric literal', code: `const doSomething = () => {}; const x = (doSomething(), "42");`, expected: '42' }, + ]; -test('resolveMemberExpression should handle TS as const assertions', async () => { await runTest({}, async () => { - const code = ` - const THEMES = { dark: "dark-theme" } as const; - const x = THEMES.dark; - `; - const found = findExpression(code, (n) => t.isMemberExpression(n)); - assert(found); - - const opts: ResolveOpts = { throwOnFail: true, source: code }; - const result = resolveMemberExpression(found.node as t.MemberExpression, found.path, literalFromNode, opts); - assert.strictEqual(result, 'dark-theme'); + for (const testCase of cases) { + const { code, expected, description } = testCase; + const found = findExpression(code, (n) => t.isSequenceExpression(n)); + assert(found, `Expected expression for: ${description}`); + const opts: ResolveOpts = { throwOnFail: true, source: code }; + + const result = literalFromNode(found.node as t.Expression, found.path, opts); + assert.strictEqual(result, expected, `Failed: ${description}`); + } }); }); diff --git a/packages/core/src/postcss/resolvers.ts b/packages/core/src/postcss/resolvers.ts index 0c61854..c164b89 100644 --- a/packages/core/src/postcss/resolvers.ts +++ b/packages/core/src/postcss/resolvers.ts @@ -10,37 +10,93 @@ export interface ResolveOpts { } /** - * This function will decide which function to call based on the node type - * 1. String literal - * 2. Template literal with no expressions - * 3. Binary expression (a + b) - * 4. Logical expression (a || b, a ?? b) - * 5. Identifier bound to local const - * 6. Template literal with expressions - * 7. Member expression - * 8. Optional member expression - * 9. Everything else is illegal + * Higher up the call tree we verify with typescript that the node resolves to a string literal. and has no whitespace. + * We just have to resolve it at build time. + * + * This function will decide which function to call based on the node type (ALL RESOLVE TO STRINGS) + * StringLiteral + * TemplateLiteral (both static and dynamic expressions resolved recursively) + * Identifier (local const) + * BinaryExpression (+ operator) + * UnaryExpression (covers numeric/string coercions and logical negation explicitly) + * LogicalExpression (||, ??) + * ConditionalExpression (condition ? expr1 : expr2) + * ArrayExpression (["a", "b"][index]) + * NumericLiteral (coerced) + * MemberExpression (static, computed, nested objects/arrays) + * OptionalMemberExpression (a?.b) + * ObjectExpression (via MemberExpression chains) + * BooleanLiteral (handled explicitly by isBooleanLiteral) + SequenceExpression (already handled explicitly by taking last expression) + * @param node - The node to convert * @param path - The path to the node - * @returns The string literal or null if the node is not a string literal or template literal with no expressions or identifier bound to local const + * @returns - The string literal resolved or null */ export function literalFromNode(node: t.Expression, path: NodePath, opts: ResolveOpts): string | null { + // StringLiteral + if (t.isStringLiteral(node)) return node.value; + // NumericLiteral - convert numbers to strings + if (t.isNumericLiteral(node)) return String(node.value); + // BooleanLiteral returned as string + if (t.isBooleanLiteral(node)) return String(node.value); // → 'true' or 'false' + // TemplateLiteral without ${} + if (t.isTemplateLiteral(node) && node.expressions.length === 0) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw; + /* ── Fast path via Babel constant-folder ───────────── */ const ev = fastEval(node, path); - if (ev.confident && typeof ev.value === 'string') return ev.value; - // String / template (no ${}) - if (t.isStringLiteral(node)) return node.value; - if (t.isTemplateLiteral(node) && node.expressions.length === 0) { - const text = node.quasis[0].value.cooked ?? node.quasis[0].value.raw; - return text; + if (ev.confident && typeof ev.value === 'string') { + containsIllegalIdentifiers(node, path, opts); // 👈 throws if invalid + return ev.value; + } + + // ConditionalExpression + if (t.isConditionalExpression(node)) { + const testResult = fastEval(node.test, path); + if (testResult.confident) { + const branch = testResult.value ? node.consequent : node.alternate; + return literalFromNode(branch as t.Expression, path, opts); + } + // If test isn't statically evaluable, return null + return null; } + + // BinaryExpression with + operator if (t.isBinaryExpression(node) && node.operator === '+') { + // Resolve left const left = literalFromNode(node.left as t.Expression, path, opts); + // Resolve right const right = literalFromNode(node.right as t.Expression, path, opts); return left !== null && right !== null ? left + right : null; } + // SequenceExpression (already handled explicitly by taking last expression) + if (t.isSequenceExpression(node)) { + const last = node.expressions.at(-1); + if (last) return literalFromNode(last, path, opts); + } + + if (t.isUnaryExpression(node)) { + const arg = literalFromNode(node.argument as t.Expression, path, opts); + if (arg === null) return null; + + switch (node.operator) { + case 'typeof': + return typeof arg; + case '+': + return typeof arg === 'number' || !isNaN(Number(arg)) ? String(+arg) : null; + case '-': + return typeof arg === 'number' || !isNaN(Number(arg)) ? String(-arg) : null; + case '!': + return String(!arg); + case 'void': + return 'undefined'; + default: + return null; + } + } + /* ── Logical fallback (a || b , a ?? b) ───────────── */ if (t.isLogicalExpression(node) && (node.operator === '||' || node.operator === '??')) { // try left; if it resolves, use it, otherwise fall back to right @@ -49,10 +105,10 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts return literalFromNode(node.right as t.Expression, path, opts); } - // Identifier bound to local const (also handles object/array literals - // via the recursive call inside resolveLocalConstIdentifier) + // "Is this node an Identifier?" + // "If yes, can I resolve it to a literal value like a string, number, or boolean?" const idLit = resolveLocalConstIdentifier(node, path, opts); - if (idLit !== null) return idLit; + if (idLit !== null) return String(idLit); // Template literal with ${expr} or ${CONSTANT} if (t.isTemplateLiteral(node)) { @@ -74,17 +130,17 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts Returns {confident, value} or {confident: false} \*──────────────────────────────────────────────────────────*/ -export function fastEval(node: t.Expression, path: NodePath) { +export function fastEval(node: t.Expression, path: NodePath): { confident: boolean; value?: string } { // ❶ If the node *is* the current visitor path, we can evaluate directly. - if (node === path.node && (path as any).evaluate) { - return (path as any).evaluate(); // safe, returns {confident, value} + if (node === path.node && (path as NodePath).evaluate) { + return path.evaluate(); // safe, returns {confident, value} } // ❷ Otherwise try to locate a child-path that wraps `node`. // (Babel exposes .get() only for *named* keys, so we must scan.) for (const key of Object.keys(path.node)) { - const sub = (path as any).get?.(key); - if (sub?.node === node && sub.evaluate) { + const sub = (path as NodePath).get?.(key) as NodePath | undefined; + if (sub?.node === node && sub?.evaluate) { return sub.evaluate(); } } @@ -100,7 +156,7 @@ export function fastEval(node: t.Expression, path: NodePath) { 1. It is bound in the **same file** (Program scope), 2. Declared with **`const`** (not `let` / `var`), - 3. Initialised to a **string literal** or a **static template literal**, + 3. Initialized to a **string literal** or a **static template literal**, 4. The final string has **no whitespace** (`/^\S+$/`). Anything else (inner-scope `const`, dynamic value, imported binding, spaces) @@ -109,11 +165,7 @@ export function fastEval(node: t.Expression, path: NodePath) { If the binding is *imported*, we delegate to `throwCodeFrame()` so the developer gets a consistent, actionable error message. \*──────────────────────────────────────────────────────────*/ -export function resolveLocalConstIdentifier( - node: t.Expression, // <- widened - path: NodePath, - opts: ResolveOpts -): string | null { +export function resolveLocalConstIdentifier(node: t.Expression, path: NodePath, opts: ResolveOpts): string | number | boolean | null { /* Fast-exit when node isn't an Identifier */ if (!t.isIdentifier(node)) return null; @@ -143,6 +195,14 @@ export function resolveLocalConstIdentifier( /* 2. Allow only top-level `const` */ if (!binding.path.isVariableDeclarator() || binding.scope.block.type !== 'Program' || (binding.path.parent as t.VariableDeclaration).kind !== 'const') { + if ((binding.path.parent as t.VariableDeclaration).kind !== 'const') { + throwCodeFrame( + path, + path.opts?.filename, + opts.source ?? path.opts?.source?.code, + `[Zero-UI] Only top-level \`const\` variables are allowed. '${node.name}' is not valid.` + ); + } return null; } @@ -155,12 +215,17 @@ export function resolveLocalConstIdentifier( init = (init as any).expression; // step into the real value } - let text: string | null = null; + let text: string | number | boolean | null = null; if (t.isStringLiteral(init)) { - text = init.value; + text = init?.value; } else if (t.isTemplateLiteral(init)) { text = resolveTemplateLiteral(init, binding.path, literalFromNode, opts); + } else if (t.isNumericLiteral(init)) { + const raw = String(init.value); + if (/^\S+$/.test(raw)) text = raw; + } else if (t.isBooleanLiteral(init)) { + text = init.value; // → 'true' or 'false' } return text; @@ -267,15 +332,15 @@ export function resolveMemberExpression( } else if (t.isNumericLiteral(expr)) { props.unshift(expr.value); } else { - const lit = literalFromNode(expr, path, opts); - if (lit === null) + const lit = literalFromNode(expr, path, { ...opts, throwOnFail: true }); + if (lit === null) { throwCodeFrame( path, path.opts?.filename, opts.source ?? path.opts?.source?.code, - '[Zero-UI] Member expression must resolve to a static space-free string.' + '[Zero-UI] Member expression must resolve to a static space-free string.\n' + 'only use const identifiers as keys.' ); - + } const num = Number(lit); props.unshift(Number.isFinite(num) ? num : lit); } @@ -358,12 +423,60 @@ export function resolveMemberExpression( \*──────────────────────────────────────────────────────────*/ function resolveObjectValue(obj: t.ObjectExpression, key: string): t.Expression | null | undefined { for (const p of obj.properties) { + // Matches: { dark: 'theme' } — key = 'dark' if (t.isObjectProperty(p) && !p.computed && t.isIdentifier(p.key) && p.key.name === key) { return p.value as t.Expression; } + + // Matches: { ['dark']: 'theme' } — key = 'dark' if (t.isObjectProperty(p) && p.computed && t.isStringLiteral(p.key) && p.key.value === key) { return p.value as t.Expression; } + + // Matches: { "dark": "theme" } or { "1": "theme" } — key = 'dark' or '1' + // if (t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key) { + // return p.value as t.Expression; + // } + + // // ✅ New: Matches { 1: "theme" } — key = '1' + // if (t.isObjectProperty(p) && t.isNumericLiteral(p.key) && String(p.key.value) === key) { + // return p.value as t.Expression; + // } } + return null; } + +function containsIllegalIdentifiers(node: t.Node, path: NodePath, opts: ResolveOpts): void { + t.traverseFast(node, (subNode) => { + if (!t.isIdentifier(subNode)) return; + + const binding = path.scope.getBinding(subNode.name); + if (!binding) { + throwCodeFrame( + path, + path.opts?.filename, + opts.source ?? path.opts?.source?.code, + `[Zero-UI] Identifier '${subNode.name}' is not declared. Only local top-level consts are allowed.` + ); + } + + if (binding.path.isImportSpecifier() || binding.path.isImportDefaultSpecifier() || binding.path.isImportNamespaceSpecifier()) { + throwCodeFrame( + path, + path.opts?.filename, + opts.source ?? path.opts?.source?.code, + `[Zero-UI] Imports Not Allowed:\n Inline it or alias to a local const first.` + ); + } + + if (binding.scope.block.type !== 'Program' || (binding.path.parent as t.VariableDeclaration).kind !== 'const') { + throwCodeFrame( + path, + path.opts?.filename, + opts.source ?? path.opts?.source?.code, + `[Zero-UI] Only top-level \`const\` variables are allowed. '${subNode.name}' is not valid.` + ); + } + }); +} diff --git a/packages/core/src/postcss/utilities.ts b/packages/core/src/postcss/utilities.ts index cd9248b..98c8eca 100644 --- a/packages/core/src/postcss/utilities.ts +++ b/packages/core/src/postcss/utilities.ts @@ -1,5 +1,3 @@ -import { Result } from 'postcss'; - export function formatError(err: unknown) { const error = err instanceof Error ? err : new Error(String(err)); const eWithLoc = error as Error & { loc?: { file?: string; line?: number; column?: number } }; @@ -24,6 +22,13 @@ export function formatError(err: unknown) { return { friendly, loc: eWithLoc.loc }; } +export type Result = { + messages: { type: string; plugin: string; file: string; parent: string }[]; + opts: { from: string }; + prepend: (css: string) => void; + warn: (message: string, options?: { endIndex?: number; index?: number; node?: Node; plugin?: string; word?: string }) => void; +}; + export function registerDeps(result: Result, plugin: string, files: string[], parent: string) { files.forEach((file) => { result.messages.push({ type: 'dependency', plugin, file, parent }); diff --git a/packages/core/src/postcss/vite.ts b/packages/core/src/postcss/vite.ts index 26280f0..a87b06a 100644 --- a/packages/core/src/postcss/vite.ts +++ b/packages/core/src/postcss/vite.ts @@ -1,18 +1,18 @@ //src/postcss/vite.ts // vite-plugin-zero-ui.ts (ESM wrapper) -import tailwindcss from '@tailwindcss/postcss'; import zeroUiPostcss from './index.cjs'; +import tailwindcssInternal from '@tailwindcss/postcss'; import path from 'path'; // @ts-ignore import type { Plugin } from 'vite'; -export default function zeroUI(): Plugin { +export default function zeroUI({ tailwind }: { tailwind?: () => any } = {}): Plugin { return { name: 'vite-react-zero-ui', enforce: 'pre' as const, // run before other Vite plugins async config() { - return { css: { postcss: { plugins: [zeroUiPostcss, tailwindcss()] } } }; + return { css: { postcss: { plugins: [zeroUiPostcss, tailwind ? tailwind : tailwindcssInternal()] } } }; }, async transformIndexHtml(html: string): Promise { const { bodyAttributes } = await import(path.join(process.cwd(), './.zero-ui/attributes.js')); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10c2fa5..cf08479 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,13 +13,7 @@ importers: version: 9.32.0 '@types/node': specifier: ^24.1.0 - version: 24.1.0 - '@typescript-eslint/eslint-plugin': - specifier: ^8.38.0 - version: 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/parser': - specifier: ^8.38.0 - version: 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) + version: 24.2.0 esbuild: specifier: ^0.25.8 version: 0.25.8 @@ -28,7 +22,7 @@ importers: version: 9.32.0(jiti@2.5.1) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)) + version: 2.32.0(eslint@9.32.0(jiti@2.5.1)) eslint-plugin-n: specifier: ^17.21.3 version: 17.21.3(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) @@ -97,17 +91,8 @@ importers: packages/cli: dependencies: '@react-zero-ui/core': - specifier: ^0.2.2 - version: 0.2.7(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11) - '@tailwindcss/postcss': - specifier: ^4.1.8 - version: 4.1.11 - postcss: - specifier: ^8.4.27 - version: 8.5.6 - tailwindcss: - specifier: ^4.0.0 - version: 4.1.11 + specifier: ^0.3.1 + version: 0.3.1(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11) packages/core: dependencies: @@ -135,15 +120,9 @@ importers: lru-cache: specifier: ^11.1.0 version: 11.1.0 - postcss: - specifier: ^8.5.5 - version: 8.5.6 react: specifier: '>=16.8.0' version: 19.1.1 - tailwindcss: - specifier: ^4.1.10 - version: 4.1.11 devDependencies: '@playwright/test': specifier: ^1.54.0 @@ -167,18 +146,18 @@ importers: packages/eslint-plugin-react-zero-ui: dependencies: '@typescript-eslint/parser': - specifier: ^6.21.0 - version: 6.21.0(eslint@7.32.0)(typescript@5.9.2) + specifier: ^8.39.0 + version: 8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) eslint: - specifier: ^7.7.0 - version: 7.32.0 - devDependencies: - '@typescript-eslint/utils': - specifier: ^8.38.0 - version: 8.38.0(eslint@7.32.0)(typescript@5.9.2) + specifier: ^9.0.0 + version: 9.32.0(jiti@2.5.1) typescript: - specifier: ^5.4.3 + specifier: ^5.9.2 version: 5.9.2 + devDependencies: + '@typescript-eslint/utils': + specifier: ^8.39.0 + version: 8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) packages: @@ -190,9 +169,6 @@ packages: resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.12.11': - resolution: {integrity: sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==} - '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -243,10 +219,6 @@ packages: resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.25.9': - resolution: {integrity: sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==} - engines: {node: '>=6.9.0'} - '@babel/parser@7.28.0': resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} engines: {node: '>=6.0.0'} @@ -454,10 +426,6 @@ packages: resolution: {integrity: sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@0.4.3': - resolution: {integrity: sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==} - engines: {node: ^10.12.0 || >=12.0.0} - '@eslint/eslintrc@3.3.1': resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -486,19 +454,10 @@ packages: resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} - '@humanwhocodes/config-array@0.5.0': - resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/object-schema@1.2.1': - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - deprecated: Use @eslint/object-schema instead - '@humanwhocodes/retry@0.3.1': resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} @@ -804,8 +763,8 @@ packages: react: '>=16.8.0' tailwindcss: ^4.1.10 - '@react-zero-ui/core@0.2.7': - resolution: {integrity: sha512-LMu+Zm4VHiJOiLNbs5fhLUK0+RxpnlurjgA7D24LuXBDLuYd22cQKiYBUOidi92nmo9uq4s8hMz7TGiAqhcc3w==} + '@react-zero-ui/core@0.3.1': + resolution: {integrity: sha512-h3A3GOxFyiJ62Ot6g5hAvfF+kEtSimzcCfW41147P2+HUzgdWsYI5ZjadIEwhXF2inBU7eFJcmnm3gSVdfbiOg==} engines: {node: '>=18.0.0'} peerDependencies: '@tailwindcss/postcss': ^4.1.10 @@ -1040,8 +999,8 @@ packages: '@types/node@20.19.9': resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} - '@types/node@24.1.0': - resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + '@types/node@24.2.0': + resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -1071,94 +1030,48 @@ packages: '@types/yargs@16.0.9': resolution: {integrity: sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==} - '@typescript-eslint/eslint-plugin@8.38.0': - resolution: {integrity: sha512-CPoznzpuAnIOl4nhj4tRr4gIPj5AfKgkiJmGQDaq+fQnRJTYlcBjbX3wbciGmpoPf8DREufuPRe1tNMZnGdanA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.38.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@8.38.0': - resolution: {integrity: sha512-Zhy8HCvBUEfBECzIl1PKqF4p11+d0aUJS1GeUiuqK9WmOug8YCmC4h4bjyBvMyAMI9sbRczmrYL5lKg/YMbrcQ==} + '@typescript-eslint/parser@8.39.0': + resolution: {integrity: sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.38.0': - resolution: {integrity: sha512-dbK7Jvqcb8c9QfH01YB6pORpqX1mn5gDZc9n63Ak/+jD67oWXn3Gs0M6vddAN+eDXBCS5EmNWzbSxsn9SzFWWg==} + '@typescript-eslint/project-service@8.39.0': + resolution: {integrity: sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.38.0': - resolution: {integrity: sha512-WJw3AVlFFcdT9Ri1xs/lg8LwDqgekWXWhH3iAF+1ZM+QPd7oxQ6jvtW/JPwzAScxitILUIFs0/AnQ/UWHzbATQ==} + '@typescript-eslint/scope-manager@8.39.0': + resolution: {integrity: sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.38.0': - resolution: {integrity: sha512-Lum9RtSE3EroKk/bYns+sPOodqb2Fv50XOl/gMviMKNvanETUuUcC9ObRbzrJ4VSd2JalPqgSAavwrPiPvnAiQ==} + '@typescript-eslint/tsconfig-utils@8.39.0': + resolution: {integrity: sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.38.0': - resolution: {integrity: sha512-c7jAvGEZVf0ao2z+nnz8BUaHZD09Agbh+DY7qvBQqLiz8uJzRgVPj5YvOh8I8uEiH8oIUGIfHzMwUcGVco/SJg==} + '@typescript-eslint/types@8.39.0': + resolution: {integrity: sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' - - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} - '@typescript-eslint/types@8.38.0': - resolution: {integrity: sha512-wzkUfX3plUqij4YwWaJyqhiPE5UCRVlFpKn1oCRn2O1bJ592XxWJj8ROQ3JD5MYXLORW84063z3tZTb/cs4Tyw==} + '@typescript-eslint/typescript-estree@8.39.0': + resolution: {integrity: sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/typescript-estree@8.38.0': - resolution: {integrity: sha512-fooELKcAKzxux6fA6pxOflpNS0jc+nOQEEOipXFNjSlBS6fqrJOVY/whSn70SScHrcJ2LDsxWrneFoWYSVfqhQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.38.0': - resolution: {integrity: sha512-hHcMA86Hgt+ijJlrD8fX0j1j8w4C92zue/8LOPAFioIno+W0+L7KqE8QZKCcPGc/92Vs9x36w/4MPTJhqXdyvg==} + '@typescript-eslint/utils@8.39.0': + resolution: {integrity: sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <5.9.0' + typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/visitor-keys@8.38.0': - resolution: {integrity: sha512-pWrTcoFNWuwHlA9CvlfSsGWs14JxfN1TH25zM5L7o0pRLhsoZkDnTsXfQRJBEWJoV5DL0jf+Z+sxiud+K0mq1g==} + '@typescript-eslint/visitor-keys@8.39.0': + resolution: {integrity: sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@vercel/analytics@1.5.0': @@ -1196,11 +1109,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -1213,28 +1121,14 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - - 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'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1249,10 +1143,6 @@ packages: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - array.prototype.findlastindex@1.2.6: resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} engines: {node: '>= 0.4'} @@ -1273,10 +1163,6 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -1344,10 +1230,6 @@ packages: caniuse-lite@1.0.30001731: resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1375,16 +1257,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1501,18 +1377,10 @@ packages: resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} engines: {node: '>=0.3.1'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} @@ -1534,8 +1402,8 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.194: - resolution: {integrity: sha512-SdnWJwSUot04UR51I2oPD8kuP2VI37/CADR1OHsFOUzZIvfWJBO6q11k5P/uKNyTT3cdOsnyjkrZ+DDShqYqJA==} + electron-to-chromium@1.5.195: + resolution: {integrity: sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1544,10 +1412,6 @@ packages: resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} 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'} @@ -1652,26 +1516,10 @@ packages: peerDependencies: eslint: '>=8.23.0' - eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - eslint-scope@8.4.0: resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint-utils@2.1.0: - resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} - engines: {node: '>=6'} - - eslint-visitor-keys@1.3.0: - resolution: {integrity: sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==} - engines: {node: '>=4'} - - eslint-visitor-keys@2.1.0: - resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} - engines: {node: '>=10'} - eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1680,12 +1528,6 @@ packages: resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@7.32.0: - resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} - engines: {node: ^10.12.0 || >=12.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - eslint@9.32.0: resolution: {integrity: sha512-LSehfdpgMeWcTZkWZVIJl+tkZ2nuSkyyB9C27MZqFWXuph7DvaowgcTvKqxvpLW1JZIk8PN7hFY3Rj9LQ7m7lg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1700,15 +1542,6 @@ packages: resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - espree@7.3.1: - resolution: {integrity: sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==} - engines: {node: ^10.12.0 || >=12.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'} @@ -1717,10 +1550,6 @@ packages: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} - estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - estraverse@5.3.0: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} @@ -1748,9 +1577,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.0.6: - resolution: {integrity: sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==} - fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -1758,10 +1584,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1778,10 +1600,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} @@ -1827,9 +1645,6 @@ packages: resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} - functional-red-black-tree@1.0.1: - resolution: {integrity: sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==} - functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} @@ -1868,10 +1683,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1884,10 +1695,6 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - globrex@0.1.2: resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} @@ -1898,9 +1705,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -1914,10 +1718,6 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1960,18 +1760,10 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - ignore@4.0.6: - resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} - engines: {node: '>= 4'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -2126,10 +1918,6 @@ 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 @@ -2152,9 +1940,6 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -2271,9 +2056,6 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.truncate@4.4.2: - resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lru-cache@11.1.0: resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==} engines: {node: 20 || >=22} @@ -2323,10 +2105,6 @@ packages: resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} engines: {node: '>=10'} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -2514,10 +2292,6 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2563,10 +2337,6 @@ packages: engines: {node: '>=14'} hasBin: true - progress@2.0.3: - resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} - engines: {node: '>=0.4.0'} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2627,10 +2397,6 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regexpp@3.2.0: - resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} - engines: {node: '>=8'} - release-please@17.1.1: resolution: {integrity: sha512-baFGx79P5hGxbtDqf8p+ThwQjHT/byst6EtnCkjfA6FcK5UnaERn6TBI+8wSsaVIohPG2urYjZaPkITxDS3/Pw==} engines: {node: '>=18.0.0'} @@ -2640,10 +2406,6 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2664,11 +2426,6 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - rollup@4.46.2: resolution: {integrity: sha512-WMmLFI+Boh6xbop+OAGo9cQ3OgX9MIg7xOQjn+pTCwOkk+FNDAeAemXkJ3HzDJrVXleLOFVa1ipuc1AmEx1Dwg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -2751,14 +2508,6 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2782,9 +2531,6 @@ packages: split@1.0.1: resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -2834,10 +2580,6 @@ packages: babel-plugin-macros: optional: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2846,10 +2588,6 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - table@6.9.0: - resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} - engines: {node: '>=10.0.0'} - tailwindcss@4.1.11: resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} @@ -2861,9 +2599,6 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} @@ -2875,12 +2610,6 @@ packages: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2911,10 +2640,6 @@ packages: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - type-fest@0.6.0: resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} engines: {node: '>=8'} @@ -2965,8 +2690,8 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici-types@7.8.0: - resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + undici-types@7.10.0: + resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} unist-util-is@4.1.0: resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} @@ -2993,9 +2718,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - v8-compile-cache@2.4.0: - resolution: {integrity: sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==} - validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} @@ -3055,8 +2777,8 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yaml@2.8.0: - resolution: {integrity: sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} hasBin: true @@ -3089,10 +2811,6 @@ snapshots: '@jridgewell/gen-mapping': 0.3.12 '@jridgewell/trace-mapping': 0.3.29 - '@babel/code-frame@7.12.11': - dependencies: - '@babel/highlight': 7.25.9 - '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -3166,13 +2884,6 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.2 - '@babel/highlight@7.25.9': - dependencies: - '@babel/helper-validator-identifier': 7.27.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - '@babel/parser@7.28.0': dependencies: '@babel/types': 7.28.2 @@ -3299,11 +3010,6 @@ snapshots: '@esbuild/win32-x64@0.25.8': optional: true - '@eslint-community/eslint-utils@4.7.0(eslint@7.32.0)': - dependencies: - eslint: 7.32.0 - eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.7.0(eslint@9.32.0(jiti@2.5.1))': dependencies: eslint: 9.32.0(jiti@2.5.1) @@ -3325,20 +3031,6 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@0.4.3': - dependencies: - ajv: 6.12.6 - debug: 4.4.1 - espree: 7.3.1 - globals: 13.24.0 - ignore: 4.0.6 - import-fresh: 3.3.1 - js-yaml: 3.14.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 @@ -3375,18 +3067,8 @@ snapshots: '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.3.1 - '@humanwhocodes/config-array@0.5.0': - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.4.1 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/object-schema@1.2.1': {} - '@humanwhocodes/retry@0.3.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -3635,7 +3317,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-zero-ui/core@0.2.7(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11)': + '@react-zero-ui/core@0.3.1(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11)': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.0 @@ -3819,9 +3501,9 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/node@24.1.0': + '@types/node@24.2.0': dependencies: - undici-types: 7.8.0 + undici-types: 7.10.0 '@types/normalize-package-data@2.4.4': {} @@ -3847,108 +3529,44 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/type-utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.38.0 - eslint: 9.32.0(jiti@2.5.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.2) - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@6.21.0(eslint@7.32.0)(typescript@5.9.2)': - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.1 - eslint: 7.32.0 - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - '@typescript-eslint/visitor-keys': 8.38.0 + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.39.0 debug: 4.4.1 eslint: 9.32.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.38.0(typescript@5.9.2)': + '@typescript-eslint/project-service@8.39.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.9.2) - '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 debug: 4.4.1 typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@6.21.0': + '@typescript-eslint/scope-manager@8.39.0': dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - - '@typescript-eslint/scope-manager@8.38.0': - dependencies: - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/visitor-keys': 8.38.0 - - '@typescript-eslint/tsconfig-utils@8.38.0(typescript@5.9.2)': - dependencies: - typescript: 5.9.2 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 - '@typescript-eslint/type-utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/tsconfig-utils@8.39.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - '@typescript-eslint/utils': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) - debug: 4.4.1 - eslint: 9.32.0(jiti@2.5.1) - ts-api-utils: 2.1.0(typescript@5.9.2) typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/types@6.21.0': {} - - '@typescript-eslint/types@8.38.0': {} - - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.2)': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.1 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.7.2 - ts-api-utils: 1.4.3(typescript@5.9.2) - optionalDependencies: - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color + '@typescript-eslint/types@8.39.0': {} - '@typescript-eslint/typescript-estree@8.38.0(typescript@5.9.2)': + '@typescript-eslint/typescript-estree@8.39.0(typescript@5.9.2)': dependencies: - '@typescript-eslint/project-service': 8.38.0(typescript@5.9.2) - '@typescript-eslint/tsconfig-utils': 8.38.0(typescript@5.9.2) - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/visitor-keys': 8.38.0 + '@typescript-eslint/project-service': 8.39.0(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.39.0(typescript@5.9.2) + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/visitor-keys': 8.39.0 debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -3959,36 +3577,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.38.0(eslint@7.32.0)(typescript@5.9.2)': - dependencies: - '@eslint-community/eslint-utils': 4.7.0(eslint@7.32.0) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) - eslint: 7.32.0 - typescript: 5.9.2 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': + '@typescript-eslint/utils@8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) - '@typescript-eslint/scope-manager': 8.38.0 - '@typescript-eslint/types': 8.38.0 - '@typescript-eslint/typescript-estree': 8.38.0(typescript@5.9.2) + '@typescript-eslint/scope-manager': 8.39.0 + '@typescript-eslint/types': 8.39.0 + '@typescript-eslint/typescript-estree': 8.39.0(typescript@5.9.2) eslint: 9.32.0(jiti@2.5.1) typescript: 5.9.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@6.21.0': + '@typescript-eslint/visitor-keys@8.39.0': dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 - - '@typescript-eslint/visitor-keys@8.38.0': - dependencies: - '@typescript-eslint/types': 8.38.0 + '@typescript-eslint/types': 8.39.0 eslint-visitor-keys: 4.2.1 '@vercel/analytics@1.5.0(next@15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1))(react@19.1.1)': @@ -3998,16 +3600,10 @@ snapshots: '@xmldom/xmldom@0.8.10': {} - acorn-jsx@5.3.2(acorn@7.4.1): - dependencies: - acorn: 7.4.1 - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 - acorn@7.4.1: {} - acorn@8.15.0: {} agent-base@7.1.4: {} @@ -4019,29 +3615,12 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.0.6 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-colors@4.1.3: {} - ansi-regex@5.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - argparse@2.0.1: {} array-buffer-byte-length@1.0.2: @@ -4062,8 +3641,6 @@ snapshots: is-string: 1.1.1 math-intrinsics: 1.1.0 - array-union@2.1.0: {} - array.prototype.findlastindex@1.2.6: dependencies: call-bind: 1.0.8 @@ -4100,8 +3677,6 @@ snapshots: arrify@1.0.1: {} - astral-regex@2.0.0: {} - async-function@1.0.0: {} async-retry@1.3.3: @@ -4141,7 +3716,7 @@ snapshots: browserslist@4.25.1: dependencies: caniuse-lite: 1.0.30001731 - electron-to-chromium: 1.5.194 + electron-to-chromium: 1.5.195 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) @@ -4174,12 +3749,6 @@ snapshots: caniuse-lite@1.0.30001731: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4213,16 +3782,10 @@ snapshots: parse-diff: 0.11.1 yargs: 16.2.0 - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} color-string@1.9.1: @@ -4342,18 +3905,10 @@ snapshots: diff@7.0.0: {} - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - doctrine@2.1.0: dependencies: esutils: 2.0.3 - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - dom-serializer@2.0.0: dependencies: domelementtype: 2.3.0 @@ -4382,7 +3937,7 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.194: {} + electron-to-chromium@1.5.195: {} emoji-regex@8.0.0: {} @@ -4391,11 +3946,6 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.2 - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - entities@4.5.0: {} error-ex@1.3.2: @@ -4532,11 +4082,10 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.5.1)): + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: @@ -4549,7 +4098,7 @@ snapshots: eslint: 9.32.0(jiti@2.5.1) eslint-compat-utils: 0.5.1(eslint@9.32.0(jiti@2.5.1)) - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint@9.32.0(jiti@2.5.1)): + eslint-plugin-import@2.32.0(eslint@9.32.0(jiti@2.5.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4560,7 +4109,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.32.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2))(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.5.1)) + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint@9.32.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4571,8 +4120,6 @@ snapshots: semver: 6.3.1 string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 - optionalDependencies: - '@typescript-eslint/parser': 8.38.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4593,73 +4140,15 @@ snapshots: transitivePeerDependencies: - typescript - eslint-scope@5.1.1: - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - eslint-scope@8.4.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-utils@2.1.0: - dependencies: - eslint-visitor-keys: 1.3.0 - - eslint-visitor-keys@1.3.0: {} - - eslint-visitor-keys@2.1.0: {} - eslint-visitor-keys@3.4.3: {} eslint-visitor-keys@4.2.1: {} - eslint@7.32.0: - dependencies: - '@babel/code-frame': 7.12.11 - '@eslint/eslintrc': 0.4.3 - '@humanwhocodes/config-array': 0.5.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.1 - doctrine: 3.0.0 - enquirer: 2.4.1 - escape-string-regexp: 4.0.0 - eslint-scope: 5.1.1 - eslint-utils: 2.1.0 - eslint-visitor-keys: 2.1.0 - espree: 7.3.1 - esquery: 1.6.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - functional-red-black-tree: 1.0.1 - glob-parent: 5.1.2 - globals: 13.24.0 - ignore: 4.0.6 - import-fresh: 3.3.1 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - js-yaml: 3.14.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.4 - progress: 2.0.3 - regexpp: 3.2.0 - semver: 7.7.2 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - table: 6.9.0 - text-table: 0.2.0 - v8-compile-cache: 2.4.0 - transitivePeerDependencies: - - supports-color - eslint@9.32.0(jiti@2.5.1): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.5.1)) @@ -4708,14 +4197,6 @@ snapshots: acorn-jsx: 5.3.2(acorn@8.15.0) eslint-visitor-keys: 4.2.1 - espree@7.3.1: - dependencies: - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - eslint-visitor-keys: 1.3.0 - - esprima@4.0.1: {} - esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -4724,8 +4205,6 @@ snapshots: dependencies: estraverse: 5.3.0 - estraverse@4.3.0: {} - estraverse@5.3.0: {} estree-walker@2.0.2: {} @@ -4750,8 +4229,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.0.6: {} - fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -4760,10 +4237,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4782,12 +4255,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat-cache@3.2.0: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - rimraf: 3.0.2 - flat-cache@4.0.1: dependencies: flatted: 3.3.3 @@ -4827,8 +4294,6 @@ snapshots: hasown: 2.0.2 is-callable: 1.2.7 - functional-red-black-tree@1.0.1: {} - functions-have-names@1.2.3: {} gensync@1.0.0-beta.2: {} @@ -4880,10 +4345,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - globals@14.0.0: {} globals@15.15.0: {} @@ -4893,23 +4354,12 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - globrex@0.1.2: {} gopd@1.2.0: {} graceful-fs@4.2.11: {} - graphemer@1.4.0: {} - handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -4923,8 +4373,6 @@ snapshots: has-bigints@1.1.0: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -4967,12 +4415,8 @@ snapshots: transitivePeerDependencies: - supports-color - ignore@4.0.6: {} - ignore@5.3.2: {} - ignore@7.0.5: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -5121,11 +4565,6 @@ 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 @@ -5140,8 +4579,6 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-safe@5.0.1: {} @@ -5230,8 +4667,6 @@ snapshots: lodash.merge@4.6.2: {} - lodash.truncate@4.4.2: {} - lru-cache@11.1.0: {} lru-cache@5.1.1: @@ -5283,10 +4718,6 @@ snapshots: dependencies: brace-expansion: 2.0.2 - minimatch@9.0.3: - dependencies: - brace-expansion: 2.0.2 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 @@ -5475,8 +4906,6 @@ snapshots: path-parse@1.0.7: {} - path-type@4.0.0: {} - picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -5511,8 +4940,6 @@ snapshots: prettier@3.6.2: {} - progress@2.0.3: {} - punycode@2.3.1: {} queue-microtask@1.2.3: {} @@ -5593,8 +5020,6 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - regexpp@3.2.0: {} - release-please@17.1.1: dependencies: '@conventional-commits/parser': 0.4.1 @@ -5626,15 +5051,13 @@ snapshots: unist-util-visit: 2.0.3 unist-util-visit-parents: 3.1.1 xpath: 0.0.34 - yaml: 2.8.0 + yaml: 2.8.1 yargs: 17.7.2 transitivePeerDependencies: - supports-color require-directory@2.1.1: {} - require-from-string@2.0.2: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -5649,10 +5072,6 @@ snapshots: reusify@1.1.0: {} - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - rollup@4.46.2: dependencies: '@types/estree': 1.0.8 @@ -5804,14 +5223,6 @@ snapshots: sisteransi@1.0.5: {} - slash@3.0.0: {} - - slice-ansi@4.0.0: - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - source-map-js@1.2.1: {} source-map@0.6.1: {} @@ -5834,8 +5245,6 @@ snapshots: dependencies: through: 2.3.8 - sprintf-js@1.0.3: {} - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -5889,24 +5298,12 @@ snapshots: optionalDependencies: '@babel/core': 7.28.0 - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} - table@6.9.0: - dependencies: - ajv: 8.17.1 - lodash.truncate: 4.4.2 - slice-ansi: 4.0.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - tailwindcss@4.1.11: {} tapable@2.2.2: {} @@ -5920,8 +5317,6 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - text-table@0.2.0: {} - through@2.3.8: {} to-regex-range@5.0.1: @@ -5930,10 +5325,6 @@ snapshots: trim-newlines@3.0.1: {} - ts-api-utils@1.4.3(typescript@5.9.2): - dependencies: - typescript: 5.9.2 - ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -5965,8 +5356,6 @@ snapshots: type-fest@0.18.1: {} - type-fest@0.20.2: {} - type-fest@0.6.0: {} type-fest@0.8.1: {} @@ -6022,7 +5411,7 @@ snapshots: undici-types@6.21.0: {} - undici-types@7.8.0: {} + undici-types@7.10.0: {} unist-util-is@4.1.0: {} @@ -6055,8 +5444,6 @@ snapshots: dependencies: punycode: 2.3.1 - v8-compile-cache@2.4.0: {} - validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 @@ -6132,7 +5519,7 @@ snapshots: yallist@5.0.0: {} - yaml@2.8.0: {} + yaml@2.8.1: {} yargs-parser@20.2.9: {} From f90237b461e7ff007d1e75b11b831729d7ee2d05 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 18:40:19 -0700 Subject: [PATCH 03/11] added verbose mode --- docs/internal.md | 21 +- docs/rules.md | 0 .../__tests__/fixtures/vite/vite.config.ts | 3 +- packages/core/src/cli/postInstall.ts | 7 - packages/core/src/postcss/ast-parsing.test.ts | 188 +++++++++--------- packages/core/src/postcss/ast-parsing.ts | 12 +- packages/core/src/postcss/resolvers.ts | 54 ++++- .../fixtures/next/app/ChildComponent.tsx | 4 +- .../__tests__/fixtures/next/app/page.tsx | 8 +- 9 files changed, 163 insertions(+), 134 deletions(-) create mode 100644 docs/rules.md diff --git a/docs/internal.md b/docs/internal.md index 4cd1a62..a8fa913 100644 --- a/docs/internal.md +++ b/docs/internal.md @@ -51,14 +51,19 @@ source files ─► ├─► Map> }; ``` -5. **Emit Tailwind `@custom-variant`s** for every `key‑value` pair `(helpers.cts) → buildCss` - - ```css - @custom-variant ${keySlug}-${valSlug} { - &:where(body[data-${keySlug}="${valSlug}"] *) { @slot; } - [data-${keySlug}="${valSlug}"] &, &[data-${keySlug}="${valSlug}"] { @slot; } - } - ``` +5. **Emit Tailwind** `@custom-variant` for every `key‑value` pair `(helpers.cts) → buildCss` + +```ts +function buildLocalSelector(keySlug: string, valSlug: string): string { + return + `[data-${keySlug}="${valSlug}"] &, &[data-${keySlug}="${valSlug}"] { @slot; }`; +} + +function buildGlobalSelector(keySlug: string, valSlug: string): string { + return + `&:where(body[data-${keySlug}='${valSlug}'] &) { @slot; }`; +} +``` 6. **Generate the attributes file** so SSR can inject the `` data‑attributes `(helpers.cts) → generateAttributesFile`. diff --git a/docs/rules.md b/docs/rules.md new file mode 100644 index 0000000..e69de29 diff --git a/packages/core/__tests__/fixtures/vite/vite.config.ts b/packages/core/__tests__/fixtures/vite/vite.config.ts index 574010f..1d610da 100644 --- a/packages/core/__tests__/fixtures/vite/vite.config.ts +++ b/packages/core/__tests__/fixtures/vite/vite.config.ts @@ -1,9 +1,8 @@ import zeroUI from "@react-zero-ui/core/vite"; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import tailwindCss from '@tailwindcss/postcss'; // https://vite.dev/config/ export default defineConfig({ - plugins: [zeroUI({tailwind: tailwindCss}), react()] + plugins: [zeroUI(), react()] }); \ No newline at end of file diff --git a/packages/core/src/cli/postInstall.ts b/packages/core/src/cli/postInstall.ts index 34cadbe..d8aeef3 100644 --- a/packages/core/src/cli/postInstall.ts +++ b/packages/core/src/cli/postInstall.ts @@ -4,13 +4,6 @@ import { generateAttributesFile, patchTsConfig, patchPostcssConfig, patchViteCon import { processVariants } from '../postcss/ast-parsing.js'; export async function runZeroUiInit() { - // 0️⃣ Check peer dependency - try { - require.resolve('@tailwindcss/postcss'); - } catch { - console.error('\n[Zero-UI] ❌ Missing peer dependency "@tailwindcss/postcss".\n' + 'Run: npm install @tailwindcss/postcss\n'); - process.exit(1); - } try { console.log('[Zero-UI] Initializing...'); diff --git a/packages/core/src/postcss/ast-parsing.test.ts b/packages/core/src/postcss/ast-parsing.test.ts index 90f9eef..4fd3902 100644 --- a/packages/core/src/postcss/ast-parsing.test.ts +++ b/packages/core/src/postcss/ast-parsing.test.ts @@ -4,123 +4,119 @@ import { collectUseUIHooks, processVariants } from './ast-parsing.js'; import { parse } from '@babel/parser'; import { readFile, runTest } from './test-utilities.js'; -test('collectUseUIHooks should collect setters from a component', async () => { - await runTest( - { - 'app/boolean-edge-cases.tsx': ` - import { useUI } from '@react-zero-ui/core'; +// test('collectUseUIHooks should collect setters from a component', async () => { +// await runTest( +// { +// 'app/boolean-edge-cases.tsx': ` +// import { useUI } from '@react-zero-ui/core'; - const featureEnabled = 'feature-enabled'; - const bool = false; +// const featureEnabled = 'feature-enabled'; +// const bool = false; - const theme = ['true']; +// const theme = ['true']; -export function Component() { - const [isVisible, setIsVisible] = useUI<'modal-visible', 'false'>('modal-visible', 'false'); - const [isEnabled, setIsEnabled] = useUI<'feature-enabled', 'true'>(bool ?? featureEnabled, theme[0]); -return ( -
-
- Open Modal -
-
-); }`, - }, +// export function Component() { +// const [isVisible, setIsVisible] = useUI<'modal-visible', 'false'>('modal-visible', 'false'); +// const [isEnabled, setIsEnabled] = useUI<'feature-enabled', 'true'>(bool ?? featureEnabled, theme[0]); +// return ( +//
+//
+// Open Modal +//
+//
+// ); }`, +// }, - async () => { - const src = readFile('app/boolean-edge-cases.tsx'); - const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); - const setters = collectUseUIHooks(ast, src); - // assert(setters[0].binding !== null, 'binding should not be null'); - assert(setters[0].initialValue === 'false'); - assert(setters[0].stateKey === 'modal-visible'); - assert(setters[0].setterFnName === 'setIsVisible'); - // assert(setters[1].binding !== null, 'binding should not be null'); - assert(setters[1].initialValue === 'true'); - assert(setters[1].stateKey === 'feature-enabled'); - assert(setters[1].setterFnName === 'setIsEnabled'); +// async () => { +// const src = readFile('app/boolean-edge-cases.tsx'); +// const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); +// const setters = collectUseUIHooks(ast, src); +// // assert(setters[0].binding !== null, 'binding should not be null'); +// assert(setters[0].initialValue === 'false'); +// assert(setters[0].stateKey === 'modal-visible'); +// assert(setters[0].setterFnName === 'setIsVisible'); +// // assert(setters[1].binding !== null, 'binding should not be null'); +// assert(setters[1].initialValue === 'true'); +// assert(setters[1].stateKey === 'feature-enabled'); +// assert(setters[1].setterFnName === 'setIsEnabled'); - const { finalVariants } = await processVariants(['app/boolean-edge-cases.tsx']); - assert(finalVariants[0].key === 'feature-enabled'); - assert(finalVariants[0].values.includes('false')); - assert(finalVariants[0].initialValue === 'true'); - assert(finalVariants[1].key === 'modal-visible'); - assert(finalVariants[1].values.includes('true')); - assert(finalVariants[1].initialValue === 'false', 'initialValue should be false'); - } - ); -}); +// const { finalVariants } = await processVariants(['app/boolean-edge-cases.tsx']); +// assert(finalVariants[0].key === 'feature-enabled'); +// assert(finalVariants[0].values.includes('false')); +// assert(finalVariants[0].initialValue === 'true'); +// assert(finalVariants[1].key === 'modal-visible'); +// assert(finalVariants[1].values.includes('true')); +// assert(finalVariants[1].initialValue === 'false', 'initialValue should be false'); +// } +// ); +// }); -test('collectUseUIHooks should resolve const-based args for useScopedUI', async () => { - await runTest( - { - 'app/tabs.tsx': ` - import { useScopedUI } from '@react-zero-ui/core'; +// test('collectUseUIHooks should resolve const-based args for useScopedUI', async () => { +// await runTest( +// { +// 'app/tabs.tsx': ` +// import { useScopedUI } from '@react-zero-ui/core'; - const KEY = 'tabs'; - const DEFAULT = 'first'; +// const KEY = 'tabs'; +// const DEFAULT = 'first'; - export function Tabs() { - const [, setTab] = useScopedUI<'tabs', 'first'>(KEY, DEFAULT); - return
; - } - `, - }, - async () => { - const src = readFile('app/tabs.tsx'); - const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); - const [meta] = collectUseUIHooks(ast, src); +// export function Tabs() { +// const [, setTab] = useScopedUI<'tabs', 'first'>(KEY, DEFAULT); +// return
; +// } +// `, +// }, +// async () => { +// const src = readFile('app/tabs.tsx'); +// const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); +// const [meta] = collectUseUIHooks(ast, src); - assert.equal(meta.stateKey, 'tabs'); - assert.equal(meta.initialValue, 'first'); - assert.equal(meta.scope, 'scoped'); - } - ); -}); +// assert.equal(meta.stateKey, 'tabs'); +// assert.equal(meta.initialValue, 'first'); +// assert.equal(meta.scope, 'scoped'); +// } +// ); +// }); -test('collectUseUIHooks handles + both hooks', async () => { - const code = ` - import { useUI, useScopedUI } from '@react-zero-ui/core'; - export function Comp() { - const [, setTheme] = useUI<'theme', 'dark'>('theme','dark'); - const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); - return
; - } - `; - const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); - const hooks = collectUseUIHooks(ast, code); - console.log('hooks: ', hooks); +// test('collectUseUIHooks handles + both hooks', async () => { +// const code = ` +// import { useUI, useScopedUI } from '@react-zero-ui/core'; +// export function Comp() { +// const [, setTheme] = useUI<'theme', 'dark'>('theme','dark'); +// const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); +// return
; +// } +// `; +// const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); +// const hooks = collectUseUIHooks(ast, code); +// console.log('hooks: ', hooks); - assert.equal(hooks.length, 2); - assert.deepEqual( - hooks.map((h) => [h.stateKey, h.scope]), - [ - ['theme', 'global'], - ['accordion', 'scoped'], - ] - ); -}); +// assert.equal(hooks.length, 2); +// assert.deepEqual( +// hooks.map((h) => [h.stateKey, h.scope]), +// [ +// ['theme', 'global'], +// ['accordion', 'scoped'], +// ] +// ); +// }); test('collectUseUIHooks NumericLiterals bound to const identifiers', async () => { const code = ` import { useUI, useScopedUI } from '@react-zero-ui/core'; const T = "true"; const M = { "true": "dark", "false": "light" }; const x = M[T] export function Comp() { - const [, setTheme] = useUI<'theme', 'dark'>('theme', x); - const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); + const [, setThemeM] = useUI<'light' | 'dark'>('theme-m', x); + const [, setAcc] = useScopedUI<'accordion'| 'closed'>('accordion','closed'); return
; } `; const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); const hooks = collectUseUIHooks(ast, code); + console.log('hooks: ', hooks); - assert.equal(hooks.length, 2); - assert.deepEqual( - hooks.map((h) => [h.stateKey, h.scope]), - [ - ['theme', 'global'], - ['accordion', 'scoped'], - ] - ); + assert.throws(() => { + collectUseUIHooks(ast, code); + }, /Cannot use imported variables. Assign to a local const first./); }); diff --git a/packages/core/src/postcss/ast-parsing.ts b/packages/core/src/postcss/ast-parsing.ts index fcb5f51..acc1f57 100644 --- a/packages/core/src/postcss/ast-parsing.ts +++ b/packages/core/src/postcss/ast-parsing.ts @@ -99,22 +99,26 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { keyArg, path.opts?.filename, sourceCode, - // TODO add link to docs - `[Zero-UI] State key cannot be resolved at build-time.\n` + `Only local, fully-static strings are supported. - collectUseUIHooks-stateKey` + + `[Zero-UI] State key cannot be resolved at build-time.\n` + + `Only local, fully-static strings are supported. - collectUseUIHooks-stateKey` + + `\nSee: https://github.com/react-zero-ui/core/blob/main/docs/assets/internal.md` ); } // resolve initial value with helpers const initialValue = resolveLiteralMemoized(initialArg as t.Expression, path as NodePath, 'initialValue'); + console.log('initialValue: ', initialValue); if (initialValue === null) { throwCodeFrame( initialArg, path.opts?.filename, sourceCode, - // TODO add link to docs + `[Zero-UI] initial value cannot be resolved at build-time.\n` + - `Only local, fully-static objects/arrays are supported. - collectUseUIHooks-initialValue` + `Only local, fully-static objects/arrays are supported. - collectUseUIHooks-initialValue` + + `\nSee: https://github.com/react-zero-ui/core/blob/main/docs/assets/internal.md` ); } diff --git a/packages/core/src/postcss/resolvers.ts b/packages/core/src/postcss/resolvers.ts index c164b89..55cd394 100644 --- a/packages/core/src/postcss/resolvers.ts +++ b/packages/core/src/postcss/resolvers.ts @@ -3,6 +3,8 @@ import * as t from '@babel/types'; import { NodePath } from '@babel/traverse'; import { throwCodeFrame } from './ast-parsing.js'; import { generate } from '@babel/generator'; + +const VERBOSE = true; export interface ResolveOpts { throwOnFail?: boolean; // default false source?: string; // optional; fall back to path.hub.file.code @@ -43,6 +45,8 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts // TemplateLiteral without ${} if (t.isTemplateLiteral(node) && node.expressions.length === 0) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw; + VERBOSE && console.log('48 -> literalFromNode'); + /* ── Fast path via Babel constant-folder ───────────── */ const ev = fastEval(node, path); @@ -51,6 +55,8 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts return ev.value; } + VERBOSE && console.log('58 -> literalFromNode -> ev: '); + // ConditionalExpression if (t.isConditionalExpression(node)) { const testResult = fastEval(node.test, path); @@ -62,6 +68,8 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts return null; } + VERBOSE && console.log('70 -> literalFromNode'); + // BinaryExpression with + operator if (t.isBinaryExpression(node) && node.operator === '+') { // Resolve left @@ -71,12 +79,16 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts return left !== null && right !== null ? left + right : null; } + VERBOSE && console.log('82 -> literalFromNode'); + // SequenceExpression (already handled explicitly by taking last expression) if (t.isSequenceExpression(node)) { const last = node.expressions.at(-1); if (last) return literalFromNode(last, path, opts); } + VERBOSE && console.log('89 -> literalFromNode'); + if (t.isUnaryExpression(node)) { const arg = literalFromNode(node.argument as t.Expression, path, opts); if (arg === null) return null; @@ -97,6 +109,8 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts } } + VERBOSE && console.log('112 -> literalFromNode'); + /* ── Logical fallback (a || b , a ?? b) ───────────── */ if (t.isLogicalExpression(node) && (node.operator === '||' || node.operator === '??')) { // try left; if it resolves, use it, otherwise fall back to right @@ -105,21 +119,31 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts return literalFromNode(node.right as t.Expression, path, opts); } + VERBOSE && console.log('122 -> literalFromNode'); + // "Is this node an Identifier?" // "If yes, can I resolve it to a literal value like a string, number, or boolean?" const idLit = resolveLocalConstIdentifier(node, path, opts); + VERBOSE && console.log('127 -> literalFromNode -> idLit: ', idLit); if (idLit !== null) return String(idLit); + VERBOSE && console.log('130 -> literalFromNode'); + // Template literal with ${expr} or ${CONSTANT} if (t.isTemplateLiteral(node)) { + VERBOSE && console.log('135 -> literalFromNode -> template literal'); return resolveTemplateLiteral(node, path, literalFromNode, opts); } + VERBOSE && console.log('138 -> literalFromNode'); + if (t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) { // treat optional-member exactly the same return resolveMemberExpression(node as t.MemberExpression, path, literalFromNode, opts); } + VERBOSE && console.log('END -> literalFromNode', node); + return null; } @@ -150,14 +174,11 @@ export function fastEval(node: t.Expression, path: NodePath): { confiden } /*──────────────────────────────────────────────────────────*\ - resolveLocalConstIdentifier - --------------------------- - Resolve an **Identifier** node to a *space-free string literal* **only when** + Resolve an **Identifier** node 1. It is bound in the **same file** (Program scope), 2. Declared with **`const`** (not `let` / `var`), 3. Initialized to a **string literal** or a **static template literal**, - 4. The final string has **no whitespace** (`/^\S+$/`). Anything else (inner-scope `const`, dynamic value, imported binding, spaces) ➜ return `null` — the caller will decide whether to throw or keep searching. @@ -166,6 +187,7 @@ export function fastEval(node: t.Expression, path: NodePath): { confiden developer gets a consistent, actionable error message. \*──────────────────────────────────────────────────────────*/ export function resolveLocalConstIdentifier(node: t.Expression, path: NodePath, opts: ResolveOpts): string | number | boolean | null { + VERBOSE && console.log('resolveLocalConstIdentifier -> 190'); /* Fast-exit when node isn't an Identifier */ if (!t.isIdentifier(node)) return null; @@ -218,7 +240,7 @@ export function resolveLocalConstIdentifier(node: t.Expression, path: NodePath string | null, opts: ResolveOpts ): string | null { + VERBOSE && console.log('resolveMemberExpression -> 352'); /** Collect the property chain (deep → shallow) */ const props: (string | number)[] = []; let current: t.Expression | t.PrivateName = node; @@ -390,7 +410,7 @@ export function resolveMemberExpression( } } - /* ── NEW: bail-out with an explicit error if nothing was found ───────── */ + /* ── bail-out with an explicit error if nothing was found ───────── */ if (value == null) { throwCodeFrame( path, @@ -401,17 +421,29 @@ export function resolveMemberExpression( } /* ─────────────────────────────────────────────────────── */ - /* existing tail logic (unwrap, recurse, return string)… */ + /* ── existing unwrap ─ */ if (t.isTSAsExpression(value)) value = value.expression; + /* ── recursively resolve nested member expressions ─ */ if (t.isMemberExpression(value)) { return resolveMemberExpression(value, path, literalFromNode, opts); } + /* ── support literals ─ */ if (t.isStringLiteral(value)) return value.value; if (t.isTemplateLiteral(value)) { return resolveTemplateLiteral(value, path, literalFromNode, opts); } + + /* ── NEW: resolve Identifier bindings recursively ─ */ + if (t.isIdentifier(value)) { + const idBinding = path.scope.getBinding(value.name); + if (!idBinding || !idBinding.path.isVariableDeclarator()) return null; + const resolvedInit = idBinding.path.node.init; + if (!resolvedInit) return null; + return literalFromNode(resolvedInit as t.Expression, idBinding.path, opts); + } + VERBOSE && console.log('resolveMemberExpression -> 460'); return null; } diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/ChildComponent.tsx b/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/ChildComponent.tsx index 6808d9c..2e87ce0 100644 --- a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/ChildComponent.tsx +++ b/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/ChildComponent.tsx @@ -1,6 +1,6 @@ -import { type ScopedSetterFn } from '@react-zero-ui/core'; +import { type GlobalSetterFn } from '@react-zero-ui/core'; -export function ChildComponent({ setIsOpen }: { setIsOpen: ScopedSetterFn }) { +export function ChildComponent({ setIsOpen }: { setIsOpen: GlobalSetterFn<'open' | 'closed'> }) { return (
('theme-three', 'light'); const [, setToggle] = useUI<'true' | 'false'>('toggle-boolean', 'true'); const [, setNumber] = useUI<'1' | '2'>('number', '1'); - const [, setOpen] = useUI<'open' | 'closed'>('faq', 'closed'); // Same key everywhere! - const [, setScope] = useUI<'off' | 'on'>('scope', 'off'); - const [, setMobile] = useUI<'true' | 'false'>('mobile', 'false'); + const [, setOpen] = useScopedUI<'open' | 'closed'>('faq', 'closed'); // Same key everywhere! + const [, setScope] = useScopedUI<'off' | 'on'>('scope', 'off'); + const [, setMobile] = useScopedUI<'true' | 'false'>('mobile', 'false'); const [, setChildOpen] = useUI<'open' | 'closed'>('child', 'closed'); const [, setToggleFunction] = useUI<'white' | 'black'>('toggle-function', 'white'); From c422a59402cf29b36df17b753d076f1d67916912 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 21:53:56 -0700 Subject: [PATCH 04/11] add zero logo --- docs/assets/zero-ui-logo.png | Bin 0 -> 93840 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/zero-ui-logo.png diff --git a/docs/assets/zero-ui-logo.png b/docs/assets/zero-ui-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..93de3cc0b06b94927089c7fb4661902093a4f356 GIT binary patch literal 93840 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 Date: Tue, 5 Aug 2025 21:53:58 -0700 Subject: [PATCH 05/11] add zero logo to assets --- packages/core/README.md | 248 ++++++++++-------- packages/core/package.json | 3 +- packages/core/src/experimental/runtime.ts | 4 +- packages/core/src/postcss/ast-parsing.test.ts | 222 +++++++++------- packages/core/src/postcss/ast-parsing.ts | 1 - packages/core/src/postcss/resolvers.test.ts | 15 +- packages/core/src/postcss/resolvers.ts | 18 +- packages/core/src/postcss/test-utilities.ts | 3 + pnpm-lock.yaml | 3 + pnpm-workspace.yaml | 8 +- 10 files changed, 300 insertions(+), 225 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index bd756c5..3befad9 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,180 +1,216 @@ -# React Zero‑UI (Beta) + + +![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) -**Instant UI state updates. ZERO React re‑renders. <1 KB runtime.** +

-Pre‑render your UI once, flip a `data-*` attribute to update — that's it. +

+ Frame 342 -npm version npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) +

---- + +
+ The fastest possible UI updates in React. Period. -## 📚 Quick Links +Zero runtime, zero React re-renders, and the simplest developer experience ever. Say goodbye to context and prop-drilling. -- [⚡️ Quick Start](#️-quick-start) -- [🏄 Usage](#-usage) -- [🧬 How it works](#-how-it-works) -- [✅ Features](#-features) -- [🏗 Best Practices](#-best-practices) +bundle size npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) ---- -## 🚀 Live Demo +[📖 See the proof](/docs/demo) [🚀 Quick Start](#-quick-start) [📚 API Reference](#-api-reference) [🤝 Contributing](#-contributing) -| Example | Link | What it shows | Link to Code | -| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -| Interactive menu with render tracker | Main Demo↗ | Compare Zero‑UI vs. React side‑by‑side while toggling a menu. | Github | -| React benchmark (10 000 nested nodes) | React 10k↗ | How long the traditional React render path takes. | Github | -| Zero‑UI benchmark (10 000 nested nodes) | Zero‑UI 10k↗ | Identical DOM, but powered by Zero‑UI's `data-*` switch. | Github | +
--- -## 🧐 Why Zero‑UI? +## 🔥 Core Concept: *"Pre-Rendering"* + +Why re-render UI if all states are known at build time? React Zero-UI **pre-renders** UI states once ( at no runtime cost ), and flips `data-*` attribute to update - that's it. + +**Example:** + +```tsx +const [, setTheme] = useUI("theme", "dark"); + +// Flip theme to "light" +setTheme("light"); // data-theme="light" on body +``` + +**Tailwind usage:** Anywhere in your app + +```html +
Fast & Reactive
+``` -Every `setState` in React triggers the full VDOM → Diff → Reconciliation → Paint pipeline. For _pure UI state_ (themes, menus, toggles) that work is wasted. +--- -**Zero‑UI introduces "_PRE‑rendering_":** +## 🚀 How it Works (Build-Time Magic) -1. Tailwind variants for every state are **generated at build‑time**. -2. The app **pre‑renders once**. -3. Runtime state changes only **flip a `data-*` attribute on ``**. +React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -Result → **5-10× faster visual updates** with **ZERO additional bundle cost**. +* `useUI` and `useScopedUI` hook usage. +* Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). +* Tailwind variant classes (e.g. `theme-dark:bg-black`). -### 📊 Micro‑benchmarks (Apple M1) +**This generates:** -| Nodes updated | React state | Zero‑UI | Speed‑up | -| ------------- | ----------- | ------- | -------- | -| 10,000 | \~50 ms | \~5 ms | **10×** | -| 25,000 | \~180 ms | \~15 ms | **12×** | -| 50,000 | \~300 ms | \~20 ms | **15×** | +* Optimal CSS with global or scoped variant selectors. +* Initial data-attributes injected onto the body (zero FOUC). +* UI state with ease, no prop-drilling. +* **Zero runtime overhead in production**. -Re‑run these numbers yourself via the links above. --- -## ⚡️ Quick Start +## 🚀 Quick Start +Zero-UI CLI -> **Prerequisite:** Tailwind CSS v4 must already be initialized in your project. +**Pre-requisites:** Vite or Next.js (App Router) ```bash -# Inside an existing *Next.js (App Router)* or *Vite* repo npx create-zero-ui ``` -That's it — the CLI patch‑installs the required Babel & PostCSS plugins and updates `configs` for you. +> For manual configuration, see [Next JS Installation](/docs/installation-next.md) +> [Vite Installation](/docs/installation-vite.md) -### Manual Install +**That's it.** Start your app and see the magic. -```bash -npm install @react-zero-ui/core -``` +--- -Then follow **Setup →** for your bundler. +## 📚 API Reference ---- +**The Basics:** -## 🔧 Setup +```tsx +const [, ] = useUI(, ); +``` -### Vite +- `stateKey` ➡️ becomes `data-{stateKey}` on ``. +- `defaultValue` ➡️ SSR, prevents FOUC. +- `staleValue` ➡️ For scoped UI, set the data-* to the `staleValue` to prevent FOUC. +- **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). -```js -// vite.config.* -import { zeroUIPlugin } from '@react-zero-ui/core/vite'; -export default { - // ❗️Remove the default `tailwindcss()` plugin — Zero‑UI extends it internally - plugins: [zeroUIPlugin()], -}; -``` -### Next.js (App Router) -1. **Spread `bodyAttributes` on ``** in your root layout. - ```tsx - // app/layout.tsx - import { bodyAttributes } from '@react-zero-ui/core/attributes'; - // or: import { bodyAttributes } from '../.zero-ui/attributes'; - export default function RootLayout({ children }) { - return ( - - {children} - - ); - } - ``` +### 🔨 `useUI` Hook (Global UI State) -2. **Add the PostCSS plugin (must come _before_ Tailwind).** +Simple hook mirroring React's `useState`: - ```js - // postcss.config.js - module.exports = { plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} } }; - ``` +```tsx +import { useUI } from '@react-zero-ui/core'; + +const [theme, setTheme] = useUI("theme", "dark"); +``` + +**Features:** +* Flips global `data-theme` attribute on ``. +* Zero React re-renders. +* Global UI state available anywhere in your app through tailwind variants. --- -## 🏄 Usage +### 🎯 `useScopedUI` Hook (Scoped UI State) + +Control UI states at the element-level: + +```diff ++ import { useScopedUI } from '@react-zero-ui/core'; + +const [theme, setTheme] = useScopedUI("theme", "dark"); + +// ❗️Flips data-* on the specific ref element ++
+ Scoped UI Here +
+``` -![react zero ui usage explained](https://raw.githubusercontent.com/react-zero-ui/core/main/docs/assets/useui-explained.webp) +**Features:** +* Data-* flips on specific target element. +* Generates scoped CSS selectors only applying within the target element. +* No FOUC, no re-renders. --- -## 🛠 API +### 🌈 CSS Variables Support -### `useUI(key, defaultValue)` +Sometimes CSS variables are more efficient. React Zero-UI makes it trivial by passing the `CssVar` option: -```ts -const [staleValue, setValue] = useUI<'open' | 'closed'>('sidebar', 'closed'); +```tsx +useUI(, , CssVar); // ❗️Pass CssVar to either hook to use CSS variables ``` +automatically adds `--` to the cssVariable -- `key` → becomes `data-{key}` on ``. -- `defaultValue` → optional, prevents FOUC. -- **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). +**Global CSS Variable:** +```diff ++ import { CssVar } from '@react-zero-ui/core'; +``` + +```tsx +const [blur, setBlur] = useUI("blur", "0px", CssVar); +setBlur("5px"); // body { --blur: 5px } +``` -### Tailwind variants +**Scoped CSS Variable:** +```tsx +const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); -```jsx -
+
+ Scoped blur effect. +
``` -Any `data-{key}="{value}"` pair becomes a variant: `{key}-{value}:`. + --- -## 🧬 How it works +## 🧪 Experimental Features + +### SSR-safe `zeroOnClick` + +Enable client-side interactivity **without leaving server components**. +Just 300 bytes of runtime overhead. -1. **`useUI`** → writes to `data-*` attributes on ``. -2. **Babel plugin** → scans code, finds every `key/value`, injects them into **PostCSS**. -3. **PostCSS plugin** → generates static Tailwind classes **at build‑time**. -4. **Runtime** → changing state only touches the attribute — no VDOM, no reconciliation, no re‑paint. +See [experimental](./docs/assets/experimental.md) for more details. --- -## ✅ Features +## 📦 Summary of Benefits -- **Zero React re‑renders** for UI‑only state. -- **Global setters** — call from any component or util. -- **Tiny**: < 1 KB gzipped runtime. -- **TypeScript‑first**. -- **SSR‑friendly** (Next.js & Vite SSR). -- **Framework‑agnostic CSS** — generated classes work in plain HTML / Vue / Svelte as well. +* **🚀 Zero React re-renders:** Pure CSS-driven UI state. +* **⚡️ Pre-rendered UI:** All states injected at build-time and only loaded when needed. +* **📦 Tiny footprint:** <350 bytes, zero runtime overhead for CSS states. +* **💫 Amazing DX:** Simple hooks, auto-generated Tailwind variants. +* **⚙️ Highly optimized AST resolver:** Fast, cached build process. + +React Zero-UI delivers the fastest, simplest, most performant way to handle global and scoped UI state in modern React applications. Say goodbye to re-renders and prop-drilling. --- -## 🏗 Best Practices +--- -1. **UI state only** → themes, layout toggles, feature flags. -2. **Business logic stays in React** → fetching, data mutation, etc. -3. **Kebab‑case keys** → e.g. `sidebar-open`. -4. **Provide defaults** to avoid Flash‑Of‑Unstyled‑Content. +## 🤝 Contributing ---- +We welcome contributions from the community! Whether it's bug fixes, feature requests, documentation improvements, or performance optimizations - every contribution helps make React Zero-UI better. -## 📜 License +**Get involved:** +- 🐛 Found a bug? [Open an issue](https://github.com/react-zero-ui/core/issues) +- 💡 Have an idea? [Start a discussion](https://github.com/react-zero-ui/core/discussions) +- 🔧 Want to contribute code? Check out our [**Contributing Guide**](/docs/CONTRIBUTING.md) -[MIT](LICENSE) © Austin Serb +> **First time contributor?** We have good first issues labeled `good-first-issue` to help you get started! ---- +-- + +
+ +Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) -Built with ❤️ for the React community. If Zero‑UI makes your app feel ZERO fast, please ⭐️ the repo! +
\ No newline at end of file diff --git a/packages/core/package.json b/packages/core/package.json index d886f3a..557eb62 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -77,8 +77,8 @@ "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.0", - "@babel/traverse": "^7.28.0", "@babel/parser": "^7.28.0", + "@babel/traverse": "^7.28.0", "@babel/types": "^7.28.0", "fast-glob": "^3.3.3", "lru-cache": "^11.1.0" @@ -89,6 +89,7 @@ "@types/babel__generator": "^7.27.0", "@types/babel__traverse": "^7.20.7", "@types/react": "^19.1.8", + "postcss": "^8.5.6", "tsx": "^4.20.3" } } \ No newline at end of file diff --git a/packages/core/src/experimental/runtime.ts b/packages/core/src/experimental/runtime.ts index a156ad1..d0d2a19 100644 --- a/packages/core/src/experimental/runtime.ts +++ b/packages/core/src/experimental/runtime.ts @@ -1,4 +1,4 @@ -/* ------------------------------------------------------------------ * +/* --- * * Zero-UI click-handler runtime * * * * Listens for clicks on any element that carries a `data-ui` * @@ -11,7 +11,7 @@ * A single `activateZeroUiRuntime()` call wires everything up. * * We guard against duplicate activation in case the component * * appears twice. * - * ------------------------------------------------------------------ */ + * --- */ /** Map emitted by the compiler: every legal data-* key → true */ export type VariantKeyMap = Record; diff --git a/packages/core/src/postcss/ast-parsing.test.ts b/packages/core/src/postcss/ast-parsing.test.ts index 4fd3902..89d8570 100644 --- a/packages/core/src/postcss/ast-parsing.test.ts +++ b/packages/core/src/postcss/ast-parsing.test.ts @@ -4,103 +4,104 @@ import { collectUseUIHooks, processVariants } from './ast-parsing.js'; import { parse } from '@babel/parser'; import { readFile, runTest } from './test-utilities.js'; -// test('collectUseUIHooks should collect setters from a component', async () => { -// await runTest( -// { -// 'app/boolean-edge-cases.tsx': ` -// import { useUI } from '@react-zero-ui/core'; - -// const featureEnabled = 'feature-enabled'; -// const bool = false; - -// const theme = ['true']; - -// export function Component() { -// const [isVisible, setIsVisible] = useUI<'modal-visible', 'false'>('modal-visible', 'false'); -// const [isEnabled, setIsEnabled] = useUI<'feature-enabled', 'true'>(bool ?? featureEnabled, theme[0]); -// return ( -//
-//
-// Open Modal -//
-//
-// ); }`, -// }, - -// async () => { -// const src = readFile('app/boolean-edge-cases.tsx'); -// const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); -// const setters = collectUseUIHooks(ast, src); -// // assert(setters[0].binding !== null, 'binding should not be null'); -// assert(setters[0].initialValue === 'false'); -// assert(setters[0].stateKey === 'modal-visible'); -// assert(setters[0].setterFnName === 'setIsVisible'); -// // assert(setters[1].binding !== null, 'binding should not be null'); -// assert(setters[1].initialValue === 'true'); -// assert(setters[1].stateKey === 'feature-enabled'); -// assert(setters[1].setterFnName === 'setIsEnabled'); - -// const { finalVariants } = await processVariants(['app/boolean-edge-cases.tsx']); -// assert(finalVariants[0].key === 'feature-enabled'); -// assert(finalVariants[0].values.includes('false')); -// assert(finalVariants[0].initialValue === 'true'); -// assert(finalVariants[1].key === 'modal-visible'); -// assert(finalVariants[1].values.includes('true')); -// assert(finalVariants[1].initialValue === 'false', 'initialValue should be false'); -// } -// ); -// }); - -// test('collectUseUIHooks should resolve const-based args for useScopedUI', async () => { -// await runTest( -// { -// 'app/tabs.tsx': ` -// import { useScopedUI } from '@react-zero-ui/core'; - -// const KEY = 'tabs'; -// const DEFAULT = 'first'; - -// export function Tabs() { -// const [, setTab] = useScopedUI<'tabs', 'first'>(KEY, DEFAULT); -// return
; -// } -// `, -// }, -// async () => { -// const src = readFile('app/tabs.tsx'); -// const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); -// const [meta] = collectUseUIHooks(ast, src); - -// assert.equal(meta.stateKey, 'tabs'); -// assert.equal(meta.initialValue, 'first'); -// assert.equal(meta.scope, 'scoped'); -// } -// ); -// }); - -// test('collectUseUIHooks handles + both hooks', async () => { -// const code = ` -// import { useUI, useScopedUI } from '@react-zero-ui/core'; -// export function Comp() { -// const [, setTheme] = useUI<'theme', 'dark'>('theme','dark'); -// const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); -// return
; -// } -// `; -// const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); -// const hooks = collectUseUIHooks(ast, code); -// console.log('hooks: ', hooks); - -// assert.equal(hooks.length, 2); -// assert.deepEqual( -// hooks.map((h) => [h.stateKey, h.scope]), -// [ -// ['theme', 'global'], -// ['accordion', 'scoped'], -// ] -// ); -// }); +test('collectUseUIHooks should collect setters from a component', async () => { + await runTest( + { + 'app/boolean-edge-cases.tsx': ` + import { useUI } from '@react-zero-ui/core'; + + const feat = 'feature-enabled'; + const bool = false; + const bool2 = true; + const feat2 = 'feature-enabled-2'; + + const theme = ['true']; + +export function Component() { + const [isVisible, setIsVisible] = useUI<'modal-visible', 'false'>('modal-visible', 'false'); + const [isEnabled, setIsEnabled] = useUI<'feature-enabled', 'true'>(bool ?? feat, theme[0]); +return ( +
+
+ Open Modal +
+
+); }`, + }, + + async () => { + const src = readFile('app/boolean-edge-cases.tsx'); + const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); + const setters = collectUseUIHooks(ast, src); + + assert(setters[0].initialValue === 'false', 'initialValue should be false'); + assert(setters[0].stateKey === 'modal-visible', 'stateKey should be modal-visible'); + assert(setters[0].setterFnName === 'setIsVisible', 'setterFnName should be setIsVisible'); + + assert(setters[1].initialValue === 'true', 'initialValue should be true'); + assert(setters[1].stateKey === 'feature-enabled', 'stateKey should be feature-enabled'); + assert(setters[1].setterFnName === 'setIsEnabled', 'setterFnName should be setIsEnabled'); + + const { finalVariants } = await processVariants(['app/boolean-edge-cases.tsx']); + assert(finalVariants[0].key === 'feature-enabled', 'key should be feature-enabled'); + assert(finalVariants[0].values.includes('false'), 'values should include false'); + assert(finalVariants[0].initialValue === 'true', 'initialValue should be true'); + assert(finalVariants[1].key === 'modal-visible', 'key should be modal-visible'); + assert(finalVariants[1].values.includes('true'), 'values should include true'); + assert(finalVariants[1].initialValue === 'false', 'initialValue should be false'); + } + ); +}); + +test('collectUseUIHooks should resolve const-based args for useScopedUI', async () => { + await runTest( + { + 'app/tabs.tsx': ` + import { useScopedUI } from '@react-zero-ui/core'; + + const KEY = 'tabs'; + const DEFAULT = 'first'; + + export function Tabs() { + const [, setTab] = useScopedUI<'tabs', 'first'>(KEY, DEFAULT); + return
; + } + `, + }, + async () => { + const src = readFile('app/tabs.tsx'); + const ast = parse(src, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); + const [meta] = collectUseUIHooks(ast, src); + + assert.equal(meta.stateKey, 'tabs'); + assert.equal(meta.initialValue, 'first'); + assert.equal(meta.scope, 'scoped'); + } + ); +}); + +test('collectUseUIHooks handles + both hooks', async () => { + const code = ` + import { useUI, useScopedUI } from '@react-zero-ui/core'; + export function Comp() { + const [, setTheme] = useUI<'theme', 'dark'>('theme','dark'); + const [, setAcc] = useScopedUI<'accordion', 'closed'>('accordion','closed'); + return
; + } + `; + const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); + const hooks = collectUseUIHooks(ast, code); + + assert.equal(hooks.length, 2); + assert.deepEqual( + hooks.map((h) => [h.stateKey, h.scope]), + [ + ['theme', 'global'], + ['accordion', 'scoped'], + ] + ); +}); test('collectUseUIHooks NumericLiterals bound to const identifiers', async () => { const code = ` @@ -108,15 +109,32 @@ test('collectUseUIHooks NumericLiterals bound to const identifiers', async () => const T = "true"; const M = { "true": "dark", "false": "light" }; const x = M[T] export function Comp() { const [, setThemeM] = useUI<'light' | 'dark'>('theme-m', x); - const [, setAcc] = useScopedUI<'accordion'| 'closed'>('accordion','closed'); - return
; + return
; } `; const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); - const hooks = collectUseUIHooks(ast, code); - console.log('hooks: ', hooks); assert.throws(() => { collectUseUIHooks(ast, code); - }, /Cannot use imported variables. Assign to a local const first./); + }, /Only local, fully-static/i); +}); + +test('collectUseUIHooks should Resolve Logical Expressions &&', async () => { + const code = ` + import { useUI, useScopedUI } from '@react-zero-ui/core'; + + const bool2 = true; + const feat2 = 'feature-enabled-2'; + + export function Comp() { + const [isEnabled2, setIsEnabled2] = useUI<'feature-enabled-2', 'true'>(bool2 && feat2, 'dark'); + + } + `; + const ast = parse(code, { sourceType: 'module', plugins: ['jsx', 'typescript'] }); + const hooks = collectUseUIHooks(ast, code); + + assert.equal(hooks.length, 1); + assert.equal(hooks[0].stateKey, 'feature-enabled-2', 'stateKey should be feature-enabled-2'); + assert.equal(hooks[0].initialValue, 'dark', 'initialValue should be true'); }); diff --git a/packages/core/src/postcss/ast-parsing.ts b/packages/core/src/postcss/ast-parsing.ts index acc1f57..e11cf68 100644 --- a/packages/core/src/postcss/ast-parsing.ts +++ b/packages/core/src/postcss/ast-parsing.ts @@ -108,7 +108,6 @@ export function collectUseUIHooks(ast: t.File, sourceCode: string): HookMeta[] { // resolve initial value with helpers const initialValue = resolveLiteralMemoized(initialArg as t.Expression, path as NodePath, 'initialValue'); - console.log('initialValue: ', initialValue); if (initialValue === null) { throwCodeFrame( diff --git a/packages/core/src/postcss/resolvers.test.ts b/packages/core/src/postcss/resolvers.test.ts index 82dcbdd..5008c25 100644 --- a/packages/core/src/postcss/resolvers.test.ts +++ b/packages/core/src/postcss/resolvers.test.ts @@ -232,11 +232,11 @@ test('literalFromNode should resolve template literals with no expressions', asy test('literalFromNode should return null or throw on invalid UnaryExpressions', async () => { const cases = [ - { description: 'typeof function call (dynamic)', code: `const x = typeof getValue();`, shouldThrow: false }, - { description: 'typeof runtime identifier (undeclared)', code: `const x = typeof runtimeValue;`, shouldThrow: false }, + { description: 'typeof function call (dynamic)', code: `const x = typeof getValue();`, shouldThrow: false, expectedResult: null }, + { description: 'typeof runtime identifier (undeclared)', code: `const x = typeof runtimeValue;`, shouldThrow: false, expectedResult: null }, { description: '+imported identifier (illegal)', code: `import { value } from './lib.js'; const x = \`\${+value}\`;`, shouldThrow: true }, - { description: 'Unary ! with non-const identifier', code: `let flag = true; const x = \`\${!flag}\`;`, shouldThrow: false }, - { description: 'typeof Symbol() (non-serializable)', code: `const x = \`\${typeof Symbol()}\`;`, shouldThrow: false }, + { description: 'Unary ! with non-const identifier', code: `let flag = true; const x = \`\${!flag}\`;`, shouldThrow: false, expectedResult: null }, + { description: 'typeof Symbol() (non-serializable)', code: `const x = \`\${typeof Symbol()}\`;`, shouldThrow: false, expectedResult: null }, ]; await runTest({}, async () => { @@ -300,7 +300,6 @@ test('literalFromNode should resolve various LogicalExpressions to strings', asy { description: 'OR: null || fallback identifier', code: `const fallback = "default"; const x = null || fallback;`, expected: 'default' }, { description: 'OR: null || fallback identifier', code: `const fallback = "default"; const x = null || fallback;`, expected: 'default' }, { description: 'AND: "truthy" && "final"', code: `const x = "truthy" && "final";`, expected: 'final' }, - { description: 'AND: null && "never hit"', code: `const x = null && "never hit";`, expected: null }, { description: 'Nullish: null ?? "default"', code: `const x = null ?? "default";`, expected: 'default' }, { description: 'Nullish: "set" ?? "default"', code: `const x = "set" ?? "default";`, expected: 'set' }, ]; @@ -613,7 +612,7 @@ test('resolveMemberExpression should handle valid and invalid member expressions }); }); -test('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { +test.skip('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { const cases = [ { description: 'const binding to numeric literal', code: `const IDX = 2; const x = IDX;`, expected: '2' }, { description: 'numeric const as object key', code: `const idx = 1; const M = { "1": "yes", 2: "no" }; const x = M[idx];`, expected: 'yes' }, @@ -649,11 +648,11 @@ test('literalFromNode should resolve NumericLiterals bound to const identifiers' }); }); -test('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { +test.skip('literalFromNode should resolve NumericLiterals bound to const identifiers', async () => { const cases = [ { description: 'object access with boolean identifier', - code: `const T = "true"; const M = { "true": "yes", "false": "no" }; const x = M[T];`, + code: `const T = true; const M = { "true": "yes", "false": "no" }; const x = M[T];`, expected: 'yes', }, // { description: 'boolean literal as key', code: `const x = { true: 'yes' }[true];`, expected: 'yes' }, diff --git a/packages/core/src/postcss/resolvers.ts b/packages/core/src/postcss/resolvers.ts index 55cd394..fac9da0 100644 --- a/packages/core/src/postcss/resolvers.ts +++ b/packages/core/src/postcss/resolvers.ts @@ -4,7 +4,7 @@ import { NodePath } from '@babel/traverse'; import { throwCodeFrame } from './ast-parsing.js'; import { generate } from '@babel/generator'; -const VERBOSE = true; +const VERBOSE = false; export interface ResolveOpts { throwOnFail?: boolean; // default false source?: string; // optional; fall back to path.hub.file.code @@ -41,7 +41,7 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts // NumericLiteral - convert numbers to strings if (t.isNumericLiteral(node)) return String(node.value); // BooleanLiteral returned as string - if (t.isBooleanLiteral(node)) return String(node.value); // → 'true' or 'false' + if (t.isBooleanLiteral(node)) return String(node.value); // TemplateLiteral without ${} if (t.isTemplateLiteral(node) && node.expressions.length === 0) return node.quasis[0].value.cooked ?? node.quasis[0].value.raw; @@ -115,8 +115,18 @@ export function literalFromNode(node: t.Expression, path: NodePath, opts if (t.isLogicalExpression(node) && (node.operator === '||' || node.operator === '??')) { // try left; if it resolves, use it, otherwise fall back to right const left = literalFromNode(node.left as t.Expression, path, opts); - if (left !== null) return left; - return literalFromNode(node.right as t.Expression, path, opts); + if (left === 'true') return left; + if (left === 'false' || left === 'undefined' || left === 'null') { + return literalFromNode(node.right as t.Expression, path, opts); + } + } + if (t.isLogicalExpression(node) && node.operator === '&&') { + const left = literalFromNode(node.left as t.Expression, path, opts); + if (left === 'false' || left === 'undefined' || left === 'null') return null; + if (left === 'true') return literalFromNode(node.right as t.Expression, path, opts); + if (opts.throwOnFail) { + throwCodeFrame(path, path.opts?.filename, opts.source ?? path.opts?.source?.code, `[Zero-UI] Logical && expression could not be resolved at build time.`); + } } VERBOSE && console.log('122 -> literalFromNode'); diff --git a/packages/core/src/postcss/test-utilities.ts b/packages/core/src/postcss/test-utilities.ts index dfb4c68..6667e31 100644 --- a/packages/core/src/postcss/test-utilities.ts +++ b/packages/core/src/postcss/test-utilities.ts @@ -1,6 +1,9 @@ import path from 'path'; import fs from 'fs'; import os from 'os'; +import assert from 'assert'; +import { literalFromNode, ResolveOpts } from './resolvers.js'; +import * as t from '@babel/types'; // Helper to create temp directory and run test export async function runTest(files: Record, callback: () => Promise) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cf08479..3c03829 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,6 +139,9 @@ importers: '@types/react': specifier: ^19.1.8 version: 19.1.9 + postcss: + specifier: ^8.5.6 + version: 8.5.6 tsx: specifier: ^4.20.3 version: 4.20.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 5ff0745..885e570 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,5 +3,11 @@ packages: - examples/* onlyBuiltDependencies: - - '@tailwindcss/oxide' + - "@tailwindcss/oxide" - sharp + +strictPeerDependencies: true + +publicHoistPattern: + - "*eslint*" + - "*typescript*" From 1e1c3a9aeba7658803d5a2fd1a9c56e7a4681e31 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 22:27:48 -0700 Subject: [PATCH 06/11] Tests pass, resolver upgraded --- AGENTS.md | 87 ------------------- README.md | 3 +- package.json | 87 ++++++++++++------- packages/cli/README.md | 165 +++++++++++++++++++++++++++---------- packages/cli/package.json | 35 +++++++- packages/core/README.md | 9 +- packages/core/package.json | 100 +++++++++++++--------- 7 files changed, 279 insertions(+), 207 deletions(-) delete mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index a545470..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,87 +0,0 @@ - - -# Guidelines for AI Agents in this Repo - -This repository contains **React Zero-UI**, a library for global UI state without React re-renders. -Use these tips when working with the codebase or generating examples. - -## How React Zero-UI works - -1. `useUI()` writes to `document.body.dataset` using keys you specify. - -```tsx -const [staleValue, setValue] = useUI<'open' | 'closed'>('sidebar', 'closed'); -``` - -- `key` → becomes `data-{key}` on `` (e.g., `sidebar` → `data-sidebar="closed"`). -- `defaultValue` → used for SSR to avoid FOUC. Added to the body as a data-attribute. at build time. -- The first value is **always stale** — do NOT rely on it for reactive updates. - -2. Consumption is done strictly with tailwind variant classNames: - -```html -
-``` - -3. Build-time tooling scans all `useUI()` keys and values, then generates matching Tailwind variants. - -4. At runtime, calling the setter updates the `data-*` attribute on `` immediately. No VDOM. No re-renders. - ---- - -## Best Practices for AI Agents - -- ✅ Use `useUI()` **only for UI state**: themes, layout flags, open/closed toggles, etc. -- ✅ Prefer **kebab-case keys**: e.g. `sidebar-open`, `theme-dark`. -- ✅ Always provide a `defaultValue`: prevents FOUC and enables SSR. -- ✅ Do **NOT** use the first value from `useUI()` for logic — it DOES NOT UPDATE. -- ✅ You can call setters **from anywhere** in the app — no prop drilling or context needed. -- ✅ Tailwind classes must use `key-value:` pattern: - - `theme-dark:bg-black` - - `accent-blue:text-blue-500` - - `sidebar-open:translate-x-0` - ---- - -## Example: Toggle Theme - -```tsx -// Set state -const [, setTheme] = useUI<'light' | 'dark'>('theme', 'light'); -; -``` - -```html - -
-``` - -## Example: Scoping Styles - -```tsx -const [, setTheme] = useUI<'light' | 'dark'>('theme', 'light'); -// Simply pass a ref to the element -
; -``` - -Now the data-\* will flip on that element, and the styles will be scoped to that element, or its children. - ---- - -## What NOT to do - -- ❌ Do not pass anything to the StateKey or InitialValue that is not a string or does not resolve to a string. -- ❌ Don't use `useUI()` for business logic or data fetching -- ❌ Don't rely on the first tuple value for reactivity -- ❌ Don't use camelCase keys (will break variant generation) - ---- - -## Summary - -**React Zero-UI is a ZERO re-render UI state engine with global state baked in.** It replaces traditional VDOM cycles with `data-*` attribute flips and compile-time CSS. No React context. No prop drilling. No runtime cost. - -Think of it as writing atomic Tailwind variants for every UI state — but flipping them dynamically at runtime without re-rendering anything. diff --git a/README.md b/README.md index 2d9260b..3101fd8 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@

- Frame 342 - + Frame 342

diff --git a/package.json b/package.json index 28ec8e0..aa71da8 100644 --- a/package.json +++ b/package.json @@ -1,45 +1,57 @@ { "name": "react-zero-ui-monorepo", - "private": true, - "type": "module", - "engines": { - "node": ">=22" + "private": false, + "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.", + "keywords": [ + "react", + "react state", + "ui state", + "global state", + "scoped state", + "state management", + "tailwind", + "tailwind variants", + "postcss plugin", + "fastest ui updates", + "zero rerenders", + "zero render", + "no context", + "no prop drilling", + "css driven state", + "data attributes", + "ssr safe", + "instant ui updates", + "react optimization", + "high performance react" + ], + "funding": { + "type": "github", + "url": "https://github.com/sponsors/austin1serb" }, + "type": "module", "workspaces": [ "packages/*" ], - "packageManager": "pnpm@10.13.1", - "references": [ - { - "path": "packages/core" - }, - { - "path": "packages/cli" - }, - { - "path": "packages/eslint-plugin-react-zero-ui" - } - ], "scripts": { - "preinstall": "npx only-allow pnpm", - "reset": "git clean -fdx && pnpm install --frozen-lockfile && pnpm prepack:core && pnpm i-tarball", "bootstrap": "pnpm install --frozen-lockfile && pnpm build && pnpm prepack:core && pnpm i-tarball", - "dev": "pnpm install && pnpm build && pnpm prepack:core && pnpm i-tarball", "build": "cd packages/core && pnpm build", - "test": "cd packages/core && pnpm test:all && pnpm smoke", - "prepack:core": "pnpm -F @react-zero-ui/core pack --pack-destination ./dist", - "i-tarball": "node scripts/install-local-tarball.js", - "test:vite": "cd packages/core && pnpm test:vite", - "test:next": "cd packages/core && pnpm test:next", - "test:integration": "cd packages/core && pnpm test:integration", - "test:unit": "cd packages/core && pnpm test:unit", - "test:cli": "cd packages/core && pnpm test:cli", + "build-output": "npx esbuild ./packages/core/dist/index.js --bundle --minify --format=esm --external:react --define:process.env.NODE_ENV='\"production\"'", + "dev": "pnpm install && pnpm build && pnpm prepack:core && pnpm i-tarball", "format": "prettier --write .", + "i-tarball": "node scripts/install-local-tarball.js", + "preinstall": "npx only-allow pnpm", "lint": "eslint .", "lint:fix": "eslint . --fix", - "typecheck": "tsc --noEmit --project tsconfig.base.json", + "prepack:core": "pnpm -F @react-zero-ui/core pack --pack-destination ./dist", + "reset": "git clean -fdx && pnpm install --frozen-lockfile && pnpm prepack:core && pnpm i-tarball", "size": "npx esbuild ./packages/core/dist/index.js --bundle --minify --format=esm --external:react --define:process.env.NODE_ENV='\"production\"' | gzip -c | wc -c", - "build-output": "npx esbuild ./packages/core/dist/index.js --bundle --minify --format=esm --external:react --define:process.env.NODE_ENV='\"production\"'" + "test": "cd packages/core && pnpm test:all && pnpm smoke", + "test:cli": "cd packages/core && pnpm test:cli", + "test:integration": "cd packages/core && pnpm test:integration", + "test:next": "cd packages/core && pnpm test:next", + "test:unit": "cd packages/core && pnpm test:unit", + "test:vite": "cd packages/core && pnpm test:vite", + "typecheck": "tsc --noEmit --project tsconfig.base.json" }, "devDependencies": { "@eslint/js": "^9.32.0", @@ -52,5 +64,20 @@ "release-please": "^17.1.1", "tsx": "^4.20.3", "typescript": "^5.9.2" - } + }, + "packageManager": "pnpm@10.13.1", + "engines": { + "node": ">=22" + }, + "references": [ + { + "path": "packages/core" + }, + { + "path": "packages/cli" + }, + { + "path": "packages/eslint-plugin-react-zero-ui" + } + ] } \ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md index d59dbcf..5816c24 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,89 +1,170 @@ -# Create Zero-UI +# @react-zero-ui/core -A blazing-fast React CLI starter powered by **React Zero-UI** — the pre-rendered UI state engine with zero re-renders and zero runtime overhead. +![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) +![Zero UI logo](https://raw.githubusercontent.com/react-zero-ui/core/upgrade/resolver/docs/assets/zero-ui-logo.png) -```bash -npx create-zero-ui +**The fastest possible UI updates in React. Period.** +Zero runtime, zero React re-renders, and the simplest developer experience ever. +_Say goodbye to context and prop-drilling._ + +[![Bundle size](https://badgen.net/bundlephobia/minzip/@react-zero-ui/core@0.2.6)](https://bundlephobia.com/package/@react-zero-ui/core@0.2.6) +[![npm version](https://img.shields.io/npm/v/@react-zero-ui/core)](https://www.npmjs.com/package/@react-zero-ui/core) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) + +[📖 See the proof](https://github.com/react-zero-ui/core/blob/main/docs/assets/demo.md) • [🚀 Quick Start](#-quick-start) • [📚 API Reference](#-api-reference) • [🤝 Contributing](#-contributing) + +--- + +## 🔥 Core Concept: *"Pre-Rendering"* + +Why re-render UI if all states are known at build time? +React Zero-UI **pre-renders** UI states once — at no runtime cost — and flips `data-*` attributes to update. That’s it. + +```tsx +const [, setTheme] = useUI("theme", "dark"); + +// Flip theme to "light" +setTheme("light"); // data-theme="light" on body ``` -🏁 Instantly scaffold and patch your **Next.js (App Router)** or **Vite** app with: +Tailwind usage: -✅ Tailwind CSS v4 integration -✅ Zero-UI Babel + PostCSS setup -✅ Autogenerated `data-*` UI state system -✅ One command, production-ready result +```html +
Fast & Reactive
+``` --- -## 🧠 What is Zero-UI? +## 🚀 How it Works (Build-Time Magic) -Zero-UI is a global UI state engine that updates your interface using `data-*` attributes — **no React state, no VDOM, no re-renders**. +React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -It works by: +- `useUI` and `useScopedUI` hook usage +- Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`) +- Tailwind variant classes (e.g., `theme-dark:bg-black`) -1. Scanning your components for `useUI()` hooks -2. Pre-generating Tailwind variants for each UI state -3. Updating `data-*` attributes on `` to drive the UI instantly +This generates: -👉 Live Demo: [https://zero-ui.dev](https://zero-ui.dev) -👉 Package: [@react-zero-ui/core](https://www.npmjs.com/package/@react-zero-ui/core) +- Optimal CSS with global or scoped variant selectors +- Initial `data-*` attributes injected onto `` (zero FOUC) +- UI state with ease, no prop-drilling +- **Zero runtime overhead in production** --- -## 🚀 Quickstart +## 🚀 Quick Start -Inside your project root: +**Requires:** Vite or Next.js (App Router) ```bash npx create-zero-ui ``` -That's it — you'll get: +Manual config: +- [Next.js Setup](/docs/installation-next.md) +- [Vite Setup](/docs/installation-vite.md) + +--- + +## 📚 API Reference + +### Basic Hook Signature + +```tsx +const [staleValue, setValue] = useUI("key", "value"); +``` + +- `key` → becomes `data-key` on `` +- `value` → default SSR value +- `staleValue` → SSR fallback (doesn't update after mount) -- `.zero-ui/attributes.js` autogen -- Working PostCSS + Tailwind integration -- Babel config patched if needed -- Ready to use `useUI()` in components +--- + +### 🔨 `useUI` – Global UI State + +```tsx +import { useUI } from '@react-zero-ui/core'; + +const [theme, setTheme] = useUI("theme", "dark"); +``` + +- Updates `data-theme` on `` +- No React re-renders +- Globally accessible with Tailwind --- -## 📦 Package Used +### 🎯 `useScopedUI` – Scoped UI State + +```tsx +import { useScopedUI } from '@react-zero-ui/core'; -Powered by: +const [theme, setTheme] = useScopedUI("theme", "dark"); -```json -"@react-zero-ui/core": "^0.1.0" +
+ Scoped UI Here +
``` -Full docs and live benchmarks: -👉 [https://github.com/react-zero-ui/core](https://github.com/react-zero-ui/core) +- Sets `data-*` on a specific DOM node +- Scoped Tailwind variants only apply inside that element +- Prevents FOUC, no re-renders --- -## 🧬 Example Usage +### 🌈 CSS Variable Support + +Pass the `CssVar` flag for variable-based state: + +```tsx +import { CssVar } from '@react-zero-ui/core'; -## 🏄‍♂️ Usage +const [blur, setBlur] = useUI("blur", "0px", CssVar); +setBlur("5px"); // body { --blur: 5px } +``` + +Scoped example: -![react zero ui usage explained](https://raw.githubusercontent.com/react-zero-ui/core/main/docs/assets/useui-explained.webp) +```tsx +const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); + +
+ Scoped blur effect +
+``` --- -## 🛠 Tech Notes +## 🧪 Experimental Feature: `zeroOnClick` + +Enables interactivity **inside Server Components** without useEffect. +Only ~300 bytes of runtime. -- This CLI only supports Tailwind v4+ projects -- It does **not** install Tailwind for you — install Tailwind first -- You must restart your dev server after running the CLI to see effects +Read more: [experimental.md](/docs/experimental.md) --- -## 🙌 Author +## 📦 Summary of Benefits -Made by [@austinserb](https://github.com/austin1serb) +- 🚀 **Zero React re-renders** +- ⚡️ **Pre-rendered UI**: state embedded at build time +- 📦 **<350B runtime footprint** +- 💫 **Simple DX**: hooks + Tailwind variants +- ⚙️ **AST-powered**: cached fast builds -Built with ❤️ for the React community. If Zero‑UI makes your app feel ZERO fast, please ⭐️ the repo! +Zero re-renders. Zero runtime. Infinite scalability. --- -## 📜 License +## 🤝 Contributing + +We welcome all contributions! + +- 🐛 [Open an issue](https://github.com/react-zero-ui/core/issues) +- 💡 [Start a discussion](https://github.com/react-zero-ui/core/discussions) +- 🔧 [Read the contributing guide](/docs/CONTRIBUTING.md) + +--- -[MIT](LICENSE) © Austin Serb +Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) \ No newline at end of file diff --git a/packages/cli/package.json b/packages/cli/package.json index 0159cba..768da68 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,20 +1,47 @@ { "name": "create-zero-ui", - "version": "1.0.9", + "version": "1.1.0", + "description": "Zero-UI project scaffolder for React. Instantly sets up zero-runtime UI state, Tailwind variants, PostCSS, and SSR-safe config in Vite or Next.js apps.", + "keywords": [ + "react", + "react state", + "ui state", + "global state", + "scoped state", + "state management", + "tailwind", + "tailwind variants", + "postcss plugin", + "fastest ui updates", + "zero rerenders", + "zero render", + "no context", + "no prop drilling", + "css driven state", + "data attributes", + "ssr safe", + "instant ui updates", + "react optimization", + "high performance react" + ], + "funding": { + "type": "github", + "url": "https://github.com/sponsors/austin1serb" + }, "type": "module", "main": "./bin.js", "bin": { "react-zero-ui": "bin.js" }, - "scripts": { - "test": "node --test ../core/__tests__/unit/cli.test.cjs" - }, "files": [ "bin.js", "package.json", "README.md", "LICENSE" ], + "scripts": { + "test": "node --test ../core/__tests__/unit/cli.test.cjs" + }, "dependencies": { "@react-zero-ui/core": "^0.3.1" } diff --git a/packages/core/README.md b/packages/core/README.md index 3befad9..0159915 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -2,12 +2,11 @@ ![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) -

+ -

- Frame 342 - -

+ + Frame 342 +
diff --git a/packages/core/package.json b/packages/core/package.json index 557eb62..fd4d2fa 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,22 +1,49 @@ { "name": "@react-zero-ui/core", "version": "0.3.1-beta.2", - "description": "Zero re-render, global UI state management for React", "private": false, - "type": "module", - "main": "./dist/index.js", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", + "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.", + "keywords": [ + "react", + "react state", + "ui state", + "global state", + "scoped state", + "state management", + "tailwind", + "tailwind variants", + "postcss plugin", + "fastest ui updates", + "zero rerenders", + "zero render", + "no context", + "no prop drilling", + "css driven state", + "data attributes", + "ssr safe", + "instant ui updates", + "react optimization", + "high performance react" + ], + "homepage": "https://github.com/react-zero-ui/core#readme", + "bugs": { + "url": "https://github.com/react-zero-ui/core/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/react-zero-ui/core.git", + "directory": "packages/core" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/austin1serb" + }, + "license": "MIT", + "author": "Austinserb ", "sideEffects": [ "./dist/experimental/runtime.js" ], - "files": [ - "dist/**/*", - "README.md", - "LICENSE", - "!*.test.js", - "!*.test.cjs" - ], + "type": "module", "exports": { ".": { "types": "./dist/index.d.ts", @@ -44,35 +71,27 @@ "import": "./dist/experimental/runtime.js" } }, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist/**/*", + "README.md", + "LICENSE", + "!*.test.js", + "!*.test.cjs" + ], "scripts": { - "prepack": "pnpm run build", - "smoke": "node scripts/smoke-test.js", "build": "rm -rf dist && tsc -p tsconfig.build.json", "dev": "tsc -p tsconfig.json --watch", - "test:next": "playwright test -c __tests__/config/playwright.next.config.js", - "test:vite": "playwright test -c __tests__/config/playwright.vite.config.js", - "test:integration": "node --test __tests__/unit/index.test.cjs", - "test:cli": "node --test __tests__/unit/*.test.cjs", + "prepack": "pnpm run build", + "smoke": "node scripts/smoke-test.js", "test:all": "pnpm run test:vite && pnpm run test:next && pnpm run test:unit && pnpm run test:cli && pnpm run test:integration", - "test:unit": "tsx --test src/**/*.test.ts" - }, - "author": "Austinserb ", - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/react-zero-ui/core.git", - "directory": "packages/core" - }, - "bugs": { - "url": "https://github.com/react-zero-ui/core/issues" - }, - "homepage": "https://github.com/react-zero-ui/core#readme", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "@tailwindcss/postcss": "^4.1.10", - "react": ">=16.8.0" + "test:cli": "node --test __tests__/unit/*.test.cjs", + "test:integration": "node --test __tests__/unit/index.test.cjs", + "test:next": "playwright test -c __tests__/config/playwright.next.config.js", + "test:unit": "tsx --test src/**/*.test.ts", + "test:vite": "playwright test -c __tests__/config/playwright.vite.config.js" }, "dependencies": { "@babel/code-frame": "^7.27.1", @@ -91,5 +110,12 @@ "@types/react": "^19.1.8", "postcss": "^8.5.6", "tsx": "^4.20.3" + }, + "peerDependencies": { + "@tailwindcss/postcss": "^4.1.10", + "react": ">=16.8.0" + }, + "engines": { + "node": ">=18.0.0" } } \ No newline at end of file From b780bba4e3a2e748daa191bc7036c6618d477a92 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 23:15:23 -0700 Subject: [PATCH 07/11] update eslint package name --- .github/pull_request_template.md | 2 +- .../{release.yml => release.yml.disable} | 4 +- .prettierrc.json | 2 +- .release-please-config.json | 9 - README.md | 91 +- docs/CONTRIBUTING.md | 21 +- docs/demo.md | 14 +- docs/experimental.md | 85 +- docs/installation-next.md | 52 +- docs/installation-vite.md | 15 +- docs/internal.md | 50 +- examples/demo/.zero-ui/init-zero-ui.ts | 4 +- package.json | 3 +- packages/cli/README.md | 175 +-- packages/cli/package.json | 2 +- packages/core/README.md | 198 ++- .../fixtures/next/app/CssVarDemo.tsx | 2 +- .../fixtures/next/app/LintFailures.tsx | 4 +- .../core/__tests__/fixtures/vite/index.html | 26 +- .../__tests__/fixtures/vite/tsconfig.json | 19 +- .../core/__tests__/helpers/overwriteFile.js | 2 +- .../__tests__/helpers/resetProjectState.js | 4 +- .../unit/fixtures/test-components.jsx | 14 - .../unit/fixtures/ts-test-components.tsx | 88 -- .../core/__tests__/unit/fixtures/variables.js | 5 - packages/core/package.json | 2 +- packages/core/src/experimental/README.md | 23 +- packages/core/src/experimental/runtime.ts | 4 +- packages/core/src/internal.ts | 2 +- packages/core/src/postcss/ast-parsing.ts | 16 +- packages/core/src/postcss/helpers.test.ts | 4 +- packages/core/src/postcss/helpers.ts | 2 +- packages/core/src/postcss/resolvers.ts | 24 +- packages/core/src/postcss/scanner.ts | 4 +- packages/core/tsconfig.build.json | 6 +- packages/core/tsconfig.json | 16 +- .../fixtures/next/.zero-ui/attributes.d.ts | 0 .../fixtures/next/.zero-ui/attributes.js | 0 .../fixtures/next/app/ChildComponent.tsx | 0 .../fixtures/next/app/ChildWithoutSetter.tsx | 0 .../__tests__/fixtures/next/app/FAQ.tsx | 0 .../fixtures/next/app/LintFailures.tsx | 4 +- .../fixtures/next/app/UseEffectComponent.tsx | 0 .../__tests__/fixtures/next/app/globals.css | 0 .../__tests__/fixtures/next/app/layout.tsx | 0 .../__tests__/fixtures/next/app/page.tsx | 0 .../__tests__/fixtures/next/app/variables.ts | 0 .../__tests__/fixtures/next/eslint.config.mjs | 2 +- .../__tests__/fixtures/next/next-env.d.ts | 0 .../__tests__/fixtures/next/package.json | 5 +- .../fixtures/next/postcss.config.mjs | 0 .../__tests__/fixtures/next/tsconfig.json | 27 +- .../package.json | 2 +- .../src/index.ts | 0 .../src/rules/require-data-attr.ts | 2 +- .../tsconfig.json | 0 pnpm-lock.yaml | 1152 +---------------- pnpm-workspace.yaml | 6 +- scripts/install-local-tarball.js | 6 +- 59 files changed, 405 insertions(+), 1795 deletions(-) rename .github/workflows/{release.yml => release.yml.disable} (92%) delete mode 100644 .release-please-config.json delete mode 100644 packages/core/__tests__/unit/fixtures/test-components.jsx delete mode 100644 packages/core/__tests__/unit/fixtures/ts-test-components.tsx delete mode 100644 packages/core/__tests__/unit/fixtures/variables.js rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/.zero-ui/attributes.d.ts (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/.zero-ui/attributes.js (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/ChildComponent.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/ChildWithoutSetter.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/FAQ.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/LintFailures.tsx (92%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/UseEffectComponent.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/globals.css (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/layout.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/page.tsx (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/app/variables.ts (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/eslint.config.mjs (95%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/next-env.d.ts (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/package.json (79%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/postcss.config.mjs (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/__tests__/fixtures/next/tsconfig.json (54%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/package.json (94%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/src/index.ts (100%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/src/rules/require-data-attr.ts (97%) rename packages/{eslint-plugin-react-zero-ui => eslint-zero-ui}/tsconfig.json (100%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index bbc294c..4c63b45 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -23,7 +23,7 @@ - [ ] Linting and formatting pass (`pnpm lint && pnpm format`) - [ ] PR title follows semantic commit format (`feat:`, `fix:`, etc.) - [ ] Changes are documented (README, JSDoc, or comments if needed) -- [ ] Ready for review — not a draft +- [ ] Ready for review - not a draft --- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml.disable similarity index 92% rename from .github/workflows/release.yml rename to .github/workflows/release.yml.disable index 92e9b47..2e17038 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml.disable @@ -60,8 +60,8 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: | echo "ℹ️ Packages flagged for publish:" - echo "• core → ${{ steps.release.outputs['packages/core--release_version'] }}" - echo "• cli → ${{ steps.release.outputs['packages/cli--release_version'] }}" + echo "• core ➡️ ${{ steps.release.outputs['packages/core--release_version'] }}" + echo "• cli ➡️ ${{ steps.release.outputs['packages/cli--release_version'] }}" if [[ '${{ steps.release.outputs['packages/core--release_created'] }}' == 'true' ]]; then echo "📦 Publishing @react-zero-ui/core…" diff --git a/.prettierrc.json b/.prettierrc.json index aa184a9..295e1be 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -12,4 +12,4 @@ "bracketSameLine": true, "embeddedLanguageFormatting": "auto", "singleAttributePerLine": true -} \ No newline at end of file +} diff --git a/.release-please-config.json b/.release-please-config.json deleted file mode 100644 index 288e79b..0000000 --- a/.release-please-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "release-type": "node", - "monorepo-tags": true, - "include-component-in-tag": true, - "packages": { - "packages/core": { "package-name": "@react-zero-ui/core", "release-type": "node" }, - "packages/cli": { "package-name": "create-zero-ui", "release-type": "node" } - } -} diff --git a/README.md b/README.md index 3101fd8..e870000 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,6 @@ Frame 342

-
The fastest possible UI updates in React. Period. @@ -16,24 +15,23 @@ Zero runtime, zero React re-renders, and the simplest developer experience ever. bundle size npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) - -[📖 See the proof](/docs/demo) [🚀 Quick Start](#-quick-start) [📚 API Reference](#-api-reference) [🤝 Contributing](#-contributing) +[📖 See the proof](/docs/demo.md) [🚀 Quick Start](#-quick-start) [📚 API Reference](#-api-reference) [🤝 Contributing](#-contributing)
--- -## 🔥 Core Concept: *"Pre-Rendering"* +## 🔥 Core Concept: _"Pre-Rendering"_ Why re-render UI if all states are known at build time? React Zero-UI **pre-renders** UI states once ( at no runtime cost ), and flips `data-*` attribute to update - that's it. -**Example:** +**Example:** ```tsx -const [, setTheme] = useUI("theme", "dark"); +const [, setTheme] = useUI('theme', 'dark'); // Flip theme to "light" -setTheme("light"); // data-theme="light" on body +setTheme('light'); // data-theme="light" on body ``` **Tailwind usage:** Anywhere in your app @@ -48,21 +46,21 @@ setTheme("light"); // data-theme="light" on body React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -* `useUI` and `useScopedUI` hook usage. -* Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). -* Tailwind variant classes (e.g. `theme-dark:bg-black`). +- `useUI` and `useScopedUI` hook usage. +- Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). +- Tailwind variant classes (e.g. `theme-dark:bg-black`). **This generates:** -* Optimal CSS with global or scoped variant selectors. -* Initial data-attributes injected onto the body (zero FOUC). -* UI state with ease, no prop-drilling. -* **Zero runtime overhead in production**. - +- Optimal CSS with global or scoped variant selectors. +- Initial data-attributes injected onto the body (zero FOUC). +- UI state with ease, no prop-drilling. +- **Zero runtime overhead in production**. --- ## 🚀 Quick Start + Zero-UI CLI **Pre-requisites:** Vite or Next.js (App Router) @@ -72,7 +70,7 @@ npx create-zero-ui ``` > For manual configuration, see [Next JS Installation](/docs/installation-next.md) -> [Vite Installation](/docs/installation-vite.md) +> [Vite Installation](/docs/installation-vite.md) **That's it.** Start your app and see the magic. @@ -86,16 +84,11 @@ npx create-zero-ui const [, ] = useUI(, ); ``` -- `stateKey` ➡️ becomes `data-{stateKey}` on ``. -- `defaultValue` ➡️ SSR, prevents FOUC. -- `staleValue` ➡️ For scoped UI, set the data-* to the `staleValue` to prevent FOUC. +- `stateKey` ➡️ becomes `data-{stateKey}` on ``. +- `defaultValue` ➡️ SSR, prevents FOUC. +- `staleValue` ➡️ For scoped UI, set the data-\* to the `staleValue` to prevent FOUC. - **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). - - - - - ### 🔨 `useUI` Hook (Global UI State) Simple hook mirroring React's `useState`: @@ -103,13 +96,14 @@ Simple hook mirroring React's `useState`: ```tsx import { useUI } from '@react-zero-ui/core'; -const [theme, setTheme] = useUI("theme", "dark"); +const [theme, setTheme] = useUI('theme', 'dark'); ``` **Features:** -* Flips global `data-theme` attribute on ``. -* Zero React re-renders. -* Global UI state available anywhere in your app through tailwind variants. + +- Flips global `data-theme` attribute on ``. +- Zero React re-renders. +- Global UI state available anywhere in your app through tailwind variants. --- @@ -123,7 +117,7 @@ Control UI states at the element-level: const [theme, setTheme] = useScopedUI("theme", "dark"); // ❗️Flips data-* on the specific ref element -+
@@ -132,9 +126,10 @@ const [theme, setTheme] = useScopedUI("theme", "dark"); ``` **Features:** -* Data-* flips on specific target element. -* Generates scoped CSS selectors only applying within the target element. -* No FOUC, no re-renders. + +- Data-\* flips on specific target element. +- Generates scoped CSS selectors only applying within the target element. +- No FOUC, no re-renders. --- @@ -145,29 +140,32 @@ Sometimes CSS variables are more efficient. React Zero-UI makes it trivial by pa ```tsx useUI(, , CssVar); // ❗️Pass CssVar to either hook to use CSS variables ``` + automatically adds `--` to the cssVariable **Global CSS Variable:** + ```diff + import { CssVar } from '@react-zero-ui/core'; ``` ```tsx -const [blur, setBlur] = useUI("blur", "0px", CssVar); -setBlur("5px"); // body { --blur: 5px } +const [blur, setBlur] = useUI('blur', '0px', CssVar); +setBlur('5px'); // body { --blur: 5px } ``` **Scoped CSS Variable:** + ```tsx -const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); +const [blur, setBlur] = useScopedUI('blur', '0px', CssVar); -
- Scoped blur effect. -
+
+ Scoped blur effect. +
; ``` - - --- ## 🧪 Experimental Features @@ -183,11 +181,11 @@ See [experimental](./docs/assets/experimental.md) for more details. ## 📦 Summary of Benefits -* **🚀 Zero React re-renders:** Pure CSS-driven UI state. -* **⚡️ Pre-rendered UI:** All states injected at build-time and only loaded when needed. -* **📦 Tiny footprint:** <350 bytes, zero runtime overhead for CSS states. -* **💫 Amazing DX:** Simple hooks, auto-generated Tailwind variants. -* **⚙️ Highly optimized AST resolver:** Fast, cached build process. +- **🚀 Zero React re-renders:** Pure CSS-driven UI state. +- **⚡️ Pre-rendered UI:** All states injected at build-time and only loaded when needed. +- **📦 Tiny footprint:** <350 bytes, zero runtime overhead for CSS states. +- **💫 Amazing DX:** Simple hooks, auto-generated Tailwind variants. +- **⚙️ Highly optimized AST resolver:** Fast, cached build process. React Zero-UI delivers the fastest, simplest, most performant way to handle global and scoped UI state in modern React applications. Say goodbye to re-renders and prop-drilling. @@ -200,6 +198,7 @@ React Zero-UI delivers the fastest, simplest, most performant way to handle glob We welcome contributions from the community! Whether it's bug fixes, feature requests, documentation improvements, or performance optimizations - every contribution helps make React Zero-UI better. **Get involved:** + - 🐛 Found a bug? [Open an issue](https://github.com/react-zero-ui/core/issues) - 💡 Have an idea? [Start a discussion](https://github.com/react-zero-ui/core/discussions) - 🔧 Want to contribute code? Check out our [**Contributing Guide**](/docs/CONTRIBUTING.md) @@ -212,4 +211,4 @@ We welcome contributions from the community! Whether it's bug fixes, feature req Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) -
\ No newline at end of file +
diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 2a71571..b6bdd83 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing to React Zero-UI -**Thanks for stopping by.** This project exists because builders like you push boundaries. If you're here to experiment, break things, or ship speed — you're in the right place. +**Thanks for stopping by.** This project exists because builders like you push boundaries. If you're here to experiment, break things, or ship speed - you're in the right place. --- @@ -11,7 +11,7 @@ > UI state should not require re-rendering. > CSS and `data-*` attributes can be enough. -It's fast because it **skips the VDOM entirely** — no state triggers, no diffing, no component redraws. +It's fast because it **skips the VDOM entirely** - no state triggers, no diffing, no component redraws. ### If you contribute: @@ -22,9 +22,10 @@ Stay **pre-rendered, declarative, and brutally fast.** ## 🧠 Monorepo Structure ``` -packages/ -├── core → @react-zero-ui/core (library logic + postcss) -└── cli → create-zero-ui (npx installer) +📁 packages/ +├── core ➡️ @react-zero-ui/core (library logic + postcss) +├── cli ➡️ create-zero-ui (npx installer) +└── eslint-zero-ui ➡️ eslint-zero-ui - in development ``` --- @@ -51,15 +52,15 @@ For questions, proposals, or early feedback. Share ideas before building. Use the templates. -- **Bug** → Include steps to reproduce, expected vs. actual behavior. -- **Feature** → Explain the _why_, and sketch a possible approach. +- **Bug** ➡️ Include steps to reproduce, expected vs. actual behavior. +- **Feature** ➡️ Explain the _why_, and sketch a possible approach. ### 3. Pull Requests - Use semantic commit prefixes: `feat:`, `fix:`, `chore:`, `refactor:` - Add tests if you touch logic, CLI, or rendering behavior. -- Keep PRs focused — one change per pull. -- Fill out the PR template — no empty descriptions. +- Keep PRs focused - one change per pull. +- Fill out the PR template - no empty descriptions. --- @@ -78,7 +79,7 @@ pnpm test # Runs all of the above ## 🤝 Code of Conduct -Keep it respectful, accessible. Push ideas hard, not people. +Keep it respectful and accessible. Push ideas hard, not people. --- diff --git a/docs/demo.md b/docs/demo.md index 05346af..6a4d104 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -15,7 +15,7 @@ Experience the difference between React re-renders and Zero-UI's instant updates ## 🎯 Interactive Examples | Demo | Description | Live Link | Source Code | -|------|-------------|-----------|-------------| +| -- | -- | -- | -- | | **🎛️ Interactive Menu** | Side-by-side comparison with render tracker | [Main Demo](https://zero-ui.dev/) | [GitHub](https://zero-ui.dev/react) | | **⚛️ React Benchmark** | Traditional React render path (10k nodes) | [React 10k](https://zero-ui.dev/react) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/react) | | **⚡️ Zero-UI Benchmark** | Identical DOM with `data-*` switching (10k nodes) | [Zero-UI 10k](https://zero-ui.dev/zero-ui) | [GitHub](https://github.com/react-zero-ui/core/tree/main/examples/demo/src/app/zero-ui) | @@ -26,12 +26,12 @@ Experience the difference between React re-renders and Zero-UI's instant updates ## 🧐 Why Zero-UI? -Every `setState` in React triggers the full **VDOM → Diff → Reconciliation → Paint** pipeline. For _pure UI state_ (themes, menus, toggles) **that work is wasted**. +Every `setState` in React triggers the full **VDOM ➡️ Diff ➡️ Reconciliation ➡️ Paint** pipeline. For _pure UI state_ (themes, menus, toggles) **that work is wasted**. ### 🔄 Zero-UI's "PRE-rendering" Approach: 1. **🏗️ Build-time:** Tailwind variants generated for every state -2. **🎨 Pre-render:** App renders once with all possible states +2. **🎨 Pre-render:** App renders once with all possible states 3. **⚡️ Runtime:** State changes only flip a `data-*` attribute **Result:** **5-10× faster visual updates** with **ZERO additional bundle cost**. @@ -42,15 +42,15 @@ Every `setState` in React triggers the full **VDOM → Diff → Reconciliation
-*Tested on Apple M1 - Chrome DevTools Performance Tab* +_Tested on Apple M1 - Chrome DevTools Performance Tab_
| **Nodes Updated** | **React State** | **Zero-UI** | **Speed Improvement** | -|:-----------------:|:---------------:|:-----------:|:--------------------:| +| :--: | :--: | :--: | :--: | | 10,000 | ~50 ms | ~5 ms | **🚀 10× faster** | | 25,000 | ~180 ms | ~15 ms | **🚀 12× faster** | -| 50,000 | ~300 ms | ~20 ms | **🚀 15× faster** | +| 50,000 | ~300 ms | ~20 ms | **🚀 15× faster** | > **🔬 Try it yourself:** Re-run these benchmarks using the demo links above with Chrome DevTools. @@ -62,4 +62,4 @@ Every `setState` in React triggers the full **VDOM → Diff → Reconciliation [**🚀 Get Started**](https://github.com/react-zero-ui/core/#-quick-start) and never re-render again. -
\ No newline at end of file +
diff --git a/docs/experimental.md b/docs/experimental.md index dc99c17..965492a 100644 --- a/docs/experimental.md +++ b/docs/experimental.md @@ -1,8 +1,7 @@ -

🧪 Experimental Runtime (Zero-UI)

-**SSR-safe runtime logic** for handling interactivity in React server components without using +**SSR-safe runtime logic** for handling interactivity in React server components without using ```diff - use client @@ -20,14 +19,15 @@ React Zero-UI's pre-rendered data-attribute model. ### ❓ Why This Approach? -**The Problem:** A single `onClick` event forces your entire component tree to become client-rendered. In Next.js, this means shipping extra JavaScript, losing SSR benefits, and adding hydration overhead—all for basic interactivity. +**The Problem:** A single `onClick` event forces your entire component tree to become client-rendered. In Next.js, this means shipping extra JavaScript, losing SSR benefits, and adding hydration overhead-all for basic interactivity. **The Solution:** This design creates the perfect bridge between **static HTML** and **interactive UX**, while maintaining: + - Server-rendered performance -- Zero JavaScript bundle overhead +- Zero JavaScript bundle overhead - Instant visual feedback -*Why sacrifice server-side rendering for a simple click handler when 300 bytes of runtime can handle all the clicks in your app?* +_Why sacrifice server-side rendering for a simple click handler when 300 bytes of runtime can handle all the clicks in your app?_ --- @@ -42,10 +42,12 @@ The core runtime entrypoint that enables client-side interactivity in server com 1. **🎯 Single Global Listener** - Registers one click event listener on `document` 2. **👂 Smart Detection** - Listens for clicks on elements with `data-ui` attributes 3. **🔍 Directive Parsing** - Interprets `data-ui` directives in this format. + ```diff -+ data-ui="global:key(val1,val2,...)" → flips data-key on document.body -+ data-ui="scoped:key(val1,val2,...)" → flips data-key on closest ancestor/self ++ data-ui="global:key(val1,val2,...)" ➡️ flips data-key on document.body ++ data-ui="scoped:key(val1,val2,...)" ➡️ flips data-key on closest ancestor/self ``` + 4. **🔄 Round-Robin Cycling** - Cycles through values in sequence 5. **⚡️ Instant DOM Updates** - Updates DOM immediately for Tailwind responsiveness @@ -60,18 +62,21 @@ The core runtime entrypoint that enables client-side interactivity in server com Utility functions that generate valid `data-ui` attributes for JSX/TSX: **Global Example:** + ```tsx -zeroSSR.onClick("theme", ["dark", "light"]) +zeroSSR.onClick('theme', ['dark', 'light']); // Returns: { 'data-ui': 'global:theme(dark,light)' } ``` **Scoped Example:** + ```tsx -scopedZeroSSR.onClick("modal", ["open", "closed"]) +scopedZeroSSR.onClick('modal', ['open', 'closed']); // Returns: { 'data-ui': 'scoped:modal(open,closed)' } ``` **Development Validation:** + - ✅ Ensures keys are kebab-case - ✅ Validates at least one value is provided @@ -98,10 +103,10 @@ This creates `.zero-ui/attributes.ts` containing the variant map needed for runt ### Step 3: Create `` Component ```tsx -"use client"; +'use client'; -import { variantKeyMap } from "path/to/.zero-ui/attributes"; -import { activateZeroUiRuntime } from "@react-zero-ui/core/experimental/runtime"; +import { variantKeyMap } from 'path/to/.zero-ui/attributes'; +import { activateZeroUiRuntime } from '@react-zero-ui/core/experimental/runtime'; activateZeroUiRuntime(variantKeyMap); @@ -111,17 +116,17 @@ export const InitZeroUI = () => null; ### Step 4: Add to Root Layout ```tsx -import { InitZeroUI } from "path/to/InitZeroUI"; +import { InitZeroUI } from 'path/to/InitZeroUI'; export default function RootLayout({ children }) { - return ( - - - - {children} - - - ); + return ( + + + + {children} + + + ); } ``` @@ -132,32 +137,26 @@ export default function RootLayout({ children }) { ### Global Theme Toggle ```tsx -import { zeroSSR } from "@react-zero-ui/core/experimental"; +import { zeroSSR } from '@react-zero-ui/core/experimental'; -
- Click me to cycle themes! -
+
Click me to cycle themes!
; ``` **Pair with Tailwind variants:** ```html -
- Interactive Server Component! -
+
Interactive Server Component!
``` ### Scoped Modal Toggle ```tsx -import { scopedZeroSSR } from "@react-zero-ui/experimental"; +import { scopedZeroSSR } from '@react-zero-ui/experimental'; // ❗️ Scopes based on matching data-* attribute (e.g. data-modal)
- -
+ +
; ``` --- @@ -167,22 +166,21 @@ import { scopedZeroSSR } from "@react-zero-ui/experimental"; ### Core Principles - **🚫 No React State** - Zero re-renders involved -- **🎯 Pure DOM Mutations** - Works entirely via `data-*` attribute changes +- **🎯 Pure DOM Mutations** - Works entirely via `data-*` attribute changes - **🔧 Server Component Compatible** - Full compatibility with all server components - **⚡️ Tailwind-First** - Designed for conditional CSS classes - --- ## 📋 Summary -| Feature | Description | -|---------|-------------| -| **`activateZeroUiRuntime()`** | Enables click handling on static components via `data-ui` | -| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props | -| **Runtime Overhead** | ~300 bytes total | -| **React Re-renders** | Zero | -| **Server Component Support** | ✅ Full compatibility | +| Feature | Description | +| ------------------------------- | --------------------------------------------------------- | +| **`activateZeroUiRuntime()`** | Enables click handling on static components via `data-ui` | +| **`zeroSSR` / `scopedZeroSSR`** | Generate valid click handlers as JSX props | +| **Runtime Overhead** | ~300 bytes total | +| **React Re-renders** | Zero | +| **Server Component Support** | ✅ Full compatibility | > **Source Code:** See [experimental](/packages/core/src/experimental) for implementation details. @@ -192,9 +190,8 @@ import { scopedZeroSSR } from "@react-zero-ui/experimental"; **The bridge between static HTML and interactive UX** -*No state. No runtime overhead. Works in server components. ZERO re-renders.* +_No state. No runtime overhead. Works in server components. ZERO re-renders._ [**🚀 Get Started in less than 5 minutes**](/#-quick-start)
- diff --git a/docs/installation-next.md b/docs/installation-next.md index d00eb57..916fe7f 100644 --- a/docs/installation-next.md +++ b/docs/installation-next.md @@ -1,6 +1,7 @@ -### Next.js (App Router) Setup +### Next.js (App Router) Setup 1. **Install the dependencies** + ```bash npm install @react-zero-ui/core ``` @@ -11,23 +12,24 @@ npm install @tailwindcss/postcss --- - 2. **Add the PostCSS plugin (must come _before_ Tailwind).** ```js - // postcss.config.* ESM Syntax - const config = { - // ❗️ Zero-UI must come before Tailwind - plugins: ["@react-zero-ui/core/postcss", "@tailwindcss/postcss"]} - export default config; +// postcss.config.* ESM Syntax +const config = { + // ❗️ Zero-UI must come before Tailwind + plugins: ['@react-zero-ui/core/postcss', '@tailwindcss/postcss'], +}; +export default config; ``` - ```js - // postcss.config.* Common Module Syntax - module.exports = { - // ❗️ Zero-UI must come before Tailwind - plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} } }; - ``` +```js +// postcss.config.* Common Module Syntax +module.exports = { + // ❗️ Zero-UI must come before Tailwind + plugins: { '@react-zero-ui/core/postcss': {}, tailwindcss: {} }, +}; +``` --- @@ -36,8 +38,8 @@ npm install @tailwindcss/postcss ```bash npm run dev ``` -> Zero-UI will generate a .zero-ui folder in your project root. and generate the attributes.ts and type definitions for it. +> Zero-UI will generate a .zero-ui folder in your project root. and generate the attributes.ts and type definitions for it. --- @@ -46,22 +48,22 @@ npm run dev Spread `bodyAttributes` on `` in your root layout. ```tsx - // app/layout.tsx +// app/layout.tsx import { bodyAttributes } from './.zero-ui/attributes'; - + export default function RootLayout({ children }) { - return ( - - // ❗️ Spread the bodyAttributes on the body tag - {children} - - ); - } + return ( + + // ❗️ Spread the bodyAttributes on the body tag + {children} + + ); +} ``` **Thats it.** -Zero-UI will now add used data-* attributes to the body tag and the CSS will be injected and transformed by tailwind. +Zero-UI will now add used data-\* attributes to the body tag and the CSS will be injected and transformed by tailwind. ** 🧪 Checkout our Experimental SSR Safe OnClick Handler ** -[**🚀 Zero UI OnClick**](/docs/experimental.md) \ No newline at end of file +[**🚀 Zero UI OnClick**](/docs/experimental.md) diff --git a/docs/installation-vite.md b/docs/installation-vite.md index a07641c..0a2e6ee 100644 --- a/docs/installation-vite.md +++ b/docs/installation-vite.md @@ -1,9 +1,11 @@ ### Vite Setup 1. **Install the dependencies** + ```bash npm install @react-zero-ui/core ``` + ```bash npm install @tailwindcss/postcss ``` @@ -18,20 +20,19 @@ npm install @tailwindcss/postcss ```js // vite.config.* -import zeroUI from "@react-zero-ui/core/vite"; +import zeroUI from '@react-zero-ui/core/vite'; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; -import tailwindCss from '@tailwindcss/postcss'; +import tailwindCss from '@tailwindcss/postcss'; export default defineConfig({ - // ❗️Remove the default `tailwindcss()` plugin — and pass it into the `zeroUI` plugin - plugins: [zeroUI({tailwind: tailwindCss}), react()] + // ❗️Remove the default `tailwindcss()` plugin - and pass it into the `zeroUI` plugin + plugins: [zeroUI({ tailwind: tailwindCss }), react()], }); ``` + **Thats it.** -The plugin will add the data-* attributes to the body tag (no FOUC) and the CSS will be injected and transformed by tailwind. +The plugin will add the data-\* attributes to the body tag (no FOUC) and the CSS will be injected and transformed by tailwind. - - diff --git a/docs/internal.md b/docs/internal.md index a8fa913..d9c0d5b 100644 --- a/docs/internal.md +++ b/docs/internal.md @@ -1,12 +1,12 @@ # Internal docs -Below is a **"mental model"** of the Zero‑UI variant extractor—distilled so that _another_ human (or LLM) can reason about, extend, or safely refactor the code‑base. +Below is a **"mental model"** of the Zero‑UI variant extractor-distilled so that _another_ human (or LLM) can reason about, extend, or safely refactor the code‑base. --- ## 1. Top‑level goal -1. **Locate every hook call** `(ast-parsing.cts) → collectUseUIHooks` +1. **Locate every hook call** `(ast-parsing.cts) ➡️ collectUseUIHooks` ```ts const [value, setterFn] = useUI('stateKey', 'initialValue'); @@ -16,14 +16,14 @@ Below is a **"mental model"** of the Zero‑UI variant extractor—distilled so - `stateKey` must be a _local_, static string. - `initialValue` follows the same rule. -3. **Globally scan all project files** for **variant tokens that match any discovered `stateKey`**—regardless of where hooks are declared. `(scanner.cts) → scanVariantTokens` +3. **Globally scan all project files** for **variant tokens that match any discovered `stateKey`**-regardless of where hooks are declared. `(scanner.cts) ➡️ scanVariantTokens` _Examples_ ```html - +
- + ``` ```bash @@ -47,36 +47,36 @@ source files ─► ├─► Map> key: string; // 'stateKey' values: string[]; // ['light', 'dark', …] (unique & sorted) initialValue: string; // from 2nd arg of useUI() - scope: 'global' | 'scoped'; + scope: 'global' | 'scoped'; }; ``` -5. **Emit Tailwind** `@custom-variant` for every `key‑value` pair `(helpers.cts) → buildCss` +5. **Emit Tailwind** `@custom-variant` for every `key‑value` pair `(helpers.cts) ➡️ buildCss` ```ts function buildLocalSelector(keySlug: string, valSlug: string): string { - return - `[data-${keySlug}="${valSlug}"] &, &[data-${keySlug}="${valSlug}"] { @slot; }`; + return; + `[data-${keySlug}="${valSlug}"] &, &[data-${keySlug}="${valSlug}"] { @slot; }`; } function buildGlobalSelector(keySlug: string, valSlug: string): string { - return - `&:where(body[data-${keySlug}='${valSlug}'] &) { @slot; }`; + return; + `&:where(body[data-${keySlug}='${valSlug}'] &) { @slot; }`; } ``` -6. **Generate the attributes file** so SSR can inject the `` data‑attributes `(helpers.cts) → generateAttributesFile`. +6. **Generate the attributes file** so SSR can inject the `` data‑attributes `(helpers.cts) ➡️ generateAttributesFile`. --- ## 2. Pipeline overview (AST + global token scan) -| Stage | Scope & algorithm | Output | -| --- | --- | --- | -| **A - collectUseUIHooks** | Single AST traversal per file.
• Validate `useUI()` shapes.
• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).
• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set` | -| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).
Matches tokens for **every stateKey discovered in Stage A**. | `Map>` | +| Stage | Scope & algorithm | Output | +| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | +| **A - collectUseUIHooks** | Single AST traversal per file.
• Validate `useUI()` shapes.
• Resolve **stateKey** & **initialValue** with **`literalFromNode`** (§3).
• Builds global set of all state keys. | `HookMeta[]` = `{ stateKey, initialValue }[]`, global `Set` | +| **B - global scanVariantTokens** | Single global regex scan pass over all files (same glob & delimiters Tailwind uses).
Matches tokens for **every stateKey discovered in Stage A**. | `Map>` | -The pipeline now ensures tokens are captured globally—regardless of hook declarations in each file. +The pipeline now ensures tokens are captured globally-regardless of hook declarations in each file. --- @@ -88,12 +88,12 @@ Everything funnels through **`literalFromNode`**. Think of it as a deterministic ```bash ┌──────────────────────────────┬──────────────────────────┐ -│ Expression │ Accepted? → Returns │ +│ Expression │ Accepted? ➡️ Returns │ ├──────────────────────────────┼──────────────────────────┤ │ "dark" │ ✅ string literal │ │ `dark` │ ✅ template literal │ │ `th-${COLOR}` │ ✅ if COLOR is const │ -│ "a" + "b" │ ✅ → "ab" │ +│ "a" + "b" │ ✅ ➡️ "ab" │ │ a || b, a ?? b │ ✅ tries left, then right│ │ const DARK = "dark" │ ✅ top-level const only │ │ THEMES.dark │ ✅ const object access │ @@ -117,12 +117,12 @@ Everything funnels through **`literalFromNode`**. Think of it as a deterministic ### 3.2 Resolvers -| Helper | Purpose | -| --- | --- | -| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. | -| **`resolveLocalConstIdentifier`** | Maps an `Identifier` → its `const` initializer _iff_ initializer is a local static string/template. Imported bindings rejected explicitly. | -| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. | -| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. | +| Helper | Purpose | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **`resolveTemplateLiteral`** | Ensures every `${expr}` resolves via `literalFromNode`. | +| **`resolveLocalConstIdentifier`** | Maps an `Identifier` ➡️ its `const` initializer _iff_ initializer is a local static string/template. Imported bindings rejected explicitly. | +| **`resolveMemberExpression`** | Static walk of `obj.prop`, `obj['prop']`, `obj?.prop`, arrays, numeric indexes, optional‑chaining… Throws if unresolved. | +| **`literalFromNode`** | Router calling above; memoised (`WeakMap`) per AST node. | Resolvers throw contextual errors via **`throwCodeFrame`** (`@babel/code-frame`). diff --git a/examples/demo/.zero-ui/init-zero-ui.ts b/examples/demo/.zero-ui/init-zero-ui.ts index 5bf0023..809f4b7 100644 --- a/examples/demo/.zero-ui/init-zero-ui.ts +++ b/examples/demo/.zero-ui/init-zero-ui.ts @@ -15,9 +15,9 @@ if (typeof window !== 'undefined') { const [, key, rawVals = ''] = el.dataset.ui!.match(/^cycle:([\w-]+)(?:\((.*?)\))?$/) || []; - if (!(`data-${key}` in bodyAttributes)) return; // unknown variant → bail + if (!(`data-${key}` in bodyAttributes)) return; // unknown variant ➡️ bail - const vals = rawVals.split(','); // '' → [''] OK for toggle + const vals = rawVals.split(','); // '' ➡️ [''] OK for toggle const dsKey = toCamel(`data-${key}`); const target = (el.closest(`[data-${key}]`) as HTMLElement) ?? document.body; diff --git a/package.json b/package.json index aa71da8..808d816 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "eslint-plugin-import": "^2.32.0", "eslint-plugin-n": "^17.21.3", "prettier": "^3.6.2", - "release-please": "^17.1.1", "tsx": "^4.20.3", "typescript": "^5.9.2" }, @@ -77,7 +76,7 @@ "path": "packages/cli" }, { - "path": "packages/eslint-plugin-react-zero-ui" + "path": "packages/eslint-zero-ui" } ] } \ No newline at end of file diff --git a/packages/cli/README.md b/packages/cli/README.md index 5816c24..399bf00 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1,170 +1,105 @@ -# @react-zero-ui/core -![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) -![Zero UI logo](https://raw.githubusercontent.com/react-zero-ui/core/upgrade/resolver/docs/assets/zero-ui-logo.png) +# create-zero-ui -**The fastest possible UI updates in React. Period.** -Zero runtime, zero React re-renders, and the simplest developer experience ever. -_Say goodbye to context and prop-drilling._ +> ⚡ Instantly scaffold React Zero-UI into your Next.js or Vite project -[![Bundle size](https://badgen.net/bundlephobia/minzip/@react-zero-ui/core@0.2.6)](https://bundlephobia.com/package/@react-zero-ui/core@0.2.6) -[![npm version](https://img.shields.io/npm/v/@react-zero-ui/core)](https://www.npmjs.com/package/@react-zero-ui/core) -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) - -[📖 See the proof](https://github.com/react-zero-ui/core/blob/main/docs/assets/demo.md) • [🚀 Quick Start](#-quick-start) • [📚 API Reference](#-api-reference) • [🤝 Contributing](#-contributing) - ---- - -## 🔥 Core Concept: *"Pre-Rendering"* - -Why re-render UI if all states are known at build time? -React Zero-UI **pre-renders** UI states once — at no runtime cost — and flips `data-*` attributes to update. That’s it. - -```tsx -const [, setTheme] = useUI("theme", "dark"); - -// Flip theme to "light" -setTheme("light"); // data-theme="light" on body -``` +```bash -Tailwind usage: +npx create-zero-ui -```html -
Fast & Reactive
``` --- -## 🚀 How it Works (Build-Time Magic) +## 🚀 What It Sets Up -React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -- `useUI` and `useScopedUI` hook usage -- Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`) -- Tailwind variant classes (e.g., `theme-dark:bg-black`) +### ✅ Shared (Next.js & Vite) -This generates: +* Adds `@react-zero-ui/core` to your project +* Generates `.zero-ui/attributes.js` + `attributes.d.ts` +* Patches your `tsconfig.json`: -- Optimal CSS with global or scoped variant selectors -- Initial `data-*` attributes injected onto `` (zero FOUC) -- UI state with ease, no prop-drilling -- **Zero runtime overhead in production** + ```json + "paths": { + "@zero-ui/attributes": ["./.zero-ui/attributes.js"] + } + ``` --- -## 🚀 Quick Start +### 🔷 Next.js Specific -**Requires:** Vite or Next.js (App Router) +* Injects initial `data-*` attributes into `app/layout.tsx` +* Adds `postcss.config` with: -```bash -npx create-zero-ui -``` - -Manual config: -- [Next.js Setup](/docs/installation-next.md) -- [Vite Setup](/docs/installation-vite.md) + ```js + plugins: [ + // ❗zero-ui must come before tailwind + "@react-zero-ui/core/postcss", + "@tailwindcss/postcss" + ] + ``` --- -## 📚 API Reference +### 🔶 Vite Specific -### Basic Hook Signature +* Patches `vite.config.ts` with: -```tsx -const [staleValue, setValue] = useUI("key", "value"); -``` - -- `key` → becomes `data-key` on `` -- `value` → default SSR value -- `staleValue` → SSR fallback (doesn't update after mount) + ```ts + export default defineConfig({ + plugins: [zeroUI(), react()] + }); + ``` +* Vite **does not require** a PostCSS config --- -### 🔨 `useUI` – Global UI State - -```tsx -import { useUI } from '@react-zero-ui/core'; - -const [theme, setTheme] = useUI("theme", "dark"); -``` +## 🧪 Works With -- Updates `data-theme` on `` -- No React re-renders -- Globally accessible with Tailwind +* `Next.js` (App Router) +* `Vite` (React projects) +* `pnpm`, `yarn`, or `npm` --- -### 🎯 `useScopedUI` – Scoped UI State +## 🛠 Usage -```tsx -import { useScopedUI } from '@react-zero-ui/core'; - -const [theme, setTheme] = useScopedUI("theme", "dark"); - -
- Scoped UI Here -
+```bash +npx create-zero-ui ``` -- Sets `data-*` on a specific DOM node -- Scoped Tailwind variants only apply inside that element -- Prevents FOUC, no re-renders +Follow the CLI prompts to scaffold your config in seconds. --- -### 🌈 CSS Variable Support - -Pass the `CssVar` flag for variable-based state: +## 📚 Related -```tsx -import { CssVar } from '@react-zero-ui/core'; - -const [blur, setBlur] = useUI("blur", "0px", CssVar); -setBlur("5px"); // body { --blur: 5px } -``` - -Scoped example: - -```tsx -const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); - -
- Scoped blur effect -
-``` +* [@react-zero-ui/core](https://github.com/react-zero-ui/core) +* [Documentation](https://github.com/react-zero-ui/core/tree/main/docs) --- -## 🧪 Experimental Feature: `zeroOnClick` - -Enables interactivity **inside Server Components** without useEffect. -Only ~300 bytes of runtime. +## 🤝 Contributing -Read more: [experimental.md](/docs/experimental.md) +Found a bug or want to help? +PRs welcome at [react-zero-ui/core](https://github.com/react-zero-ui/core). --- -## 📦 Summary of Benefits +## License -- 🚀 **Zero React re-renders** -- ⚡️ **Pre-rendered UI**: state embedded at build time -- 📦 **<350B runtime footprint** -- 💫 **Simple DX**: hooks + Tailwind variants -- ⚙️ **AST-powered**: cached fast builds +MIT -Zero re-renders. Zero runtime. Infinite scalability. --- -## 🤝 Contributing - -We welcome all contributions! +### ✅ This README: +- Is **npm-ready** and GitHub-friendly. +- Makes **no assumptions** (Vite vs Next.js clearly separated). +- Avoids unnecessary branding/markup. +- Gives devs **trust** by showing exactly what gets modified. -- 🐛 [Open an issue](https://github.com/react-zero-ui/core/issues) -- 💡 [Start a discussion](https://github.com/react-zero-ui/core/discussions) -- 🔧 [Read the contributing guide](/docs/CONTRIBUTING.md) - ---- - -Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) \ No newline at end of file +Let me know if you want to auto-generate or publish it with a `postinstall` script that shows a success message with the same info. +``` diff --git a/packages/cli/package.json b/packages/cli/package.json index 768da68..dd15ff5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -45,4 +45,4 @@ "dependencies": { "@react-zero-ui/core": "^0.3.1" } -} \ No newline at end of file +} diff --git a/packages/core/README.md b/packages/core/README.md index 0159915..b7d43a6 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,42 +1,34 @@ - - -![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) - - - - - Frame 342 - - - -
- The fastest possible UI updates in React. Period. +# @react-zero-ui/core -Zero runtime, zero React re-renders, and the simplest developer experience ever. Say goodbye to context and prop-drilling. - -bundle size npm version License: MIT ![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) +![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) +![Zero UI logo](https://raw.githubusercontent.com/react-zero-ui/core/upgrade/resolver/docs/assets/zero-ui-logo.png) +**The fastest possible UI updates in React. Period.** +Zero runtime, zero React re-renders, and the simplest developer experience ever. +_Say goodbye to context and prop-drilling._ -[📖 See the proof](/docs/demo) [🚀 Quick Start](#-quick-start) [📚 API Reference](#-api-reference) [🤝 Contributing](#-contributing) +[![Bundle size](https://badgen.net/bundlephobia/minzip/@react-zero-ui/core@0.2.6)](https://bundlephobia.com/package/@react-zero-ui/core@0.2.6) +[![npm version](https://img.shields.io/npm/v/@react-zero-ui/core)](https://www.npmjs.com/package/@react-zero-ui/core) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) +![CI](https://github.com/react-zero-ui/core/actions/workflows/ci.yml/badge.svg?branch=main) -
+[📖 See the proof](https://github.com/react-zero-ui/core/blob/main/docs/assets/demo.md) • [🚀 Quick Start](#-quick-start) • [📚 API Reference](#-api-reference) • [🤝 Contributing](#-contributing) --- -## 🔥 Core Concept: *"Pre-Rendering"* - -Why re-render UI if all states are known at build time? React Zero-UI **pre-renders** UI states once ( at no runtime cost ), and flips `data-*` attribute to update - that's it. +## 🔥 Core Concept: _"Pre-Rendering"_ -**Example:** +Why re-render UI if all states are known at build time? +React Zero-UI **pre-renders** UI states once - at no runtime cost - and flips `data-*` attributes to update. That's it. ```tsx -const [, setTheme] = useUI("theme", "dark"); +const [, setTheme] = useUI('theme', 'dark'); // Flip theme to "light" -setTheme("light"); // data-theme="light" on body +setTheme('light'); // data-theme="light" on body ``` -**Tailwind usage:** Anywhere in your app +Tailwind usage: ```html
Fast & Reactive
@@ -48,168 +40,136 @@ setTheme("light"); // data-theme="light" on body React Zero-UI uses a hyper-optimized AST resolver in development that scans your codebase for: -* `useUI` and `useScopedUI` hook usage. -* Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`). -* Tailwind variant classes (e.g. `theme-dark:bg-black`). +- `useUI` and `useScopedUI` hook usage +- Any variables resolving to strings (e.g., `'theme'`, `'modal-open'`) +- Tailwind variant classes (e.g., `theme-dark:bg-black`) -**This generates:** - -* Optimal CSS with global or scoped variant selectors. -* Initial data-attributes injected onto the body (zero FOUC). -* UI state with ease, no prop-drilling. -* **Zero runtime overhead in production**. +This generates: +- Optimal CSS with global or scoped variant selectors +- Initial `data-*` attributes injected onto `` (zero FOUC) +- UI state with ease, no prop-drilling +- **Zero runtime overhead in production** --- ## 🚀 Quick Start -Zero-UI CLI -**Pre-requisites:** Vite or Next.js (App Router) +**Requires:** Vite or Next.js (App Router) ```bash npx create-zero-ui ``` -> For manual configuration, see [Next JS Installation](/docs/installation-next.md) -> [Vite Installation](/docs/installation-vite.md) +Manual config: -**That's it.** Start your app and see the magic. +- [Next.js Setup](/docs/installation-next.md) +- [Vite Setup](/docs/installation-vite.md) --- ## 📚 API Reference -**The Basics:** +### Basic Hook Signature ```tsx -const [, ] = useUI(, ); +const [staleValue, setValue] = useUI('key', 'value'); ``` -- `stateKey` ➡️ becomes `data-{stateKey}` on ``. -- `defaultValue` ➡️ SSR, prevents FOUC. -- `staleValue` ➡️ For scoped UI, set the data-* to the `staleValue` to prevent FOUC. -- **Note:** the returned `staleValue` does **not** update (`useUI` is write‑only). - - - +- `key` ➡️ becomes `data-key` on `` +- `value` ➡️ default SSR value +- `staleValue` ➡️ SSR fallback (doesn't update after mount) +--- - -### 🔨 `useUI` Hook (Global UI State) - -Simple hook mirroring React's `useState`: +### 🔨 `useUI` – Global UI State ```tsx import { useUI } from '@react-zero-ui/core'; -const [theme, setTheme] = useUI("theme", "dark"); +const [theme, setTheme] = useUI('theme', 'dark'); ``` -**Features:** -* Flips global `data-theme` attribute on ``. -* Zero React re-renders. -* Global UI state available anywhere in your app through tailwind variants. +- Updates `data-theme` on `` +- No React re-renders +- Globally accessible with Tailwind --- -### 🎯 `useScopedUI` Hook (Scoped UI State) +### 🎯 `useScopedUI` – Scoped UI State -Control UI states at the element-level: - -```diff -+ import { useScopedUI } from '@react-zero-ui/core'; +```tsx +import { useScopedUI } from '@react-zero-ui/core'; -const [theme, setTheme] = useScopedUI("theme", "dark"); +const [theme, setTheme] = useScopedUI('theme', 'dark'); -// ❗️Flips data-* on the specific ref element -+
- Scoped UI Here -
+
+ Scoped UI Here +
; ``` -**Features:** -* Data-* flips on specific target element. -* Generates scoped CSS selectors only applying within the target element. -* No FOUC, no re-renders. +- Sets `data-*` on a specific DOM node +- Scoped Tailwind variants only apply inside that element +- Prevents FOUC, no re-renders --- -### 🌈 CSS Variables Support +### 🌈 CSS Variable Support -Sometimes CSS variables are more efficient. React Zero-UI makes it trivial by passing the `CssVar` option: +Pass the `CssVar` flag for variable-based state: ```tsx -useUI(, , CssVar); // ❗️Pass CssVar to either hook to use CSS variables -``` -automatically adds `--` to the cssVariable +import { CssVar } from '@react-zero-ui/core'; -**Global CSS Variable:** -```diff -+ import { CssVar } from '@react-zero-ui/core'; +const [blur, setBlur] = useUI('blur', '0px', CssVar); +setBlur('5px'); // body { --blur: 5px } ``` -```tsx -const [blur, setBlur] = useUI("blur", "0px", CssVar); -setBlur("5px"); // body { --blur: 5px } -``` +Scoped example: -**Scoped CSS Variable:** ```tsx -const [blur, setBlur] = useScopedUI("blur", "0px", CssVar); +const [blur, setBlur] = useScopedUI('blur', '0px', CssVar); -
- Scoped blur effect. -
+
+ Scoped blur effect +
; ``` - - --- -## 🧪 Experimental Features +## 🧪 Experimental Feature: `zeroOnClick` -### SSR-safe `zeroOnClick` +Enables interactivity **inside Server Components** without useEffect. +Only ~300 bytes of runtime. -Enable client-side interactivity **without leaving server components**. -Just 300 bytes of runtime overhead. - -See [experimental](./docs/assets/experimental.md) for more details. +Read more: [experimental.md](/docs/experimental.md) --- ## 📦 Summary of Benefits -* **🚀 Zero React re-renders:** Pure CSS-driven UI state. -* **⚡️ Pre-rendered UI:** All states injected at build-time and only loaded when needed. -* **📦 Tiny footprint:** <350 bytes, zero runtime overhead for CSS states. -* **💫 Amazing DX:** Simple hooks, auto-generated Tailwind variants. -* **⚙️ Highly optimized AST resolver:** Fast, cached build process. - -React Zero-UI delivers the fastest, simplest, most performant way to handle global and scoped UI state in modern React applications. Say goodbye to re-renders and prop-drilling. +- 🚀 **Zero React re-renders** +- ⚡️ **Pre-rendered UI**: state embedded at build time +- 📦 **<350B runtime footprint** +- 💫 **Simple DX**: hooks + Tailwind variants +- ⚙️ **AST-powered**: cached fast builds ---- +Zero re-renders. Zero runtime. Infinite scalability. --- ## 🤝 Contributing -We welcome contributions from the community! Whether it's bug fixes, feature requests, documentation improvements, or performance optimizations - every contribution helps make React Zero-UI better. - -**Get involved:** -- 🐛 Found a bug? [Open an issue](https://github.com/react-zero-ui/core/issues) -- 💡 Have an idea? [Start a discussion](https://github.com/react-zero-ui/core/discussions) -- 🔧 Want to contribute code? Check out our [**Contributing Guide**](/docs/CONTRIBUTING.md) +We welcome all contributions! -> **First time contributor?** We have good first issues labeled `good-first-issue` to help you get started! +- 🐛 [Open an issue](https://github.com/react-zero-ui/core/issues) +- 💡 [Start a discussion](https://github.com/react-zero-ui/core/discussions) +- 🔧 [Read the contributing guide](/docs/CONTRIBUTING.md) --- - -
+--- Made with ❤️ for the React community by [@austin1serb](https://github.com/austin1serb) - -
\ No newline at end of file diff --git a/packages/core/__tests__/fixtures/next/app/CssVarDemo.tsx b/packages/core/__tests__/fixtures/next/app/CssVarDemo.tsx index 01e3808..edd816b 100644 --- a/packages/core/__tests__/fixtures/next/app/CssVarDemo.tsx +++ b/packages/core/__tests__/fixtures/next/app/CssVarDemo.tsx @@ -13,7 +13,7 @@ export default function CssVarDemo({ index = 0 }) { // 👇 pass `cssVar` flag to switch makeSetter into CSS-var mode const [blur, setBlur] = useScopedUI<'0px' | '4px'>('blur', '0px', cssVar); // global test - return ( + return (
('scope', 'off'); - // #2 No ref at all → missingRef error + // #2 No ref at all ➡️ missingRef error const [, setDialog] = useScopedUI<'open' | 'closed'>('dialog', 'closed'); return ( diff --git a/packages/core/__tests__/fixtures/vite/index.html b/packages/core/__tests__/fixtures/vite/index.html index b7292f8..6828453 100644 --- a/packages/core/__tests__/fixtures/vite/index.html +++ b/packages/core/__tests__/fixtures/vite/index.html @@ -1,15 +1,17 @@ + + + + Vite + React + TS + - - - - Vite + React + TS - - - -
- - - - \ No newline at end of file + +
+ + + diff --git a/packages/core/__tests__/fixtures/vite/tsconfig.json b/packages/core/__tests__/fixtures/vite/tsconfig.json index 3ee3cc8..2503c99 100644 --- a/packages/core/__tests__/fixtures/vite/tsconfig.json +++ b/packages/core/__tests__/fixtures/vite/tsconfig.json @@ -1,16 +1,5 @@ { - "files": [], - "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.node.json" - } - ], - "compilerOptions": { - "baseUrl": ".", - "paths": {}, - "strict": true - } -} \ No newline at end of file + "files": [], + "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], + "compilerOptions": { "baseUrl": ".", "paths": {}, "strict": true } +} diff --git a/packages/core/__tests__/helpers/overwriteFile.js b/packages/core/__tests__/helpers/overwriteFile.js index 152fd4c..99a61b8 100644 --- a/packages/core/__tests__/helpers/overwriteFile.js +++ b/packages/core/__tests__/helpers/overwriteFile.js @@ -8,7 +8,7 @@ import fs from 'fs'; */ export async function overwriteFile(filePath, content) { if (!fs.existsSync(filePath)) { - console.warn(`[Reset] ⚠️ File not found: ${filePath} — skipping overwrite.`); + console.warn(`[Reset] ⚠️ File not found: ${filePath} - skipping overwrite.`); return; } diff --git a/packages/core/__tests__/helpers/resetProjectState.js b/packages/core/__tests__/helpers/resetProjectState.js index 8ede516..aa1f6fb 100644 --- a/packages/core/__tests__/helpers/resetProjectState.js +++ b/packages/core/__tests__/helpers/resetProjectState.js @@ -8,8 +8,8 @@ import { overwriteFile } from './overwriteFile.js'; * Reset everything the Zero-UI CLI generates inside a fixture. * * @param projectDir absolute path to the fixture root - * @param isNext true → Next.js fixture (tsconfig + postcss) - * false → Vite fixture (vite.config.ts) + * @param isNext true ➡️ Next.js fixture (tsconfig + postcss) + * false ➡️ Vite fixture (vite.config.ts) */ export async function resetZeroUiState(projectDir, isNext = false) { console.log(`[Reset] Starting Zero-UI state reset for ${isNext ? 'Next.js' : 'Vite'} project`); diff --git a/packages/core/__tests__/unit/fixtures/test-components.jsx b/packages/core/__tests__/unit/fixtures/test-components.jsx deleted file mode 100644 index eb812e1..0000000 --- a/packages/core/__tests__/unit/fixtures/test-components.jsx +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable import/no-unresolved */ -import { THEME, MENU_SIZES, VARS } from './variables'; - -export function ComponentImports() { - const [, setTheme] = useUI(VARS, THEME); - const [, setSize] = useUI('size', MENU_SIZES.medium); - - return ( -
- - -
- ); -} diff --git a/packages/core/__tests__/unit/fixtures/ts-test-components.tsx b/packages/core/__tests__/unit/fixtures/ts-test-components.tsx deleted file mode 100644 index 290116e..0000000 --- a/packages/core/__tests__/unit/fixtures/ts-test-components.tsx +++ /dev/null @@ -1,88 +0,0 @@ -/* eslint-disable import/no-unresolved */ - -// @ts-ignore -import { useUI } from '@zero-ui/core'; - -/*───────────────────────────────────────────┐ -│ Top-level local constants (legal sources) │ -└───────────────────────────────────────────*/ -const DARK = 'dark' as const; -const PREFIX = `th-${DARK}` as const; -const SIZES = { small: 'sm', large: 'lg' } as const; -const MODES = ['auto', 'manual'] as const; - -const COLORS = { primary: 'blue', secondary: 'green' } as const; -const VARIANTS = { dark: `th-${DARK}`, light: COLORS.primary } as const; - -const isMobile = false; - -/*───────────────────────────────────────────┐ -│ Component covering every legal pattern │ -└───────────────────────────────────────────*/ -export function AllPatternsComponent() { - /* ① literal */ - const [theme, setTheme] = useUI('theme', 'light'); - /* ② identifier */ - const [altTheme, setAltTheme] = useUI('altTheme', DARK); - /* ③ static template literal */ - const [variant, setVariant] = useUI('variant', PREFIX); - /* ④ object-member */ - const [size, setSize] = useUI('size', SIZES.large); - /* ⑤ array-index */ - const [mode, setMode] = useUI('mode', MODES[0]); - /* ⑥ nested template + member */ - const [color, setColor] = useUI('color', `bg-${COLORS.primary}`); - /* ⑦ object-member */ - const [variant2, setVariant2] = useUI('variant', VARIANTS.dark); - /* ⑧ nested template + member */ - const [variant3, setVariant3] = useUI('variant', `th-${VARIANTS.light}`); - /* ⑨ BinaryExpression (template + member) */ - const [variant4, setVariant4] = useUI('variant', `th-${VARIANTS.light + '-inverse'}`); - /* ⑩ BinaryExpression (member + template) */ - const [variant5, setVariant5] = useUI('variant', `${VARIANTS.light}-inverse`); - /* ⑪ BinaryExpression (member + member) */ - const [variant6, setVariant6] = useUI('variant', `${VARIANTS.light}-${VARIANTS.dark}`); - /* ⑫ BinaryExpression (template + member) */ - const [variant7, setVariant7] = useUI('variant', `th-${VARIANTS.light}-${VARIANTS.dark}`); - /* ⑬ Optional-chaining w/ unresolvable member should pick th-light */ - // @ts-ignore - const [variant8, setVariant8] = useUI('variant', VARIANTS?.light.d ?? 'th-light'); - /* ⑭ nullish-coalesce */ - const [variant9, setVariant9] = useUI('variant', VARIANTS.light ?? 'th-light'); - - /* ── setters exercised in every allowed style ── */ - const clickHandler = () => { - /* direct literals */ - setTheme('dark'); - - /* identifier */ - setSize(SIZES.small); - - /* template literal */ - setVariant(`th-${DARK}-inverse`); - - /* member expression */ - setColor(COLORS.secondary); - - /* array index */ - setMode(MODES[1]); - - setVariant9(isMobile ? VARIANTS.light : 'th-light'); - }; - - /* conditional toggle with prev-state */ - const toggleAlt = () => setAltTheme((prev: string) => (prev === 'dark' ? 'light' : 'dark')); - - /* logical expression setter */ - useEffect(() => { - mode === 'auto' && setMode(MODES[1]); - }, [mode]); - - return ( -
- - -
{JSON.stringify({ theme, altTheme, variant, size, mode, color }, null, 2)}
-
- ); -} diff --git a/packages/core/__tests__/unit/fixtures/variables.js b/packages/core/__tests__/unit/fixtures/variables.js deleted file mode 100644 index b87369d..0000000 --- a/packages/core/__tests__/unit/fixtures/variables.js +++ /dev/null @@ -1,5 +0,0 @@ -export const THEME = 'dark'; - -export const MENU_SIZES = { small: 'sm', medium: 'md', large: 'lg' }; - -export const VARS = 'theme'; diff --git a/packages/core/package.json b/packages/core/package.json index fd4d2fa..2a2a217 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -118,4 +118,4 @@ "engines": { "node": ">=18.0.0" } -} \ No newline at end of file +} diff --git a/packages/core/src/experimental/README.md b/packages/core/src/experimental/README.md index 3e5f869..a590d49 100644 --- a/packages/core/src/experimental/README.md +++ b/packages/core/src/experimental/README.md @@ -15,9 +15,8 @@ This is the core runtime entrypoint. When called: 2. **Listens for clicks** on any element (or ancestor) with a `data-ui` attribute. 3. **Parses the `data-ui` directive** with this format: - - * `data-ui="global:key(val1,val2,...)"` → flips `data-key` on `document.body` - * `data-ui="scoped:key(val1,val2,...)"` → flips `data-key` on closest matching ancestor or self + - `data-ui="global:key(val1,val2,...)"` ➡️ flips `data-key` on `document.body` + - `data-ui="scoped:key(val1,val2,...)"` ➡️ flips `data-key` on closest matching ancestor or self 4. **Cycles the value** of the matching `data-*` attribute in a round-robin fashion. @@ -45,17 +44,17 @@ or **In development**, they also perform validation: -* Ensures the key is kebab-case. -* Ensures at least one value is provided. +- Ensures the key is kebab-case. +- Ensures at least one value is provided. --- ## 🧠 Design Notes -* **No React state or re-renders** involved. -* Works entirely via DOM `data-*` mutations. -* Compatible with all server components. -* Fully tree-shakable and side-effect-free unless `activateZeroUiRuntime()` is called. +- **No React state or re-renders** involved. +- Works entirely via DOM `data-*` mutations. +- Compatible with all server components. +- Fully tree-shakable and side-effect-free unless `activateZeroUiRuntime()` is called. This design makes it ideal for pairing with Tailwind-style conditional classes in static components. @@ -63,8 +62,8 @@ This design makes it ideal for pairing with Tailwind-style conditional classes i ## 🧼 Summary -* `activateZeroUiRuntime()` → enables click handling on static components via `data-ui` -* `zeroSSR` / `scopedZeroSSR` → emit valid click handlers as JSX props -* No state. No runtime overhead. Works in server components. +- `activateZeroUiRuntime()` ➡️ enables click handling on static components via `data-ui` +- `zeroSSR` / `scopedZeroSSR` ➡️ emit valid click handlers as JSX props +- No state. No runtime overhead. Works in server components. This runtime is the bridge between **static HTML** and **interactive UX**, while keeping everything **server-rendered** and blazing fast. diff --git a/packages/core/src/experimental/runtime.ts b/packages/core/src/experimental/runtime.ts index d0d2a19..5735266 100644 --- a/packages/core/src/experimental/runtime.ts +++ b/packages/core/src/experimental/runtime.ts @@ -13,10 +13,10 @@ * appears twice. * * --- */ -/** Map emitted by the compiler: every legal data-* key → true */ +/** Map emitted by the compiler: every legal data-* key ➡️ true */ export type VariantKeyMap = Record; -/* kebab → camel ("data-theme-dark" → "themeDark") */ +/* kebab ➡️ camel ("data-theme-dark" ➡️ "themeDark") */ const kebabToCamel = (attr: string) => attr.slice(5).replace(/-([a-z])/g, (_, c) => c.toUpperCase()); /* One shared RegExp - avoids per-click re-parsing */ diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 1528ace..3e5b133 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -14,7 +14,7 @@ export function makeSetter(key: string, initialValue: T, getTa // enforce kebab-case for the key: lowercase letters, digits and single dashes if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(key)) { throw new Error( - `[Zero-UI] useUI(key, …); key must be kebab-case (e.g. "theme-dark"), got "${key}". ` + `Avoid camelCase or uppercase — they break variant generation.` + `[Zero-UI] useUI(key, …); key must be kebab-case (e.g. "theme-dark"), got "${key}". ` + `Avoid camelCase or uppercase - they break variant generation.` ); } diff --git a/packages/core/src/postcss/ast-parsing.ts b/packages/core/src/postcss/ast-parsing.ts index e11cf68..530235c 100644 --- a/packages/core/src/postcss/ast-parsing.ts +++ b/packages/core/src/postcss/ast-parsing.ts @@ -19,7 +19,7 @@ const PARSE_OPTS = (f: string): Partial => ({ }); export interface HookMeta { - /** Babel binding object — use `binding.referencePaths` */ + /** Babel binding object - use `binding.referencePaths` */ // binding: Binding; /** Variable name (`setTheme`) */ setterFnName: string; @@ -233,7 +233,7 @@ export interface ProcessVariantsResult { export async function processVariants(changedFiles: string[] | null = null): Promise { const srcFiles = changedFiles ?? findAllSourceFiles(); - /* Phase A — refresh hooks in cache (no token scan yet) */ + /* Phase A - refresh hooks in cache (no token scan yet) */ // Count the number of CPUs and use 1 less than that for concurrency const cpu = Math.max(os.cpus().length - 1, 1); @@ -259,11 +259,11 @@ export async function processVariants(changedFiles: string[] | null = null): Pro fileCache.set(fp, { hash: sig, hooks, tokens: new Map() }); }); - /* Phase B — build global key set */ + /* Phase B - build global key set */ const keySet = new Set(); for (const { hooks } of fileCache.values()) hooks.forEach((h) => keySet.add(h.stateKey)); - /* Phase C — ensure every cache entry has up-to-date tokens */ + /* Phase C - ensure every cache entry has up-to-date tokens */ for (const [fp, entry] of fileCache) { // Re-scan if tokens missing OR if keySet now contains keys we didn't scan for const needsRescan = entry.tokens.size === 0 || [...keySet].some((k) => !entry.tokens.has(k)); @@ -274,7 +274,7 @@ export async function processVariants(changedFiles: string[] | null = null): Pro entry.tokens = scanVariantTokens(code, keySet); } - /* Phase D — aggregate variant & initial-value maps */ + /* Phase D - aggregate variant & initial-value maps */ const variantMap = new Map>(); const initMap = new Map(); const scopeMap = new Map(); @@ -290,7 +290,7 @@ export async function processVariants(changedFiles: string[] | null = null): Pro initMap.set(h.stateKey, h.initialValue); } - /* scope aggregation — always run */ + /* scope aggregation - always run */ const prevScope = scopeMap.get(h.stateKey); if (prevScope && prevScope !== h.scope) { throw new Error(`[Zero-UI] Key "${h.stateKey}" used with both global and scoped hooks.`); @@ -298,14 +298,14 @@ export async function processVariants(changedFiles: string[] | null = null): Pro scopeMap.set(h.stateKey, h.scope); }); - // tokens → variantMap + // tokens ➡️ variantMap tokens.forEach((vals, k) => { if (!variantMap.has(k)) variantMap.set(k, new Set()); vals.forEach((v) => variantMap.get(k)!.add(v)); }); } - /* Phase E — final assembly */ + /* Phase E - final assembly */ const finalVariants: VariantData[] = [...variantMap] .map(([key, set]) => ({ key, values: [...set].sort(), initialValue: initMap.get(key) ?? null, scope: scopeMap.get(key)! })) .sort((a, b) => a.key.localeCompare(b.key)); diff --git a/packages/core/src/postcss/helpers.test.ts b/packages/core/src/postcss/helpers.test.ts index d78be79..a481bc2 100644 --- a/packages/core/src/postcss/helpers.test.ts +++ b/packages/core/src/postcss/helpers.test.ts @@ -62,7 +62,7 @@ test('buildCss emits @custom-variant blocks in stable order', () => { // test('generateAttributesFile writes files once and stays stable', async () => { // await runTest({}, async () => { // /* ------------------------------------------------------------------ * -// * 1. first call — files should be created +// * 1. first call - files should be created // * ------------------------------------------------------------------ */ // const first = await generateAttributesFile(expectedVariants, initialValues); // console.log('first: ', first); @@ -91,7 +91,7 @@ test('buildCss emits @custom-variant blocks in stable order', () => { // `; // assert.strictEqual(norm(tsText), norm(expectedTs), 'attributes.d.ts snapshot mismatch'); // /* ------------------------------------------------------------------ * -// * 2. second call — nothing should change +// * 2. second call - nothing should change // * ------------------------------------------------------------------ */ // const second = await generateAttributesFile(expectedVariants, initialValues); // console.log('second: ', second); diff --git a/packages/core/src/postcss/helpers.ts b/packages/core/src/postcss/helpers.ts index c4da87e..5b55dc7 100644 --- a/packages/core/src/postcss/helpers.ts +++ b/packages/core/src/postcss/helpers.ts @@ -141,7 +141,7 @@ export async function patchTsConfig(): Promise { const configFile = fs.existsSync(path.join(cwd, 'tsconfig.json')) ? 'tsconfig.json' : fs.existsSync(path.join(cwd, 'jsconfig.json')) ? 'jsconfig.json' : null; - // Ignore Vite fixtures — they patch their own config + // Ignore Vite fixtures - they patch their own config const hasViteConfig = ['js', 'mjs', 'ts', 'mts'].some((ext) => fs.existsSync(path.join(cwd, `vite.config.${ext}`))); if (hasViteConfig) { console.log('[Zero-UI] Vite config found, skipping tsconfig patch'); diff --git a/packages/core/src/postcss/resolvers.ts b/packages/core/src/postcss/resolvers.ts index fac9da0..e3f483b 100644 --- a/packages/core/src/postcss/resolvers.ts +++ b/packages/core/src/postcss/resolvers.ts @@ -179,7 +179,7 @@ export function fastEval(node: t.Expression, path: NodePath): { confiden } } - // ❸ Give up → undefined (caller falls back to manual resolver) + // ❸ Give up ➡️ undefined (caller falls back to manual resolver) return { confident: false }; } @@ -191,7 +191,7 @@ export function fastEval(node: t.Expression, path: NodePath): { confiden 3. Initialized to a **string literal** or a **static template literal**, Anything else (inner-scope `const`, dynamic value, imported binding, spaces) - ➜ return `null` — the caller will decide whether to throw or keep searching. + ➜ return `null` - the caller will decide whether to throw or keep searching. If the binding is *imported*, we delegate to `throwCodeFrame()` so the developer gets a consistent, actionable error message. @@ -257,7 +257,7 @@ export function resolveLocalConstIdentifier(node: t.Expression, path: NodePath 352'); - /** Collect the property chain (deep → shallow) */ + /** Collect the property chain (deep ➡️ shallow) */ const props: (string | number)[] = []; let current: t.Expression | t.PrivateName = node; @@ -465,22 +465,22 @@ export function resolveMemberExpression( \*──────────────────────────────────────────────────────────*/ function resolveObjectValue(obj: t.ObjectExpression, key: string): t.Expression | null | undefined { for (const p of obj.properties) { - // Matches: { dark: 'theme' } — key = 'dark' + // Matches: { dark: 'theme' } - key = 'dark' if (t.isObjectProperty(p) && !p.computed && t.isIdentifier(p.key) && p.key.name === key) { return p.value as t.Expression; } - // Matches: { ['dark']: 'theme' } — key = 'dark' + // Matches: { ['dark']: 'theme' } - key = 'dark' if (t.isObjectProperty(p) && p.computed && t.isStringLiteral(p.key) && p.key.value === key) { return p.value as t.Expression; } - // Matches: { "dark": "theme" } or { "1": "theme" } — key = 'dark' or '1' + // Matches: { "dark": "theme" } or { "1": "theme" } - key = 'dark' or '1' // if (t.isObjectProperty(p) && t.isStringLiteral(p.key) && p.key.value === key) { // return p.value as t.Expression; // } - // // ✅ New: Matches { 1: "theme" } — key = '1' + // // ✅ New: Matches { 1: "theme" } - key = '1' // if (t.isObjectProperty(p) && t.isNumericLiteral(p.key) && String(p.key.value) === key) { // return p.value as t.Expression; // } diff --git a/packages/core/src/postcss/scanner.ts b/packages/core/src/postcss/scanner.ts index e8d46f5..b748889 100644 --- a/packages/core/src/postcss/scanner.ts +++ b/packages/core/src/postcss/scanner.ts @@ -7,7 +7,7 @@ export function scanVariantTokens(src: string, keys: Set): Map"'`\s]*[^<>"'`\s:]/g; const TOK2 = /[^<>"'`\s.(){}[\]#=%]*[^<>"'`\s.(){}[\]#=%:]/g; const tokens = [...(src.match(TOK1) ?? []), ...(src.match(TOK2) ?? [])]; @@ -19,7 +19,7 @@ export function scanVariantTokens(src: string, keys: Set): Map('scope', 'off'); - // #2 No ref at all → missingRef error + // #2 No ref at all ➡️ missingRef error const [, setDialog] = useScopedUI<'open' | 'closed'>('dialog', 'closed'); return ( diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/UseEffectComponent.tsx b/packages/eslint-zero-ui/__tests__/fixtures/next/app/UseEffectComponent.tsx similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/UseEffectComponent.tsx rename to packages/eslint-zero-ui/__tests__/fixtures/next/app/UseEffectComponent.tsx diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/globals.css b/packages/eslint-zero-ui/__tests__/fixtures/next/app/globals.css similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/globals.css rename to packages/eslint-zero-ui/__tests__/fixtures/next/app/globals.css diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/layout.tsx b/packages/eslint-zero-ui/__tests__/fixtures/next/app/layout.tsx similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/layout.tsx rename to packages/eslint-zero-ui/__tests__/fixtures/next/app/layout.tsx diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/page.tsx b/packages/eslint-zero-ui/__tests__/fixtures/next/app/page.tsx similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/page.tsx rename to packages/eslint-zero-ui/__tests__/fixtures/next/app/page.tsx diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/variables.ts b/packages/eslint-zero-ui/__tests__/fixtures/next/app/variables.ts similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/app/variables.ts rename to packages/eslint-zero-ui/__tests__/fixtures/next/app/variables.ts diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/eslint.config.mjs b/packages/eslint-zero-ui/__tests__/fixtures/next/eslint.config.mjs similarity index 95% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/eslint.config.mjs rename to packages/eslint-zero-ui/__tests__/fixtures/next/eslint.config.mjs index 44277cc..fcf93a9 100644 --- a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/eslint.config.mjs +++ b/packages/eslint-zero-ui/__tests__/fixtures/next/eslint.config.mjs @@ -2,7 +2,7 @@ import { dirname } from 'path'; import { fileURLToPath } from 'url'; import { FlatCompat } from '@eslint/eslintrc'; import tsParser from '@typescript-eslint/parser'; -import zeroUiPlugin from 'eslint-plugin-react-zero-ui'; +import zeroUiPlugin from 'eslint-zero-ui'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/next-env.d.ts b/packages/eslint-zero-ui/__tests__/fixtures/next/next-env.d.ts similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/next-env.d.ts rename to packages/eslint-zero-ui/__tests__/fixtures/next/next-env.d.ts diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/package.json b/packages/eslint-zero-ui/__tests__/fixtures/next/package.json similarity index 79% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/package.json rename to packages/eslint-zero-ui/__tests__/fixtures/next/package.json index 092b106..1e669b2 100644 --- a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/package.json +++ b/packages/eslint-zero-ui/__tests__/fixtures/next/package.json @@ -9,6 +9,7 @@ "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, "dependencies": { + "@react-zero-ui/core": "file:/Users/austinserb/Desktop/React-Zero/react-zero-ui/dist/react-zero-ui-core-0.3.1-beta.2.tgz", "next": "^15.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -20,9 +21,9 @@ "@types/react": "19.1.7", "eslint": "^9.32.0", "eslint-config-next": "^15.4.5", - "eslint-plugin-react-zero-ui": "workspace:*", + "eslint-zero-ui": "workspace:*", "postcss": "^8.5.5", "tailwindcss": "^4.1.10", "typescript": "5.8.3" } -} +} \ No newline at end of file diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/postcss.config.mjs b/packages/eslint-zero-ui/__tests__/fixtures/next/postcss.config.mjs similarity index 100% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/postcss.config.mjs rename to packages/eslint-zero-ui/__tests__/fixtures/next/postcss.config.mjs diff --git a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/tsconfig.json b/packages/eslint-zero-ui/__tests__/fixtures/next/tsconfig.json similarity index 54% rename from packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/tsconfig.json rename to packages/eslint-zero-ui/__tests__/fixtures/next/tsconfig.json index 2530bc6..f0e1067 100644 --- a/packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next/tsconfig.json +++ b/packages/eslint-zero-ui/__tests__/fixtures/next/tsconfig.json @@ -1,10 +1,6 @@ { "compilerOptions": { - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "forceConsistentCasingInFileNames": true, "skipLibCheck": true, @@ -19,21 +15,8 @@ "jsx": "preserve", "target": "ES2017", "baseUrl": ".", - "paths": { - "@zero-ui/attributes": [ - "./.zero-ui/attributes.js" - ] - } + "paths": { "@zero-ui/attributes": ["./.zero-ui/attributes.js"] } }, - "include": [ - "**/*.ts", - "**/*.tsx", - ".next/**/*.d.ts", - ".next/types/**/*.ts", - ".zero-ui/**/*.d.ts", - "next-env.d.ts" - ], - "exclude": [ - "node_modules" - ] -} \ No newline at end of file + "include": ["**/*.ts", "**/*.tsx", ".next/**/*.d.ts", ".next/types/**/*.ts", ".zero-ui/**/*.d.ts", "next-env.d.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/eslint-plugin-react-zero-ui/package.json b/packages/eslint-zero-ui/package.json similarity index 94% rename from packages/eslint-plugin-react-zero-ui/package.json rename to packages/eslint-zero-ui/package.json index a07b22d..3a1de12 100644 --- a/packages/eslint-plugin-react-zero-ui/package.json +++ b/packages/eslint-zero-ui/package.json @@ -1,5 +1,5 @@ { - "name": "eslint-plugin-react-zero-ui", + "name": "eslint-zero-ui", "version": "0.0.1-beta.1", "description": "ESLint rules for React Zero-UI", "type": "module", diff --git a/packages/eslint-plugin-react-zero-ui/src/index.ts b/packages/eslint-zero-ui/src/index.ts similarity index 100% rename from packages/eslint-plugin-react-zero-ui/src/index.ts rename to packages/eslint-zero-ui/src/index.ts diff --git a/packages/eslint-plugin-react-zero-ui/src/rules/require-data-attr.ts b/packages/eslint-zero-ui/src/rules/require-data-attr.ts similarity index 97% rename from packages/eslint-plugin-react-zero-ui/src/rules/require-data-attr.ts rename to packages/eslint-zero-ui/src/rules/require-data-attr.ts index 30bfe99..ea85b29 100644 --- a/packages/eslint-plugin-react-zero-ui/src/rules/require-data-attr.ts +++ b/packages/eslint-zero-ui/src/rules/require-data-attr.ts @@ -1,6 +1,6 @@ import { ESLintUtils, TSESTree as T } from '@typescript-eslint/utils'; -const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/serbyte/eslint-plugin-react-zero-ui/blob/main/docs/${name}.md`); +const createRule = ESLintUtils.RuleCreator((name) => `https://github.com/serbyte/eslint-zero-ui/blob/main/docs/${name}.md`); // At top of create() const hookLoc = new Map(); diff --git a/packages/eslint-plugin-react-zero-ui/tsconfig.json b/packages/eslint-zero-ui/tsconfig.json similarity index 100% rename from packages/eslint-plugin-react-zero-ui/tsconfig.json rename to packages/eslint-zero-ui/tsconfig.json diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c03829..1bfa0cf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,9 +29,6 @@ importers: prettier: specifier: ^3.6.2 version: 3.6.2 - release-please: - specifier: ^17.1.1 - version: 17.1.1 tsx: specifier: ^4.20.3 version: 4.20.3 @@ -146,7 +143,7 @@ importers: specifier: ^4.20.3 version: 4.20.3 - packages/eslint-plugin-react-zero-ui: + packages/eslint-zero-ui: dependencies: '@typescript-eslint/parser': specifier: ^8.39.0 @@ -245,9 +242,6 @@ packages: '@clack/prompts@0.8.2': resolution: {integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==} - '@conventional-commits/parser@0.4.1': - resolution: {integrity: sha512-H2ZmUVt6q+KBccXfMBhbBF14NlANeqHTXL4qCL6QGbMzrc4HDXyzWuxPxPNbz71f/5UkR5DrycP5VO9u7crahg==} - '@emnapi/runtime@1.4.5': resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==} @@ -445,10 +439,6 @@ packages: resolution: {integrity: sha512-Ul5l+lHEcw3L5+k8POx6r74mxEYKG5kOb6Xpy2gCRW6zweT6TEhAf8vhxGgjhqrd/VO/Dirhsb+1hNpD1ue9hw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@google-automations/git-file-utils@3.0.0': - resolution: {integrity: sha512-e+WLoKR0TchIhKsSDOnd/su171eXKAAdLpP2tS825UAloTgfYus53kW8uKoVj9MAsMjXGXsJ2s1ASgjq81xVdA==} - engines: {node: '>= 18'} - '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -469,9 +459,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@iarna/toml@3.0.0': - resolution: {integrity: sha512-td6ZUkz2oS3VeleBcN+m//Q6HlCFCPrnI0FZhrt/h4XqLEdOyYp2u21nd8MdsR+WJy5r9PTDaHTDDfhf4H4l6Q==} - '@img/sharp-darwin-arm64@0.34.3': resolution: {integrity: sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -611,18 +598,6 @@ packages: '@jridgewell/trace-mapping@0.3.29': resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} - '@jsep-plugin/assignment@1.3.0': - resolution: {integrity: sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==} - engines: {node: '>= 10.16.0'} - peerDependencies: - jsep: ^0.4.0||^1.0.0 - - '@jsep-plugin/regex@1.0.4': - resolution: {integrity: sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==} - engines: {node: '>= 10.16.0'} - peerDependencies: - jsep: ^0.4.0||^1.0.0 - '@next/env@15.4.5': resolution: {integrity: sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==} @@ -686,58 +661,6 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} - '@octokit/auth-token@4.0.0': - resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==} - engines: {node: '>= 18'} - - '@octokit/core@5.2.2': - resolution: {integrity: sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==} - engines: {node: '>= 18'} - - '@octokit/endpoint@9.0.6': - resolution: {integrity: sha512-H1fNTMA57HbkFESSt3Y9+FBICv+0jFceJFPWDePYlR/iMGrwM5ph+Dd4XRQs+8X+PUFURLQgX9ChPfhJ/1uNQw==} - engines: {node: '>= 18'} - - '@octokit/graphql@7.1.1': - resolution: {integrity: sha512-3mkDltSfcDUoa176nlGoA32RGjeWjl3K7F/BwHwRMJUW/IteSa4bnSV8p2ThNkcIcZU2umkZWxwETSSCJf2Q7g==} - engines: {node: '>= 18'} - - '@octokit/openapi-types@24.2.0': - resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==} - - '@octokit/plugin-paginate-rest@11.4.4-cjs.2': - resolution: {integrity: sha512-2dK6z8fhs8lla5PaOTgqfCGBxgAv/le+EhPs27KklPhm1bKObpu6lXzwfUEQ16ajXzqNrKMujsFyo9K2eaoISw==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '5' - - '@octokit/plugin-request-log@4.0.1': - resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': '5' - - '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1': - resolution: {integrity: sha512-VUjIjOOvF2oELQmiFpWA1aOPdawpyaCUqcEBc/UOUnj3Xp6DJGrJ1+bjUIIDzdHjnFNO6q57ODMfdEZnoBkCwQ==} - engines: {node: '>= 18'} - peerDependencies: - '@octokit/core': ^5 - - '@octokit/request-error@5.1.1': - resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==} - engines: {node: '>= 18'} - - '@octokit/request@8.4.1': - resolution: {integrity: sha512-qnB2+SY3hkCmBxZsR/MPCybNmbJe4KAlfWErXq+rBKkQJlbjdJeS85VI9r8UqeLYLvnAenU8Q1okM/0MBsAGXw==} - engines: {node: '>= 18'} - - '@octokit/rest@20.1.2': - resolution: {integrity: sha512-GmYiltypkHHtihFwPRxlaorG5R9VAHuk/vbszVoRTGXnAsY60wYLkh/E2XiFmdZmqrisw+9FaazS1i5SbdWYgA==} - engines: {node: '>= 18'} - - '@octokit/types@13.10.0': - resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==} - '@pivanov/utils@0.0.2': resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==} peerDependencies: @@ -996,21 +919,12 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/node@20.19.9': resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} '@types/node@24.2.0': resolution: {integrity: sha512-3xyG3pMCq3oYCNg7/ZP+E1ooTaGB4cG8JWRsqqOYQdbWNY4zbaV0Ennrd7stjiJEFZCaybcIgpTjJWHRfBSIDw==} - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@types/npm-package-arg@6.1.4': - resolution: {integrity: sha512-vDgdbMy2QXHnAruzlv68pUtXCjmqUk3WrBAsRboRovsOmxbfn/WiYCjmecyKjGztnMps5dWp4Uq2prp+Ilo17Q==} - '@types/react-dom@19.1.7': resolution: {integrity: sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==} peerDependencies: @@ -1024,15 +938,6 @@ packages: '@types/react@19.1.9': resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==} - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - '@types/yargs@16.0.9': - resolution: {integrity: sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==} - '@typescript-eslint/parser@8.39.0': resolution: {integrity: sha512-g3WpVQHngx0aLXn6kfIYCZxM6rRJlWzEkVpqEFLT3SgEDsp9cpCbxxgwnE504q4H+ruSDh/VGS6nqZIDynP+vg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1103,10 +1008,6 @@ packages: vue-router: optional: true - '@xmldom/xmldom@0.8.10': - resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} - engines: {node: '>=10.0.0'} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1117,17 +1018,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1139,9 +1032,6 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} - array-includes@3.1.9: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} @@ -1162,17 +1052,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} - async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} - async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} - available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -1180,17 +1063,11 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - before-after-hook@2.2.3: - resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==} - bippy@0.3.17: resolution: {integrity: sha512-0wG0kF9IM2MfS65mpU/3oU+0bRT5Wlh0oTqeRU8eJUU2+ga/QTGr3ZxzVEbQ1051NgADC8W+im1fP3Ln0Vof+Q==} peerDependencies: react: '>=17.0.1' - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1222,14 +1099,6 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - caniuse-lite@1.0.30001731: resolution: {integrity: sha512-lDdp2/wrOmTRWuoB5DpfNkC0rJDU8DqRa6nYL6HK6sytw70QMopt/NIc/9SM7ylItlBWfACXk0tEn37UWM/+mg==} @@ -1244,22 +1113,10 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - code-suggester@5.0.0: - resolution: {integrity: sha512-/xyGfSM/hMYxl12kqoYoOwUm0D1uuVT2nWcMiTq2Fn5MLi+BlWkHq5AUvtniDJwVSdI3jgbK4AOzGws+v/dFPQ==} - engines: {node: '>=18.0.0'} - hasBin: true - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1274,25 +1131,9 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - conventional-changelog-conventionalcommits@6.1.0: - resolution: {integrity: sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==} - engines: {node: '>=14'} - - conventional-changelog-writer@6.0.1: - resolution: {integrity: sha512-359t9aHorPw+U+nHzUXHS5ZnPBOizRxfQsWT5ZDHBfvfxQOAik+yfuhKXG66CN5LEWPpMNnIMHUTCKeYNprvHQ==} - engines: {node: '>=14'} - hasBin: true - - conventional-commits-filter@3.0.0: - resolution: {integrity: sha512-1ymej8b5LouPx9Ox0Dw/qAO2dVdfpRFq28e5Y0jJEU8ZrLdy0vOSkkIInwmxErFGhg6SALro60ZrwYFVTUDo4Q==} - engines: {node: '>=14'} - convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -1300,13 +1141,6 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -1322,9 +1156,6 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -1342,14 +1173,6 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -1361,67 +1184,25 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - deprecation@2.3.1: - resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==} - - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} - detect-libc@2.0.4: resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} engines: {node: '>=8'} - diff@5.2.0: - resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} - engines: {node: '>=0.3.1'} - - diff@7.0.0: - resolution: {integrity: sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==} - engines: {node: '>=0.3.1'} - doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} - engines: {node: '>=8'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.195: - resolution: {integrity: sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + electron-to-chromium@1.5.196: + resolution: {integrity: sha512-FnnXV0dXANe7YNtKl/Af1raw+sBBUPuwcNEWfLOJyumXBvfQEBsnc0Gn+yEnVscq4x3makTtrlf4TjAo7lcXTQ==} enhanced-resolve@5.18.2: resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} engines: {node: '>=10.13.0'} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -1459,10 +1240,6 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1583,10 +1360,6 @@ packages: fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1595,10 +1368,6 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1628,9 +1397,6 @@ packages: react-dom: optional: true - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1655,10 +1421,6 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1682,10 +1444,6 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -1708,15 +1466,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true - - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -1744,25 +1493,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} - ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1775,17 +1505,6 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1794,9 +1513,6 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-arrayish@0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} @@ -1836,10 +1552,6 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-generator-function@1.1.0: resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} engines: {node: '>= 0.4'} @@ -1864,14 +1576,6 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -1925,10 +1629,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsep@1.4.0: - resolution: {integrity: sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==} - engines: {node: '>= 10.16.0'} - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1937,18 +1637,12 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -1958,18 +1652,9 @@ packages: engines: {node: '>=6'} hasBin: true - jsonpath-plus@10.3.0: - resolution: {integrity: sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==} - engines: {node: '>=18.0.0'} - hasBin: true - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} @@ -2042,20 +1727,10 @@ packages: resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} engines: {node: '>= 12.0.0'} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -2066,29 +1741,13 @@ packages: lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - magic-string@0.30.17: resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -2097,25 +1756,13 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -2132,10 +1779,6 @@ packages: engines: {node: '>=10'} hasBin: true - modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} - motion-dom@12.23.12: resolution: {integrity: sha512-RcR4fvMCTESQBD/uKQe49D5RUeDOokkGRmz4ceaJKDBgHYtZtntC/s2vLvY38gqGaytinij/yi3hMcWVcEF5Kw==} @@ -2171,9 +1814,6 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - next@15.4.5: resolution: {integrity: sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -2195,22 +1835,9 @@ packages: sass: optional: true - node-html-parser@6.1.13: - resolution: {integrity: sha512-qIsTMOY4C/dAa5Q5vsobRpOOvPfC4pB61UVW2uSwZNUp0QU/jCekTal1vMmbO0DgdHeLUJpv/ARmDqErVxA3Sg==} - node-releases@2.0.19: resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2235,9 +1862,6 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2246,48 +1870,22 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-diff@0.11.1: - resolution: {integrity: sha512-Oq4j8LAOPOcssanQkIjxosjATBIEJhCxMCxPhMu+Ci4wdNmAEdx0O+a7gzbR2PyKXgKPvRLIN5g224+dJAsKHA==} - - parse-github-repo-url@1.4.1: - resolution: {integrity: sha512-bSWyzBKqcSL4RrncTpGsEKoJ7H8a4L3++ifTAbTFeMHyq2wRV+42DGmQcHIrJIvdcacjIOxEuKH/w4tthF17gg==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -2347,10 +1945,6 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - react-dom@19.1.1: resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} peerDependencies: @@ -2380,18 +1974,6 @@ packages: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - reflect.getprototypeof@1.0.10: resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} engines: {node: '>= 0.4'} @@ -2400,15 +1982,6 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - release-please@17.1.1: - resolution: {integrity: sha512-baFGx79P5hGxbtDqf8p+ThwQjHT/byst6EtnCkjfA6FcK5UnaERn6TBI+8wSsaVIohPG2urYjZaPkITxDS3/Pw==} - engines: {node: '>=18.0.0'} - hasBin: true - - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2421,10 +1994,6 @@ packages: engines: {node: '>= 0.4'} hasBin: true - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -2452,10 +2021,6 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2515,33 +2080,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.21: - resolution: {integrity: sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==} - - split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - string.prototype.trim@1.2.10: resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} @@ -2554,18 +2096,10 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2602,17 +2136,10 @@ packages: resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} engines: {node: '>=18'} - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -2639,22 +2166,6 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - type-fest@3.13.1: - resolution: {integrity: sha512-tLq3bSNx+xSpwvAJnzrK0Ep5CLNWjvFTOp71URMaAEWBfRb9nnJiBoUe0tF8bI4ZFO3omgBR6NvnbzVUT3Ly4g==} - engines: {node: '>=14.16'} - typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -2671,21 +2182,11 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - typescript@5.9.2: resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -2696,18 +2197,6 @@ packages: undici-types@7.10.0: resolution: {integrity: sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==} - unist-util-is@4.1.0: - resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==} - - unist-util-visit-parents@3.1.1: - resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==} - - unist-util-visit@2.0.3: - resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==} - - universal-user-agent@6.0.1: - resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==} - unplugin@2.1.0: resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==} engines: {node: '>=18.12.0'} @@ -2721,9 +2210,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} @@ -2752,55 +2238,13 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - xpath@0.0.34: - resolution: {integrity: sha512-FxF6+rkr1rNSQrhUNYrAFJpRXNzlDoMxeXN5qI84939ylEv3qqPFKa85Oxr6tDaJKqwW6KKyo2v26TSv3k6LeA==} - engines: {node: '>=0.6.0'} - - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yallist@5.0.0: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} - engines: {node: '>= 14.6'} - hasBin: true - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2925,11 +2369,6 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 - '@conventional-commits/parser@0.4.1': - dependencies: - unist-util-visit: 2.0.3 - unist-util-visit-parents: 3.1.1 - '@emnapi/runtime@1.4.5': dependencies: tslib: 2.8.1 @@ -3057,12 +2496,6 @@ snapshots: '@eslint/core': 0.15.1 levn: 0.4.1 - '@google-automations/git-file-utils@3.0.0': - dependencies: - '@octokit/rest': 20.1.2 - '@octokit/types': 13.10.0 - minimatch: 5.1.6 - '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -3076,8 +2509,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@iarna/toml@3.0.0': {} - '@img/sharp-darwin-arm64@0.34.3': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.2.0 @@ -3182,14 +2613,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.4 - '@jsep-plugin/assignment@1.3.0(jsep@1.4.0)': - dependencies: - jsep: 1.4.0 - - '@jsep-plugin/regex@1.0.4(jsep@1.4.0)': - dependencies: - jsep: 1.4.0 - '@next/env@15.4.5': {} '@next/swc-darwin-arm64@15.4.5': @@ -3228,69 +2651,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 - '@octokit/auth-token@4.0.0': {} - - '@octokit/core@5.2.2': - dependencies: - '@octokit/auth-token': 4.0.0 - '@octokit/graphql': 7.1.1 - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - before-after-hook: 2.2.3 - universal-user-agent: 6.0.1 - - '@octokit/endpoint@9.0.6': - dependencies: - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/graphql@7.1.1': - dependencies: - '@octokit/request': 8.4.1 - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/openapi-types@24.2.0': {} - - '@octokit/plugin-paginate-rest@11.4.4-cjs.2(@octokit/core@5.2.2)': - dependencies: - '@octokit/core': 5.2.2 - '@octokit/types': 13.10.0 - - '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.2)': - dependencies: - '@octokit/core': 5.2.2 - - '@octokit/plugin-rest-endpoint-methods@13.3.2-cjs.1(@octokit/core@5.2.2)': - dependencies: - '@octokit/core': 5.2.2 - '@octokit/types': 13.10.0 - - '@octokit/request-error@5.1.1': - dependencies: - '@octokit/types': 13.10.0 - deprecation: 2.3.1 - once: 1.4.0 - - '@octokit/request@8.4.1': - dependencies: - '@octokit/endpoint': 9.0.6 - '@octokit/request-error': 5.1.1 - '@octokit/types': 13.10.0 - universal-user-agent: 6.0.1 - - '@octokit/rest@20.1.2': - dependencies: - '@octokit/core': 5.2.2 - '@octokit/plugin-paginate-rest': 11.4.4-cjs.2(@octokit/core@5.2.2) - '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.2) - '@octokit/plugin-rest-endpoint-methods': 13.3.2-cjs.1(@octokit/core@5.2.2) - - '@octokit/types@13.10.0': - dependencies: - '@octokit/openapi-types': 24.2.0 - '@pivanov/utils@0.0.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': dependencies: react: 19.1.1 @@ -3498,8 +2858,6 @@ snapshots: '@types/json5@0.0.29': {} - '@types/minimist@1.2.5': {} - '@types/node@20.19.9': dependencies: undici-types: 6.21.0 @@ -3508,10 +2866,6 @@ snapshots: dependencies: undici-types: 7.10.0 - '@types/normalize-package-data@2.4.4': {} - - '@types/npm-package-arg@6.1.4': {} - '@types/react-dom@19.1.7(@types/react@19.1.9)': dependencies: '@types/react': 19.1.9 @@ -3524,14 +2878,6 @@ snapshots: dependencies: csstype: 3.1.3 - '@types/unist@2.0.11': {} - - '@types/yargs-parser@21.0.3': {} - - '@types/yargs@16.0.9': - dependencies: - '@types/yargs-parser': 21.0.3 - '@typescript-eslint/parser@8.39.0(eslint@9.32.0(jiti@2.5.1))(typescript@5.9.2)': dependencies: '@typescript-eslint/scope-manager': 8.39.0 @@ -3601,16 +2947,12 @@ snapshots: next: 15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: 19.1.1 - '@xmldom/xmldom@0.8.10': {} - acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 acorn@8.15.0: {} - agent-base@7.1.4: {} - ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -3618,8 +2960,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ansi-regex@5.0.1: {} - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -3631,8 +2971,6 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 - array-ify@1.0.0: {} - array-includes@3.1.9: dependencies: call-bind: 1.0.8 @@ -3678,22 +3016,14 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 - arrify@1.0.1: {} - async-function@1.0.0: {} - async-retry@1.3.3: - dependencies: - retry: 0.13.1 - available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.1.0 balanced-match@1.0.2: {} - before-after-hook@2.2.3: {} - bippy@0.3.17(@types/react@19.1.9)(react@19.1.1): dependencies: '@types/react-reconciler': 0.28.9(@types/react@19.1.9) @@ -3701,8 +3031,6 @@ snapshots: transitivePeerDependencies: - '@types/react' - boolbase@1.0.0: {} - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3719,7 +3047,7 @@ snapshots: browserslist@4.25.1: dependencies: caniuse-lite: 1.0.30001731 - electron-to-chromium: 1.5.195 + electron-to-chromium: 1.5.196 node-releases: 2.0.19 update-browserslist-db: 1.1.3(browserslist@4.25.1) @@ -3742,14 +3070,6 @@ snapshots: callsites@3.1.0: {} - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - - camelcase@5.3.1: {} - caniuse-lite@1.0.30001731: {} chalk@4.1.2: @@ -3761,30 +3081,8 @@ snapshots: client-only@0.0.1: {} - cliui@7.0.4: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - clsx@2.1.1: {} - code-suggester@5.0.0: - dependencies: - '@octokit/rest': 20.1.2 - '@types/yargs': 16.0.9 - async-retry: 1.3.3 - diff: 5.2.0 - glob: 7.2.3 - parse-diff: 0.11.1 - yargs: 16.2.0 - color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -3803,32 +3101,8 @@ snapshots: color-string: 1.9.1 optional: true - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 - concat-map@0.0.1: {} - conventional-changelog-conventionalcommits@6.1.0: - dependencies: - compare-func: 2.0.0 - - conventional-changelog-writer@6.0.1: - dependencies: - conventional-commits-filter: 3.0.0 - dateformat: 3.0.3 - handlebars: 4.7.8 - json-stringify-safe: 5.0.1 - meow: 8.1.2 - semver: 7.7.2 - split: 1.0.1 - - conventional-commits-filter@3.0.0: - dependencies: - lodash.ismatch: 4.4.0 - modify-values: 1.0.1 - convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -3837,16 +3111,6 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-select@5.2.2: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 - - css-what@6.2.2: {} - csstype@3.1.3: {} data-view-buffer@1.0.2: @@ -3867,8 +3131,6 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - dateformat@3.0.3: {} - debug@3.2.7: dependencies: ms: 2.1.3 @@ -3877,13 +3139,6 @@ snapshots: dependencies: ms: 2.1.3 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - deep-is@0.1.4: {} define-data-property@1.1.4: @@ -3898,63 +3153,25 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - deprecation@2.3.1: {} - - detect-indent@6.1.0: {} - detect-libc@2.0.4: {} - diff@5.2.0: {} - - diff@7.0.0: {} - doctrine@2.1.0: dependencies: esutils: 2.0.3 - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.195: {} - - emoji-regex@8.0.0: {} + electron-to-chromium@1.5.196: {} enhanced-resolve@5.18.2: dependencies: graceful-fs: 4.2.11 tapable: 2.2.2 - entities@4.5.0: {} - - error-ex@1.3.2: - dependencies: - is-arrayish: 0.2.1 - es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -4068,8 +3285,6 @@ snapshots: escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@4.0.0: {} eslint-compat-utils@0.5.1(eslint@9.32.0(jiti@2.5.1)): @@ -4236,10 +3451,6 @@ snapshots: dependencies: reusify: 1.1.0 - figures@3.2.0: - dependencies: - escape-string-regexp: 1.0.5 - file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -4248,11 +3459,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - find-up@4.1.0: - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -4278,8 +3484,6 @@ snapshots: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - fs.realpath@1.0.0: {} - fsevents@2.3.2: optional: true @@ -4301,8 +3505,6 @@ snapshots: gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4339,15 +3541,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - globals@14.0.0: {} globals@15.15.0: {} @@ -4363,17 +3556,6 @@ snapshots: graceful-fs@4.2.11: {} - handlebars@4.7.8: - dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 - - hard-rejection@2.1.0: {} - has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -4396,28 +3578,6 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - - hosted-git-info@2.8.9: {} - - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 - - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.1 - transitivePeerDependencies: - - supports-color - ignore@5.3.2: {} import-fresh@3.3.1: @@ -4427,15 +3587,6 @@ snapshots: imurmurhash@0.1.4: {} - indent-string@4.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -4448,8 +3599,6 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 - is-arrayish@0.2.1: {} - is-arrayish@0.3.2: optional: true @@ -4493,8 +3642,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.1.0: dependencies: call-bound: 1.0.4 @@ -4517,10 +3664,6 @@ snapshots: is-number@7.0.0: {} - is-obj@2.0.0: {} - - is-plain-obj@1.1.0: {} - is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -4572,38 +3715,24 @@ snapshots: dependencies: argparse: 2.0.1 - jsep@1.4.0: {} - jsesc@3.1.0: {} json-buffer@3.0.1: {} - json-parse-even-better-errors@2.3.1: {} - json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} - json-stringify-safe@5.0.1: {} - json5@1.0.2: dependencies: minimist: 1.2.8 json5@2.2.3: {} - jsonpath-plus@10.3.0: - dependencies: - '@jsep-plugin/assignment': 1.3.0(jsep@1.4.0) - '@jsep-plugin/regex': 1.0.4(jsep@1.4.0) - jsep: 1.4.0 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 - kind-of@6.0.3: {} - kleur@4.1.5: {} levn@0.4.1: @@ -4656,18 +3785,10 @@ snapshots: lightningcss-win32-arm64-msvc: 1.30.1 lightningcss-win32-x64-msvc: 1.30.1 - lines-and-columns@1.2.4: {} - - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash.ismatch@4.4.0: {} - lodash.merge@4.6.2: {} lru-cache@11.1.0: {} @@ -4676,34 +3797,12 @@ snapshots: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - magic-string@0.30.17: dependencies: '@jridgewell/sourcemap-codec': 1.5.4 - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - math-intrinsics@1.1.0: {} - meow@8.1.2: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - merge2@1.4.1: {} micromatch@4.0.8: @@ -4711,26 +3810,14 @@ snapshots: braces: 3.0.3 picomatch: 2.3.1 - min-indent@1.0.1: {} - minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 - minimatch@5.1.6: - dependencies: - brace-expansion: 2.0.2 - minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - minimist@1.2.8: {} minipass@7.1.2: {} @@ -4741,8 +3828,6 @@ snapshots: mkdirp@3.0.1: {} - modify-values@1.0.1: {} - motion-dom@12.23.12: dependencies: motion-utils: 12.23.6 @@ -4765,8 +3850,6 @@ snapshots: natural-compare@1.4.0: {} - neo-async@2.6.2: {} - next@15.4.5(@babel/core@7.28.0)(@playwright/test@1.54.2)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 15.4.5 @@ -4791,31 +3874,8 @@ snapshots: - '@babel/core' - babel-plugin-macros - node-html-parser@6.1.13: - dependencies: - css-select: 5.2.2 - he: 1.2.0 - node-releases@2.0.19: {} - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.10 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 - - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.2 - validate-npm-package-license: 3.0.4 - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - object-inspect@1.13.4: {} object-keys@1.1.1: {} @@ -4849,10 +3909,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - once@1.4.0: - dependencies: - wrappy: 1.0.2 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -4868,43 +3924,20 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - p-locate@5.0.0: dependencies: p-limit: 3.1.0 - p-try@2.2.0: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-diff@0.11.1: {} - - parse-github-repo-url@1.4.1: {} - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.27.1 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - path-exists@4.0.0: {} - path-is-absolute@1.0.1: {} - path-key@3.1.1: {} path-parse@1.0.7: {} @@ -4947,8 +3980,6 @@ snapshots: queue-microtask@1.2.3: {} - quick-lru@4.0.1: {} - react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 @@ -4985,24 +4016,6 @@ snapshots: react@19.1.1: {} - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - reflect.getprototypeof@1.0.10: dependencies: call-bind: 1.0.8 @@ -5023,44 +4036,6 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 - release-please@17.1.1: - dependencies: - '@conventional-commits/parser': 0.4.1 - '@google-automations/git-file-utils': 3.0.0 - '@iarna/toml': 3.0.0 - '@octokit/graphql': 7.1.1 - '@octokit/request': 8.4.1 - '@octokit/request-error': 5.1.1 - '@octokit/rest': 20.1.2 - '@types/npm-package-arg': 6.1.4 - '@xmldom/xmldom': 0.8.10 - chalk: 4.1.2 - code-suggester: 5.0.0 - conventional-changelog-conventionalcommits: 6.1.0 - conventional-changelog-writer: 6.0.1 - conventional-commits-filter: 3.0.0 - detect-indent: 6.1.0 - diff: 7.0.0 - figures: 3.2.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - js-yaml: 4.1.0 - jsonpath-plus: 10.3.0 - node-html-parser: 6.1.13 - parse-github-repo-url: 1.4.1 - semver: 7.7.2 - type-fest: 3.13.1 - typescript: 4.9.5 - unist-util-visit: 2.0.3 - unist-util-visit-parents: 3.1.1 - xpath: 0.0.34 - yaml: 2.8.1 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color - - require-directory@2.1.1: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -5071,8 +4046,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - retry@0.13.1: {} - reusify@1.1.0: {} rollup@4.46.2: @@ -5127,8 +4100,6 @@ snapshots: scheduler@0.26.0: {} - semver@5.7.2: {} - semver@6.3.1: {} semver@7.7.2: {} @@ -5228,37 +4199,11 @@ snapshots: source-map-js@1.2.1: {} - source-map@0.6.1: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.21 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.21 - - spdx-license-ids@3.0.21: {} - - split@1.0.1: - dependencies: - through: 2.3.8 - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 internal-slot: 1.1.0 - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - string.prototype.trim@1.2.10: dependencies: call-bind: 1.0.8 @@ -5282,16 +4227,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - strip-bom@3.0.0: {} - strip-indent@3.0.0: - dependencies: - min-indent: 1.0.1 - strip-json-comments@3.1.1: {} styled-jsx@5.1.6(@babel/core@7.28.0)(react@19.1.1): @@ -5320,14 +4257,10 @@ snapshots: mkdirp: 3.0.1 yallist: 5.0.0 - through@2.3.8: {} - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - trim-newlines@3.0.1: {} - ts-api-utils@2.1.0(typescript@5.9.2): dependencies: typescript: 5.9.2 @@ -5357,14 +4290,6 @@ snapshots: dependencies: prelude-ls: 1.2.1 - type-fest@0.18.1: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - type-fest@3.13.1: {} - typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -5398,13 +4323,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript@4.9.5: {} - typescript@5.9.2: {} - uglify-js@3.19.3: - optional: true - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -5416,21 +4336,6 @@ snapshots: undici-types@7.10.0: {} - unist-util-is@4.1.0: {} - - unist-util-visit-parents@3.1.1: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 4.1.0 - - unist-util-visit@2.0.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 4.1.0 - unist-util-visit-parents: 3.1.1 - - universal-user-agent@6.0.1: {} - unplugin@2.1.0: dependencies: acorn: 8.15.0 @@ -5447,11 +4352,6 @@ snapshots: dependencies: punycode: 2.3.1 - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - webpack-virtual-modules@0.6.2: optional: true @@ -5502,50 +4402,8 @@ snapshots: word-wrap@1.2.5: {} - wordwrap@1.0.0: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrappy@1.0.2: {} - - xpath@0.0.34: {} - - y18n@5.0.8: {} - yallist@3.1.1: {} - yallist@4.0.0: {} - yallist@5.0.0: {} - yaml@2.8.1: {} - - yargs-parser@20.2.9: {} - - yargs-parser@21.1.1: {} - - yargs@16.2.0: - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 885e570..be4cf04 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,11 +3,11 @@ packages: - examples/* onlyBuiltDependencies: - - "@tailwindcss/oxide" + - '@tailwindcss/oxide' - sharp strictPeerDependencies: true publicHoistPattern: - - "*eslint*" - - "*typescript*" + - '*eslint*' + - '*typescript*' diff --git a/scripts/install-local-tarball.js b/scripts/install-local-tarball.js index 1c52f39..5f53f60 100644 --- a/scripts/install-local-tarball.js +++ b/scripts/install-local-tarball.js @@ -7,11 +7,7 @@ const pkg = readdirSync(dist) .filter((f) => f.endsWith('.tgz')) .sort((a, b) => statSync(join(dist, b)).mtimeMs - statSync(join(dist, a)).mtimeMs)[0]; -const fixtures = [ - 'packages/core/__tests__/fixtures/next', - 'packages/core/__tests__/fixtures/vite', - 'packages/eslint-plugin-react-zero-ui/__tests__/fixtures/next', -]; +const fixtures = ['packages/core/__tests__/fixtures/next', 'packages/core/__tests__/fixtures/vite', 'packages/eslint-zero-ui/__tests__/fixtures/next']; for (const dir of fixtures) { const pkgJson = join(dir, 'package.json'); From 9181bf99861b9af5e39723bfb6a597de2c3ab7e3 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Tue, 5 Aug 2025 23:25:40 -0700 Subject: [PATCH 08/11] v0.3.2 --- README.md | 13 +++++++------ packages/core/package.json | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e870000..318a619 100644 --- a/README.md +++ b/README.md @@ -69,8 +69,7 @@ React Zero-UI uses a hyper-optimized AST resolver in development that scans your npx create-zero-ui ``` -> For manual configuration, see [Next JS Installation](/docs/installation-next.md) -> [Vite Installation](/docs/installation-vite.md) +> For manual configuration, see [Next JS Installation](/docs/installation-next.md) | [Vite Installation](/docs/installation-vite.md) **That's it.** Start your app and see the magic. @@ -137,11 +136,13 @@ const [theme, setTheme] = useScopedUI("theme", "dark"); Sometimes CSS variables are more efficient. React Zero-UI makes it trivial by passing the `CssVar` option: -```tsx -useUI(, , CssVar); // ❗️Pass CssVar to either hook to use CSS variables -``` +```diff ++ Pass `CssVar` to either hook to use CSS variables -automatically adds `--` to the cssVariable +useUI(, , CssVar); + +``` +automatically adds `--` to the Css Variable **Global CSS Variable:** diff --git a/packages/core/package.json b/packages/core/package.json index 2a2a217..cffb848 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@react-zero-ui/core", - "version": "0.3.1-beta.2", + "version": "0.3.2", "private": false, "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.", "keywords": [ @@ -118,4 +118,4 @@ "engines": { "node": ">=18.0.0" } -} +} \ No newline at end of file From 9706f7424fa10f0121e5ec3bd7cee3ffb8c9c32f Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Wed, 6 Aug 2025 07:01:28 -0700 Subject: [PATCH 09/11] convert init.ts to ESM --- packages/cli/package.json | 6 +-- packages/cli/tests/test-cli.js | 80 ++++++++++++++++++++++++++++++++++ packages/core/package.json | 3 +- packages/core/src/cli/init.ts | 9 ++-- 4 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 packages/cli/tests/test-cli.js diff --git a/packages/cli/package.json b/packages/cli/package.json index dd15ff5..f71fe4f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "create-zero-ui", - "version": "1.1.0", + "version": "1.1.1", "description": "Zero-UI project scaffolder for React. Instantly sets up zero-runtime UI state, Tailwind variants, PostCSS, and SSR-safe config in Vite or Next.js apps.", "keywords": [ "react", @@ -43,6 +43,6 @@ "test": "node --test ../core/__tests__/unit/cli.test.cjs" }, "dependencies": { - "@react-zero-ui/core": "^0.3.1" + "@react-zero-ui/core": "^0.3.3" } -} +} \ No newline at end of file diff --git a/packages/cli/tests/test-cli.js b/packages/cli/tests/test-cli.js new file mode 100644 index 0000000..2790e3d --- /dev/null +++ b/packages/cli/tests/test-cli.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node +import { execSync } from 'node:child_process'; +import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +const TARBALL = '../create-zero-ui-1.1.0.tgz'; + +console.log('🧪 Testing CLI package with npx (no global install)...\n'); + +// Create temp test directory +const testDir = mkdtempSync(join(tmpdir(), 'test-zero-ui-')); +const tarballPath = join(process.cwd(), TARBALL); + +try { + console.log('🚀 Testing CLI execution with npx...'); + process.chdir(testDir); + + // Run the CLI using npx (completely isolated, no global install) + try { + execSync(`npx ${tarballPath} .`, { stdio: 'pipe', timeout: 30000 }); + console.log('✅ CLI executed without errors'); + } catch (error) { + console.log('⚠️ CLI execution had issues:', error.message); + // Check if basic setup still worked despite the known core CLI issue + } + + // Validate results + console.log('\n🔍 Validating installation results...'); + + const checks = [ + { name: 'package.json created', test: () => existsSync('package.json') }, + { + name: '@react-zero-ui/core installed', + test: () => { + if (!existsSync('package.json')) return false; + const pkg = JSON.parse(readFileSync('package.json', 'utf-8')); + return pkg.dependencies && '@react-zero-ui/core' in pkg.dependencies; + }, + }, + { + name: '@tailwindcss/postcss dev dependency', + test: () => { + if (!existsSync('package.json')) return false; + const pkg = JSON.parse(readFileSync('package.json', 'utf-8')); + return pkg.devDependencies && '@tailwindcss/postcss' in pkg.devDependencies; + }, + }, + { name: 'node_modules created', test: () => existsSync('node_modules') }, + ]; + + let allPassed = true; + for (const check of checks) { + const passed = check.test(); + console.log(`${passed ? '✅' : '❌'} ${check.name}`); + if (!passed) allPassed = false; + } + + console.log('\n📊 Test Summary:'); + if (allPassed) { + console.log('🎉 All checks passed! CLI package is ready to publish.'); + process.exit(0); + } else { + console.log('❌ Some checks failed. Review before publishing.'); + process.exit(1); + } +} catch (error) { + console.error('💥 Test failed:', error.message); + process.exit(1); +} finally { + // Cleanup temp directory only + console.log('\n🧹 Cleaning up...'); + try { + process.chdir(process.cwd()); + rmSync(testDir, { recursive: true, force: true }); + console.log('✅ Cleanup complete'); + } catch (e) { + console.log('⚠️ Cleanup had issues:', e.message); + } +} diff --git a/packages/core/package.json b/packages/core/package.json index cffb848..11dcdd7 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@react-zero-ui/core", - "version": "0.3.2", + "version": "0.3.3", "private": false, "description": "Ultra-fast React UI state library with zero runtime, zero re-renders, Tailwind variant support, and automatic data-attribute/css-vars based styling. Replace context, prop drilling, and global stores with build-time magic.", "keywords": [ @@ -59,7 +59,6 @@ }, "./cli": { "types": "./dist/cli/init.d.ts", - "require": "./dist/cli/init.js", "import": "./dist/cli/init.js" }, "./experimental": { diff --git a/packages/core/src/cli/init.ts b/packages/core/src/cli/init.ts index a26bf9a..e1aa1fb 100755 --- a/packages/core/src/cli/init.ts +++ b/packages/core/src/cli/init.ts @@ -10,13 +10,14 @@ async function cli() { return await runZeroUiInit(); } -/* -------- CL I -------- */ -if (require.main === module) { +/* -------- CLI -------- */ +// ES module equivalent of require.main === module +if (import.meta.url === `file://${process.argv[1]}`) { cli().catch((error) => { console.error('CLI failed:', error); process.exit(1); }); } -/* -------- CJS -------- */ -module.exports = cli; // `require('@…/cli')()` +/* -------- ES Module Export -------- */ +export default cli; From 18dd37cd4f37c9ce79d514fe8a23d4ea0095e8f6 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Wed, 6 Aug 2025 07:59:43 -0700 Subject: [PATCH 10/11] update cli --- README.md | 6 +- docs/installation-next.md | 11 +- docs/installation-vite.md | 7 + packages/cli/README.md | 12 - packages/cli/package.json | 2 +- packages/cli/tests/test-cli.js | 435 +++++++++++++++--- .../fixtures/vite/.zero-ui/attributes.d.ts | 18 - .../fixtures/vite/.zero-ui/attributes.js | 24 - .../__tests__/fixtures/vite/src/index.css | 0 .../core/__tests__/fixtures/vite/src/main.tsx | 1 - .../__tests__/fixtures/vite/vite.config.ts | 3 +- packages/core/src/postcss/ast-generating.ts | 4 +- pnpm-lock.yaml | 14 +- scripts/install-local-tarball.js | 2 +- 14 files changed, 399 insertions(+), 140 deletions(-) delete mode 100644 packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts delete mode 100644 packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js delete mode 100644 packages/core/__tests__/fixtures/vite/src/index.css diff --git a/README.md b/README.md index 318a619..fcc1eec 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Tagline](https://img.shields.io/badge/The_ZERO_re--render_UI_state_library-%235500AD?style=flat&label=) -

+

Frame 342 @@ -63,7 +63,9 @@ React Zero-UI uses a hyper-optimized AST resolver in development that scans your Zero-UI CLI -**Pre-requisites:** Vite or Next.js (App Router) +**Pre-requisites:** +* Vite or Next.js (App Router) +* Tailwind V4 Configured. See [Tailwind V4 Installation](https://tailwindcss.com/docs/installation/using-vite) ```bash npx create-zero-ui diff --git a/docs/installation-next.md b/docs/installation-next.md index 916fe7f..38ee391 100644 --- a/docs/installation-next.md +++ b/docs/installation-next.md @@ -31,9 +31,16 @@ module.exports = { }; ``` +3. **Import Tailwind CSS** + +```css +// global.css +@import "tailwindcss"; +``` + --- -3. **Start the App** +4. **Start the App** ```bash npm run dev @@ -43,7 +50,7 @@ npm run dev --- -4. **Preventing FOUC (Flash Of Unstyled Content)** +5. **Preventing FOUC (Flash Of Unstyled Content)** Spread `bodyAttributes` on `` in your root layout. diff --git a/docs/installation-vite.md b/docs/installation-vite.md index 0a2e6ee..21cda94 100644 --- a/docs/installation-vite.md +++ b/docs/installation-vite.md @@ -31,6 +31,13 @@ export default defineConfig({ }); ``` +3. **Import Tailwind CSS** + +```css +// global.css +@import "tailwindcss"; +``` + **Thats it.** The plugin will add the data-\* attributes to the body tag (no FOUC) and the CSS will be injected and transformed by tailwind. diff --git a/packages/cli/README.md b/packages/cli/README.md index 399bf00..f7438f2 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -91,15 +91,3 @@ PRs welcome at [react-zero-ui/core](https://github.com/react-zero-ui/core). ## License MIT - - ---- - -### ✅ This README: -- Is **npm-ready** and GitHub-friendly. -- Makes **no assumptions** (Vite vs Next.js clearly separated). -- Avoids unnecessary branding/markup. -- Gives devs **trust** by showing exactly what gets modified. - -Let me know if you want to auto-generate or publish it with a `postinstall` script that shows a success message with the same info. -``` diff --git a/packages/cli/package.json b/packages/cli/package.json index f71fe4f..2f2d000 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "create-zero-ui", - "version": "1.1.1", + "version": "1.1.2", "description": "Zero-UI project scaffolder for React. Instantly sets up zero-runtime UI state, Tailwind variants, PostCSS, and SSR-safe config in Vite or Next.js apps.", "keywords": [ "react", diff --git a/packages/cli/tests/test-cli.js b/packages/cli/tests/test-cli.js index 2790e3d..4cc8020 100644 --- a/packages/cli/tests/test-cli.js +++ b/packages/cli/tests/test-cli.js @@ -1,80 +1,383 @@ -#!/usr/bin/env node -import { execSync } from 'node:child_process'; -import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs'; -import { join } from 'node:path'; -import { tmpdir } from 'node:os'; +import { test } from 'node:test'; +import assert from 'node:assert'; +import fs from 'node:fs'; +import path from 'node:path'; +import os from 'node:os'; +import { spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const binScript = path.resolve(__dirname, '../bin.js'); -const TARBALL = '../create-zero-ui-1.1.0.tgz'; +test('CLI script uses existing package.json if it exists', async () => { + const testDir = createTestDir(); -console.log('🧪 Testing CLI package with npx (no global install)...\n'); + try { + // Create a custom package.json + const customPackageJson = { name: 'my-test-app', version: '2.0.0', description: 'Custom test app' }; + + const packageJsonPath = path.join(testDir, 'package.json'); + fs.writeFileSync(packageJsonPath, JSON.stringify(customPackageJson, null, 2)); + + // Run CLI (this will timeout on npm install, but that's ok for this test) + await runCLIScript(testDir, 5000); -// Create temp test directory -const testDir = mkdtempSync(join(tmpdir(), 'test-zero-ui-')); -const tarballPath = join(process.cwd(), TARBALL); + // Check that our custom package.json is preserved + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + assert.strictEqual(packageJson.name, 'my-test-app', 'Custom name should be preserved'); + assert.strictEqual(packageJson.version, '2.0.0', 'Custom version should be preserved'); + assert.strictEqual(packageJson.description, 'Custom test app', 'Custom description should be preserved'); + + console.log('✅ Existing package.json preserved'); + } finally { + cleanupTestDir(testDir); + } +}); -try { - console.log('🚀 Testing CLI execution with npx...'); - process.chdir(testDir); +test('CLI script installs correct dependencies', async () => { + const testDir = createTestDir(); - // Run the CLI using npx (completely isolated, no global install) try { - execSync(`npx ${tarballPath} .`, { stdio: 'pipe', timeout: 30000 }); - console.log('✅ CLI executed without errors'); - } catch (error) { - console.log('⚠️ CLI execution had issues:', error.message); - // Check if basic setup still worked despite the known core CLI issue + // Create package.json to avoid npm init + const packageJson = { name: 'test-app', version: '1.0.0', scripts: {}, dependencies: {}, devDependencies: {} }; + fs.writeFileSync(path.join(testDir, 'package.json'), JSON.stringify(packageJson, null, 2)); + + // Mock npm by creating a fake npm script that logs what would be installed + const mockNpmScript = `#!/bin/bash + echo "Mock npm called with: $@" >> npm-calls.log + + # Simulate package installation by updating package.json + if [[ "$*" == *"install"* ]]; then + node -e " + import fs from 'fs'; + const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8')); + + // Handle production dependency + if (process.argv.slice(2).includes('@react-zero-ui/core') && !process.argv.slice(2).includes('--save-dev')) { + if (!pkg.dependencies) pkg.dependencies = {}; + pkg.dependencies['@react-zero-ui/core'] = '^1.0.0'; + } + + // Handle dev dependencies + if (process.argv.slice(2).includes('--save-dev')) { + if (!pkg.devDependencies) pkg.devDependencies = {}; + + pkg.devDependencies['@tailwindcss/postcss'] = '^4.1.8'; + } + + fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)); + " -- $@ + fi + `; + + const mockNpmPath = path.join(testDir, 'npm'); + fs.writeFileSync(mockNpmPath, mockNpmScript); + fs.chmodSync(mockNpmPath, '755'); + + // Update PATH to use our mock npm + const originalPath = process.env.PATH; + process.env.PATH = `${testDir}:${originalPath}`; + + try { + // Run CLI script + await runCLIScript(testDir, 10000).catch((err) => { + console.log('CLI run resulted in:', err.message); + return { error: err.message }; + }); + + // Check that npm was called + const npmCallsPath = path.join(testDir, 'npm-calls.log'); + if (fs.existsSync(npmCallsPath)) { + const npmCalls = fs.readFileSync(npmCallsPath, 'utf-8'); + console.log('NPM calls:', npmCalls); + + assert(npmCalls.includes('install'), 'npm install should be called'); + assert(npmCalls.includes('@react-zero-ui/core'), 'Should install react-zero-ui'); + assert(npmCalls.includes('@tailwindcss/postcss'), 'Should install @tailwindcss/postcss'); + } + + // Check package.json was updated with dependencies + const finalPackageJson = JSON.parse(fs.readFileSync(path.join(testDir, 'package.json'), 'utf-8')); + + // react-zero-ui should be in dependencies (production), not devDependencies + assert(finalPackageJson.dependencies['@react-zero-ui/core'], 'react-zero-ui should be in dependencies'); + assert(finalPackageJson.devDependencies['@tailwindcss/postcss'], '@tailwindcss/postcss should be in devDependencies'); + + console.log('✅ All required dependencies installed'); + } finally { + process.env.PATH = originalPath; + } + } finally { + cleanupTestDir(testDir); } +}); + +test('CLI script handles different target directories', async () => { + const baseTestDir = createTestDir(); + const subDir = path.join(baseTestDir, 'my-project'); + + try { + // Create subdirectory + fs.mkdirSync(subDir, { recursive: true }); + + await new Promise((resolve, reject) => { + const child = spawn('node', [binScript, 'my-project'], { cwd: baseTestDir, stdio: ['pipe', 'pipe', 'pipe'] }); + + const timer = setTimeout(() => { + child.kill('SIGKILL'); + resolve({ timedOut: true }); + }, 5000); + + child.on('close', (code) => { + clearTimeout(timer); + resolve({ code }); + }); + + child.on('error', (error) => { + clearTimeout(timer); + reject(error); + }); + }); + + // Check that package.json was created in subdirectory + const packageJsonPath = path.join(subDir, 'package.json'); + assert(fs.existsSync(packageJsonPath), 'package.json should be created in target subdirectory'); + + console.log('✅ CLI script works with target directories'); + } finally { + cleanupTestDir(baseTestDir); + } +}); + +// Additional test for library CLI functionality +test('Library CLI initializes project correctly', async () => { + const testDir = createTestDir(); + + try { + // Create a test React component with useUI hook + const componentDir = path.join(testDir, 'components'); + fs.mkdirSync(componentDir, { recursive: true }); + + const testComponent = ` +import { useUI } from '@react-zero-ui/core'; + +export function TestComponent() { + const [count, setCount] = useUI("0", "count"); + + return ( +

+ +
+ ); +} +`; + + fs.writeFileSync(path.join(componentDir, 'TestComponent.jsx'), testComponent); + + // Import and run the library CLI directly + + // Mock console to capture output + const originalConsoleLog = console.log; + const logMessages = []; + console.log = (...args) => { + logMessages.push(args.join(' ')); + originalConsoleLog(...args); + }; - // Validate results - console.log('\n🔍 Validating installation results...'); - - const checks = [ - { name: 'package.json created', test: () => existsSync('package.json') }, - { - name: '@react-zero-ui/core installed', - test: () => { - if (!existsSync('package.json')) return false; - const pkg = JSON.parse(readFileSync('package.json', 'utf-8')); - return pkg.dependencies && '@react-zero-ui/core' in pkg.dependencies; - }, - }, - { - name: '@tailwindcss/postcss dev dependency', - test: () => { - if (!existsSync('package.json')) return false; - const pkg = JSON.parse(readFileSync('package.json', 'utf-8')); - return pkg.devDependencies && '@tailwindcss/postcss' in pkg.devDependencies; - }, - }, - { name: 'node_modules created', test: () => existsSync('node_modules') }, - ]; - - let allPassed = true; - for (const check of checks) { - const passed = check.test(); - console.log(`${passed ? '✅' : '❌'} ${check.name}`); - if (!passed) allPassed = false; + // Change to test directory and run CLI + const originalCwd = process.cwd(); + process.chdir(testDir); + + try { + await runCLIScript(testDir); + + // Check that initialization messages were logged + console.log('✅ Library CLI initializes project correctly'); + } finally { + process.chdir(originalCwd); + console.log = originalConsoleLog; + } + } finally { + cleanupTestDir(testDir); } +}); + +// Test error handling in library CLI + +// Test CLI script with no arguments (current directory) +test('CLI script works with no target directory (defaults to current)', async () => { + const testDir = createTestDir(); + + try { + // Mock npm to avoid actual installation but make it create package.json + const mockNpmScript = `#!/bin/bash +echo "Mock npm completed" +# Simulate npm init -y creating package.json when it doesn't exist +if [[ "$*" == *"init"* ]] && [[ ! -f "package.json" ]]; then + echo '{"name": "test-project", "version": "1.0.0", "description": ""}' > package.json +fi +`; + const mockNpmPath = path.join(testDir, 'npm'); + fs.writeFileSync(mockNpmPath, mockNpmScript); + fs.chmodSync(mockNpmPath, '755'); + + const originalPath = process.env.PATH; + process.env.PATH = `${testDir}:${originalPath}`; + + try { + await new Promise((resolve, reject) => { + const child = spawn('node', [binScript], { + // No target directory argument + cwd: testDir, + stdio: ['pipe', 'pipe', 'pipe'], + }); + + const timer = setTimeout(() => { + child.kill('SIGKILL'); + resolve({ timedOut: true }); + }, 5000); - console.log('\n📊 Test Summary:'); - if (allPassed) { - console.log('🎉 All checks passed! CLI package is ready to publish.'); - process.exit(0); - } else { - console.log('❌ Some checks failed. Review before publishing.'); - process.exit(1); + child.on('close', (code) => { + clearTimeout(timer); + resolve({ code }); + }); + + child.on('error', (error) => { + clearTimeout(timer); + reject(error); + }); + }); + + // Check that package.json was created in current directory + const packageJsonPath = path.join(testDir, 'package.json'); + assert(fs.existsSync(packageJsonPath), 'package.json should be created in current directory'); + + console.log('✅ CLI script works with no target directory specified'); + } finally { + process.env.PATH = originalPath; + } + } finally { + cleanupTestDir(testDir); } -} catch (error) { - console.error('💥 Test failed:', error.message); - process.exit(1); -} finally { - // Cleanup temp directory only - console.log('\n🧹 Cleaning up...'); +}); + +// Test CLI script with invalid target directory +test('CLI script handles invalid target directory', async () => { + const testDir = createTestDir(); + try { - process.chdir(process.cwd()); - rmSync(testDir, { recursive: true, force: true }); - console.log('✅ Cleanup complete'); - } catch (e) { - console.log('⚠️ Cleanup had issues:', e.message); + const result = await new Promise((resolve) => { + const child = spawn('node', [binScript, 'non-existent-dir'], { cwd: testDir, stdio: ['pipe', 'pipe', 'pipe'] }); + + let stderr = ''; + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + const timer = setTimeout(() => { + child.kill('SIGKILL'); + resolve({ timedOut: true, stderr }); + }, 5000); + + child.on('close', (code) => { + clearTimeout(timer); + resolve({ code, stderr }); + }); + + child.on('error', (error) => { + clearTimeout(timer); + resolve({ error: error.message }); + }); + }); + + // The CLI should either create the directory or handle the error gracefully + assert(result.code !== undefined || result.error || result.timedOut, 'CLI should handle invalid directory gracefully'); + + console.log('✅ CLI script handles invalid target directory'); + } finally { + cleanupTestDir(testDir); + } +}); + +// Helper to create isolated test directory +function createTestDir() { + const testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'zero-ui-cli-test-')); + return testDir; +} + +// Helper to clean up test directory +function cleanupTestDir(testDir) { + if (fs.existsSync(testDir)) { + try { + fs.rmSync(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); + } catch (error) { + // On Windows/macOS, sometimes files are locked. Try alternative cleanup + console.warn(`Warning: Could not clean up test directory ${testDir}: ${error.message}`); + try { + // Try to remove files individually + const cleanup = (dir) => { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + cleanup(fullPath); + try { + fs.rmdirSync(fullPath); + } catch (e) { + // Ignore + console.log(`Error deleting directory ${fullPath}: ${e.message}`); + } + } else { + try { + fs.unlinkSync(fullPath); + } catch (e) { + // Ignore + console.log(`Error deleting file ${fullPath}: ${e.message}`); + } + } + } + }; + cleanup(testDir); + fs.rmdirSync(testDir); + } catch (secondError) { + console.warn(`Final cleanup attempt failed: ${secondError.message}`); + } + } } } + +// Helper to run CLI script and capture output +function runCLIScript(targetDir, timeout = 30000) { + return new Promise((resolve, reject) => { + // Updated path to the correct CLI script location + + const child = spawn('node', [binScript, '.'], { cwd: targetDir, stdio: ['pipe', 'pipe', 'pipe'] }); + + let stdout = ''; + let stderr = ''; + + child.stdout.on('data', (data) => { + stdout += data.toString(); + }); + + child.stderr.on('data', (data) => { + stderr += data.toString(); + }); + + const timer = setTimeout(() => { + child.kill('SIGKILL'); + reject(new Error(`CLI script timed out after ${timeout}ms`)); + }, timeout); + + child.on('close', (code) => { + clearTimeout(timer); + resolve({ code, stdout, stderr, success: code === 0 }); + }); + + child.on('error', (error) => { + clearTimeout(timer); + reject(error); + }); + }); +} diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts deleted file mode 100644 index 6da0d21..0000000 --- a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* AUTO-GENERATED - DO NOT EDIT */ -export declare const bodyAttributes: { - "data-child": "closed" | "open"; - "data-faq": "closed" | "open"; - "data-mobile": "false" | "true"; - "data-number": "1" | "2"; - "data-scope": "off" | "on"; - "data-theme": "dark" | "light"; - "data-theme-2": "dark" | "light"; - "data-theme-three": "dark" | "light"; - "data-toggle-boolean": "false" | "true"; - "data-toggle-function": "black" | "blue" | "green" | "red" | "white"; - "data-use-effect-theme": "dark" | "light"; -}; - -export declare const variantKeyMap: { - [key: string]: true; -}; diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js deleted file mode 100644 index 39b4b9c..0000000 --- a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js +++ /dev/null @@ -1,24 +0,0 @@ -/* AUTO-GENERATED - DO NOT EDIT */ -export const bodyAttributes = { - "data-child": "closed", - "data-number": "1", - "data-theme": "light", - "data-theme-2": "light", - "data-theme-three": "light", - "data-toggle-boolean": "true", - "data-toggle-function": "white", - "data-use-effect-theme": "light" -}; -export const variantKeyMap = { - "data-child": true, - "data-faq": true, - "data-mobile": true, - "data-number": true, - "data-scope": true, - "data-theme": true, - "data-theme-2": true, - "data-theme-three": true, - "data-toggle-boolean": true, - "data-toggle-function": true, - "data-use-effect-theme": true -}; diff --git a/packages/core/__tests__/fixtures/vite/src/index.css b/packages/core/__tests__/fixtures/vite/src/index.css deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/__tests__/fixtures/vite/src/main.tsx b/packages/core/__tests__/fixtures/vite/src/main.tsx index 98c2e6c..017e0ba 100644 --- a/packages/core/__tests__/fixtures/vite/src/main.tsx +++ b/packages/core/__tests__/fixtures/vite/src/main.tsx @@ -1,6 +1,5 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import './index.css'; import App from './App.tsx'; createRoot(document.getElementById('root')!).render( diff --git a/packages/core/__tests__/fixtures/vite/vite.config.ts b/packages/core/__tests__/fixtures/vite/vite.config.ts index 1d610da..4e5dabb 100644 --- a/packages/core/__tests__/fixtures/vite/vite.config.ts +++ b/packages/core/__tests__/fixtures/vite/vite.config.ts @@ -1,8 +1,7 @@ -import zeroUI from "@react-zero-ui/core/vite"; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vite.dev/config/ export default defineConfig({ - plugins: [zeroUI(), react()] + plugins: [react()], }); \ No newline at end of file diff --git a/packages/core/src/postcss/ast-generating.ts b/packages/core/src/postcss/ast-generating.ts index d41bfe3..06f3094 100644 --- a/packages/core/src/postcss/ast-generating.ts +++ b/packages/core/src/postcss/ast-generating.ts @@ -181,9 +181,9 @@ export function parseAndUpdateViteConfig(source: string, zeroUiImportPath: strin return modified ? generate(ast).code : null; } -/* ------------------------------------------------------------------ * +/* ------------ * * processConfigObject - mutate a { plugins: [...] } - * ------------------------------------------------------------------ */ + * ------------ */ function processConfigObject(obj: t.ObjectExpression): boolean { let touched = false; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bfa0cf..e89a19c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,8 +88,8 @@ importers: packages/cli: dependencies: '@react-zero-ui/core': - specifier: ^0.3.1 - version: 0.3.1(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11) + specifier: ^0.3.3 + version: 0.3.3(@tailwindcss/postcss@4.1.11)(react@19.1.1) packages/core: dependencies: @@ -689,14 +689,12 @@ packages: react: '>=16.8.0' tailwindcss: ^4.1.10 - '@react-zero-ui/core@0.3.1': - resolution: {integrity: sha512-h3A3GOxFyiJ62Ot6g5hAvfF+kEtSimzcCfW41147P2+HUzgdWsYI5ZjadIEwhXF2inBU7eFJcmnm3gSVdfbiOg==} + '@react-zero-ui/core@0.3.3': + resolution: {integrity: sha512-ePcck57gfZrspPf8OqFc8gFsjGkCeBEYGXwIxuSsKwg/Z/AENcIt2RoAsRjY0chywAY5h3Xdk3dOeqrb7jnlqg==} engines: {node: '>=18.0.0'} peerDependencies: '@tailwindcss/postcss': ^4.1.10 - postcss: ^8.5.5 react: '>=16.8.0' - tailwindcss: ^4.1.10 '@rollup/pluginutils@5.2.0': resolution: {integrity: sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==} @@ -2680,7 +2678,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@react-zero-ui/core@0.3.1(@tailwindcss/postcss@4.1.11)(postcss@8.5.6)(react@19.1.1)(tailwindcss@4.1.11)': + '@react-zero-ui/core@0.3.3(@tailwindcss/postcss@4.1.11)(react@19.1.1)': dependencies: '@babel/code-frame': 7.27.1 '@babel/generator': 7.28.0 @@ -2690,9 +2688,7 @@ snapshots: '@tailwindcss/postcss': 4.1.11 fast-glob: 3.3.3 lru-cache: 11.1.0 - postcss: 8.5.6 react: 19.1.1 - tailwindcss: 4.1.11 transitivePeerDependencies: - supports-color diff --git a/scripts/install-local-tarball.js b/scripts/install-local-tarball.js index 5f53f60..ecebb46 100644 --- a/scripts/install-local-tarball.js +++ b/scripts/install-local-tarball.js @@ -7,7 +7,7 @@ const pkg = readdirSync(dist) .filter((f) => f.endsWith('.tgz')) .sort((a, b) => statSync(join(dist, b)).mtimeMs - statSync(join(dist, a)).mtimeMs)[0]; -const fixtures = ['packages/core/__tests__/fixtures/next', 'packages/core/__tests__/fixtures/vite', 'packages/eslint-zero-ui/__tests__/fixtures/next']; +const fixtures = ['packages/core/__tests__/fixtures/next', 'packages/core/__tests__/fixtures/vite']; for (const dir of fixtures) { const pkgJson = join(dir, 'package.json'); From 9ffc138dcc51d088dd53ff1e974ad5c280b5f743 Mon Sep 17 00:00:00 2001 From: Austin1serb Date: Wed, 6 Aug 2025 08:09:29 -0700 Subject: [PATCH 11/11] fix test --- .../fixtures/vite/.zero-ui/attributes.d.ts | 18 +++++++++ .../fixtures/vite/.zero-ui/attributes.js | 24 ++++++++++++ .../__tests__/fixtures/vite/vite.config.ts | 3 +- packages/core/__tests__/helpers/loadCli.js | 38 +++++++++++-------- 4 files changed, 67 insertions(+), 16 deletions(-) create mode 100644 packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts create mode 100644 packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts new file mode 100644 index 0000000..6da0d21 --- /dev/null +++ b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.d.ts @@ -0,0 +1,18 @@ +/* AUTO-GENERATED - DO NOT EDIT */ +export declare const bodyAttributes: { + "data-child": "closed" | "open"; + "data-faq": "closed" | "open"; + "data-mobile": "false" | "true"; + "data-number": "1" | "2"; + "data-scope": "off" | "on"; + "data-theme": "dark" | "light"; + "data-theme-2": "dark" | "light"; + "data-theme-three": "dark" | "light"; + "data-toggle-boolean": "false" | "true"; + "data-toggle-function": "black" | "blue" | "green" | "red" | "white"; + "data-use-effect-theme": "dark" | "light"; +}; + +export declare const variantKeyMap: { + [key: string]: true; +}; diff --git a/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js new file mode 100644 index 0000000..39b4b9c --- /dev/null +++ b/packages/core/__tests__/fixtures/vite/.zero-ui/attributes.js @@ -0,0 +1,24 @@ +/* AUTO-GENERATED - DO NOT EDIT */ +export const bodyAttributes = { + "data-child": "closed", + "data-number": "1", + "data-theme": "light", + "data-theme-2": "light", + "data-theme-three": "light", + "data-toggle-boolean": "true", + "data-toggle-function": "white", + "data-use-effect-theme": "light" +}; +export const variantKeyMap = { + "data-child": true, + "data-faq": true, + "data-mobile": true, + "data-number": true, + "data-scope": true, + "data-theme": true, + "data-theme-2": true, + "data-theme-three": true, + "data-toggle-boolean": true, + "data-toggle-function": true, + "data-use-effect-theme": true +}; diff --git a/packages/core/__tests__/fixtures/vite/vite.config.ts b/packages/core/__tests__/fixtures/vite/vite.config.ts index 4e5dabb..1d610da 100644 --- a/packages/core/__tests__/fixtures/vite/vite.config.ts +++ b/packages/core/__tests__/fixtures/vite/vite.config.ts @@ -1,7 +1,8 @@ +import zeroUI from "@react-zero-ui/core/vite"; import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [zeroUI(), react()] }); \ No newline at end of file diff --git a/packages/core/__tests__/helpers/loadCli.js b/packages/core/__tests__/helpers/loadCli.js index cd66d3b..2dfb588 100644 --- a/packages/core/__tests__/helpers/loadCli.js +++ b/packages/core/__tests__/helpers/loadCli.js @@ -1,27 +1,35 @@ // __tests__/helpers/loadCli.js -import { createRequire } from 'node:module'; +import { dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { execSync } from 'node:child_process'; import path from 'node:path'; export async function loadCliFromFixture(fixtureDir) { - const r = createRequire(path.join(fixtureDir, 'package.json')); - const modulePath = r.resolve('../../../dist/cli/init.js'); // get the path - const mod = r(modulePath); // actually require the module - console.log('[Global Setup] Loaded CLI from fixture:', modulePath); - // Return a wrapper function that changes directory before running CLI - const wrappedCli = async (args = []) => { + // Get the directory of this helper file + const __dirname = dirname(fileURLToPath(import.meta.url)); + console.log('__dirname: ', __dirname); + + // Build the path to the CLI module - from helpers/ go up to core/ then to dist/ + const modulePath = path.resolve(__dirname, '../../dist/cli/init.js'); + console.log('[Global Setup] CLI path:', modulePath); + + // Return a wrapper function that runs CLI as separate process to avoid module loading issues + const wrappedCli = async () => { const originalCwd = process.cwd(); try { process.chdir(fixtureDir); // Change to fixture directory + console.log('[Global Setup] Changed to fixture directory:', fixtureDir); - // The init.js exports a cli function, so call it + // Run the CLI as a separate Node.js process to avoid module loading conflicts + const result = execSync(`node "${modulePath}"`, { stdio: 'pipe', encoding: 'utf-8', timeout: 30000 }); - if (typeof mod === 'function') { - return await Promise.resolve(mod(args)); // run the CLI - } else if (typeof mod.default === 'function') { - return await Promise.resolve(mod.default(args)); // run the CLI (ESM default export) - } else { - throw new Error('Could not find CLI function in init.js'); - } + console.log('[Global Setup] CLI executed successfully'); + return result; + } catch (error) { + console.error('[Global Setup] CLI execution failed:', error.message); + if (error.stdout) console.log('STDOUT:', error.stdout); + if (error.stderr) console.log('STDERR:', error.stderr); + throw error; } finally { process.chdir(originalCwd); // Always restore original directory console.log('[Global Setup] Restored original directory:', originalCwd);