From cc6e9aa698124075a71f7581766704c421396a41 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Mon, 8 Jun 2026 02:30:57 +0700 Subject: [PATCH 1/2] feat: eslint plugin --- CLAUDE.md | 1 + package.json | 6 + packages/eslint-plugin/.oxlintrc.json | 8 + packages/eslint-plugin/CHANGELOG.md | 10 + packages/eslint-plugin/CLAUDE.md | 20 + packages/eslint-plugin/LICENSE | 21 + packages/eslint-plugin/README.md | 70 ++ packages/eslint-plugin/knip.json | 13 + packages/eslint-plugin/package.json | 70 ++ packages/eslint-plugin/src/index.ts | 21 + .../insert-options-order.test.ts | 451 ++++++++++++ .../insert-options-order.ts | 86 +++ packages/eslint-plugin/src/ruleset.ts | 7 + packages/eslint-plugin/src/setup.ts | 9 + .../eslint-plugin/src/shared/constants.ts | 9 + packages/eslint-plugin/src/shared/create.ts | 3 + packages/eslint-plugin/src/shared/tag.ts | 12 + packages/eslint-plugin/tsconfig.json | 8 + packages/eslint-plugin/tsdown.config.ts | 17 + packages/eslint-plugin/vitest.config.ts | 10 + packages/react-slots/package.json | 6 + pnpm-lock.yaml | 695 ++++++++++++++++++ 22 files changed, 1553 insertions(+) create mode 100644 packages/eslint-plugin/.oxlintrc.json create mode 100644 packages/eslint-plugin/CHANGELOG.md create mode 100644 packages/eslint-plugin/CLAUDE.md create mode 100644 packages/eslint-plugin/LICENSE create mode 100644 packages/eslint-plugin/README.md create mode 100644 packages/eslint-plugin/knip.json create mode 100644 packages/eslint-plugin/package.json create mode 100644 packages/eslint-plugin/src/index.ts create mode 100644 packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.test.ts create mode 100644 packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.ts create mode 100644 packages/eslint-plugin/src/ruleset.ts create mode 100644 packages/eslint-plugin/src/setup.ts create mode 100644 packages/eslint-plugin/src/shared/constants.ts create mode 100644 packages/eslint-plugin/src/shared/create.ts create mode 100644 packages/eslint-plugin/src/shared/tag.ts create mode 100644 packages/eslint-plugin/tsconfig.json create mode 100644 packages/eslint-plugin/tsdown.config.ts create mode 100644 packages/eslint-plugin/vitest.config.ts diff --git a/CLAUDE.md b/CLAUDE.md index 6b58d64..a9d10b6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,6 +12,7 @@ pnpm workspace (`packages/*`), structured like app-compose: - **Root** — private `@grlt-hub/react-slots-dev`: shared toolchain in devDependencies (typescript, tsdown, vitest + @vitest/browser + @vitest/browser-playwright + playwright, knip, oxlint, oxfmt), base `tsconfig.json`, `.oxfmtrc.json`, scripts delegate via `pnpm -r`. - **`packages/react-slots`** — the published library: own runtime/peer deps plus package-specific devDeps (react, react-dom, happy-dom, @types/\*), own `tsconfig.json` (extends root), `tsdown.config.ts`, `vitest.config.ts`, `knip.json`, `.oxlintrc.json`, README, CHANGELOG, LICENSE (a copy of the root one — npm auto-includes LICENSE only from the package dir; CHANGELOG ships via `files`). `tsdown.config.ts` prepends a `"use client"` banner to both JS bundles (RSC boundary — a react-server build has no `useSyncExternalStore`, so hooks/Root would otherwise die deep in the shim; audit F6); the banner must NOT leak into the d.ts. +- **`packages/eslint-plugin`** — published `@grlt-hub/eslint-plugin-react-slots`, skeleton mirrors app-compose's eslint-plugin (`shared/{create,constants,tag}`, `setup.ts` RuleTester wiring, `ruleset.ts` recommended preset, `src/rules//.ts` + test side by side). One rule: `insert-options-order` (`filter -> mapProps -> Component -> order`, autofixable, `warn` in recommended). Identification is **type-aware** (insert is an api method, not an import — see the package CLAUDE.md for the gate design and its test caveats); tests need `packages/react-slots/dist` built first. devDep `@typescript-eslint/rule-tester` must stay version-aligned with the `@typescript-eslint/utils` resolution. ## Commands diff --git a/package.json b/package.json index 04e736a..a6873b5 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,12 @@ "homepage": "https://github.com/grlt-hub/react-slots", "license": "MIT", "author": "Viktor Pasynok", + "contributors": [ + { + "name": "Viktor Pasynok", + "url": "https://github.com/binjospookie" + } + ], "repository": { "type": "git", "url": "git+https://github.com/grlt-hub/react-slots.git" diff --git a/packages/eslint-plugin/.oxlintrc.json b/packages/eslint-plugin/.oxlintrc.json new file mode 100644 index 0000000..2aeaad1 --- /dev/null +++ b/packages/eslint-plugin/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "../../node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "typescript", "unicorn", "oxc"], + "rules": { + "typescript/consistent-type-imports": ["error", { "fixStyle": "inline-type-imports" }], + "typescript/no-import-type-side-effects": "error" + } +} diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md new file mode 100644 index 0000000..92a6d13 --- /dev/null +++ b/packages/eslint-plugin/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](http://semver.org). + +## Unreleased + +### Added + +- Initial release with the `insert-options-order` rule: options of `insert` must be ordered `filter -> mapProps -> Component -> order` (auto-fixable). diff --git a/packages/eslint-plugin/CLAUDE.md b/packages/eslint-plugin/CLAUDE.md new file mode 100644 index 0000000..1289921 --- /dev/null +++ b/packages/eslint-plugin/CLAUDE.md @@ -0,0 +1,20 @@ +# CLAUDE.md + +This file provides context for AI assistants working on the ESLint plugin (`@grlt-hub/eslint-plugin-react-slots`). It extends the root `CLAUDE.md`. + +## Rule conventions + +- One directory per rule: `src/rules//` holds `.ts` and `.test.ts` side by side (no `__tests__/` here). +- Rules are built with `createRule` from `@/shared/create` and match nodes via esquery selectors. Symbol and package names come from `UNITS` / `PACKAGE_NAME` in `@/shared/constants` — never hard-code them. +- `insert` is a method on the slot `api`, not a named import — the app-compose approach (tracking `ImportSpecifier` locals) cannot identify it, and the primary use case is cross-module (a plugin inserting into a slot imported from elsewhere), which no syntactic data-flow can follow. Identification is therefore **type-aware**: `services.getTypeAtLocation(callee).aliasSymbol` must be named `InsertWithProps` / `InsertWithoutProps` (the `payload.ts` aliases, preserved in the rolled-up d.ts) AND declared in a file whose path contains `/react-slots/`. This catches every alias by construction — member call, destructured `insert`, renamed destructuring, re-export — and rejects same-shape `insert`s from other libraries (pinned by the two "shape twin" valid cases; verified RED by removing the gate). +- The path check is `/react-slots/`, NOT `@grlt-hub/react-slots`: TS resolves pnpm workspace symlinks to realpaths (`packages/react-slots/dist/index.d.ts` in this repo's tests), so the scope is not reliably in the path. In consumer installs (npm flat, pnpm `.pnpm`) the path still contains `/react-slots/`. +- Check ordering in the handler is load-bearing for performance: all syntactic guards first (one object-literal argument, no spread/computed keys, key set ⊆ `{filter, mapProps, Component, order}` with `Component` required), then `isCorrectOrder` — and only for misordered shape-matching candidates the type gate. Clean files never touch the checker. +- The shape guard doubles as the false-positive firewall before the type gate and as fixer safety (unknown keys would sort wrong); both bail silently. +- The type gate means consumers MUST enable typed linting (`parserOptions.projectService`); without it `getParserServices` throws the standard typescript-eslint error. Same trade-off as app-compose's type-aware rules. +- Tests use `RuleTester` from `@typescript-eslint/rule-tester` with `projectService.allowDefaultProject` + `tsconfigRootDir: import.meta.dirname`; code samples resolve the real `@grlt-hub/react-slots` (workspace devDep), so its `dist/` must be built before tests run — CI builds first; a fresh clone running only `pnpm test` will fail in this package. +- Code samples are written with the `ts` template tag from `@/shared/tag`. Tag caveat: it dedents every line by the FIRST line's indent, so a multi-line string interpolated into a sample (the app-compose `${commonCode}` pattern) gets its inner lines mangled. Write each sample self-contained at one uniform indent. +- In `invalid` case outputs, the fixer emits properties at column 0 (`{\n,\n\n}`) — write expected output property lines at the template's base indent. Formatting the result is the consumer's formatter's job, same as app-compose. +- A new rule must be registered in both `src/index.ts` (`rules`) and `src/ruleset.ts` (`recommended`). +- Rule messages are user-facing docs copy: plain English; the same text appears in the package README — keep them in sync. +- Exception to "no default exports": rule modules and the plugin entry default-export, as the ESLint plugin contract expects. +- `@typescript-eslint/rule-tester` (exact-pinned devDep) must stay aligned with what `@typescript-eslint/utils` (caret dep) resolves to — a patch-level skew installs two copies of the types and `tsc --noEmit` fails on a `RuleModule` private-member mismatch. diff --git a/packages/eslint-plugin/LICENSE b/packages/eslint-plugin/LICENSE new file mode 100644 index 0000000..821129d --- /dev/null +++ b/packages/eslint-plugin/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 The grlt-hub Authors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md new file mode 100644 index 0000000..c37c52a --- /dev/null +++ b/packages/eslint-plugin/README.md @@ -0,0 +1,70 @@ +# @grlt-hub/eslint-plugin-react-slots + +ESLint plugin that enforces [React-Slots](https://github.com/grlt-hub/react-slots) conventions in TypeScript code. + +[![npm version](https://img.shields.io/npm/v/%40grlt-hub%2Feslint-plugin-react-slots?color=orange)](https://www.npmjs.com/package/@grlt-hub/eslint-plugin-react-slots) +![npm license](https://img.shields.io/npm/l/%40grlt-hub%2Feslint-plugin-react-slots?color=blue) +[![npm provenance](https://img.shields.io/badge/provenance-yes-brightgreen?logo=npm)](https://www.npmjs.com/package/@grlt-hub/eslint-plugin-react-slots) + +[React-Slots](https://github.com/grlt-hub/react-slots) + +## Installation + +```bash +npm install --save-dev --save-exact @grlt-hub/eslint-plugin-react-slots +``` + +Requires ESLint 9+, TypeScript 5+, and [typed linting](https://typescript-eslint.io/getting-started/typed-linting/) (`parserOptions.projectService`) — the rule identifies `insert` by its type, so it works through any alias: a member call, a destructured `insert`, or a slot imported from another module. + +## Usage + +Add the recommended preset to your flat config: + +```js +import reactSlots from "@grlt-hub/eslint-plugin-react-slots" +import tseslint from "typescript-eslint" + +export default tseslint.config(reactSlots.configs.recommended) +``` + +Or wire the plugin manually: + +```js +{ + plugins: { "react-slots": reactSlots }, + rules: { + "react-slots/insert-options-order": "warn", + }, +} +``` + +## Rules + +- ⚠️ — set to `warn` in the `recommended` config +- 🔧 — auto-fixable + +| Name | Description | ⚠️ | 🔧 | +| --------------------------------------------- | ---------------------------------- | --- | --- | +| [insert-options-order](#insert-options-order) | Enforce options order for `insert` | ⚠️ | 🔧 | + +### insert-options-order + +Options of `insert` must be ordered `filter -> mapProps -> Component -> order`. Missing options are fine — the rule only checks the relative order of the options that are present. + +```js +// ✗ wrong +slots.Header.insert({ + Component: (props) => , + mapProps: (slotProps) => ({ userName: getUserName(slotProps.userId) }), +}) + +// ✓ correct +slots.Header.insert({ + mapProps: (slotProps) => ({ userName: getUserName(slotProps.userId) }), + Component: (props) => , +}) +``` + +The order is load-bearing for types, not just style: with `mapProps` written above `filter`, TypeScript's type-predicate inference for `filter` does not fire — the call silently falls into the boolean overload and narrowing is lost without any error. + +The rule is type-aware: it only fires on `insert` that comes from a slot created via `createSlot` — an `insert` from any other library is left alone. diff --git a/packages/eslint-plugin/knip.json b/packages/eslint-plugin/knip.json new file mode 100644 index 0000000..8f5b144 --- /dev/null +++ b/packages/eslint-plugin/knip.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://unpkg.com/knip@6/schema.json", + + "ignore": ["*.config.ts"], + + "entry": ["./src/index.ts!"], + + "ignoreBinaries": ["tsdown", "knip", "oxlint", "vitest"], + + "ignoreDependencies": ["@grlt-hub/react-slots", "vitest"], + + "vitest": { "config": "vitest.config.ts", "entry": "src/**/*.test.ts" } +} diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json new file mode 100644 index 0000000..55ee7bb --- /dev/null +++ b/packages/eslint-plugin/package.json @@ -0,0 +1,70 @@ +{ + "name": "@grlt-hub/eslint-plugin-react-slots", + "version": "4.0.0-beta.0", + "private": false, + "description": "Enforcing best practices for @grlt-hub/react-slots", + "keywords": [ + "eslint", + "eslint-plugin", + "eslintplugin", + "grlt", + "grlt-hub", + "react-slots" + ], + "homepage": "https://github.com/grlt-hub/react-slots", + "license": "MIT", + "author": "Viktor Pasynok", + "contributors": [ + { + "name": "Viktor Pasynok", + "url": "https://github.com/binjospookie" + } + ], + "repository": { + "type": "git", + "url": "git+https://github.com/grlt-hub/react-slots.git" + }, + "files": [ + "dist" + ], + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.mjs", + "types": "./dist/index.d.cts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "build": "tsc --noEmit -p tsconfig.json && tsdown", + "dev": "tsdown --watch", + "prepack": "pnpm build", + "test": "vitest run", + "lint": "knip && oxlint ./src" + }, + "dependencies": { + "@typescript-eslint/utils": "^8.60.0" + }, + "devDependencies": { + "@grlt-hub/react-slots": "workspace:*", + "@typescript-eslint/rule-tester": "8.60.1", + "eslint": "10.4.1" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0", + "typescript": "^5.0.0 || ^6.0.0" + }, + "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" +} diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts new file mode 100644 index 0000000..1638061 --- /dev/null +++ b/packages/eslint-plugin/src/index.ts @@ -0,0 +1,21 @@ +import type { TSESLint } from "@typescript-eslint/utils" +import { name, version } from "../package.json" +import insertOptionsOrder from "./rules/insert-options-order/insert-options-order" +import { ruleset } from "./ruleset" + +const base = { + meta: { name, version, namespace: "react-slots" }, + rules: { + "insert-options-order": insertOptionsOrder, + }, +} + +const configs = { + recommended: { plugins: { "react-slots": base as TSESLint.FlatConfig.Plugin }, rules: ruleset.recommended }, +} + +const plugin = base as typeof base & { configs: typeof configs } + +plugin.configs = configs + +export default plugin diff --git a/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.test.ts b/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.test.ts new file mode 100644 index 0000000..af79504 --- /dev/null +++ b/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.test.ts @@ -0,0 +1,451 @@ +import { RuleTester } from "@typescript-eslint/rule-tester" +import { ts } from "@/shared/tag" +import rule from "./insert-options-order" + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + projectService: { + allowDefaultProject: ["*.ts*"], + }, + tsconfigRootDir: import.meta.dirname, + }, + }, +}) + +ruleTester.run("insert-options-order", rule, { + valid: [ + { + name: "full", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null, + order: 1, + }) + `, + }, + { + name: "without filter", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null, + order: 1, + }) + `, + }, + { + name: "component only", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + Component: () => null, + }) + `, + }, + { + name: "destructured insert", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + const { insert } = Header.api + + insert({ + Component: () => null, + order: 1, + }) + `, + }, + { + name: "misordered shape twin from another source", + code: ts` + declare const db: { insert: (options: { order: number; Component: () => null }) => void } + + db.insert({ + order: 1, + Component: () => null, + }) + `, + }, + { + name: "misordered shape twin behind same-name import", + code: ts` + import { insert } from "some-other-package" + + insert({ + order: 1, + Component: () => null, + }) + `, + }, + { + name: "unknown option bails", + code: ts` + declare const db: { insert: (options: object) => void } + + db.insert({ + order: 1, + Component: () => null, + table: "users", + }) + `, + }, + { + name: "spread bails", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + const base = {} + + Header.api.insert({ + order: 1, + ...base, + Component: () => null, + }) + `, + }, + { + name: "no component bails", + code: ts` + declare const collection: { insert: (options: object) => void } + + collection.insert({ + order: 1, + filter: (doc: { active: boolean }) => doc.active, + }) + `, + }, + { + name: "computed Component key bails", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + const Component = "Component" + + Header.api.insert({ + order: 1, + [Component]: () => null, + }) + `, + }, + { + name: "duplicate option key bails", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + order: 1, + Component: () => null, + order: 2, + }) + `, + }, + { + name: "imported slot insert in correct order", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + declare const Header: ReturnType> + + Header.api.insert({ + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null, + order: 1, + }) + `, + }, + ], + invalid: [ + { + name: "mapProps after Component", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + Component: (props) => null, + mapProps: (props) => ({ id: props.userId }), + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "mapProps -> Component", + currentOrder: "Component -> mapProps", + }, + }, + ], + }, + { + name: "order first", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + order: 1, + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null, + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null, + order: 1 + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "filter -> mapProps -> Component -> order", + currentOrder: "order -> filter -> mapProps -> Component", + }, + }, + ], + }, + { + name: "filter after mapProps", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + mapProps: (props) => ({ id: props.userId }), + filter: (props) => props.userId > 0, + Component: (props) => null, + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + + Header.api.insert({ + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "filter -> mapProps -> Component", + currentOrder: "mapProps -> filter -> Component", + }, + }, + ], + }, + { + name: "order before Component", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + order: 2, + Component: () => null, + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + Component: () => null, + order: 2 + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "Component -> order", + currentOrder: "order -> Component", + }, + }, + ], + }, + { + name: "destructured insert", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + const { insert } = Header.api + + insert({ + Component: (props) => null, + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot<{ userId: number }>() + const { insert } = Header.api + + insert({ + filter: (props) => props.userId > 0, + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "filter -> mapProps -> Component", + currentOrder: "Component -> filter -> mapProps", + }, + }, + ], + }, + { + name: "renamed destructured insert", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + const { insert: insertWidget } = Header.api + + insertWidget({ + order: 1, + Component: () => null, + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + const { insert: insertWidget } = Header.api + + insertWidget({ + Component: () => null, + order: 1 + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "Component -> order", + currentOrder: "order -> Component", + }, + }, + ], + }, + { + name: "multiline option keeps its text", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + order: 1, + Component: () => { + return null + }, + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + const Header = createSlot() + + Header.api.insert({ + Component: () => { + return null + }, + order: 1 + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "Component -> order", + currentOrder: "order -> Component", + }, + }, + ], + }, + { + name: "imported slot insert is reordered without a traceable createSlot call", + code: ts` + import { createSlot } from "@grlt-hub/react-slots" + + declare const Header: ReturnType> + + Header.api.insert({ + Component: (props) => null, + mapProps: (props) => ({ id: props.userId }), + }) + `, + output: ts` + import { createSlot } from "@grlt-hub/react-slots" + + declare const Header: ReturnType> + + Header.api.insert({ + mapProps: (props) => ({ id: props.userId }), + Component: (props) => null + }) + `, + errors: [ + { + messageId: "invalidOrder", + data: { + correctOrder: "mapProps -> Component", + currentOrder: "Component -> mapProps", + }, + }, + ], + }, + ], +}) diff --git a/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.ts b/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.ts new file mode 100644 index 0000000..e0c1c28 --- /dev/null +++ b/packages/eslint-plugin/src/rules/insert-options-order/insert-options-order.ts @@ -0,0 +1,86 @@ +import { AST_NODE_TYPES as NodeType, ESLintUtils, type TSESTree as Node } from "@typescript-eslint/utils" +import { PACKAGE_NAME, UNITS } from "@/shared/constants" +import { createRule } from "@/shared/create" + +const TRUE_ORDER = ["filter", "mapProps", "Component", "order"] +const REQUIRED = "Component" +const INSERT_TYPES = new Set(["InsertWithProps", "InsertWithoutProps"]) + +const argumentSelector = `ObjectExpression.arguments` + +export default createRule({ + name: "insert-options-order", + meta: { + type: "problem", + docs: { + description: `Enforce options order for ${UNITS.INSERT}`, + }, + messages: { + invalidOrder: `Order of options should be \`{{ correctOrder }}\`, but found \`{{ currentOrder }}\`.`, + }, + schema: [], + hasSuggestions: false, + fixable: "code", + }, + defaultOptions: [], + create: (context) => { + const source = context.sourceCode + const services = ESLintUtils.getParserServices(context) + + const isSlotInsert = (callee: Node.Expression) => { + const alias = services.getTypeAtLocation(callee).aliasSymbol + if (!alias || !INSERT_TYPES.has(alias.name)) return false + + const declarations = alias.getDeclarations() ?? [] + return declarations.some((declaration) => + declaration.getSourceFile().fileName.includes(`/${PACKAGE_NAME.UNSCOPED}/`), + ) + } + + return { + [`CallExpression[arguments.length=1] > ${argumentSelector}`]: (node: Node.ObjectExpression) => { + let hasRequired = false + + for (const prop of node.properties) { + if (prop.type === NodeType.SpreadElement || prop.computed || prop.key.type !== NodeType.Identifier) return + if (prop.key.name === REQUIRED) hasRequired = true + } + + if (!hasRequired) return + + const properties = node.properties as (Node.Property & { key: Node.Identifier })[] + const current = properties.map((prop) => prop.key.name) + + if (new Set(current).size !== current.length) return + if (current.some((key) => !TRUE_ORDER.includes(key))) return + if (isCorrectOrder(current)) return + if (!isSlotInsert((node.parent as Node.CallExpression).callee)) return + + const correctOrder = TRUE_ORDER.filter((item) => current.includes(item)) + const snippets = properties + .toSorted((a, b) => TRUE_ORDER.indexOf(a.key.name) - TRUE_ORDER.indexOf(b.key.name)) + .map((prop) => source.getText(prop)) + + const data = { correctOrder: correctOrder.join(" -> "), currentOrder: current.join(" -> ") } + context.report({ + node, + messageId: "invalidOrder", + data, + fix: (fixer) => [fixer.replaceText(node, `{\n${snippets.join(",\n")}\n}`)], + }) + }, + } + }, +}) + +const isCorrectOrder = (current: string[]) => { + let seen = -1 + + for (const item of current) { + const index = TRUE_ORDER.indexOf(item) + if (index <= seen) return false + seen = index + } + + return true +} diff --git a/packages/eslint-plugin/src/ruleset.ts b/packages/eslint-plugin/src/ruleset.ts new file mode 100644 index 0000000..6c70ef3 --- /dev/null +++ b/packages/eslint-plugin/src/ruleset.ts @@ -0,0 +1,7 @@ +import type { TSESLint } from "@typescript-eslint/utils" + +const recommended = { + "react-slots/insert-options-order": "warn", +} satisfies TSESLint.Linter.RulesRecord + +export const ruleset = { recommended } diff --git a/packages/eslint-plugin/src/setup.ts b/packages/eslint-plugin/src/setup.ts new file mode 100644 index 0000000..56c5f54 --- /dev/null +++ b/packages/eslint-plugin/src/setup.ts @@ -0,0 +1,9 @@ +import { RuleTester } from "@typescript-eslint/rule-tester" +import { afterAll, describe, it } from "vitest" + +RuleTester.afterAll = afterAll +RuleTester.describe = describe +RuleTester.describeSkip = describe.skip +RuleTester.it = it +RuleTester.itOnly = it.only +RuleTester.itSkip = it.skip diff --git a/packages/eslint-plugin/src/shared/constants.ts b/packages/eslint-plugin/src/shared/constants.ts new file mode 100644 index 0000000..b5de860 --- /dev/null +++ b/packages/eslint-plugin/src/shared/constants.ts @@ -0,0 +1,9 @@ +const PACKAGE_NAME = { + UNSCOPED: "react-slots", +} + +const UNITS = { + INSERT: "insert", +} + +export { PACKAGE_NAME, UNITS } diff --git a/packages/eslint-plugin/src/shared/create.ts b/packages/eslint-plugin/src/shared/create.ts new file mode 100644 index 0000000..25abf09 --- /dev/null +++ b/packages/eslint-plugin/src/shared/create.ts @@ -0,0 +1,3 @@ +import { ESLintUtils } from "@typescript-eslint/utils" + +export const createRule = ESLintUtils.RuleCreator(() => "") diff --git a/packages/eslint-plugin/src/shared/tag.ts b/packages/eslint-plugin/src/shared/tag.ts new file mode 100644 index 0000000..03230a2 --- /dev/null +++ b/packages/eslint-plugin/src/shared/tag.ts @@ -0,0 +1,12 @@ +const code = (strings: TemplateStringsArray, ...insert: string[]) => { + const code = String.raw({ raw: strings.raw }, ...insert).replaceAll(/(^\n*|\n*$)/g, "") + + const lines = code.split("\n") + const indent = code.search(/\S|$/) + + return lines.map((line) => line.slice(indent)).join("\n") +} + +code.noformat = code // prevents auto-formatting in test cases + +export { code as ts } diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json new file mode 100644 index 0000000..d2daa2e --- /dev/null +++ b/packages/eslint-plugin/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "resolveJsonModule": true, + "paths": { "@/*": ["./src/*"] } + }, + "include": ["src"] +} diff --git a/packages/eslint-plugin/tsdown.config.ts b/packages/eslint-plugin/tsdown.config.ts new file mode 100644 index 0000000..a87e28b --- /dev/null +++ b/packages/eslint-plugin/tsdown.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "tsdown" + +export default defineConfig({ + entry: "src/index.ts", + + target: "es2022", + format: ["esm", "cjs"], + platform: "node", + + minify: "dce-only", + + deps: { + neverBundle: ["typescript", "@typescript-eslint/utils"], + }, + + dts: { tsgo: true }, +}) diff --git a/packages/eslint-plugin/vitest.config.ts b/packages/eslint-plugin/vitest.config.ts new file mode 100644 index 0000000..6087d6f --- /dev/null +++ b/packages/eslint-plugin/vitest.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + setupFiles: ["src/setup.ts"], + watch: false, + }, + + resolve: { alias: { "@": "./src" } }, +}) diff --git a/packages/react-slots/package.json b/packages/react-slots/package.json index b7a8201..3049eff 100644 --- a/packages/react-slots/package.json +++ b/packages/react-slots/package.json @@ -26,6 +26,12 @@ "homepage": "https://github.com/grlt-hub/react-slots", "license": "MIT", "author": "Viktor Pasynok", + "contributors": [ + { + "name": "Viktor Pasynok", + "url": "https://github.com/binjospookie" + } + ], "repository": { "type": "git", "url": "git+https://github.com/grlt-hub/react-slots.git" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 15c88fa..56c7a48 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,6 +54,25 @@ importers: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.1)(@vitest/browser-playwright@4.1.8)(happy-dom@20.10.1)(vite@8.0.16(@types/node@25.9.1)(jiti@2.7.0)(yaml@2.9.0)) + packages/eslint-plugin: + dependencies: + '@typescript-eslint/utils': + specifier: ^8.60.0 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + typescript: + specifier: ^5.0.0 || ^6.0.0 + version: 6.0.3 + devDependencies: + '@grlt-hub/react-slots': + specifier: workspace:* + version: link:../react-slots + '@typescript-eslint/rule-tester': + specifier: 8.60.1 + version: 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + eslint: + specifier: 10.4.1 + version: 10.4.1(jiti@2.7.0) + packages/react-slots: dependencies: use-sync-external-store: @@ -114,6 +133,56 @@ packages: '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -824,12 +893,18 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.9': resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/jsesc@2.5.1': resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/node@25.9.1': resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} @@ -853,6 +928,57 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + '@typescript-eslint/parser@8.60.1': + resolution: {integrity: sha512-A0M6ua6H252bVjPvvtSgl2QA4+ET9S5Mtkb2GDyTxIhH/C4qDItT7RQNO5PhMC6NXGYXOR9dIalcDDgBKT7oFA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.60.1': + resolution: {integrity: sha512-eXkTH2bxmXlqD1RnOPmLZ9ZM9D3VwSx04JOwBnP9RQ+yUA5a2Mu7SfW8uaV2Aon53NJzZlZYuX7tn91Izf+xaw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/rule-tester@8.60.1': + resolution: {integrity: sha512-ly/WFKd5EwhTpuFbgQ81Z+67o4DRnlgKy+yHTuHWTy/u8yb0nEVPjDKqVUkJ1545vX2aAW1TELNSPPs+AOC+KA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.60.1': + resolution: {integrity: sha512-gvI5OQoptnxQnchOirukCuQ55svJSTuD/4k5+pC267xyBtYry748R9/c3tYUzb/iE6RZfllRz2lVulLCHkTm4w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.60.1': + resolution: {integrity: sha512-nh8w4qAteiKuZu3pSSzG/yGKpw0OlkrKnzFmbVRenKaD4qc+7i1GrmZaLVkr8rk4uipiPGMOW4YsM6WmKZ5CvA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.60.1': + resolution: {integrity: sha512-4h0tY8ppCkdCzcrl2YM5M3my0xsE1Tf8om3owEu5oPWmXwkKRmk0j0LGDzYBGUcAlesEbxBhazqu/K4cu3Ug7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.60.1': + resolution: {integrity: sha512-alpRkfG8hlVE5kdJW2GkfgDgXxold3e8e4l6EnmhRmRLbekgAPCCGDVD++sABy9FcgPFroq+uFcCSM1vR57Cew==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.60.1': + resolution: {integrity: sha512-h2MPBLoNtjc3qZWfY3Tl51yPorQ2McHn8pJfcMNTcIvrrZrr90Ykffit0yjrPFWQcRcUxzH20+6OcVdW4yHtUg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.60.1': + resolution: {integrity: sha512-EbGRQg4FhrmwLodl+t3JNAnXHWVr9Vp+Zl1QBZVPY4ByfkzIT8cX3K6QWODHtkIZqqJVEWvhHSx3v5PDHsaQag==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260605.1': resolution: {integrity: sha512-FR2ToauEC9dIAMV8OeTD7cXjgnpR09p1YZYrQzIpIOfzkg5FPccil+ca75BMbF0jT9h/0x+6+oy7JxzPO8sT+Q==} engines: {node: '>=16.20.0'} @@ -940,6 +1066,19 @@ packages: '@vitest/utils@4.1.8': resolution: {integrity: sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==} + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ansis@4.3.1: resolution: {integrity: sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==} engines: {node: '>=14'} @@ -955,9 +1094,17 @@ packages: resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} engines: {node: '>=20.19.0'} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + birpc@4.0.0: resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + buffer-image-size@0.6.4: resolution: {integrity: sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==} engines: {node: '>=4.0'} @@ -978,9 +1125,25 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} @@ -1008,13 +1171,68 @@ packages: es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + 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} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@10.4.1: + resolution: {integrity: sha512-AyIKhnOBuOAdueD7RB3xB+YeAWScb9jHsJBgH2Hcde8InP5JYhqrRR6iTMHyTEwgENK54Cp44e4v8BwNhsuHuw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} @@ -1027,6 +1245,21 @@ packages: picomatch: optional: true + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + formatly@0.3.0: resolution: {integrity: sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==} engines: {node: '>=18.3.0'} @@ -1049,6 +1282,10 @@ packages: resolution: {integrity: sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==} engines: {node: '>=20.20.0'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + happy-dom@20.10.1: resolution: {integrity: sha512-awPoqPjx8CgjapJllyDlgzgVHjBExcitKK5ZJkxwhQJyQpHFkyS2bEcqCm7IeW20cQvuCI0cz2Ifq79CJKqtiw==} engines: {node: '>=20.0.0'} @@ -1056,10 +1293,29 @@ packages: hookable@6.1.1: resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + import-without-cache@0.4.0: resolution: {integrity: sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==} engines: {node: ^22.18.0 || >=24.0.0} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -1069,9 +1325,21 @@ packages: engines: {node: '>=6'} hasBin: true + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + 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==} + jsonc-parser@3.3.1: resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + knip@6.15.0: resolution: {integrity: sha512-uBaKFEGcu/HG4EY2gWFBMr+fBF43Jftoc2riJX51TKME1Z46C8UQIbNEusenYbEWihphxe2PY0Kns0yPvPYz4A==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1131,6 +1399,10 @@ packages: resolution: {integrity: sha512-bwDaIOViTktE8kJLf9jP0p+H2/RDTlFFlc43Am2YgUsX22hI6Sq4RbzsrecwzY5y+MHTipOH7WsmWSEniePHWQ==} hasBin: true + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1205,9 +1477,20 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -1215,14 +1498,24 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.12: resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + oxc-parser@0.133.0: resolution: {integrity: sha512-661RSx+ZcjBmjBYid+Fpp/2F5EbtildpeoZh5HdgnGs+jZ03nqQEQW8yGkt4BGyOC3OMPDQQRl8M5kqD2/g6jw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1256,9 +1549,25 @@ packages: vite-plus: optional: true + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -1287,6 +1596,14 @@ packages: resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + quansync@1.0.0: resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} @@ -1339,6 +1656,14 @@ packages: engines: {node: '>=10'} hasBin: true + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -1391,6 +1716,12 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + tsdown@0.22.1: resolution: {integrity: sha512-Ldx1jLyDFEzsN/fMBi2TBVaZe4fuEJhIiHjQhX0pV7oa5uYz5Imdivs5mNzEXOrMEtFRR6C9BQ2YqLoroffB+Q==} engines: {node: ^22.18.0 || >=24.0.0} @@ -1428,6 +1759,10 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -1446,6 +1781,9 @@ packages: undici-types@7.24.6: resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -1543,11 +1881,20 @@ packages: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} engines: {node: '>=12'} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -1565,6 +1912,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} @@ -1610,6 +1961,52 @@ snapshots: tslib: 2.8.1 optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@10.4.1(jiti@2.7.0))': + dependencies: + eslint: 10.4.1(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.23.5': + dependencies: + '@eslint/object-schema': 3.0.5 + debug: 4.4.3 + minimatch: 10.2.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.6.0': + dependencies: + '@eslint/core': 1.2.1 + + '@eslint/core@1.2.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.2': + dependencies: + '@eslint/core': 1.2.1 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1994,10 +2391,14 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/esrecurse@4.3.1': {} + '@types/estree@1.0.9': {} '@types/jsesc@2.5.1': {} + '@types/json-schema@7.0.15': {} + '@types/node@25.9.1': dependencies: undici-types: 7.24.6 @@ -2020,6 +2421,83 @@ snapshots: dependencies: '@types/node': 25.9.1 + '@typescript-eslint/parser@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/rule-tester@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/parser': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + '@typescript-eslint/utils': 8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3) + ajv: 6.15.0 + eslint: 10.4.1(jiti@2.7.0) + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + semver: 7.8.1 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + + '@typescript-eslint/tsconfig-utils@8.60.1(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/types@8.60.1': {} + + '@typescript-eslint/typescript-estree@8.60.1(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.60.1(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.60.1(typescript@6.0.3) + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/visitor-keys': 8.60.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.1 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.60.1(eslint@10.4.1(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.60.1 + '@typescript-eslint/types': 8.60.1 + '@typescript-eslint/typescript-estree': 8.60.1(typescript@6.0.3) + eslint: 10.4.1(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.60.1': + dependencies: + '@typescript-eslint/types': 8.60.1 + eslint-visitor-keys: 5.0.1 + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260605.1': optional: true @@ -2122,6 +2600,19 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ansis@4.3.1: {} args-tokenizer@0.3.0: {} @@ -2134,8 +2625,14 @@ snapshots: estree-walker: 3.0.3 pathe: 2.0.3 + balanced-match@4.0.4: {} + birpc@4.0.0: {} + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + buffer-image-size@0.6.4: dependencies: '@types/node': 25.9.1 @@ -2158,8 +2655,20 @@ snapshots: convert-source-map@2.0.0: {} + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + csstype@3.2.3: {} + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + defu@6.1.7: {} detect-libc@2.1.2: {} @@ -2174,12 +2683,86 @@ snapshots: es-module-lexer@2.1.0: {} + escape-string-regexp@4.0.0: {} + + eslint-scope@9.1.2: + dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@10.4.1(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@10.4.1(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + minimatch: 10.2.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@11.2.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fd-package-json@2.0.0: dependencies: walk-up-path: 4.0.0 @@ -2188,6 +2771,22 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + formatly@0.3.0: dependencies: fd-package-json: 2.0.0 @@ -2206,6 +2805,10 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + happy-dom@20.10.1: dependencies: '@types/node': 25.9.1 @@ -2221,14 +2824,36 @@ snapshots: hookable@6.1.1: {} + ignore@5.3.2: {} + import-without-cache@0.4.0: {} + imurmurhash@0.1.4: {} + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + jiti@2.7.0: {} jsesc@3.1.0: {} + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + jsonc-parser@3.3.1: {} + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + knip@6.15.0: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -2289,6 +2914,11 @@ snapshots: lefthook-windows-arm64: 2.1.9 lefthook-windows-x64: 2.1.9 + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: optional: true @@ -2338,18 +2968,41 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + minimist@1.2.8: {} mrmime@2.0.1: {} + ms@2.1.3: {} + nanoid@3.3.12: {} + natural-compare@1.4.0: {} + obug@2.1.1: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + oxc-parser@0.133.0: dependencies: '@oxc-project/types': 0.133.0 @@ -2443,8 +3096,20 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.68.0 '@oxlint/binding-win32-x64-msvc': 1.68.0 + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + package-manager-detector@1.6.0: {} + path-exists@4.0.0: {} + + path-key@3.1.1: {} + pathe@2.0.3: {} picocolors@1.1.1: {} @@ -2467,6 +3132,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + quansync@1.0.0: {} react-dom@19.2.7(react@19.2.7): @@ -2541,6 +3210,12 @@ snapshots: semver@7.8.1: {} + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + siginfo@2.0.0: {} sirv@3.0.2: @@ -2576,6 +3251,10 @@ snapshots: tree-kill@1.2.2: {} + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + tsdown@0.22.1(@typescript/native-preview@7.0.0-dev.20260605.1)(oxc-resolver@11.20.0)(typescript@6.0.3): dependencies: ansis: 4.3.1 @@ -2604,6 +3283,10 @@ snapshots: tslib@2.8.1: optional: true + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + typescript@6.0.3: {} unbash@3.0.0: {} @@ -2623,6 +3306,10 @@ snapshots: undici-types@7.24.6: {} + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + use-sync-external-store@1.6.0(react@19.2.7): dependencies: react: 19.2.7 @@ -2673,13 +3360,21 @@ snapshots: whatwg-mimetype@3.0.0: {} + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + ws@8.21.0: {} yaml@2.9.0: {} + yocto-queue@0.1.0: {} + zod@4.4.3: {} From 01c6fb7bcf1dd51239f24c1f7ddaf8ee5a8ccf31 Mon Sep 17 00:00:00 2001 From: Viktor Pasynok Date: Mon, 8 Jun 2026 02:31:12 +0700 Subject: [PATCH 2/2] chore: release v4.0.0-beta.1 --- package.json | 2 +- packages/eslint-plugin/package.json | 2 +- packages/react-slots/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a6873b5..ea1cd60 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots-dev", - "version": "4.0.0-beta.0", + "version": "4.0.0-beta.1", "private": true, "homepage": "https://github.com/grlt-hub/react-slots", "license": "MIT", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 55ee7bb..29ff237 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/eslint-plugin-react-slots", - "version": "4.0.0-beta.0", + "version": "4.0.0-beta.1", "private": false, "description": "Enforcing best practices for @grlt-hub/react-slots", "keywords": [ diff --git a/packages/react-slots/package.json b/packages/react-slots/package.json index 3049eff..8d3374d 100644 --- a/packages/react-slots/package.json +++ b/packages/react-slots/package.json @@ -1,6 +1,6 @@ { "name": "@grlt-hub/react-slots", - "version": "4.0.0-beta.0", + "version": "4.0.0-beta.1", "private": false, "description": "Declarative slot system for React. Build extensible, plugin-ready components with dynamic injection", "keywords": [