diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94643aa..def0979 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: fail-fast: false matrix: node-version: - - '22.15.0' + - '22.17.0' - '24.11.1' steps: - name: Checkout diff --git a/README.md b/README.md index c887e5b..5d13ddc 100644 --- a/README.md +++ b/README.md @@ -198,6 +198,16 @@ CSS Modules hash class names after the loader extracts selectors, so the stylesh
``` +### Stable selector type generation + +Run `npx knighted-css-generate-types --root .` to scan your project for `?knighted-css&types` imports. The CLI: + +- extracts selectors via the loader, then writes literal module declarations into `node_modules/@knighted/css/node_modules/.knighted-css` +- updates the packaged stub at `node_modules/@knighted/css/types-stub/index.d.ts` +- exposes the declarations automatically because `types.d.ts` references the stub, so no `tsconfig` wiring is required + +Re-run the command whenever imports change (add it to a `types:css` npm script or your build). If you need a different destination, pass `--out-dir` and/or `--types-root` to override the defaults. + Sass/Less projects can import the shared mixins directly: ```scss @@ -238,15 +248,84 @@ Need a zero-JS approach? Import the optional layer helper and co-locate your fal Override the namespace via `:root { --knighted-stable-namespace: 'acme'; }` if you want a different prefix in pure CSS. +#### Type-safe selector maps (`?knighted-css&types`) + +Append `&types` to any loader import to receive a literal map of the discovered stable selectors alongside the raw CSS: + +```ts +import { knightedCss, stableSelectors } from './styles.css?knighted-css&types' + +stableSelectors.demo // "knighted-demo" +type StableSelectors = typeof stableSelectors +``` + +The map ships as `as const`, so every key/value pair is type-safe without additional tooling. Need the combined import? Add the flag there too and destructure everything from one place: + +```ts +import type { KnightedCssCombinedModule } from '@knighted/css/loader' +import combined, { stableSelectors } from './button.js?knighted-css&combined&types' + +const { knightedCss } = combined as KnightedCssCombinedModule< + typeof import('./button.js') +> + +stableSelectors.demo // "knighted-demo" +``` + +Namespaces default to `knighted`, but you can configure a global fallback via the loader’s `stableNamespace` option: + +```js +{ + loader: '@knighted/css/loader', + options: { + stableNamespace: 'storybook', + }, +} +``` + +All imports share the namespace resolved by the loader (or the `knighted-css-generate-types` CLI). Use the loader option or CLI flag to align runtime + type generation, and the loader still emits highlighted warnings when the namespace trims to an empty value or when no selectors match. For best editor support, keep `&types` at the end of the query (`?knighted-css&combined&types`, `?knighted-css&combined&named-only&types`, etc.). + #### TypeScript support for loader queries Loader query types ship directly with `@knighted/css`. Reference them once in your project—either by adding `"types": ["@knighted/css/loader-queries"]` to `tsconfig.json` or dropping `/// ` into a global `.d.ts`—and the following ambient modules become available everywhere: - `*?knighted-css` imports expose a `knightedCss: string` export. -- `*?knighted-css&combined` (and any query that includes both flags) expose `knightedCss` and return the original module exports, which you can narrow with `KnightedCssCombinedModule` before destructuring named members. +- `*?knighted-css&types` exposes both `knightedCss` and `stableSelectors`, the readonly selector map. +- `*?knighted-css&combined` (plus `&named-only` / `&no-default`) mirror the source module exports while adding `knightedCss`, which you can narrow with `KnightedCssCombinedModule` before destructuring named members. +- `*?knighted-css&combined&types` variants add the same `stableSelectors` map on top of the combined behavior so a single import can surface everything. No vendor copies are necessary—the declarations live inside `@knighted/css`, you just need to point your TypeScript config at the shipped `loader-queries` subpath once. +#### Generate literal selector types + +The runtime `stableSelectors` export is always a literal `as const` map, but TypeScript can only see those exact tokens if your project emits matching `.d.ts` files. Run the bundled CLI whenever you change a module that imports `?knighted-css&types` (or any `&combined&types` variants): + +```bash +npx knighted-css-generate-types --root . +``` + +or wire it into `package.json` for local workflows: + +```json +{ + "scripts": { + "knighted:types": "knighted-css-generate-types --root . --include src" + } +} +``` + +The CLI scans every file you include (by default the project root, skipping `node_modules`, `dist`, etc.), finds imports containing `?knighted-css&types`, reuses the loader to extract CSS, and writes deterministic `.d.ts` files into `node_modules/.knighted-css/knt-*.d.ts`. It also maintains `node_modules/@knighted/css/types-stub/index.d.ts`, so TypeScript picks up the generated declarations automatically—no extra `typeRoots` configuration is required. + +Key flags: + +- `--root` / `-r` – project root (defaults to `process.cwd()`). +- `--include` / `-i` – additional directories or files to scan (repeatable). +- `--out-dir` – custom output folder for the generated `knt-*` declarations. +- `--types-root` – override the `@types` directory used for the aggregator. +- `--stable-namespace` – namespace prefix for the generated selector map. + +Re-run the CLI (or add it to a pre-build hook) whenever selectors change so new tokens land in the literal declaration files. + #### Combined module + CSS import If you prefer a single import that returns both your module exports and the compiled stylesheet, append `&combined` to the query. Then narrow the import once so TypeScript understands the shape: diff --git a/codecov.yml b/codecov.yml index 8960148..8b780dd 100644 --- a/codecov.yml +++ b/codecov.yml @@ -8,6 +8,7 @@ coverage: - packages/css/src patch: default: + target: 80.0 threshold: 5.0 paths: - packages/css/src diff --git a/docs/combined-queries.md b/docs/combined-queries.md index 3b4e4ea..f4a73b0 100644 --- a/docs/combined-queries.md +++ b/docs/combined-queries.md @@ -4,11 +4,14 @@ This document summarizes how `?knighted-css&combined` behaves for different modu ## Decision Matrix -| Source module exports | Recommended query | TypeScript import pattern | Notes | -| --------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| **Named exports only** | `?knighted-css&combined&named-only` | [Snippet](#named-exports-only) | `&named-only` disables the synthetic default export so you only destructure the original named members plus `knightedCss`. | -| **Default export only** | `?knighted-css&combined` | [Snippet](#default-export-only) | Loader mirrors the default export and adds `knightedCss`, so default-import code keeps working. | -| **Default + named exports** | `?knighted-css&combined` (append `&named-only` when you never consume the default) | [Snippet](#default-and-named-exports) | Without the flag you get both default + named exports; adding it drops the synthetic default for stricter codebases. | +| Source module exports | Recommended query | TypeScript import pattern | Notes | +| ---------------------------------------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| **Named exports only** | `?knighted-css&combined&named-only` | [Snippet](#named-exports-only) | `&named-only` disables the synthetic default export so you only destructure the original named members plus `knightedCss`. | +| **Default export only** | `?knighted-css&combined` | [Snippet](#default-export-only) | Loader mirrors the default export and adds `knightedCss`, so default-import code keeps working. | +| **Default + named exports** | `?knighted-css&combined` (append `&named-only` when you never consume the default) | [Snippet](#default-and-named-exports) | Without the flag you get both default + named exports; adding it drops the synthetic default for stricter codebases. | +| **Named exports + stable selector map** | `?knighted-css&combined&named-only&types` | [Snippet](#named-exports-with-stable-selectors) | Adds a `stableSelectors` named export; configure namespaces via the loader option or CLI flag. | +| **Default export only + stable selector map** | `?knighted-css&combined&types` | [Snippet](#default-export-with-stable-selectors) | Keep your default-import flow and add `stableSelectors`; namespaces come from loader/CLI configuration. | +| **Default + named exports + stable selectors** | `?knighted-css&combined&types` (append `&named-only` if you skip the default) | [Snippet](#default-and-named-exports-with-stable-selectors) | Best of both worlds—`stableSelectors` is exported alongside `knightedCss`; add `&named-only` if you don’t use the default. | ## Named exports only @@ -50,8 +53,56 @@ const { Prefer `?knighted-css&combined&named-only` plus the [named exports only](#named-exports-only) snippet when you intentionally avoid default exports but still need the named members and `knightedCss`. +## Named exports with stable selectors + +```ts +import type { KnightedCssCombinedModule } from '@knighted/css/loader' +import combined, { + stableSelectors, +} from './module.js?knighted-css&combined&named-only&types' + +const { Component, knightedCss } = combined as KnightedCssCombinedModule< + typeof import('./module.js') +> + +stableSelectors.card // "knighted-card" +``` + +Need a different prefix? Configure the loader’s `stableNamespace` option (or pass `--stable-namespace` to the CLI) so both runtime and generated types stay in sync. + +## Default export with stable selectors + +```ts +import type { KnightedCssCombinedModule } from '@knighted/css/loader' +import combined, { stableSelectors } from './module.js?knighted-css&combined&types' + +const { default: Component, knightedCss } = combined as KnightedCssCombinedModule< + typeof import('./module.js') +> + +stableSelectors.badge // "knighted-badge" +``` + +## Default and named exports with stable selectors + +```ts +import type { KnightedCssCombinedModule } from '@knighted/css/loader' +import combined, { stableSelectors } from './module.js?knighted-css&combined&types' + +const { + default: Component, + helper, + knightedCss, +} = combined as KnightedCssCombinedModule + +stableSelectors.card // "knighted-card" (or your configured namespace) +``` + +Append `&named-only` before `&types` when you want to drop the synthetic default export while still receiving `stableSelectors`. + ## Key Takeaways - The loader always injects `knightedCss` alongside the module’s exports. - To avoid synthetic defaults (and TypeScript warnings) for modules that only expose named exports, add `&named-only` and use a namespace import. - Namespace imports plus `KnightedCssCombinedModule` work universally; default imports are optional conveniences when the source module exposes a default you actually consume. +- Add `&types` when you also need the `stableSelectors` map. Configure the namespace globally (loader option or CLI flag) so runtime + generated types stay consistent. diff --git a/package-lock.json b/package-lock.json index a82be7b..c8e291b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,8 +36,11 @@ "typescript": "^5.9.3" }, "engines": { - "node": ">= 22.15.0", + "node": ">= 22.17.0", "npm": ">= 10.9.0" + }, + "optionalDependencies": { + "@oxc-parser/binding-wasm32-wasi": "^0.99.0" } }, "node_modules/@babel/code-frame": { @@ -395,9 +398,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.1.tgz", - "integrity": "sha512-HHB50pdsBX6k47S4u5g/CaLjqS3qwaOVE5ILsq64jyzgMhLuCuZ8rGzM9yhsAjfjkbgUPMzZEPa7DAp7yz6vuA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -412,9 +415,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.1.tgz", - "integrity": "sha512-kFqa6/UcaTbGm/NncN9kzVOODjhZW8e+FRdSeypWe6j33gzclHtwlANs26JrupOntlcWmB0u8+8HZo8s7thHvg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -429,9 +432,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.1.tgz", - "integrity": "sha512-45fuKmAJpxnQWixOGCrS+ro4Uvb4Re9+UTieUY2f8AEc+t7d4AaZ6eUJ3Hva7dtrxAAWHtlEFsXFMAgNnGU9uQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -446,9 +449,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.1.tgz", - "integrity": "sha512-LBEpOz0BsgMEeHgenf5aqmn/lLNTFXVfoWMUox8CtWWYK9X4jmQzWjoGoNb8lmAYml/tQ/Ysvm8q7szu7BoxRQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -463,9 +466,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.1.tgz", - "integrity": "sha512-veg7fL8eMSCVKL7IW4pxb54QERtedFDfY/ASrumK/SbFsXnRazxY4YykN/THYqFnFwJ0aVjiUrVG2PwcdAEqQQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -480,9 +483,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.1.tgz", - "integrity": "sha512-+3ELd+nTzhfWb07Vol7EZ+5PTbJ/u74nC6iv4/lwIU99Ip5uuY6QoIf0Hn4m2HoV0qcnRivN3KSqc+FyCHjoVQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -497,9 +500,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.1.tgz", - "integrity": "sha512-/8Rfgns4XD9XOSXlzUDepG8PX+AVWHliYlUkFI3K3GB6tqbdjYqdhcb4BKRd7C0BhZSoaCxhv8kTcBrcZWP+xg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -514,9 +517,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.1.tgz", - "integrity": "sha512-GITpD8dK9C+r+5yRT/UKVT36h/DQLOHdwGVwwoHidlnA168oD3uxA878XloXebK4Ul3gDBBIvEdL7go9gCUFzQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -531,9 +534,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.1.tgz", - "integrity": "sha512-ieMID0JRZY/ZeCrsFQ3Y3NlHNCqIhTprJfDgSB3/lv5jJZ8FX3hqPyXWhe+gvS5ARMBJ242PM+VNz/ctNj//eA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -548,9 +551,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.1.tgz", - "integrity": "sha512-W9//kCrh/6in9rWIBdKaMtuTTzNj6jSeG/haWBADqLLa9P8O5YSRDzgD5y9QBok4AYlzS6ARHifAb75V6G670Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -565,9 +568,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.1.tgz", - "integrity": "sha512-VIUV4z8GD8rtSVMfAj1aXFahsi/+tcoXXNYmXgzISL+KB381vbSTNdeZHHHIYqFyXcoEhu9n5cT+05tRv13rlw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -582,9 +585,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.1.tgz", - "integrity": "sha512-l4rfiiJRN7sTNI//ff65zJ9z8U+k6zcCg0LALU5iEWzY+a1mVZ8iWC1k5EsNKThZ7XCQ6YWtsZ8EWYm7r1UEsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -599,9 +602,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.1.tgz", - "integrity": "sha512-U0bEuAOLvO/DWFdygTHWY8C067FXz+UbzKgxYhXC0fDieFa0kDIra1FAhsAARRJbvEyso8aAqvPdNxzWuStBnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -616,9 +619,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.1.tgz", - "integrity": "sha512-NzdQ/Xwu6vPSf/GkdmRNsOfIeSGnh7muundsWItmBsVpMoNPVpM61qNzAVY3pZ1glzzAxLR40UyYM23eaDDbYQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -633,9 +636,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.1.tgz", - "integrity": "sha512-7zlw8p3IApcsN7mFw0O1Z1PyEk6PlKMu18roImfl3iQHTnr/yAfYv6s4hXPidbDoI2Q0pW+5xeoM4eTCC0UdrQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -650,9 +653,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.1.tgz", - "integrity": "sha512-cGj5wli+G+nkVQdZo3+7FDKC25Uh4ZVwOAK6A06Hsvgr8WqBBuOy/1s+PUEd/6Je+vjfm6stX0kmib5b/O2Ykw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -667,9 +670,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.1.tgz", - "integrity": "sha512-z3H/HYI9MM0HTv3hQZ81f+AKb+yEoCRlUby1F80vbQ5XdzEMyY/9iNlAmhqiBKw4MJXwfgsh7ERGEOhrM1niMA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -684,9 +687,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.1.tgz", - "integrity": "sha512-wzC24DxAvk8Em01YmVXyjl96Mr+ecTPyOuADAvjGg+fyBpGmxmcr2E5ttf7Im8D0sXZihpxzO1isus8MdjMCXQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -701,9 +704,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.1.tgz", - "integrity": "sha512-1YQ8ybGi2yIXswu6eNzJsrYIGFpnlzEWRl6iR5gMgmsrR0FcNoV1m9k9sc3PuP5rUBLshOZylc9nqSgymI+TYg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -718,9 +721,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.1.tgz", - "integrity": "sha512-5Z+DzLCrq5wmU7RDaMDe2DVXMRm2tTDvX2KU14JJVBN2CT/qov7XVix85QoJqHltpvAOZUAc3ndU56HSMWrv8g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -735,9 +738,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.1.tgz", - "integrity": "sha512-Q73ENzIdPF5jap4wqLtsfh8YbYSZ8Q0wnxplOlZUOyZy7B4ZKW8DXGWgTCZmF8VWD7Tciwv5F4NsRf6vYlZtqg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -752,9 +755,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.1.tgz", - "integrity": "sha512-ajbHrGM/XiK+sXM0JzEbJAen+0E+JMQZ2l4RR4VFwvV9JEERx+oxtgkpoKv1SevhjavK2z2ReHk32pjzktWbGg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -769,9 +772,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.1.tgz", - "integrity": "sha512-IPUW+y4VIjuDVn+OMzHc5FV4GubIwPnsz6ubkvN8cuhEqH81NovB53IUlrlBkPMEPxvNnf79MGBoz8rZ2iW8HA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -786,9 +789,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.1.tgz", - "integrity": "sha512-RIVRWiljWA6CdVu8zkWcRmGP7iRRIIwvhDKem8UMBjPql2TXM5PkDVvvrzMtj1V+WFPB4K7zkIGM7VzRtFkjdg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -803,9 +806,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.1.tgz", - "integrity": "sha512-2BR5M8CPbptC1AK5JbJT1fWrHLvejwZidKx3UMSF0ecHMa+smhi16drIrCEggkgviBwLYd5nwrFLSl5Kho96RQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -820,9 +823,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.1.tgz", - "integrity": "sha512-d5X6RMYv6taIymSk8JBP+nxv8DQAMY6A51GPgusqLdK9wBz5wWIXy1KjTck6HnjE9hqJzJRdk+1p/t5soSbCtw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -933,6 +936,18 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", @@ -1162,16 +1177,20 @@ } }, "node_modules/@knighted/jsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@knighted/jsx/-/jsx-1.2.1.tgz", - "integrity": "sha512-zqkzK7wC1IZRdcEaAJGPqiy+YzqtJ5qOBCWiXMiNdipLAJaJgTXMxYTJtZLQCs4ux+HGSm5/OYYdq9tD7JZG0g==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@knighted/jsx/-/jsx-1.4.1.tgz", + "integrity": "sha512-JHdbNEeTgOL4jBYHFMqBCuHrQ3XnMZ1RZBvjFeam2fNW+S20kLJjxjp4Lz6CO9DCGcAZtbb80UO8XVyCMGb/cQ==", "license": "MIT", "dependencies": { "magic-string": "^0.30.21", - "oxc-parser": "^0.99.0" + "oxc-parser": "^0.99.0", + "tar": "^7.4.3" + }, + "bin": { + "jsx": "dist/cli/init.js" }, "engines": { - "node": ">=22.3.0" + "node": ">=22.17.0" }, "optionalDependencies": { "@oxc-parser/binding-darwin-arm64": "^0.99.0", @@ -1387,22 +1406,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz", - "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==", - "cpu": [ - "wasm32" - ], - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@knighted/jsx/node_modules/@oxc-parser/binding-win32-arm64-msvc": { "version": "0.99.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz", @@ -1714,23 +1717,6 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@knighted/specifier/node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.99.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz", - "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@knighted/specifier/node_modules/@oxc-parser/binding-win32-arm64-msvc": { "version": "0.99.0", "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.99.0.tgz", @@ -2122,17 +2108,16 @@ } }, "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.78.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.78.0.tgz", - "integrity": "sha512-Pq0uT2CuN3J7Tv3KLuO7Sh4C7zTuqdJl0IDg3zB5keKx0BSbaEWewJL2CUNYUlG8txf+sMpUV+bkAIS5MEcKAw==", + "version": "0.99.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.99.0.tgz", + "integrity": "sha512-DKA4j0QerUWSMADziLM5sAyM7V53Fj95CV9SjP77bPfEfT7MnvFKnneaRMqPK1cpzjAGiQF52OBUIKyk0dwOQA==", "cpu": [ "wasm32" ], - "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.0.1" + "@napi-rs/wasm-runtime": "^1.0.7" }, "engines": { "node": ">=14.0.0" @@ -2639,28 +2624,28 @@ "license": "MIT" }, "node_modules/@rspack/binding": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.6.7.tgz", - "integrity": "sha512-7ICabuBN3gHc6PPN52+m1kruz3ogiJjg1C0gSWdLRk18m/4jlcM2aAy6wfXjgODJdB0Yh2ro/lIpBbj+AYWUGA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding/-/binding-1.6.8.tgz", + "integrity": "sha512-lUeL4mbwGo+nqRKqFDCm9vH2jv9FNMVt1X8jqayWRcOCPlj/2UVMEFgqjR7Pp2vlvnTKq//31KbDBJmDZq31RQ==", "dev": true, "license": "MIT", "optionalDependencies": { - "@rspack/binding-darwin-arm64": "1.6.7", - "@rspack/binding-darwin-x64": "1.6.7", - "@rspack/binding-linux-arm64-gnu": "1.6.7", - "@rspack/binding-linux-arm64-musl": "1.6.7", - "@rspack/binding-linux-x64-gnu": "1.6.7", - "@rspack/binding-linux-x64-musl": "1.6.7", - "@rspack/binding-wasm32-wasi": "1.6.7", - "@rspack/binding-win32-arm64-msvc": "1.6.7", - "@rspack/binding-win32-ia32-msvc": "1.6.7", - "@rspack/binding-win32-x64-msvc": "1.6.7" + "@rspack/binding-darwin-arm64": "1.6.8", + "@rspack/binding-darwin-x64": "1.6.8", + "@rspack/binding-linux-arm64-gnu": "1.6.8", + "@rspack/binding-linux-arm64-musl": "1.6.8", + "@rspack/binding-linux-x64-gnu": "1.6.8", + "@rspack/binding-linux-x64-musl": "1.6.8", + "@rspack/binding-wasm32-wasi": "1.6.8", + "@rspack/binding-win32-arm64-msvc": "1.6.8", + "@rspack/binding-win32-ia32-msvc": "1.6.8", + "@rspack/binding-win32-x64-msvc": "1.6.8" } }, "node_modules/@rspack/binding-darwin-arm64": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.6.7.tgz", - "integrity": "sha512-QiIAP8JTAtht0j8/xZZEQTJRB9e+KrOm9c7JJm73CewVg55rDWRrwopiVfBNlTu1coem1ztUHJYdQhg2uXfqww==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-arm64/-/binding-darwin-arm64-1.6.8.tgz", + "integrity": "sha512-e8CTQtzaeGnf+BIzR7wRMUwKfIg0jd/sxMRc1Vd0bCMHBhSN9EsGoMuJJaKeRrSmy2nwMCNWHIG+TvT1CEKg+A==", "cpu": [ "arm64" ], @@ -2672,9 +2657,9 @@ ] }, "node_modules/@rspack/binding-darwin-x64": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.7.tgz", - "integrity": "sha512-DpQRxxTXkMMNPmBXeJBaAB8HmWKxH2IfvHv7vU+kBhJ3xdPtXU4/xBv1W3biluoNRG11gc1WLIgjzeGgaLCxmw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-darwin-x64/-/binding-darwin-x64-1.6.8.tgz", + "integrity": "sha512-ku1XpTEPt6Za11zhpFWhfwrTQogcgi9RJrOUVC4FESiPO9aKyd4hJ+JiPgLY0MZOqsptK6vEAgOip+uDVXrCpg==", "cpu": [ "x64" ], @@ -2686,9 +2671,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-gnu": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.7.tgz", - "integrity": "sha512-211/XoBiooGGgUo/NxNpsrzGUXtH1d7g/4+UTtjYtfc8QHwu7ZMHcsqg0wss53fXzn/yyxd0DZ56vBHq52BiFw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.6.8.tgz", + "integrity": "sha512-fvZX6xZPvBT8qipSpvkKMX5M7yd2BSpZNCZXcefw6gA3uC7LI3gu+er0LrDXY1PtPzVuHTyDx+abwWpagV3PiQ==", "cpu": [ "arm64" ], @@ -2700,9 +2685,9 @@ ] }, "node_modules/@rspack/binding-linux-arm64-musl": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.7.tgz", - "integrity": "sha512-0WnqAWz3WPDsXGvOOA++or7cHpoidVsH3FlqNaAfRu6ni6n7ig/s0/jKUB+C5FtXOgmGjAGkZHfFgNHsvZ0FWw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.6.8.tgz", + "integrity": "sha512-++XMKcMNrt59HcFBLnRaJcn70k3X0GwkAegZBVpel8xYIAgvoXT5+L8P1ExId/yTFxqedaz8DbcxQnNmMozviw==", "cpu": [ "arm64" ], @@ -2714,9 +2699,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-gnu": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.7.tgz", - "integrity": "sha512-iMrE0Q4IuYpkE0MjpaOVaUDYbQFiCRI9D3EPoXzlXJj4kJSdNheODpHTBVRlWt8Xp7UAoWuIFXCvKFKcSMm3aQ==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.6.8.tgz", + "integrity": "sha512-tv3BWkTE1TndfX+DsE1rSTg8fBevCxujNZ3MlfZ22Wfy9x1FMXTJlWG8VIOXmaaJ1wUHzv8S7cE2YUUJ2LuiCg==", "cpu": [ "x64" ], @@ -2728,9 +2713,9 @@ ] }, "node_modules/@rspack/binding-linux-x64-musl": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.7.tgz", - "integrity": "sha512-e7gKFxpdEQwYGk7lTC/hukTgNtaoAstBXehnZNk4k3kuU6+86WDrkn18Cd949iNqfIPtIG/wIsFNGbkHsH69hQ==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-linux-x64-musl/-/binding-linux-x64-musl-1.6.8.tgz", + "integrity": "sha512-DCGgZ5/in1O3FjHWqXnDsncRy+48cMhfuUAAUyl0yDj1NpsZu9pP+xfGLvGcQTiYrVl7IH9Aojf1eShP/77WGA==", "cpu": [ "x64" ], @@ -2742,9 +2727,9 @@ ] }, "node_modules/@rspack/binding-wasm32-wasi": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.7.tgz", - "integrity": "sha512-yx88EFdE9RP3hh7VhjjW6uc6wGU0KcpOcZp8T8E/a+X8L98fX0aVrtM1IDbndhmdluIMqGbfJNap2+QqOCY9Mw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-wasm32-wasi/-/binding-wasm32-wasi-1.6.8.tgz", + "integrity": "sha512-VUwdhl/lI4m6o1OGCZ9JwtMjTV/yLY5VZTQdEPKb40JMTlmZ5MBlr5xk7ByaXXYHr6I+qnqEm73iMKQvg6iknw==", "cpu": [ "wasm32" ], @@ -2769,9 +2754,9 @@ } }, "node_modules/@rspack/binding-win32-arm64-msvc": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.7.tgz", - "integrity": "sha512-vgxVYpFK8P5ulSXQQA+EbX78R/SUU+WIf0JIY+LoUoP89gZOsise/lKAJMAybzpeTJ1t0ndLchFznDYnzq+l4Q==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.6.8.tgz", + "integrity": "sha512-23YX7zlOZlub+nPGDBUzktb4D5D6ETUAluKjXEeHIZ9m7fSlEYBnGL66YE+3t1DHXGd0OqsdwlvrNGcyo6EXDQ==", "cpu": [ "arm64" ], @@ -2783,9 +2768,9 @@ ] }, "node_modules/@rspack/binding-win32-ia32-msvc": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.7.tgz", - "integrity": "sha512-bV5RTW0Va0UQKJm9HWLt7fWNBPaBBBxCJOA2pJT3nGGm6CCXKnZSyEiVbFUk4jI/uiwBfqenlLkzaGoMRbeDhA==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.6.8.tgz", + "integrity": "sha512-cFgRE3APxrY4AEdooVk2LtipwNNT/9mrnjdC5lVbsIsz+SxvGbZR231bxDJEqP15+RJOaD07FO1sIjINFqXMEg==", "cpu": [ "ia32" ], @@ -2797,9 +2782,9 @@ ] }, "node_modules/@rspack/binding-win32-x64-msvc": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.7.tgz", - "integrity": "sha512-8xlbuJQtYktlBjZupOHlO8FeZqSIhsV3ih7xBSiOYar6LI6uQzA7XiO3I5kaPSDirBMMMKv1Z4rKCxWx10a3TQ==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.6.8.tgz", + "integrity": "sha512-cIuhVsZYd3o3Neo1JSAhJYw6BDvlxaBoqvgwRkG1rs0ExFmEmgYyG7ip9pFKnKNWph/tmW3rDYypmEfjs1is7g==", "cpu": [ "x64" ], @@ -2811,9 +2796,9 @@ ] }, "node_modules/@rspack/cli": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/cli/-/cli-1.6.7.tgz", - "integrity": "sha512-lWU4Gfw3HIysXnBuN1Ta43BeQHewoHd3MAzRlxmEYsUPxau26h6oTpL5WnLsT/Eh4G7XqnW1NJ8COujnn9EGfg==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/cli/-/cli-1.6.8.tgz", + "integrity": "sha512-pFMYsov8Av7bNWEU9l0HCTk2A5vOPaaZBkZSkCs68U07tkMOQ58IvUiC5Uy1B780bqE2jBt/b6yA41uNmXScZg==", "dev": true, "license": "MIT", "dependencies": { @@ -2830,15 +2815,15 @@ } }, "node_modules/@rspack/core": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.7.tgz", - "integrity": "sha512-tkd4nSzTf+pDa9OAE4INi/JEa93HNszjWy5C9+trf4ZCXLLHsHxHQFbzoreuz4Vv2PlCWajgvAdiPMV1vGIkuw==", + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@rspack/core/-/core-1.6.8.tgz", + "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.21.6", - "@rspack/binding": "1.6.7", + "@rspack/binding": "1.6.8", "@rspack/lite-tapable": "1.1.0" }, "engines": { @@ -3123,9 +3108,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.0.tgz", - "integrity": "sha512-rl78HwuZlaDIUSeUKkmogkhebA+8K1Hy7tddZuJ3D0xV8pZSfsYGTsliGUol1JPzu9EKnTxPC4L1fiWouStRew==", + "version": "25.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz", + "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -3273,13 +3258,13 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.49.0.tgz", - "integrity": "sha512-/wJN0/DKkmRUMXjZUXYZpD1NEQzQAAn9QWfGwo+Ai8gnzqH7tvqS7oNVdTjKqOcPyVIdZdyCMoqN66Ia789e7g==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz", + "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.49.0", - "@typescript-eslint/types": "^8.49.0", + "@typescript-eslint/tsconfig-utils": "^8.50.0", + "@typescript-eslint/types": "^8.50.0", "debug": "^4.3.4" }, "engines": { @@ -3294,9 +3279,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.49.0.tgz", - "integrity": "sha512-8prixNi1/6nawsRYxet4YOhnbW+W9FK/bQPxsGB1D3ZrDzbJ5FXw5XmzxZv82X3B+ZccuSxo/X8q9nQ+mFecWA==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz", + "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3310,9 +3295,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.49.0.tgz", - "integrity": "sha512-e9k/fneezorUo6WShlQpMxXh8/8wfyc+biu6tnAqA81oWrEic0k21RHzP9uqqpyBBeBKu4T+Bsjy9/b8u7obXQ==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz", + "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==", "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3323,15 +3308,15 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.49.0.tgz", - "integrity": "sha512-jrLdRuAbPfPIdYNppHJ/D0wN+wwNfJ32YTAm10eJVsFmrVpXQnDWBn8niCSMlWjvml8jsce5E/O+86IQtTbJWA==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz", + "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.49.0", - "@typescript-eslint/tsconfig-utils": "8.49.0", - "@typescript-eslint/types": "8.49.0", - "@typescript-eslint/visitor-keys": "8.49.0", + "@typescript-eslint/project-service": "8.50.0", + "@typescript-eslint/tsconfig-utils": "8.50.0", + "@typescript-eslint/types": "8.50.0", + "@typescript-eslint/visitor-keys": "8.50.0", "debug": "^4.3.4", "minimatch": "^9.0.4", "semver": "^7.6.0", @@ -3377,12 +3362,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.49.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.49.0.tgz", - "integrity": "sha512-LlKaciDe3GmZFphXIc79THF/YYBugZ7FS1pO581E/edlVVNbZKDy93evqmrfQ9/Y4uN0vVhX4iuchq26mK/iiA==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz", + "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.49.0", + "@typescript-eslint/types": "8.50.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -3404,9 +3389,9 @@ } }, "node_modules/@vanilla-extract/css": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.17.5.tgz", - "integrity": "sha512-u29cUVL5Z2qjJ2Eh8pusT1ToGtTeA4eb/y0ygaw2vWv9XFQSixtkBYEsVkrJExSI/0+SR1g8n5NYas4KlWOdfA==", + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.18.0.tgz", + "integrity": "sha512-/p0dwOjr0o8gE5BRQ5O9P0u/2DjUd6Zfga2JGmE4KaY7ZITWMszTzk4x4CPlM5cKkRr2ZGzbE6XkuPNfp9shSQ==", "devOptional": true, "license": "MIT", "peer": true, @@ -3426,9 +3411,9 @@ } }, "node_modules/@vanilla-extract/integration": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-8.0.6.tgz", - "integrity": "sha512-BlDtXtb6Fin8XEGwf4BhsJkKQh0rhj/YiN6ylNNOqXtRU0+DQmzE5WGE056ScKg3p5e0IFaeH7PPxuWJca9aXw==", + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/@vanilla-extract/integration/-/integration-8.0.7.tgz", + "integrity": "sha512-ILob4F9cEHXpbWAVt3Y2iaQJpqYq/c/5TJC8Fz58C2XmX3QW2Y589krvViiyJhQfydCGK3EbwPQhVFjQaBeKfg==", "devOptional": true, "license": "MIT", "peer": true, @@ -3436,7 +3421,7 @@ "@babel/core": "^7.23.9", "@babel/plugin-syntax-typescript": "^7.23.3", "@vanilla-extract/babel-plugin-debug-ids": "^1.2.2", - "@vanilla-extract/css": "^1.17.5", + "@vanilla-extract/css": "^1.18.0", "dedent": "^1.5.3", "esbuild": "npm:esbuild@>=0.17.6 <0.28.0", "eval": "0.1.8", @@ -3551,13 +3536,13 @@ } }, "node_modules/@vanilla-extract/webpack-plugin": { - "version": "2.3.24", - "resolved": "https://registry.npmjs.org/@vanilla-extract/webpack-plugin/-/webpack-plugin-2.3.24.tgz", - "integrity": "sha512-8NcZaDn4HuV/P882uCZLjabnXX9TQbm70I9kLcqkCFRVE3EzX0EdSrx9YQzr7Hg5wFZpN5oedjK+3AumhSSL3A==", + "version": "2.3.25", + "resolved": "https://registry.npmjs.org/@vanilla-extract/webpack-plugin/-/webpack-plugin-2.3.25.tgz", + "integrity": "sha512-tnaYN3KGF8V9JZ5FuQY9T9/KmLTfiPxGCw+hhIBD7lgn8AKpg53fm+4lJDDSudTFFN3YNiO2WDjHH62IuoyLIQ==", "dev": true, "license": "MIT", "dependencies": { - "@vanilla-extract/integration": "^8.0.6", + "@vanilla-extract/integration": "^8.0.7", "debug": "^4.3.1", "loader-utils": "^2.0.0", "picocolors": "^1.0.0" @@ -3567,14 +3552,14 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", - "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.26.tgz", + "integrity": "sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/shared": "3.5.25", - "entities": "^4.5.0", + "@vue/shared": "3.5.26", + "entities": "^7.0.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } @@ -3586,26 +3571,26 @@ "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.25.tgz", - "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.26.tgz", + "integrity": "sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-core": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.25.tgz", - "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.26.tgz", + "integrity": "sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==", "license": "MIT", "dependencies": { "@babel/parser": "^7.28.5", - "@vue/compiler-core": "3.5.25", - "@vue/compiler-dom": "3.5.25", - "@vue/compiler-ssr": "3.5.25", - "@vue/shared": "3.5.25", + "@vue/compiler-core": "3.5.26", + "@vue/compiler-dom": "3.5.26", + "@vue/compiler-ssr": "3.5.26", + "@vue/shared": "3.5.26", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -3619,19 +3604,19 @@ "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.25.tgz", - "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.26.tgz", + "integrity": "sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.25", - "@vue/shared": "3.5.25" + "@vue/compiler-dom": "3.5.26", + "@vue/shared": "3.5.26" } }, "node_modules/@vue/shared": { - "version": "3.5.25", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", - "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", + "version": "3.5.26", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.26.tgz", + "integrity": "sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==", "license": "MIT" }, "node_modules/@webassemblyjs/ast": { @@ -4105,9 +4090,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.6.tgz", - "integrity": "sha512-v9BVVpOTLB59C9E7aSnmIF8h7qRsFpx+A2nugVMTszEOMcfjlZMsXRm4LF23I3Z9AJxc8ANpIvzbzONoX9VJlg==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -4494,9 +4479,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001760", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz", - "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==", + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", "devOptional": true, "funding": [ { @@ -4556,6 +4541,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -5019,9 +5013,9 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "devOptional": true, "license": "MIT", "peerDependencies": { @@ -5414,9 +5408,9 @@ } }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", + "integrity": "sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -5486,10 +5480,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", + "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "license": "MIT" }, "node_modules/es-object-atoms": { @@ -5506,9 +5499,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.1.tgz", - "integrity": "sha512-yY35KZckJJuVVPXpvjgxiCuVEJT67F6zDeVTv4rizyPrfGBUpZQsvmxnN+C371c2esD/hNMjj4tpBhuueLN7aA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "devOptional": true, "hasInstallScript": true, "license": "MIT", @@ -5519,32 +5512,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.1", - "@esbuild/android-arm": "0.27.1", - "@esbuild/android-arm64": "0.27.1", - "@esbuild/android-x64": "0.27.1", - "@esbuild/darwin-arm64": "0.27.1", - "@esbuild/darwin-x64": "0.27.1", - "@esbuild/freebsd-arm64": "0.27.1", - "@esbuild/freebsd-x64": "0.27.1", - "@esbuild/linux-arm": "0.27.1", - "@esbuild/linux-arm64": "0.27.1", - "@esbuild/linux-ia32": "0.27.1", - "@esbuild/linux-loong64": "0.27.1", - "@esbuild/linux-mips64el": "0.27.1", - "@esbuild/linux-ppc64": "0.27.1", - "@esbuild/linux-riscv64": "0.27.1", - "@esbuild/linux-s390x": "0.27.1", - "@esbuild/linux-x64": "0.27.1", - "@esbuild/netbsd-arm64": "0.27.1", - "@esbuild/netbsd-x64": "0.27.1", - "@esbuild/openbsd-arm64": "0.27.1", - "@esbuild/openbsd-x64": "0.27.1", - "@esbuild/openharmony-arm64": "0.27.1", - "@esbuild/sunos-x64": "0.27.1", - "@esbuild/win32-arm64": "0.27.1", - "@esbuild/win32-ia32": "0.27.1", - "@esbuild/win32-x64": "0.27.1" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -5598,15 +5591,6 @@ "source-map": "~0.6.1" } }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -5621,6 +5605,16 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/eslint-visitor-keys": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", @@ -5659,21 +5653,10 @@ "node": ">=4.0" } }, - "node_modules/esrecurse/node_modules/estraverse": { + "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -7106,10 +7089,11 @@ } }, "node_modules/less": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/less/-/less-4.4.2.tgz", - "integrity": "sha512-j1n1IuTX1VQjIy3tT7cyGbX7nvQOsFLoIqobZv4ttI5axP923gA44zUj6miiA6R5Aoms4sEGVIIcucXUbRI14g==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/less/-/less-4.5.1.tgz", + "integrity": "sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==", "devOptional": true, + "hasInstallScript": true, "license": "Apache-2.0", "peer": true, "dependencies": { @@ -7858,12 +7842,23 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/mlly": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", @@ -8112,7 +8107,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/node-module-type/-/node-module-type-1.0.4.tgz", "integrity": "sha512-jJEWdpQFDFhjA87debF8AgaBXPkHyFCw/6atsRwOGu/bpVwje1ZAl8/0WuwxrYNRUdWQ5YNUOT0ARSZeqjFlGA==", - "dev": true, "license": "MIT", "workspaces": [ "test/ambiguous", @@ -8371,7 +8365,6 @@ "integrity": "sha512-Kw6DlVJCG1HwArP3uF9kXc6nnAahpGaW7kZ7x1O7OugxbjSzkQqdKdA9loXCv7OeksFF/DfnLDupwqUjr1EOYQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@oxc-project/types": "^0.78.0" }, @@ -8399,6 +8392,23 @@ "@oxc-parser/binding-win32-x64-msvc": "0.78.0" } }, + "node_modules/oxc-parser/node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.78.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.78.0.tgz", + "integrity": "sha512-Pq0uT2CuN3J7Tv3KLuO7Sh4C7zTuqdJl0IDg3zB5keKx0BSbaEWewJL2CUNYUlG8txf+sMpUV+bkAIS5MEcKAw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/oxlint": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-0.4.4.tgz", @@ -9344,9 +9354,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.96.0", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.96.0.tgz", - "integrity": "sha512-8u4xqqUeugGNCYwr9ARNtQKTOj4KmYiJAVKXf2CTIivTCR51j96htbMKWDru8H5SaQWpyVgTfOF8Ylyf5pun1Q==", + "version": "1.97.1", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz", + "integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==", "devOptional": true, "license": "MIT", "peer": true, @@ -9493,9 +9503,9 @@ } }, "node_modules/send": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.1.tgz", - "integrity": "sha512-p4rRk4f23ynFEfcD9LA0xRYngj+IyGiEYyqqOak8kaN0TvNmuxC2dcVeBn62GpCeR2CpWqyHCNScTP91QbAVFg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", "dev": true, "license": "MIT", "dependencies": { @@ -9505,13 +9515,13 @@ "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "range-parser": "~1.2.1", - "statuses": "2.0.1" + "statuses": "~2.0.2" }, "engines": { "node": ">= 0.8.0" @@ -9534,33 +9544,6 @@ "dev": true, "license": "MIT" }, - "node_modules/send/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/serialize-javascript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", @@ -9658,100 +9641,21 @@ } }, "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", "dev": true, "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.19.0" + "send": "~0.19.1" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-static/node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/serve-static/node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10327,6 +10231,31 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.2.tgz", + "integrity": "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/terser": { "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", @@ -10795,9 +10724,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", - "integrity": "sha512-E85pfNzMQ9jpKkA7+TJAi4TJN+tBCuWh5rUcS/sv6cFi+1q9LYDwDI5dpUL0u/73EElyQ8d3TEaeW4sPedBqYA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "devOptional": true, "funding": [ { @@ -10940,9 +10869,9 @@ } }, "node_modules/webpack": { - "version": "5.103.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", - "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", + "version": "5.104.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz", + "integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==", "dev": true, "license": "MIT", "peer": true, @@ -10955,10 +10884,10 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.17.4", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", @@ -10969,7 +10898,7 @@ "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", + "terser-webpack-plugin": "^5.3.16", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, @@ -11054,7 +10983,6 @@ "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -11592,12 +11520,16 @@ }, "packages/css": { "name": "@knighted/css", - "version": "1.0.0-rc.7", + "version": "1.0.0-rc.8", "license": "MIT", "dependencies": { "dependency-tree": "^11.2.0", "es-module-lexer": "^2.0.0", - "lightningcss": "^1.30.2" + "lightningcss": "^1.30.2", + "node-module-type": "^1.0.1" + }, + "bin": { + "knighted-css-generate-types": "bin/generate-types.js" }, "peerDependencies": { "@vanilla-extract/integration": "^8.0.0", @@ -11616,18 +11548,12 @@ } } }, - "packages/css/node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "license": "MIT" - }, "packages/playwright": { "name": "@knighted/css-playwright-fixture", "version": "0.0.0", "dependencies": { - "@knighted/css": "1.0.0-rc.7", - "@knighted/jsx": "^1.2.1", + "@knighted/css": "1.0.0-rc.8", + "@knighted/jsx": "^1.4.1", "lit": "^3.2.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/package.json b/package.json index 624555b..c33cd52 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "packages/playwright" ], "engines": { - "node": ">= 22.15.0", + "node": ">= 22.17.0", "npm": ">= 10.9.0" }, "engineStrict": true, @@ -26,7 +26,7 @@ "clean:deps": "find . -name node_modules -type d -prune -exec rm -rf {} +", "clean:dist": "find . -name dist -type d -prune -exec rm -rf {} +", "clean": "npm run clean:deps && npm run clean:dist", - "setup:jsx-wasm": "node scripts/setup-jsx-wasm.mjs" + "setup:jsx-wasm": "npx @knighted/jsx init" }, "devDependencies": { "@emnapi/core": "^1.2.0", @@ -69,5 +69,8 @@ "*": "npm run check:cycles", "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts}": "oxlint", "*.{js,jsx,ts,tsx,mjs,cjs,cts,mts,json,md,css,scss,html}": "prettier --check" + }, + "optionalDependencies": { + "@oxc-parser/binding-wasm32-wasi": "^0.99.0" } } diff --git a/packages/css/bin/generate-types.js b/packages/css/bin/generate-types.js new file mode 100755 index 0000000..731f9db --- /dev/null +++ b/packages/css/bin/generate-types.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node + +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const distPath = path.resolve(__dirname, '../dist/generateTypes.js') + +async function main() { + try { + const mod = await import(pathToFileURL(distPath).href) + const runner = + typeof mod.runGenerateTypesCli === 'function' + ? mod.runGenerateTypesCli + : typeof mod.default === 'function' + ? mod.default + : undefined + if (typeof runner !== 'function') { + console.error('[knighted-css] Unable to load generateTypes CLI entry point.') + process.exitCode = 1 + return + } + await runner(process.argv.slice(2)) + } catch (error) { + console.error('[knighted-css] Failed to run generateTypes CLI.') + console.error(error) + process.exitCode = 1 + } +} + +void main() diff --git a/packages/css/loader-queries.d.ts b/packages/css/loader-queries.d.ts index 0a83678..ef1bfa7 100644 --- a/packages/css/loader-queries.d.ts +++ b/packages/css/loader-queries.d.ts @@ -6,6 +6,13 @@ declare module '*?knighted-css' { export const knightedCss: string } +type KnightedCssStableSelectorMap = Readonly> + +declare module '*?knighted-css&types' { + export const knightedCss: string + export const stableSelectors: KnightedCssStableSelectorMap +} + /** * Ambient declaration for combined loader imports (e.g. "./file.tsx?knighted-css&combined"). * These modules behave like the original module with an additional `knightedCss` export. @@ -31,3 +38,24 @@ declare module '*?knighted-css&combined&no-default' { export default combined export const knightedCss: string } + +declare module '*?knighted-css&combined&types' { + const combined: KnightedCssCombinedModule> + export default combined + export const knightedCss: string + export const stableSelectors: KnightedCssStableSelectorMap +} + +declare module '*?knighted-css&combined&named-only&types' { + const combined: KnightedCssCombinedModule> + export default combined + export const knightedCss: string + export const stableSelectors: KnightedCssStableSelectorMap +} + +declare module '*?knighted-css&combined&no-default&types' { + const combined: KnightedCssCombinedModule> + export default combined + export const knightedCss: string + export const stableSelectors: KnightedCssStableSelectorMap +} diff --git a/packages/css/package.json b/packages/css/package.json index a6a797d..d15f117 100644 --- a/packages/css/package.json +++ b/packages/css/package.json @@ -1,6 +1,6 @@ { "name": "@knighted/css", - "version": "1.0.0-rc.7", + "version": "1.0.0-rc.8", "description": "A build-time utility that traverses JavaScript/TypeScript module dependency graphs to extract, compile, and optimize all imported CSS into a single, in-memory string.", "type": "module", "main": "./dist/css.js", @@ -13,6 +13,9 @@ "loader-queries": [ "./loader-queries.d.ts" ], + "generate-types": [ + "./dist/generateTypes.d.ts" + ], "*": [ "./types.d.ts" ] @@ -33,6 +36,11 @@ "types": "./loader-queries.d.ts", "default": "./loader-queries.d.ts" }, + "./generate-types": { + "types": "./dist/generateTypes.d.ts", + "import": "./dist/generateTypes.js", + "default": "./dist/generateTypes.js" + }, "./stableSelectors": { "types": "./dist/stableSelectors.d.ts", "import": "./dist/stableSelectors.js", @@ -58,16 +66,20 @@ "vanilla-extract", "lightningcss" ], + "bin": { + "knighted-css-generate-types": "./bin/generate-types.js" + }, "scripts": { - "build": "duel", - "check-types": "tsc --noEmit", + "build": "duel && node ./scripts/copy-types-stub.js", + "check-types": "tsc --noEmit --project tsconfig.json && tsc --noEmit --project tsconfig.tests.json", "test": "c8 --reporter=text --reporter=text-summary --reporter=lcov --include \"src/**/*.ts\" tsx --test test/**/*.test.ts", "prepack": "npm run build" }, "dependencies": { "dependency-tree": "^11.2.0", "es-module-lexer": "^2.0.0", - "lightningcss": "^1.30.2" + "lightningcss": "^1.30.2", + "node-module-type": "^1.0.1" }, "overrides": { "module-lookup-amd": { @@ -94,7 +106,9 @@ "dist", "loader-queries.d.ts", "types.d.ts", - "stable" + "stable", + "bin", + "types-stub" ], "author": "KCM ", "license": "MIT", diff --git a/packages/css/scripts/copy-types-stub.js b/packages/css/scripts/copy-types-stub.js new file mode 100644 index 0000000..6dcbfc5 --- /dev/null +++ b/packages/css/scripts/copy-types-stub.js @@ -0,0 +1,24 @@ +import fs from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const packageRoot = path.resolve(__dirname, '..') +const sourceDir = path.join(packageRoot, 'src', 'types-stub') +const targetDir = path.join(packageRoot, 'types-stub') + +async function copyTypesStub() { + try { + await fs.rm(targetDir, { recursive: true, force: true }) + await fs.cp(sourceDir, targetDir, { recursive: true }) + } catch (error) { + console.error('[knighted-css] Failed to copy types stub from src/types-stub.') + throw error + } +} + +copyTypesStub().catch(error => { + console.error(error) + process.exitCode = 1 +}) diff --git a/packages/css/src/css.ts b/packages/css/src/css.ts index 09d1b1b..4a2b84c 100644 --- a/packages/css/src/css.ts +++ b/packages/css/src/css.ts @@ -237,14 +237,62 @@ async function compileSass( 'Sass', peerResolver, ) - const sass = sassModule + const sass = resolveSassNamespace(sassModule) const importer = createSassImporter({ cwd, resolver }) - const result = await sass.compileAsync(filePath, { - style: 'expanded', - loadPaths: buildSassLoadPaths(filePath), - importers: importer ? [importer] : undefined, + const loadPaths = buildSassLoadPaths(filePath) + + if (typeof (sass as { compileAsync?: Function }).compileAsync === 'function') { + const result = await ( + sass as { compileAsync: typeof import('sass').compileAsync } + ).compileAsync(filePath, { + style: 'expanded', + loadPaths, + importers: importer ? [importer] : undefined, + }) + return result.css + } + + if (typeof (sass as { render?: Function }).render === 'function') { + return renderLegacySass( + sass as { render: typeof import('sass').render }, + filePath, + indented, + loadPaths, + ) + } + + throw new Error( + '@knighted/css: Installed "sass" package does not expose compileAsync or render APIs. Please update "sass" to a supported version.', + ) +} + +function renderLegacySass( + sass: { render: typeof import('sass').render }, + filePath: string, + indented: boolean, + loadPaths: string[], +): Promise { + return new Promise((resolve, reject) => { + sass.render( + { + file: filePath, + indentedSyntax: indented, + outputStyle: 'expanded', + includePaths: loadPaths, + }, + (error, result) => { + if (error) { + reject(error) + return + } + if (!result || typeof result.css === 'undefined') { + resolve('') + return + } + resolve(result.css.toString()) + }, + ) }) - return result.css } // Ensure Sass can resolve bare module specifiers by walking node_modules folders. @@ -362,6 +410,31 @@ async function optionalPeer( } } +function resolveSassNamespace(mod: unknown): typeof import('sass') { + if (isSassNamespace(mod)) { + return mod + } + if ( + typeof mod === 'object' && + mod !== null && + 'default' in (mod as Record) + ) { + const candidate = (mod as Record).default + if (isSassNamespace(candidate)) { + return candidate + } + } + return mod as typeof import('sass') +} + +function isSassNamespace(candidate: unknown): candidate is typeof import('sass') { + if (typeof candidate !== 'object' || !candidate) { + return false + } + const namespace = candidate as Record + return typeof namespace.compile === 'function' || typeof namespace.render === 'function' +} + function unwrapModuleNamespace(mod: T): T { if ( typeof mod === 'object' && diff --git a/packages/css/src/generateTypes.ts b/packages/css/src/generateTypes.ts new file mode 100644 index 0000000..92b49ba --- /dev/null +++ b/packages/css/src/generateTypes.ts @@ -0,0 +1,656 @@ +import crypto from 'node:crypto' +import fs from 'node:fs/promises' +import path from 'node:path' +import { createRequire } from 'node:module' +import { fileURLToPath, pathToFileURL } from 'node:url' + +import { init, parse } from 'es-module-lexer' +import { moduleType } from 'node-module-type' + +import { cssWithMeta } from './css.js' +import { + determineSelectorVariant, + hasQueryFlag, + TYPES_QUERY_FLAG, + type SelectorTypeVariant, +} from './loaderInternals.js' +import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js' +import { resolveStableNamespace } from './stableNamespace.js' + +interface ManifestEntry { + file: string + hash: string +} + +type Manifest = Record + +interface ImportMatch { + specifier: string + importer: string +} + +interface DeclarationRecord { + specifier: string + filePath: string +} + +interface GenerateTypesInternalOptions { + rootDir: string + include: string[] + outDir: string + typesRoot: string + stableNamespace?: string +} + +export interface GenerateTypesResult { + written: number + removed: number + declarations: DeclarationRecord[] + warnings: string[] + outDir: string + typesIndexPath: string +} + +export interface GenerateTypesOptions { + rootDir?: string + include?: string[] + outDir?: string + typesRoot?: string + stableNamespace?: string +} + +const DEFAULT_SKIP_DIRS = new Set([ + 'node_modules', + '.git', + 'dist', + 'build', + 'coverage', + '.knighted-css', + '.next', + '.nuxt', + '.svelte-kit', + '.output', + 'tmp', +]) + +const SUPPORTED_EXTENSIONS = new Set([ + '.ts', + '.tsx', + '.js', + '.jsx', + '.mts', + '.cts', + '.mjs', + '.cjs', +]) + +function resolvePackageRoot(): string { + const detectedType = moduleType() + if (detectedType === 'commonjs' && typeof __dirname === 'string') { + return path.resolve(__dirname, '..') + } + const moduleUrl = getImportMetaUrl() + if (moduleUrl) { + return path.resolve(path.dirname(fileURLToPath(moduleUrl)), '..') + } + return path.resolve(process.cwd(), 'node_modules', '@knighted', 'css') +} + +function getImportMetaUrl(): string | undefined { + try { + return (0, eval)('import.meta.url') as string + } catch { + return undefined + } +} + +const PACKAGE_ROOT = resolvePackageRoot() +const DEFAULT_TYPES_ROOT = path.join(PACKAGE_ROOT, 'types-stub') +const DEFAULT_OUT_DIR = path.join(PACKAGE_ROOT, 'node_modules', '.knighted-css') + +export async function generateTypes( + options: GenerateTypesOptions = {}, +): Promise { + const rootDir = path.resolve(options.rootDir ?? process.cwd()) + const include = normalizeIncludeOptions(options.include, rootDir) + const outDir = path.resolve(options.outDir ?? DEFAULT_OUT_DIR) + const typesRoot = path.resolve(options.typesRoot ?? DEFAULT_TYPES_ROOT) + await init + await fs.mkdir(outDir, { recursive: true }) + await fs.mkdir(typesRoot, { recursive: true }) + + const internalOptions: GenerateTypesInternalOptions = { + rootDir, + include, + outDir, + typesRoot, + stableNamespace: options.stableNamespace, + } + + return generateDeclarations(internalOptions) +} + +async function generateDeclarations( + options: GenerateTypesInternalOptions, +): Promise { + const peerResolver = createProjectPeerResolver(options.rootDir) + const files = await collectCandidateFiles(options.include) + const manifestPath = path.join(options.outDir, 'manifest.json') + const previousManifest = await readManifest(manifestPath) + const nextManifest: Manifest = {} + const selectorCache = new Map>() + const processedSpecifiers = new Set() + const declarations: DeclarationRecord[] = [] + const warnings: string[] = [] + let writes = 0 + + for (const filePath of files) { + const matches = await findSpecifierImports(filePath) + for (const match of matches) { + const cleaned = match.specifier.trim() + const inlineFree = stripInlineLoader(cleaned) + if (!inlineFree.includes('?knighted-css')) continue + const { resource, query } = splitResourceAndQuery(inlineFree) + if (!query || !hasQueryFlag(query, TYPES_QUERY_FLAG)) { + continue + } + if (processedSpecifiers.has(cleaned)) { + continue + } + const resolvedNamespace = resolveStableNamespace(options.stableNamespace) + const resolvedPath = await resolveImportPath( + resource, + match.importer, + options.rootDir, + ) + if (!resolvedPath) { + warnings.push( + `Unable to resolve ${resource} referenced by ${relativeToRoot(match.importer, options.rootDir)}.`, + ) + continue + } + + const cacheKey = `${resolvedPath}::${resolvedNamespace}` + let selectorMap = selectorCache.get(cacheKey) + if (!selectorMap) { + try { + const { css } = await cssWithMeta(resolvedPath, { + cwd: options.rootDir, + peerResolver, + }) + selectorMap = buildStableSelectorsLiteral({ + css, + namespace: resolvedNamespace, + resourcePath: resolvedPath, + emitWarning: message => warnings.push(message), + }).selectorMap + } catch (error) { + warnings.push( + `Failed to extract CSS for ${relativeToRoot(resolvedPath, options.rootDir)}: ${formatErrorMessage(error)}`, + ) + continue + } + selectorCache.set(cacheKey, selectorMap) + } + + const variant = determineSelectorVariant(query) + const declaration = formatModuleDeclaration(cleaned, variant, selectorMap) + const declarationHash = hashContent(declaration) + const fileName = buildDeclarationFileName(cleaned) + const targetPath = path.join(options.outDir, fileName) + const previousEntry = previousManifest[cleaned] + const needsWrite = + previousEntry?.hash !== declarationHash || !(await fileExists(targetPath)) + if (needsWrite) { + await fs.writeFile(targetPath, declaration, 'utf8') + writes += 1 + } + nextManifest[cleaned] = { file: fileName, hash: declarationHash } + if (needsWrite) { + declarations.push({ specifier: cleaned, filePath: targetPath }) + } + processedSpecifiers.add(cleaned) + } + } + + const removed = await removeStaleDeclarations( + previousManifest, + nextManifest, + options.outDir, + ) + await writeManifest(manifestPath, nextManifest) + const typesIndexPath = path.join(options.typesRoot, 'index.d.ts') + await writeTypesIndex(typesIndexPath, nextManifest, options.outDir) + + if (Object.keys(nextManifest).length === 0) { + declarations.length = 0 + } + + return { + written: writes, + removed, + declarations, + warnings, + outDir: options.outDir, + typesIndexPath, + } +} + +function normalizeIncludeOptions( + include: string[] | undefined, + rootDir: string, +): string[] { + if (!include || include.length === 0) { + return [rootDir] + } + return include.map(entry => + path.isAbsolute(entry) ? entry : path.resolve(rootDir, entry), + ) +} + +async function collectCandidateFiles(entries: string[]): Promise { + const files: string[] = [] + const visited = new Set() + + async function walk(entryPath: string): Promise { + const resolved = path.resolve(entryPath) + if (visited.has(resolved)) { + return + } + visited.add(resolved) + let stat + try { + stat = await fs.stat(resolved) + } catch { + return + } + if (stat.isDirectory()) { + const base = path.basename(resolved) + if (DEFAULT_SKIP_DIRS.has(base)) { + return + } + const children = await fs.readdir(resolved, { withFileTypes: true }) + for (const child of children) { + await walk(path.join(resolved, child.name)) + } + return + } + if (!stat.isFile()) { + return + } + const ext = path.extname(resolved).toLowerCase() + if (!SUPPORTED_EXTENSIONS.has(ext)) { + return + } + files.push(resolved) + } + + for (const entry of entries) { + await walk(entry) + } + + return files +} + +async function findSpecifierImports(filePath: string): Promise { + let source: string + try { + source = await fs.readFile(filePath, 'utf8') + } catch { + return [] + } + if (!source.includes('?knighted-css')) { + return [] + } + const matches: ImportMatch[] = [] + const [imports] = parse(source, filePath) + for (const record of imports) { + const specifier = record.n ?? source.slice(record.s, record.e) + if (specifier && specifier.includes('?knighted-css')) { + matches.push({ specifier, importer: filePath }) + } + } + const requireRegex = /require\((['"])([^'"`]+?\?knighted-css[^'"`]*)\1\)/g + let reqMatch: RegExpExecArray | null + while ((reqMatch = requireRegex.exec(source)) !== null) { + const spec = reqMatch[2] + if (spec) { + matches.push({ specifier: spec, importer: filePath }) + } + } + return matches +} + +function stripInlineLoader(specifier: string): string { + const idx = specifier.lastIndexOf('!') + return idx >= 0 ? specifier.slice(idx + 1) : specifier +} + +function splitResourceAndQuery(specifier: string): { resource: string; query: string } { + const hashIndex = specifier.indexOf('#') + const trimmed = hashIndex >= 0 ? specifier.slice(0, hashIndex) : specifier + const queryIndex = trimmed.indexOf('?') + if (queryIndex < 0) { + return { resource: trimmed, query: '' } + } + return { resource: trimmed.slice(0, queryIndex), query: trimmed.slice(queryIndex) } +} + +const projectRequireCache = new Map>() + +async function resolveImportPath( + resourceSpecifier: string, + importerPath: string, + rootDir: string, +): Promise { + if (!resourceSpecifier) return undefined + if (resourceSpecifier.startsWith('.')) { + return path.resolve(path.dirname(importerPath), resourceSpecifier) + } + if (resourceSpecifier.startsWith('/')) { + return path.resolve(rootDir, resourceSpecifier.slice(1)) + } + const requireFromRoot = getProjectRequire(rootDir) + try { + return requireFromRoot.resolve(resourceSpecifier) + } catch { + return undefined + } +} + +function buildDeclarationFileName(specifier: string): string { + const digest = crypto.createHash('sha1').update(specifier).digest('hex').slice(0, 12) + return `knt-${digest}.d.ts` +} + +function formatModuleDeclaration( + specifier: string, + variant: SelectorTypeVariant, + selectors: Map, +): string { + const literalSpecifier = JSON.stringify(specifier) + const selectorType = formatSelectorType(selectors) + const header = `declare module ${literalSpecifier} {` + const footer = '}' + if (variant === 'types') { + return `${header} + export const knightedCss: string + export const stableSelectors: ${selectorType} +${footer} +` + } + const stableLine = ` export const stableSelectors: ${selectorType}` + const shared = ` const combined: KnightedCssCombinedModule> + export const knightedCss: string +${stableLine}` + if (variant === 'combined') { + return `${header} +${shared} + export default combined +${footer} +` + } + return `${header} +${shared} +${footer} +` +} + +function formatSelectorType(selectors: Map): string { + if (selectors.size === 0) { + return 'Readonly>' + } + const entries = Array.from(selectors.entries()).sort(([a], [b]) => a.localeCompare(b)) + const lines = entries.map( + ([token, selector]) => + ` readonly ${JSON.stringify(token)}: ${JSON.stringify(selector)}`, + ) + return `Readonly<{ +${lines.join('\n')} + }>` +} + +function hashContent(content: string): string { + return crypto.createHash('sha1').update(content).digest('hex') +} + +async function readManifest(manifestPath: string): Promise { + try { + const raw = await fs.readFile(manifestPath, 'utf8') + return JSON.parse(raw) as Manifest + } catch { + return {} + } +} + +async function writeManifest(manifestPath: string, manifest: Manifest): Promise { + await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf8') +} + +async function removeStaleDeclarations( + previous: Manifest, + next: Manifest, + outDir: string, +): Promise { + const stale = Object.entries(previous).filter(([specifier]) => !next[specifier]) + let removed = 0 + for (const [, entry] of stale) { + const targetPath = path.join(outDir, entry.file) + try { + await fs.unlink(targetPath) + removed += 1 + } catch { + // ignore + } + } + return removed +} + +async function writeTypesIndex( + indexPath: string, + manifest: Manifest, + outDir: string, +): Promise { + const header = '// Generated by @knighted/css/generate-types\n// Do not edit.\n' + const references = Object.values(manifest) + .sort((a, b) => a.file.localeCompare(b.file)) + .map(entry => { + const rel = path + .relative(path.dirname(indexPath), path.join(outDir, entry.file)) + .split(path.sep) + .join('/') + return `/// ` + }) + const content = + references.length > 0 + ? `${header} +${references.join('\n')} +` + : `${header} +` + await fs.writeFile(indexPath, content, 'utf8') +} + +function formatErrorMessage(error: unknown): string { + if (error instanceof Error && typeof error.message === 'string') { + return error.message + } + return String(error) +} + +function relativeToRoot(filePath: string, rootDir: string): string { + return path.relative(rootDir, filePath) || filePath +} + +async function fileExists(target: string): Promise { + try { + await fs.access(target) + return true + } catch { + return false + } +} + +function createProjectPeerResolver(rootDir: string) { + const resolver = getProjectRequire(rootDir) + return async (name: string) => { + const resolved = resolver.resolve(name) + return import(pathToFileURL(resolved).href) + } +} + +function getProjectRequire(rootDir: string): ReturnType { + const cached = projectRequireCache.get(rootDir) + if (cached) { + return cached + } + const anchor = path.join(rootDir, 'package.json') + let loader: ReturnType + try { + loader = createRequire(anchor) + } catch { + loader = createRequire(path.join(process.cwd(), 'package.json')) + } + projectRequireCache.set(rootDir, loader) + return loader +} + +export async function runGenerateTypesCli(argv = process.argv.slice(2)): Promise { + let parsed: ParsedCliArgs + try { + parsed = parseCliArgs(argv) + } catch (error) { + console.error(`[knighted-css] ${formatErrorMessage(error)}`) + process.exitCode = 1 + return + } + if (parsed.help) { + printHelp() + return + } + try { + const result = await generateTypes({ + rootDir: parsed.rootDir, + include: parsed.include, + outDir: parsed.outDir, + typesRoot: parsed.typesRoot, + stableNamespace: parsed.stableNamespace, + }) + reportCliResult(result) + } catch (error) { + console.error('[knighted-css] generate-types failed.') + console.error(error) + process.exitCode = 1 + } +} + +export interface ParsedCliArgs { + rootDir: string + include?: string[] + outDir?: string + typesRoot?: string + stableNamespace?: string + help?: boolean +} + +function parseCliArgs(argv: string[]): ParsedCliArgs { + let rootDir = process.cwd() + const include: string[] = [] + let outDir: string | undefined + let typesRoot: string | undefined + let stableNamespace: string | undefined + + for (let i = 0; i < argv.length; i += 1) { + const arg = argv[i] + if (arg === '--help' || arg === '-h') { + return { rootDir, include, outDir, typesRoot, stableNamespace, help: true } + } + if (arg === '--root' || arg === '-r') { + const value = argv[++i] + if (!value) { + throw new Error('Missing value for --root') + } + rootDir = path.resolve(value) + continue + } + if (arg === '--include' || arg === '-i') { + const value = argv[++i] + if (!value) { + throw new Error('Missing value for --include') + } + include.push(value) + continue + } + if (arg === '--out-dir') { + const value = argv[++i] + if (!value) { + throw new Error('Missing value for --out-dir') + } + outDir = value + continue + } + if (arg === '--types-root') { + const value = argv[++i] + if (!value) { + throw new Error('Missing value for --types-root') + } + typesRoot = value + continue + } + if (arg === '--stable-namespace') { + const value = argv[++i] + if (!value) { + throw new Error('Missing value for --stable-namespace') + } + stableNamespace = value + continue + } + if (arg.startsWith('-')) { + throw new Error(`Unknown flag: ${arg}`) + } + include.push(arg) + } + + return { rootDir, include, outDir, typesRoot, stableNamespace } +} + +function printHelp(): void { + console.log(`Usage: knighted-css-generate-types [options] + +Options: + -r, --root Project root directory (default: cwd) + -i, --include Additional directories/files to scan (repeatable) + --out-dir Output directory for generated declarations + --types-root Directory for generated @types entrypoint + --stable-namespace Stable namespace prefix for generated selector maps + -h, --help Show this help message +`) +} + +function reportCliResult(result: GenerateTypesResult): void { + if (result.written === 0 && result.removed === 0) { + console.log( + '[knighted-css] No changes to ?knighted-css&types declarations (cache is up to date).', + ) + } else { + console.log( + `[knighted-css] Updated ${result.written} declaration(s), removed ${result.removed}, output in ${result.outDir}.`, + ) + } + console.log(`[knighted-css] Type references: ${result.typesIndexPath}`) + for (const warning of result.warnings) { + console.warn(`[knighted-css] ${warning}`) + } +} + +export const __generateTypesInternals = { + stripInlineLoader, + splitResourceAndQuery, + buildDeclarationFileName, + formatModuleDeclaration, + formatSelectorType, + normalizeIncludeOptions, + parseCliArgs, + printHelp, + reportCliResult, +} diff --git a/packages/css/src/loader.ts b/packages/css/src/loader.ts index 125c900..825a1fd 100644 --- a/packages/css/src/loader.ts +++ b/packages/css/src/loader.ts @@ -8,13 +8,15 @@ import { cssWithMeta, compileVanillaModule, type CssOptions } from './css.js' import { detectModuleDefaultExport, type ModuleDefaultSignal } from './moduleInfo.js' import { buildSanitizedQuery, - COMBINED_QUERY_FLAG, - isQueryFlag, - NAMED_ONLY_QUERY_FLAGS, + hasCombinedQuery, + hasNamedOnlyQueryFlag, + hasQueryFlag, shouldEmitCombinedDefault, shouldForwardDefaultExport, - splitQuery, + TYPES_QUERY_FLAG, } from './loaderInternals.js' +import { buildStableSelectorsLiteral } from './stableSelectorsLiteral.js' +import { resolveStableNamespace } from './stableNamespace.js' export type KnightedCssCombinedModule = TModule & { knightedCss: string @@ -26,6 +28,7 @@ export interface KnightedCssVanillaOptions { export interface KnightedCssLoaderOptions extends CssOptions { vanilla?: KnightedCssVanillaOptions + stableNamespace?: string } const DEFAULT_EXPORT_NAME = 'knightedCss' @@ -33,9 +36,25 @@ const DEFAULT_EXPORT_NAME = 'knightedCss' const loader: LoaderDefinitionFunction = async function loader( source: string | Buffer, ) { - const { cssOptions, vanillaOptions } = resolveLoaderOptions(this) + const { + cssOptions, + vanillaOptions, + stableNamespace: optionNamespace, + } = resolveLoaderOptions(this) + const resolvedNamespace = resolveStableNamespace(optionNamespace) + const typesRequested = hasQueryFlag(this.resourceQuery, TYPES_QUERY_FLAG) const css = await extractCss(this, cssOptions) - const injection = buildInjection(css) + const stableSelectorsLiteral = typesRequested + ? buildStableSelectorsLiteral({ + css, + namespace: resolvedNamespace, + resourcePath: this.resourcePath, + emitWarning: message => emitKnightedWarning(this, message), + }) + : undefined + const injection = buildInjection(css, { + stableSelectorsLiteral: stableSelectorsLiteral?.literal, + }) const isStyleModule = this.resourcePath.endsWith('.css.ts') if (isStyleModule) { const { source: compiledSource } = await compileVanillaModule( @@ -91,7 +110,9 @@ export const pitch: PitchLoaderDefinitionFunction = } const request = buildProxyRequest(this) - const { cssOptions } = resolveLoaderOptions(this) + const { cssOptions, stableNamespace: optionNamespace } = resolveLoaderOptions(this) + const typesRequested = hasQueryFlag(this.resourceQuery, TYPES_QUERY_FLAG) + const resolvedNamespace = resolveStableNamespace(optionNamespace) const skipSyntheticDefault = hasNamedOnlyQueryFlag(this.resourceQuery) const defaultSignalPromise = skipSyntheticDefault ? Promise.resolve('unknown') @@ -104,7 +125,18 @@ export const pitch: PitchLoaderDefinitionFunction = skipSyntheticDefault, detection: defaultSignal, }) - return createCombinedModule(request, css, { emitDefault }) + const stableSelectorsLiteral = typesRequested + ? buildStableSelectorsLiteral({ + css, + namespace: resolvedNamespace, + resourcePath: this.resourcePath, + emitWarning: message => emitKnightedWarning(this, message), + }) + : undefined + return createCombinedModule(request, css, { + emitDefault, + stableSelectorsLiteral: stableSelectorsLiteral?.literal, + }) }, ) } @@ -115,11 +147,12 @@ export default loader function resolveLoaderOptions(ctx: LoaderContext): { cssOptions: CssOptions vanillaOptions?: KnightedCssVanillaOptions + stableNamespace?: string } { const rawOptions = ( typeof ctx.getOptions === 'function' ? ctx.getOptions() : {} ) as KnightedCssLoaderOptions - const { vanilla, ...rest } = rawOptions + const { vanilla, stableNamespace, ...rest } = rawOptions const cssOptions: CssOptions = { ...rest, cwd: rest.cwd ?? ctx.rootContext ?? process.cwd(), @@ -127,6 +160,7 @@ function resolveLoaderOptions(ctx: LoaderContext): { return { cssOptions, vanillaOptions: vanilla, + stableNamespace, } } @@ -146,26 +180,15 @@ function toSourceString(source: string | Buffer): string { return typeof source === 'string' ? source : source.toString('utf8') } -function buildInjection(css: string): string { - return `\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n` -} - -function hasCombinedQuery(query?: string | null): boolean { - if (!query) return false - const trimmed = query.startsWith('?') ? query.slice(1) : query - if (!trimmed) return false - return trimmed - .split('&') - .filter(Boolean) - .some(part => isQueryFlag(part, COMBINED_QUERY_FLAG)) -} - -function hasNamedOnlyQueryFlag(query?: string | null): boolean { - if (!query) return false - const entries = splitQuery(query) - return entries.some(part => - NAMED_ONLY_QUERY_FLAGS.some(flag => isQueryFlag(part, flag)), - ) +function buildInjection( + css: string, + extras?: { stableSelectorsLiteral?: string }, +): string { + const lines = [`\n\nexport const ${DEFAULT_EXPORT_NAME} = ${JSON.stringify(css)};\n`] + if (extras?.stableSelectorsLiteral) { + lines.push(extras.stableSelectorsLiteral) + } + return lines.join('') } function buildProxyRequest(ctx: LoaderContext): string { @@ -203,6 +226,7 @@ function stripResourceQuery(request: string): string { interface CombinedModuleOptions { emitDefault?: boolean + stableSelectorsLiteral?: string } function createCombinedModule( @@ -227,6 +251,21 @@ typeof __knightedModule.default !== 'undefined' ) } - lines.push(buildInjection(css)) + lines.push( + buildInjection(css, { stableSelectorsLiteral: options?.stableSelectorsLiteral }), + ) return lines.join('\n') } + +function emitKnightedWarning( + ctx: LoaderContext, + message: string, +): void { + const formatted = `\x1b[33m@knighted/css warning\x1b[0m ${message}` + if (typeof ctx.emitWarning === 'function') { + ctx.emitWarning(new Error(formatted)) + return + } + // eslint-disable-next-line no-console + console.warn(formatted) +} diff --git a/packages/css/src/loaderInternals.ts b/packages/css/src/loaderInternals.ts index e57c7eb..8576793 100644 --- a/packages/css/src/loaderInternals.ts +++ b/packages/css/src/loaderInternals.ts @@ -1,7 +1,9 @@ import type { ModuleDefaultSignal } from './moduleInfo.js' export const COMBINED_QUERY_FLAG = 'combined' +export const TYPES_QUERY_FLAG = 'types' export const NAMED_ONLY_QUERY_FLAGS = ['named-only', 'no-default'] as const +export type SelectorTypeVariant = 'types' | 'combined' | 'combinedWithoutDefault' export function splitQuery(query: string): string[] { const trimmed = query.startsWith('?') ? query.slice(1) : query @@ -27,6 +29,9 @@ export function buildSanitizedQuery(query?: string | null): string { if (isQueryFlag(part, 'knighted-css')) { return false } + if (isQueryFlag(part, TYPES_QUERY_FLAG)) { + return false + } if (NAMED_ONLY_QUERY_FLAGS.some(flag => isQueryFlag(part, flag))) { return false } @@ -35,6 +40,21 @@ export function buildSanitizedQuery(query?: string | null): string { return entries.length > 0 ? `?${entries.join('&')}` : '' } +export function hasQueryFlag(query: string | null | undefined, flag: string): boolean { + if (!query) return false + const entries = splitQuery(query) + if (entries.length === 0) return false + return entries.some(part => isQueryFlag(part, flag)) +} + +function safeDecode(value: string): string { + try { + return decodeURIComponent(value) + } catch { + return value + } +} + export function shouldForwardDefaultExport(request: string): boolean { const [pathPart] = request.split('?') if (!pathPart) return true @@ -45,6 +65,21 @@ export function shouldForwardDefaultExport(request: string): boolean { return true } +export function hasCombinedQuery(query?: string | null): boolean { + return hasQueryFlag(query, COMBINED_QUERY_FLAG) +} + +export function hasNamedOnlyQueryFlag(query?: string | null): boolean { + return NAMED_ONLY_QUERY_FLAGS.some(flag => hasQueryFlag(query, flag)) +} + +export function determineSelectorVariant(query?: string | null): SelectorTypeVariant { + if (hasCombinedQuery(query)) { + return hasNamedOnlyQueryFlag(query) ? 'combinedWithoutDefault' : 'combined' + } + return 'types' +} + export function shouldEmitCombinedDefault(options: { detection: ModuleDefaultSignal request: string @@ -68,4 +103,5 @@ export function shouldEmitCombinedDefault(options: { export const __loaderInternals = { buildSanitizedQuery, shouldEmitCombinedDefault, + determineSelectorVariant, } diff --git a/packages/css/src/stableNamespace.ts b/packages/css/src/stableNamespace.ts new file mode 100644 index 0000000..e2bc489 --- /dev/null +++ b/packages/css/src/stableNamespace.ts @@ -0,0 +1,10 @@ +const DEFAULT_STABLE_NAMESPACE = 'knighted' + +export function resolveStableNamespace(optionNamespace?: string): string { + if (typeof optionNamespace === 'string') { + return optionNamespace + } + return DEFAULT_STABLE_NAMESPACE +} + +export { DEFAULT_STABLE_NAMESPACE } diff --git a/packages/css/src/stableSelectorsLiteral.ts b/packages/css/src/stableSelectorsLiteral.ts new file mode 100644 index 0000000..2086c2c --- /dev/null +++ b/packages/css/src/stableSelectorsLiteral.ts @@ -0,0 +1,131 @@ +import { transform as lightningTransform } from 'lightningcss' + +import { escapeRegex, serializeSelector } from './helpers.js' + +export interface StableSelectorsLiteralResult { + literal: string + selectorMap: Map +} + +export function buildStableSelectorsLiteral(options: { + css: string + namespace: string + resourcePath: string + emitWarning: (message: string) => void +}): StableSelectorsLiteralResult { + const trimmedNamespace = options.namespace.trim() + if (!trimmedNamespace) { + options.emitWarning( + `stableSelectors requested for ${options.resourcePath} but "stableNamespace" resolved to an empty value.`, + ) + return { + literal: 'export const stableSelectors = {} as const;\n', + selectorMap: new Map(), + } + } + + const selectorMap = collectStableSelectors( + options.css, + trimmedNamespace, + options.resourcePath, + ) + if (selectorMap.size === 0) { + options.emitWarning( + `stableSelectors requested for ${options.resourcePath} but no selectors matched namespace "${trimmedNamespace}".`, + ) + } + + return { + literal: `export const stableSelectors = ${formatStableSelectorMap(selectorMap)} as const;\n`, + selectorMap, + } +} + +export function collectStableSelectors( + css: string, + namespace: string, + filename?: string, +): Map { + if (!namespace) return new Map() + const astResult = collectStableSelectorsFromAst(css, namespace, filename) + if (astResult) { + return astResult + } + return collectStableSelectorsByRegex(css, namespace) +} + +function collectStableSelectorsFromAst( + css: string, + namespace: string, + filename?: string, +): Map | undefined { + try { + const tokens = new Map() + const escaped = escapeRegex(namespace) + const pattern = new RegExp(`\\.${escaped}-([A-Za-z0-9_-]+)`, 'g') + lightningTransform({ + filename: filename ?? 'knighted-types-probe.css', + code: Buffer.from(css), + minify: false, + visitor: { + Rule: { + style(rule: any) { + const target = Array.isArray(rule?.selectors) + ? rule + : rule?.value && Array.isArray(rule.value.selectors) + ? rule.value + : undefined + if (!target) return rule + for (const selector of target.selectors) { + const selectorStr = serializeSelector(selector as any) + pattern.lastIndex = 0 + let match: RegExpExecArray | null + while ((match = pattern.exec(selectorStr)) !== null) { + const token = match[1] + if (!token) continue + tokens.set(token, `${namespace}-${token}`) + } + } + return rule + }, + }, + }, + }) + return tokens + } catch { + return undefined + } +} + +function collectStableSelectorsByRegex( + css: string, + namespace: string, +): Map { + const escaped = escapeRegex(namespace) + const pattern = new RegExp(`\\.${escaped}-([A-Za-z0-9_-]+)`, 'g') + const tokens = new Map() + let match: RegExpExecArray | null + while ((match = pattern.exec(css)) !== null) { + const token = match[1] + if (!token) continue + tokens.set(token, `${namespace}-${token}`) + } + return tokens +} + +export function formatStableSelectorMap(map: Map): string { + if (map.size === 0) { + return 'Object.freeze({})' + } + const entries = Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b)) + const lines = entries.map(([token, selector]) => { + return ` ${JSON.stringify(token)}: ${JSON.stringify(selector)}` + }) + return `Object.freeze({\n${lines.join(',\n')}\n})` +} + +export const __stableSelectorsLiteralInternals = { + collectStableSelectors, + collectStableSelectorsByRegex, + formatStableSelectorMap, +} diff --git a/packages/css/src/types-stub/index.d.ts b/packages/css/src/types-stub/index.d.ts new file mode 100644 index 0000000..80195d5 --- /dev/null +++ b/packages/css/src/types-stub/index.d.ts @@ -0,0 +1,5 @@ +// Placeholder stub for @knighted/css stable selector declarations. +// Running `knighted-css-generate-types` replaces this file with project-specific +// references into the generated `.knighted-css` manifest. + +export {} diff --git a/packages/css/test/compileSassFallbacks.test.ts b/packages/css/test/compileSassFallbacks.test.ts new file mode 100644 index 0000000..4ad548e --- /dev/null +++ b/packages/css/test/compileSassFallbacks.test.ts @@ -0,0 +1,150 @@ +import assert from 'node:assert/strict' +import path from 'node:path' +import test from 'node:test' +import { fileURLToPath } from 'node:url' + +import { cssWithMeta } from '../src/css.js' + +import type { LegacyException, LegacyFileOptions, LegacyResult } from 'sass' + +type CompileAsyncArgs = Parameters<(typeof import('sass'))['compileAsync']> +type RenderArgs = Parameters<(typeof import('sass'))['render']> + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const sassEntry = path.join(__dirname, 'fixtures/dialects/sass/styles.scss') +const lessEntry = path.join(__dirname, 'fixtures/dialects/less/theme.less') + +function createLegacyResult(css: string): LegacyResult { + const timestamp = Date.now() + return { + css: Buffer.from(css), + map: undefined, + stats: { + entry: sassEntry, + start: timestamp, + end: timestamp, + duration: 0, + includedFiles: [], + }, + } +} + +function createLegacyException(message: string): LegacyException { + const error = new Error(message) as LegacyException + error.formatted = message + error.status = 1 + return error +} + +test('compileSass prefers compileAsync from the namespace default export', async () => { + const calls: CompileAsyncArgs[] = [] + const cssOutput = '.async { color: salmon; }' + + const peerResolver = async (name: string) => { + assert.equal(name, 'sass') + return { + default: { + compile() {}, + async compileAsync(...args: CompileAsyncArgs) { + calls.push(args) + return { css: cssOutput } + }, + }, + } + } + + const result = await cssWithMeta(sassEntry, { peerResolver }) + + assert.equal(result.css, cssOutput) + assert.ok(calls.length >= 1) + const [filePath, options] = calls[0] + assert.equal(filePath, sassEntry) + if (!options) { + assert.fail('compileAsync was invoked without options') + } + const loadPaths = Array.isArray(options.loadPaths) ? options.loadPaths : [] + assert.ok(loadPaths.length > 0) +}) + +test('compileSass falls back to render when compileAsync is missing', async () => { + const calls: RenderArgs[] = [] + const cssOutput = '.legacy { color: teal; }' + + const peerResolver = async () => ({ + render(options: RenderArgs[0], callback: RenderArgs[1]) { + calls.push([options, callback]) + setImmediate(() => callback(undefined, createLegacyResult(cssOutput))) + }, + }) + + const result = await cssWithMeta(sassEntry, { peerResolver }) + + assert.equal(result.css, cssOutput) + assert.equal(calls.length, 1) + const [options] = calls[0] as [LegacyFileOptions<'async'>, RenderArgs[1]] + assert.equal(options.file, sassEntry) + assert.ok(Array.isArray(options.includePaths)) +}) + +test('renderLegacySass resolves empty string when result payload is missing', async () => { + const peerResolver = async () => ({ + render(options: RenderArgs[0], callback: RenderArgs[1]) { + setImmediate(() => callback(undefined, undefined)) + }, + }) + + const result = await cssWithMeta(sassEntry, { peerResolver }) + + assert.equal(result.css, '') +}) + +test('renderLegacySass rejects when the renderer throws', async () => { + const peerResolver = async () => ({ + render(options: RenderArgs[0], callback: RenderArgs[1]) { + setImmediate(() => callback(createLegacyException('sass boom'))) + }, + }) + + await assert.rejects(() => cssWithMeta(sassEntry, { peerResolver }), /sass boom/) +}) + +test('compileSass throws when Sass peer lacks supported APIs', async () => { + const peerResolver = async () => ({}) + + await assert.rejects( + () => cssWithMeta(sassEntry, { peerResolver }), + /does not expose compileAsync or render APIs/i, + ) +}) + +test('resolveSassNamespace ignores falsy default exports', async () => { + const peerResolver = async () => ({ + default: null, + }) + + await assert.rejects( + () => cssWithMeta(sassEntry, { peerResolver }), + /does not expose compileAsync or render APIs/i, + ) +}) + +test('compileLess accepts modules without default exports', async () => { + const peerResolver = async (name: string) => { + if (name === 'less') { + return { + render() { + return Promise.resolve({ + css: '.less { color: blue; }', + map: '', + imports: [], + }) + }, + } + } + return import(name) + } + + const result = await cssWithMeta(lessEntry, { peerResolver }) + + assert.equal(result.css, '.less { color: blue; }') +}) diff --git a/packages/css/test/fixtures/dialects/basic/styles.css b/packages/css/test/fixtures/dialects/basic/styles.css index 4584ac8..e537e8c 100644 --- a/packages/css/test/fixtures/dialects/basic/styles.css +++ b/packages/css/test/fixtures/dialects/basic/styles.css @@ -1,3 +1,15 @@ .demo { color: rebeccapurple; } + +.knighted-demo { + color: teal; +} + +.knighted-icon { + display: inline-flex; +} + +.acme-card { + border: 1px solid currentColor; +} diff --git a/packages/css/test/generateTypes.test.ts b/packages/css/test/generateTypes.test.ts new file mode 100644 index 0000000..d1c9277 --- /dev/null +++ b/packages/css/test/generateTypes.test.ts @@ -0,0 +1,190 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs/promises' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' +import { fileURLToPath } from 'node:url' + +import { + generateTypes, + __generateTypesInternals, + type ParsedCliArgs, +} from '../src/generateTypes.ts' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +async function setupFixtureProject(): Promise<{ + root: string + cleanup: () => Promise +}> { + const tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'knighted-generate-types-')) + const srcDir = path.join(tmpRoot, 'src') + await fs.mkdir(srcDir, { recursive: true }) + const fixtureSource = path.join(__dirname, 'fixtures', 'dialects', 'basic', 'entry.js') + const relativeImport = path.relative(srcDir, fixtureSource).split(path.sep).join('/') + const specifier = `${relativeImport}?knighted-css&types` + const entrySource = `import { stableSelectors } from '${specifier}' +console.log(stableSelectors.demo) +` + await fs.writeFile(path.join(srcDir, 'entry.ts'), entrySource) + return { + root: tmpRoot, + cleanup: () => fs.rm(tmpRoot, { recursive: true, force: true }), + } +} + +test('generateTypes emits declarations and reuses cache', async () => { + const project = await setupFixtureProject() + try { + const outDir = path.join(project.root, '.knighted-css-test') + const typesRoot = path.join(project.root, '.knighted-css-types') + const sharedOptions = { rootDir: project.root, include: ['src'], outDir, typesRoot } + + const firstRun = await generateTypes(sharedOptions) + assert.ok(firstRun.written >= 1) + assert.equal(firstRun.removed, 0) + assert.equal(firstRun.warnings.length, 0) + + const manifestPath = path.join(firstRun.outDir, 'manifest.json') + const manifestRaw = await fs.readFile(manifestPath, 'utf8') + const manifest = JSON.parse(manifestRaw) as Record + const entries = Object.values(manifest) + assert.equal(entries.length, 1) + const declarationPath = path.join(firstRun.outDir, entries[0]?.file ?? '') + const declaration = await fs.readFile(declarationPath, 'utf8') + assert.ok(declaration.includes('stableSelectors')) + assert.ok(declaration.includes('knighted-demo')) + + const indexContent = await fs.readFile(firstRun.typesIndexPath, 'utf8') + assert.ok(indexContent.includes(entries[0]?.file ?? '')) + + const secondRun = await generateTypes(sharedOptions) + assert.equal(secondRun.written, 0) + assert.equal(secondRun.removed, 0) + assert.equal(secondRun.warnings.length, 0) + } finally { + await project.cleanup() + } +}) + +test('generateTypes internals format selector-aware declarations', () => { + const { + stripInlineLoader, + splitResourceAndQuery, + buildDeclarationFileName, + formatSelectorType, + formatModuleDeclaration, + normalizeIncludeOptions, + parseCliArgs, + printHelp, + reportCliResult, + } = __generateTypesInternals + + assert.equal( + stripInlineLoader('style-loader!css-loader!./demo.css?knighted-css&types'), + './demo.css?knighted-css&types', + ) + + assert.deepEqual(splitResourceAndQuery('./demo.css?knighted-css#hash'), { + resource: './demo.css', + query: '?knighted-css', + }) + + const selectorMap = new Map([ + ['beta', 'knighted-beta'], + ['alpha', 'knighted-alpha'], + ]) + const hashedName = buildDeclarationFileName('./demo.css?knighted-css') + assert.match(hashedName, /^knt-[a-f0-9]{12}\.d\.ts$/) + const selectorType = formatSelectorType(selectorMap) + assert.match(selectorType, /readonly "alpha": "knighted-alpha"/) + assert.match(selectorType, /readonly "beta": "knighted-beta"/) + + const declaration = formatModuleDeclaration( + './demo.css?knighted-css&combined', + 'combined', + selectorMap, + ) + assert.match(declaration, /declare module/) + assert.match(declaration, /export const stableSelectors/) + + const withoutDefault = formatModuleDeclaration( + './demo.css?knighted-css&combined&named-only', + 'combinedWithoutDefault', + selectorMap, + ) + assert.doesNotMatch(withoutDefault, /export default/) + + const normalized = normalizeIncludeOptions(undefined, '/tmp/demo') + assert.deepEqual(normalized, ['/tmp/demo']) + assert.deepEqual(normalizeIncludeOptions(['./src'], '/tmp/demo'), [ + path.resolve('/tmp/demo', './src'), + ]) + + const parsed = parseCliArgs([ + '--root', + '/tmp/project', + '--include', + 'src', + '--stable-namespace', + 'storybook', + '--out-dir', + '.knighted-css', + '--types-root', + './types', + ]) as ParsedCliArgs + assert.equal(parsed.rootDir, path.resolve('/tmp/project')) + assert.deepEqual(parsed.include, ['src']) + assert.equal(parsed.stableNamespace, 'storybook') + + assert.throws(() => parseCliArgs(['--root']), /Missing value/) + assert.throws(() => parseCliArgs(['--stable-namespace']), /Missing value/) + assert.throws(() => parseCliArgs(['--wat']), /Unknown flag/) + + const printed: string[] = [] + const logged = console.log + try { + console.log = (message: string) => printed.push(message) + printHelp() + } finally { + console.log = logged + } + assert.ok(printed.join('\n').includes('Usage: knighted-css-generate-types')) + + const summaryLogs: string[] = [] + const summaryWarns: string[] = [] + const originalLog = console.log + const originalWarn = console.warn + try { + console.log = (msg: string) => summaryLogs.push(msg) + console.warn = (msg: string) => summaryWarns.push(msg) + reportCliResult({ + written: 0, + removed: 0, + declarations: [], + warnings: ['warn'], + outDir: '/tmp/types', + typesIndexPath: '/tmp/types/index.d.ts', + }) + reportCliResult({ + written: 2, + removed: 1, + declarations: [], + warnings: [], + outDir: '/tmp/types', + typesIndexPath: '/tmp/types/index.d.ts', + }) + } finally { + console.log = originalLog + console.warn = originalWarn + } + assert.ok( + summaryLogs.some(log => + log.includes( + 'No changes to ?knighted-css&types declarations (cache is up to date).', + ), + ), + ) + assert.ok(summaryLogs.some(log => log.includes('Updated 2 declaration(s)'))) + assert.equal(summaryWarns.length, 1) +}) diff --git a/packages/css/test/helpers/resolver-fixture.ts b/packages/css/test/helpers/resolver-fixture.ts index 46b1347..1314f37 100644 --- a/packages/css/test/helpers/resolver-fixture.ts +++ b/packages/css/test/helpers/resolver-fixture.ts @@ -1,6 +1,6 @@ import path from 'node:path' -import type { CssResolver } from '../../src/css' +import type { CssResolver } from '../../src/css.js' const fixturesRoot = path.resolve( path.dirname(new URL(import.meta.url).pathname), @@ -44,7 +44,7 @@ export function createResolverFixture(name: FixtureName): ResolverFixture { const projectDir = path.join(fixturesRoot, name) const entryFile = path.join(projectDir, config.entry) - const resolver: CssResolver = async specifier => { + const resolver: CssResolver = async (specifier: string) => { if (specifier === config.specifier) { return entryFile } diff --git a/packages/css/test/loaderInternals.test.ts b/packages/css/test/loaderInternals.test.ts index b2c0017..e754a74 100644 --- a/packages/css/test/loaderInternals.test.ts +++ b/packages/css/test/loaderInternals.test.ts @@ -9,6 +9,12 @@ test('buildSanitizedQuery strips loader-specific flags', () => { assert.equal(buildSanitizedQuery(query), '?foo=bar&baz=qux') }) +test('buildSanitizedQuery leaves arbitrary params intact', () => { + const { buildSanitizedQuery } = __loaderInternals + const query = '?knighted-css&types&stableNamespace=acme&foo=1' + assert.equal(buildSanitizedQuery(query), '?stableNamespace=acme&foo=1') +}) + test('shouldEmitCombinedDefault honors skip flag, detection signals, and css modules', () => { const { shouldEmitCombinedDefault } = __loaderInternals @@ -57,3 +63,13 @@ test('shouldEmitCombinedDefault honors skip flag, detection signals, and css mod false, ) }) + +test('determineSelectorVariant differentiates combined permutations', () => { + const { determineSelectorVariant } = __loaderInternals + assert.equal(determineSelectorVariant('?knighted-css&types'), 'types') + assert.equal(determineSelectorVariant('?knighted-css&combined'), 'combined') + assert.equal( + determineSelectorVariant('?knighted-css&combined&named-only'), + 'combinedWithoutDefault', + ) +}) diff --git a/packages/css/test/loader_unit.test.ts b/packages/css/test/loader_unit.test.ts index dfaad61..6678763 100644 --- a/packages/css/test/loader_unit.test.ts +++ b/packages/css/test/loader_unit.test.ts @@ -158,6 +158,75 @@ test('loader falls back to process.cwd when no cwd hints are provided', async () assert.ok(ctx.added.size > 0, 'should still register dependencies') }) +test('loader emits stableSelectors export when ?types flag is present', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const ctx = createMockContext({ + resourcePath, + resourceQuery: '?knighted-css&types', + }) + const output = String( + await loader.call( + ctx as LoaderContext, + "export const noop = ''", + ), + ) + + assert.match(output, /export const stableSelectors = /) + assert.match( + output, + /export const stableSelectors = Object\.freeze\(\{\s*"demo": "knighted-demo",\s*"icon": "knighted-icon"\s*\}\) as const;/, + 'should emit map of detected selectors using default namespace', + ) +}) + +test('loader respects stableNamespace loader option', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const ctx = createMockContext({ + resourcePath, + resourceQuery: '?knighted-css&types', + getOptions: () => ({ stableNamespace: 'acme' }), + }) + const output = String( + await loader.call( + ctx as LoaderContext, + "export const noop = ''", + ), + ) + + assert.match( + output, + /export const stableSelectors = Object\.freeze\(\{\s*"card": "acme-card"\s*\}\) as const;/, + 'should scope selector discovery to provided namespace', + ) +}) + +test('loader warns when stableNamespace option resolves to empty value', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const warnings: string[] = [] + const ctx = createMockContext({ + resourcePath, + resourceQuery: '?knighted-css&types', + getOptions: () => ({ stableNamespace: ' ' }), + emitWarning: (err: Error) => { + warnings.push(err.message) + }, + }) + const output = String( + await loader.call( + ctx as LoaderContext, + "export const noop = ''", + ), + ) + + assert.match(output, /export const stableSelectors = \{\} as const;/) + assert.equal(warnings.length, 1) + assert.match( + warnings[0] ?? '', + /empty value/, + 'warning should describe empty namespace configuration', + ) +}) + test('pitch returns combined module when query includes combined flag', async () => { const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') const ctx = createMockContext({ @@ -184,6 +253,31 @@ test('pitch returns combined module when query includes combined flag', async () assert.ok(ctx.added.size > 0, 'pitch should still register dependencies') }) +test('pitch injects stableSelectors export when combined types query is used', async () => { + const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') + const ctx = createMockContext({ + resourcePath, + resourceQuery: '?knighted-css&combined&types', + loadModule: (_request, callback) => { + callback(null, 'export const Button = () => "ok";') + }, + }) + + const result = await pitch.call( + ctx as LoaderContext, + `${resourcePath}?knighted-css&combined&types`, + '', + {}, + ) + + const combinedOutput = String(result ?? '') + assert.match( + combinedOutput, + /export const stableSelectors = Object\.freeze\(\{\s*"demo": "knighted-demo",\s*"icon": "knighted-icon"\s*\}\) as const;/, + 'combined proxy should forward stable selector map', + ) +}) + test('pitch returns undefined when combined flag is missing', async () => { const resourcePath = path.resolve(__dirname, 'fixtures/dialects/basic/entry.js') const ctx = createMockContext({ diff --git a/packages/css/test/resolvers.test.ts b/packages/css/test/resolvers.test.ts index 6864cc6..351782f 100644 --- a/packages/css/test/resolvers.test.ts +++ b/packages/css/test/resolvers.test.ts @@ -1,8 +1,8 @@ import assert from 'node:assert/strict' import test from 'node:test' -import { css } from '../src/css' -import { createResolverFixture } from './helpers/resolver-fixture' +import { css } from '../src/css.js' +import { createResolverFixture } from './helpers/resolver-fixture.js' const projects = ['rspack', 'vite', 'webpack'] as const diff --git a/packages/css/test/stableSelectorsLiteral.test.ts b/packages/css/test/stableSelectorsLiteral.test.ts new file mode 100644 index 0000000..c29ef4e --- /dev/null +++ b/packages/css/test/stableSelectorsLiteral.test.ts @@ -0,0 +1,40 @@ +import assert from 'node:assert/strict' +import test from 'node:test' + +import { + buildStableSelectorsLiteral, + __stableSelectorsLiteralInternals, +} from '../src/stableSelectorsLiteral.ts' + +test('buildStableSelectorsLiteral warns when namespace is empty', () => { + const warnings: string[] = [] + const result = buildStableSelectorsLiteral({ + css: '.knighted-card {}', + namespace: ' ', + resourcePath: 'demo.css', + emitWarning: message => warnings.push(message), + }) + assert.equal(result.literal.trim(), 'export const stableSelectors = {} as const;') + assert.equal(result.selectorMap.size, 0) + assert.equal(warnings.length, 1) +}) + +test('collectStableSelectors captures selectors and formats map output', () => { + const { collectStableSelectors, formatStableSelectorMap } = + __stableSelectorsLiteralInternals + const css = '.knighted-card {} .knighted-badge {}' + const map = collectStableSelectors(css, 'knighted') + assert.equal(map.size, 2) + assert.equal(map.get('card'), 'knighted-card') + const formatted = formatStableSelectorMap(map) + assert.match(formatted, /"badge": "knighted-badge"/) + assert.match(formatted, /"card": "knighted-card"/) +}) + +test('collectStableSelectorsByRegex handles parser fallbacks', () => { + const { collectStableSelectorsByRegex } = __stableSelectorsLiteralInternals + const css = '.custom-card {} .custom-chip {}' + const map = collectStableSelectorsByRegex(css, 'custom') + assert.equal(map.size, 2) + assert.equal(map.get('chip'), 'custom-chip') +}) diff --git a/packages/css/tsconfig.tests.json b/packages/css/tsconfig.tests.json new file mode 100644 index 0000000..7b90de0 --- /dev/null +++ b/packages/css/tsconfig.tests.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "allowImportingTsExtensions": true, + "noEmit": true + }, + "include": ["test/**/*.ts"] +} diff --git a/packages/css/types-stub/index.d.ts b/packages/css/types-stub/index.d.ts new file mode 100644 index 0000000..80195d5 --- /dev/null +++ b/packages/css/types-stub/index.d.ts @@ -0,0 +1,5 @@ +// Placeholder stub for @knighted/css stable selector declarations. +// Running `knighted-css-generate-types` replaces this file with project-specific +// references into the generated `.knighted-css` manifest. + +export {} diff --git a/packages/css/types.d.ts b/packages/css/types.d.ts index 5c2bda0..de0f4d6 100644 --- a/packages/css/types.d.ts +++ b/packages/css/types.d.ts @@ -1,3 +1,4 @@ /// +/// export * from './dist/css.js' diff --git a/packages/playwright/package.json b/packages/playwright/package.json index b20bb56..99b9191 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -14,8 +14,8 @@ "pretest": "npm run build" }, "dependencies": { - "@knighted/css": "1.0.0-rc.7", - "@knighted/jsx": "^1.2.1", + "@knighted/css": "1.0.0-rc.8", + "@knighted/jsx": "^1.4.1", "lit": "^3.2.1", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/scripts/setup-jsx-wasm.mjs b/scripts/setup-jsx-wasm.mjs deleted file mode 100644 index 4a1ad46..0000000 --- a/scripts/setup-jsx-wasm.mjs +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node -import { execSync } from 'node:child_process' -import { existsSync, mkdirSync, rmSync } from 'node:fs' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const ROOT = path.resolve(__dirname, '..') -const BINDING_VERSION = process.env.KNIGHTED_JSX_WASM_VERSION ?? '0.99.0' -const NODE_MODULES = path.join(ROOT, 'node_modules') -const bindingDir = path.join(NODE_MODULES, '@oxc-parser', 'binding-wasm32-wasi') -const sentinel = path.join(bindingDir, 'parser.wasi.cjs') - -if (existsSync(sentinel)) { - console.log('[setup:jsx-wasm] Binding already present, skipping install.') - process.exit(0) -} - -const tarball = `oxc-parser-binding-wasm32-wasi-${BINDING_VERSION}.tgz` - -function run(command) { - execSync(command, { cwd: ROOT, stdio: 'inherit' }) -} - -try { - console.log( - `[setup:jsx-wasm] Packing @oxc-parser/binding-wasm32-wasi@${BINDING_VERSION}...`, - ) - run(`npm pack @oxc-parser/binding-wasm32-wasi@${BINDING_VERSION}`) - - mkdirSync(bindingDir, { recursive: true }) - - console.log('[setup:jsx-wasm] Extracting tarball into node_modules...') - run(`tar -xzf ${tarball} -C ${bindingDir} --strip-components=1`) - - console.log('[setup:jsx-wasm] Cleaning up tarball...') - rmSync(path.join(ROOT, tarball)) - - console.log('[setup:jsx-wasm] Installed WASM parser binding successfully.') -} catch (error) { - console.error('[setup:jsx-wasm] Failed to install WASM binding:', error) - process.exitCode = 1 -}