From c596f9f1c32b229bf306e1cc35b023a5bb20d897 Mon Sep 17 00:00:00 2001 From: "S.Gromov" Date: Tue, 14 Jul 2026 08:34:45 +0300 Subject: [PATCH] feat: standalone mode --- AGENTS.md | 5 +- README.md | 4 +- README_RU.md | 4 +- docs/en/programmatic-api.md | 15 + docs/en/reference.md | 41 +- docs/ru/programmatic-api.md | 15 + docs/ru/reference.md | 41 +- integration/README.md | 8 + .../index.html | 2 +- .../package.json | 4 +- integration/apps/standalone-vite/src/main.ts | 20 + .../standalone-vite/src/sprite/.gitignore | 2 + .../apps/standalone-vite/src/sprite/index.ts | 7 + .../src/sprite/svg-sprite.config.ts | 8 + .../src/style.css | 8 +- .../src/vite-env.d.ts | 0 .../tsconfig.json | 0 .../apps/standalone-webpack/index.html | 11 + .../apps/standalone-webpack/package.json | 21 + .../apps/standalone-webpack/src/main.ts | 20 + .../standalone-webpack/src/sprite/.gitignore | 2 + .../standalone-webpack/src/sprite/index.ts | 7 + .../src/sprite/svg-sprite.config.ts | 8 + .../apps/standalone-webpack/tsconfig.json | 11 + .../apps/standalone-webpack/webpack.config.js | 31 + integration/apps/standalone/build.mjs | 13 + integration/apps/standalone/index.html | 20 + integration/apps/standalone/package.json | 13 + .../apps/standalone/src/sprite/.gitignore | 2 + .../src/sprite/svg-sprite.config.json | 6 + integration/apps/vanilla-vite/src/main.ts | 8 - .../apps/vanilla-vite/svg-sprites.config.ts | 11 - integration/package-lock.json | 63 +- integration/scripts/apps.mjs | 3 + integration/tests/apps.spec.ts | 3 + package-lock.json | 1749 +---------------- package.json | 5 +- skills/svg-sprites/skill.config.mjs | 4 +- .../src/en/core/00-package-overview.md | 12 +- .../src/en/core/10-mode-selection.md | 5 +- .../src/en/core/40-generated-contract.md | 10 +- .../src/ru/core/00-package-overview.md | 12 +- .../src/ru/core/10-mode-selection.md | 5 +- .../src/ru/core/40-generated-contract.md | 10 +- src/cli/parse-args.ts | 3 + src/config.ts | 3 + src/core/mode-adapter.ts | 9 +- src/generate.ts | 5 +- src/index.ts | 10 + src/manifest-types.ts | 42 + src/mode-registry.ts | 6 + src/modes/standalone-vite/adapter.ts | 27 + src/modes/standalone-vite/output.ts | 159 ++ src/modes/standalone-webpack/adapter.ts | 27 + src/modes/standalone-webpack/output.ts | 158 ++ src/modes/standalone/adapter.ts | 26 + src/modes/standalone/output.ts | 53 + src/react/types.ts | 6 +- src/targets/types.ts | 17 +- test/asset-targets.test.mjs | 117 -- test/mode-contracts.test.mjs | 243 +++ test/next-targets.test.mjs | 204 -- test/react-mode.test.mjs | 590 ------ test/standalone-mode.test.mjs | 142 ++ 64 files changed, 1341 insertions(+), 2755 deletions(-) rename integration/apps/{vanilla-vite => standalone-vite}/index.html (84%) rename integration/apps/{vanilla-vite => standalone-vite}/package.json (74%) create mode 100644 integration/apps/standalone-vite/src/main.ts create mode 100644 integration/apps/standalone-vite/src/sprite/.gitignore create mode 100644 integration/apps/standalone-vite/src/sprite/index.ts create mode 100644 integration/apps/standalone-vite/src/sprite/svg-sprite.config.ts rename integration/apps/{vanilla-vite => standalone-vite}/src/style.css (75%) rename integration/apps/{vanilla-vite => standalone-vite}/src/vite-env.d.ts (100%) rename integration/apps/{vanilla-vite => standalone-vite}/tsconfig.json (100%) create mode 100644 integration/apps/standalone-webpack/index.html create mode 100644 integration/apps/standalone-webpack/package.json create mode 100644 integration/apps/standalone-webpack/src/main.ts create mode 100644 integration/apps/standalone-webpack/src/sprite/.gitignore create mode 100644 integration/apps/standalone-webpack/src/sprite/index.ts create mode 100644 integration/apps/standalone-webpack/src/sprite/svg-sprite.config.ts create mode 100644 integration/apps/standalone-webpack/tsconfig.json create mode 100644 integration/apps/standalone-webpack/webpack.config.js create mode 100644 integration/apps/standalone/build.mjs create mode 100644 integration/apps/standalone/index.html create mode 100644 integration/apps/standalone/package.json create mode 100644 integration/apps/standalone/src/sprite/.gitignore create mode 100644 integration/apps/standalone/src/sprite/svg-sprite.config.json delete mode 100644 integration/apps/vanilla-vite/src/main.ts delete mode 100644 integration/apps/vanilla-vite/svg-sprites.config.ts create mode 100644 src/manifest-types.ts create mode 100644 src/modes/standalone-vite/adapter.ts create mode 100644 src/modes/standalone-vite/output.ts create mode 100644 src/modes/standalone-webpack/adapter.ts create mode 100644 src/modes/standalone-webpack/output.ts create mode 100644 src/modes/standalone/adapter.ts create mode 100644 src/modes/standalone/output.ts delete mode 100644 test/asset-targets.test.mjs create mode 100644 test/mode-contracts.test.mjs delete mode 100644 test/next-targets.test.mjs delete mode 100644 test/react-mode.test.mjs create mode 100644 test/standalone-mode.test.mjs diff --git a/AGENTS.md b/AGENTS.md index a886620..fe92c92 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,7 +10,10 @@ - `next@app/webpack`; - `next@pages/turbopack`; - `next@pages/webpack`; -- будущие `vue@*`, `standalone` и другие modes. +- `standalone`; +- `standalone@vite`; +- `standalone@webpack`; +- будущие `vue@*` и другие modes. Для каждого exact mode используется отдельный каталог `src/modes//`. Adapter самостоятельно определяет: diff --git a/README.md b/README.md index b1b024a..0531b79 100644 --- a/README.md +++ b/README.md @@ -225,9 +225,9 @@ It also provides ready-to-use integration examples for: The Viewer is added only to an internal debug page and does not become part of the generated icon components. -## React and Next.js +## Standalone, React, and Next.js -The package generates typed React components and supports Vite, Webpack 5, Next.js App Router, and Pages Router with Turbopack or Webpack. +The package generates low-level standalone sprites for static HTML, Vite, and Webpack 5, plus typed React components for React and Next.js. ## Clean Git history diff --git a/README_RU.md b/README_RU.md index 8216cf9..301d579 100644 --- a/README_RU.md +++ b/README_RU.md @@ -225,9 +225,9 @@ JavaScript отвечает за интерфейс и поведение, а г Viewer подключается только к внутренней debug-странице и не становится частью generated-компонентов иконок. -## React и Next.js +## Standalone, React и Next.js -Пакет генерирует типизированные React-компоненты и поддерживает Vite, Webpack 5, Next.js App Router и Pages Router с Turbopack или Webpack. +Пакет генерирует низкоуровневые standalone-спрайты для static HTML, Vite и Webpack 5, а также типизированные React-компоненты для React и Next.js. ## Чистый Git diff --git a/docs/en/programmatic-api.md b/docs/en/programmatic-api.md index b86961c..1240785 100644 --- a/docs/en/programmatic-api.md +++ b/docs/en/programmatic-api.md @@ -14,6 +14,21 @@ const result = await generateSprite( ) ``` +For static standalone mode, use `result.spritePath` in a build script to publish the +SVG under an application URL: + +```ts +import { copyFile } from 'node:fs/promises' + +const result = await generateSprite('src/sprite/svg-sprite.config.ts', { + mode: 'standalone', +}) +await copyFile(result.spritePath, 'dist/app-icons/sprite.svg') +``` + +`spritePath` is a filesystem path, not a browser URL. A deployment-neutral JSON +manifest is available through `result.manifestPath` and is copied independently. + The first argument accepts the full path to an explicitly selected `.ts`, `.js`, or `.json` config file with any name. Passing a directory enables config-less mode and uses that directory as the sprite module root. The second argument contains optional overrides and always takes precedence over the config: diff --git a/docs/en/reference.md b/docs/en/reference.md index 9ffe21d..e2761c5 100644 --- a/docs/en/reference.md +++ b/docs/en/reference.md @@ -32,6 +32,9 @@ svg-sprites [options] | Environment | Mode | |---|---| +| Static HTML / custom publishing | `standalone` | +| Standalone + Vite | `standalone@vite` | +| Standalone + Webpack 5 | `standalone@webpack` | | React + Vite | `react@vite` | | React + Webpack 5 | `react@webpack` | | Next.js App Router + Turbopack | `next@app/turbopack` | @@ -45,7 +48,7 @@ When a directory is passed, all settings come from CLI options. When a config fi Available options are `--mode`, `--name`, `--description`, `--input-folder`, repeatable `--input-file`, plus the `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition`, and `--generated-notice`/`--no-generated-notice` pairs. Transform flags override individual fields, while supplying at least one `--input-file` replaces the complete config `inputFiles` array. -The mode must match the application's bundler. The generator creates different SVG asset integration code for Vite and for bundlers compatible with Webpack Asset Modules. +The mode must match the application's publishing strategy. Bare `standalone` leaves the public URL to the application; Vite and Webpack modes generate bundler-specific SVG asset integration. ## Unified configuration @@ -105,7 +108,7 @@ Identical absolute paths are deduplicated. Different SVG files with the same fil ## Generated module -After generation, the sprite directory looks like this: +After generation, a React or Next.js sprite directory looks like this: ```text app-icons/ @@ -140,6 +143,19 @@ app-icons/ | `.svg-sprite/*.d.ts` | TypeScript declarations for the corresponding JavaScript modules | | `.svg-sprite/state.json` | Mode, contract version, and managed file list | +Standalone contracts do not create `react/`. Bare `standalone` contains only the +runtime asset and deployment-neutral manifest data: + +```text +.svg-sprite/ +├── state.json +├── sprite.svg +└── svg-sprite.manifest.json +``` + +`standalone@vite` and `standalone@webpack` additionally create `index.*`, +`icon-data.*`, and a resolved `svg-sprite.manifest.*`. + The generator overwrites and deletes only files that contain its marker. If a user file occupies a managed path, generation fails. The root `index.ts` is user-owned; create a barrel when needed: ```ts @@ -235,7 +251,7 @@ For multiple sprites, add a separate CLI command for each directory or combine t ## Formats and rendering methods -React and Next.js modes generate the `stack` format. +All current modes generate the `stack` format. | Format | `` | `` | CSS background | |---|---:|---:|---:| @@ -253,6 +269,17 @@ For React and Next.js, use the generated component. It knows the internal IDs, c How you obtain `spriteUrl` depends on the bundler. +Static HTML after the application publishes `.svg-sprite/sprite.svg`: + +```html + +``` + +Standalone Vite/Webpack provides generated `getIconsIconHref()` and an internal ID +map. Do not construct fragments from unsafe file names manually. + Vite: ```ts @@ -306,12 +333,15 @@ The path in CSS is resolved relative to the CSS file itself. In these examples, ## Assets and caching -The generated component passes the SVG to the bundler as a separate asset: +The generated component or standalone facade passes the SVG to the bundler as a separate asset: - Vite uses a static import with `?no-inline`; - Webpack 5, Turbopack, and Next.js use `new URL(..., import.meta.url)`; - SVG path data is not serialized into generated JavaScript. +Bare `standalone` does not participate in an asset pipeline: the application copies +or publishes `sprite.svg` and owns its URL, versioning, and cache policy. + With standard asset naming, the bundler adds a content hash: ```text @@ -401,7 +431,7 @@ For a complex icon, you can disable `replaceColors` in a separate sprite configu import { SpriteViewer } from '@gromlab/svg-sprites/react' ``` -It accepts ready-made manifests, an array of lazy loaders, or a record in the format returned by `import.meta.glob`. +It accepts ready-made React/Next manifests, an array of lazy loaders, or a record in the format returned by `import.meta.glob`. The current Viewer does not load standalone manifests; standalone will use a separate viewer contract. Vite: @@ -488,6 +518,7 @@ If the sprite directory already contains a user-created `.gitignore` or a user-o - `Refusing to overwrite a user file`: a file without the generated marker occupies a managed path. - The icon does not change color: use `` or the generated component and check `replaceColors`. - Webpack emits an incorrect URL: check Asset Modules, `output.publicPath`, and SVG loaders. +- Static sprite returns 404: check the post-generation copy or server alias, and do not put a filesystem `spritePath` into HTML. - The Viewer cannot find the sprite: check the path to `.svg-sprite/svg-sprite.manifest.js` and run generation before starting the application. - Build and mode do not match: use the target that corresponds to the actual bundler. diff --git a/docs/ru/programmatic-api.md b/docs/ru/programmatic-api.md index b095747..971490f 100644 --- a/docs/ru/programmatic-api.md +++ b/docs/ru/programmatic-api.md @@ -14,6 +14,21 @@ const result = await generateSprite( ) ``` +Для static standalone mode `result.spritePath` можно использовать в build-скрипте, +чтобы опубликовать SVG по URL приложения: + +```ts +import { copyFile } from 'node:fs/promises' + +const result = await generateSprite('src/sprite/svg-sprite.config.ts', { + mode: 'standalone', +}) +await copyFile(result.spritePath, 'dist/app-icons/sprite.svg') +``` + +`spritePath` является filesystem path, а не browser URL. Deployment-neutral JSON +manifest доступен через `result.manifestPath` и копируется независимо от SVG. + Первый аргумент принимает полный путь к config-файлу с любым именем и расширением `.ts`, `.js` или `.json`. Каталог вместо файла включает config-less режим: корнем sprite-модуля становится этот каталог. Второй аргумент содержит необязательные overrides и всегда имеет приоритет над конфигом: diff --git a/docs/ru/reference.md b/docs/ru/reference.md index 8115f2f..67eaf54 100644 --- a/docs/ru/reference.md +++ b/docs/ru/reference.md @@ -32,6 +32,9 @@ svg-sprites [options] | Среда | Mode | |---|---| +| Static HTML / собственная публикация | `standalone` | +| Standalone + Vite | `standalone@vite` | +| Standalone + Webpack 5 | `standalone@webpack` | | React + Vite | `react@vite` | | React + Webpack 5 | `react@webpack` | | Next.js App Router + Turbopack | `next@app/turbopack` | @@ -45,7 +48,7 @@ Config-файл может иметь любое имя и расширение Доступны `--mode`, `--name`, `--description`, `--input-folder`, повторяемый `--input-file`, а также пары `--remove-size`/`--no-remove-size`, `--replace-colors`/`--no-replace-colors`, `--add-transition`/`--no-add-transition` и `--generated-notice`/`--no-generated-notice`. Переданные transform-флаги перекрывают отдельные поля, а хотя бы один `--input-file` заменяет весь массив `inputFiles` из config. -Mode должен соответствовать сборщику приложения. Генератор создаёт разный способ подключения SVG asset для Vite и сборщиков, совместимых с Webpack Asset Modules. +Mode должен соответствовать способу публикации приложения. Bare `standalone` оставляет публичный URL приложению; Vite и Webpack modes генерируют bundler-specific подключение SVG asset. ## Единая конфигурация @@ -105,7 +108,7 @@ file-manager → FileManagerIcon ## Generated-модуль -После генерации каталог спрайта выглядит так: +После генерации React- или Next.js-каталог спрайта выглядит так: ```text app-icons/ @@ -140,6 +143,19 @@ app-icons/ | `.svg-sprite/*.d.ts` | TypeScript-декларации соответствующих JS-модулей | | `.svg-sprite/state.json` | Mode, версия контракта и список управляемых файлов | +Standalone-контракты не создают каталог `react/`. Bare `standalone` содержит только +runtime asset и deployment-neutral manifest data: + +```text +.svg-sprite/ +├── state.json +├── sprite.svg +└── svg-sprite.manifest.json +``` + +`standalone@vite` и `standalone@webpack` дополнительно создают `index.*`, +`icon-data.*` и resolved `svg-sprite.manifest.*`. + Генератор перезаписывает и удаляет только файлы со своим marker. Если в managed-пути находится пользовательский файл, генерация завершается ошибкой. Корневой `index.ts` генератору не принадлежит; при необходимости создайте пользовательский barrel: ```ts @@ -235,7 +251,7 @@ editor-icons → EditorIcon → иконки редактора ## Форматы и способы отображения -React- и Next.js-режимы создают формат `stack`. +Все текущие modes создают формат `stack`. | Формат | `` | `` | CSS background | |---|---:|---:|---:| @@ -253,6 +269,17 @@ React- и Next.js-режимы создают формат `stack`. Способ получения `spriteUrl` зависит от сборщика. +Static HTML после публикации `.svg-sprite/sprite.svg` приложением: + +```html + +``` + +Standalone Vite/Webpack предоставляет generated `getIconsIconHref()` и mapping +внутренних IDs. Не конструируйте fragment из небезопасного имени файла вручную. + Vite: ```ts @@ -306,12 +333,15 @@ Mask не сохраняет исходные цвета, gradients и разл ## Assets и кеширование -Generated-компонент передаёт SVG сборщику как отдельный asset: +Generated component или standalone facade передаёт SVG сборщику как отдельный asset: - Vite использует статический импорт с `?no-inline`; - Webpack 5, Turbopack и Next.js используют `new URL(..., import.meta.url)`; - SVG path-данные не сериализуются в generated JavaScript. +Bare `standalone` не участвует в asset pipeline: приложение само копирует или +публикует `sprite.svg` и отвечает за URL, версионирование и cache policy. + При стандартном именовании assets сборщик добавляет content hash: ```text @@ -401,7 +431,7 @@ fill="var(--icon-color-3, #129d9d)" import { SpriteViewer } from '@gromlab/svg-sprites/react' ``` -Он принимает готовые manifests, массив lazy loaders или record формата `import.meta.glob`. +Он принимает готовые React/Next manifests, массив lazy loaders или record формата `import.meta.glob`. Текущий Viewer не загружает standalone manifests; для standalone будет отдельный viewer-контракт. Vite: @@ -488,6 +518,7 @@ CI должен устанавливать development dependencies и выпо - `Refusing to overwrite a user file`: в managed-пути находится файл без generated marker. - Иконка не меняет цвет: используйте `` или generated-компонент и проверьте `replaceColors`. - Webpack выдаёт неверный URL: проверьте Asset Modules, `output.publicPath` и SVG loaders. +- Static sprite возвращает 404: проверьте post-generation copy или server alias и не передавайте filesystem `spritePath` в HTML. - Viewer не видит спрайт: проверьте путь к `.svg-sprite/svg-sprite.manifest.js` и выполните генерацию до запуска приложения. - Build и mode не совпадают: используйте target, соответствующий фактическому сборщику. diff --git a/integration/README.md b/integration/README.md index 8471f93..1cb05d4 100644 --- a/integration/README.md +++ b/integration/README.md @@ -7,6 +7,9 @@ production build и отображения внешнего SVG-спрайта | Fixture | Генератор | Runtime | | --- | --- | --- | +| `standalone` | `standalone` | Static HTML + явный copy SVG | +| `standalone-vite` | `standalone@vite` | Vanilla TypeScript + Vite | +| `standalone-webpack` | `standalone@webpack` | Vanilla TypeScript + Webpack 5 | | `react-vite` | `react@vite` | React + Vite | | `react-webpack` | `react@webpack` | React + Webpack 5 | | `next-app-turbopack` | `next@app/turbopack` | App Router + Turbopack | @@ -51,6 +54,11 @@ npm run dev --workspace @svg-sprites-fixtures/react-vite --prefix integration последовательно, проверяет внешний `href`, HTTP status и Content-Type спрайта, наличие symbol ID, отсутствие browser errors и зелёные пиксели отрисованной иконки. +Static fixture копирует managed SVG и JSON manifest в `dist/app-icons/` и использует +literal ``. +Vite и Webpack fixtures получают URL только из generated facade и сверяют его с +resolved manifest. + ## Структура ```text diff --git a/integration/apps/vanilla-vite/index.html b/integration/apps/standalone-vite/index.html similarity index 84% rename from integration/apps/vanilla-vite/index.html rename to integration/apps/standalone-vite/index.html index b666344..eae8027 100644 --- a/integration/apps/vanilla-vite/index.html +++ b/integration/apps/standalone-vite/index.html @@ -3,7 +3,7 @@ - Vanilla Vite sprite fixture + Standalone Vite sprite fixture
diff --git a/integration/apps/vanilla-vite/package.json b/integration/apps/standalone-vite/package.json similarity index 74% rename from integration/apps/vanilla-vite/package.json rename to integration/apps/standalone-vite/package.json index 27c094f..6e8cf23 100644 --- a/integration/apps/vanilla-vite/package.json +++ b/integration/apps/standalone-vite/package.json @@ -1,10 +1,10 @@ { - "name": "@svg-sprites-fixtures/vanilla-vite", + "name": "@svg-sprites-fixtures/standalone-vite", "private": true, "version": "0.0.0", "type": "module", "scripts": { - "sprites": "svg-sprites --mode legacy .", + "sprites": "svg-sprites src/sprite/svg-sprite.config.ts", "typecheck": "tsc --noEmit", "build": "npm run sprites && tsc --noEmit && vite build", "dev": "npm run sprites && vite" diff --git a/integration/apps/standalone-vite/src/main.ts b/integration/apps/standalone-vite/src/main.ts new file mode 100644 index 0000000..d50255d --- /dev/null +++ b/integration/apps/standalone-vite/src/main.ts @@ -0,0 +1,20 @@ +import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js' +import { getIconsIconHref, iconsSpriteUrl } from './sprite' +import './style.css' + +const check = spriteManifest.icons.find((icon) => icon.name === 'check') +if (!check || spriteManifest.spriteUrl !== iconsSpriteUrl) { + throw new Error('Generated Vite facade and manifest disagree.') +} + +document.querySelector('#app')!.innerHTML = ` +

Standalone + Vite

+ + + +` diff --git a/integration/apps/standalone-vite/src/sprite/.gitignore b/integration/apps/standalone-vite/src/sprite/.gitignore new file mode 100644 index 0000000..7f3344e --- /dev/null +++ b/integration/apps/standalone-vite/src/sprite/.gitignore @@ -0,0 +1,2 @@ +# @generated by @gromlab/svg-sprites. Do not edit. +/.svg-sprite/ diff --git a/integration/apps/standalone-vite/src/sprite/index.ts b/integration/apps/standalone-vite/src/sprite/index.ts new file mode 100644 index 0000000..659338f --- /dev/null +++ b/integration/apps/standalone-vite/src/sprite/index.ts @@ -0,0 +1,7 @@ +export { + getIconsIconHref, + iconsIconIds, + iconsIconNames, + iconsSpriteUrl, +} from './.svg-sprite/index.js' +export type { IconsIconName } from './.svg-sprite/index.js' diff --git a/integration/apps/standalone-vite/src/sprite/svg-sprite.config.ts b/integration/apps/standalone-vite/src/sprite/svg-sprite.config.ts new file mode 100644 index 0000000..1ffd01d --- /dev/null +++ b/integration/apps/standalone-vite/src/sprite/svg-sprite.config.ts @@ -0,0 +1,8 @@ +import { defineSpriteConfig } from '@gromlab/svg-sprites' + +export default defineSpriteConfig({ + mode: 'standalone@vite', + name: 'icons', + inputFiles: ['../../../../fixtures/icons/check.svg'], + generatedNotice: false, +}) diff --git a/integration/apps/vanilla-vite/src/style.css b/integration/apps/standalone-vite/src/style.css similarity index 75% rename from integration/apps/vanilla-vite/src/style.css rename to integration/apps/standalone-vite/src/style.css index 55d4dfd..69666bf 100644 --- a/integration/apps/vanilla-vite/src/style.css +++ b/integration/apps/standalone-vite/src/style.css @@ -1,16 +1,12 @@ -:root { - font-family: system-ui, sans-serif; - color: #172033; - background: #fff; -} - body { margin: 0; padding: 40px; + font-family: system-ui, sans-serif; } [data-testid='icon'] { width: 64px; height: 64px; color: #16a34a; + --icon-color-1: #16a34a; } diff --git a/integration/apps/vanilla-vite/src/vite-env.d.ts b/integration/apps/standalone-vite/src/vite-env.d.ts similarity index 100% rename from integration/apps/vanilla-vite/src/vite-env.d.ts rename to integration/apps/standalone-vite/src/vite-env.d.ts diff --git a/integration/apps/vanilla-vite/tsconfig.json b/integration/apps/standalone-vite/tsconfig.json similarity index 100% rename from integration/apps/vanilla-vite/tsconfig.json rename to integration/apps/standalone-vite/tsconfig.json diff --git a/integration/apps/standalone-webpack/index.html b/integration/apps/standalone-webpack/index.html new file mode 100644 index 0000000..979dddf --- /dev/null +++ b/integration/apps/standalone-webpack/index.html @@ -0,0 +1,11 @@ + + + + + + Standalone Webpack sprite fixture + + +
+ + diff --git a/integration/apps/standalone-webpack/package.json b/integration/apps/standalone-webpack/package.json new file mode 100644 index 0000000..6d562aa --- /dev/null +++ b/integration/apps/standalone-webpack/package.json @@ -0,0 +1,21 @@ +{ + "name": "@svg-sprites-fixtures/standalone-webpack", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "sprites": "svg-sprites src/sprite/svg-sprite.config.ts", + "typecheck": "tsc --noEmit", + "build": "npm run sprites && webpack --mode production", + "dev": "npm run sprites && webpack serve --mode development" + }, + "devDependencies": { + "@gromlab/svg-sprites": "file:../../..", + "html-webpack-plugin": "5.6.7", + "ts-loader": "9.6.2", + "typescript": "6.0.2", + "webpack": "5.108.4", + "webpack-cli": "7.2.1", + "webpack-dev-server": "5.2.2" + } +} diff --git a/integration/apps/standalone-webpack/src/main.ts b/integration/apps/standalone-webpack/src/main.ts new file mode 100644 index 0000000..c0d93a6 --- /dev/null +++ b/integration/apps/standalone-webpack/src/main.ts @@ -0,0 +1,20 @@ +import spriteManifest from './sprite/.svg-sprite/svg-sprite.manifest.js' +import { getIconsIconHref, iconsSpriteUrl } from './sprite' + +const check = spriteManifest.icons.find((icon) => icon.name === 'check') +if (!check || spriteManifest.spriteUrl !== iconsSpriteUrl) { + throw new Error('Generated Webpack facade and manifest disagree.') +} + +document.querySelector('#app')!.innerHTML = ` +

Standalone + Webpack

+ + + +` diff --git a/integration/apps/standalone-webpack/src/sprite/.gitignore b/integration/apps/standalone-webpack/src/sprite/.gitignore new file mode 100644 index 0000000..7f3344e --- /dev/null +++ b/integration/apps/standalone-webpack/src/sprite/.gitignore @@ -0,0 +1,2 @@ +# @generated by @gromlab/svg-sprites. Do not edit. +/.svg-sprite/ diff --git a/integration/apps/standalone-webpack/src/sprite/index.ts b/integration/apps/standalone-webpack/src/sprite/index.ts new file mode 100644 index 0000000..659338f --- /dev/null +++ b/integration/apps/standalone-webpack/src/sprite/index.ts @@ -0,0 +1,7 @@ +export { + getIconsIconHref, + iconsIconIds, + iconsIconNames, + iconsSpriteUrl, +} from './.svg-sprite/index.js' +export type { IconsIconName } from './.svg-sprite/index.js' diff --git a/integration/apps/standalone-webpack/src/sprite/svg-sprite.config.ts b/integration/apps/standalone-webpack/src/sprite/svg-sprite.config.ts new file mode 100644 index 0000000..6d4be55 --- /dev/null +++ b/integration/apps/standalone-webpack/src/sprite/svg-sprite.config.ts @@ -0,0 +1,8 @@ +import { defineSpriteConfig } from '@gromlab/svg-sprites' + +export default defineSpriteConfig({ + mode: 'standalone@webpack', + name: 'icons', + inputFiles: ['../../../../fixtures/icons/check.svg'], + generatedNotice: false, +}) diff --git a/integration/apps/standalone-webpack/tsconfig.json b/integration/apps/standalone-webpack/tsconfig.json new file mode 100644 index 0000000..41490ed --- /dev/null +++ b/integration/apps/standalone-webpack/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "strict": true, + "skipLibCheck": true + }, + "include": ["src"] +} diff --git a/integration/apps/standalone-webpack/webpack.config.js b/integration/apps/standalone-webpack/webpack.config.js new file mode 100644 index 0000000..264f539 --- /dev/null +++ b/integration/apps/standalone-webpack/webpack.config.js @@ -0,0 +1,31 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +import HtmlWebpackPlugin from 'html-webpack-plugin' + +const root = path.dirname(fileURLToPath(import.meta.url)) + +export default { + entry: './src/main.ts', + output: { + path: path.join(root, 'dist'), + filename: 'assets/app.[contenthash].js', + assetModuleFilename: 'assets/[name].[contenthash][ext]', + clean: true, + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + use: 'ts-loader', + }, + ], + }, + plugins: [ + new HtmlWebpackPlugin({ template: './index.html' }), + ], +} diff --git a/integration/apps/standalone/build.mjs b/integration/apps/standalone/build.mjs new file mode 100644 index 0000000..257b706 --- /dev/null +++ b/integration/apps/standalone/build.mjs @@ -0,0 +1,13 @@ +import fs from 'node:fs' + +fs.rmSync(new URL('./dist/', import.meta.url), { recursive: true, force: true }) +fs.mkdirSync(new URL('./dist/app-icons/', import.meta.url), { recursive: true }) +fs.copyFileSync(new URL('./index.html', import.meta.url), new URL('./dist/index.html', import.meta.url)) +fs.copyFileSync( + new URL('./src/sprite/.svg-sprite/sprite.svg', import.meta.url), + new URL('./dist/app-icons/sprite.svg', import.meta.url), +) +fs.copyFileSync( + new URL('./src/sprite/.svg-sprite/svg-sprite.manifest.json', import.meta.url), + new URL('./dist/app-icons/manifest.json', import.meta.url), +) diff --git a/integration/apps/standalone/index.html b/integration/apps/standalone/index.html new file mode 100644 index 0000000..a3c5eba --- /dev/null +++ b/integration/apps/standalone/index.html @@ -0,0 +1,20 @@ + + + + + + Standalone sprite fixture + + + +
+

Standalone

+ + + +
+ + diff --git a/integration/apps/standalone/package.json b/integration/apps/standalone/package.json new file mode 100644 index 0000000..957de0d --- /dev/null +++ b/integration/apps/standalone/package.json @@ -0,0 +1,13 @@ +{ + "name": "@svg-sprites-fixtures/standalone", + "private": true, + "version": "0.0.0", + "scripts": { + "sprites": "svg-sprites src/sprite/svg-sprite.config.json", + "build": "npm run sprites && node build.mjs", + "dev": "npm run build && node ../../scripts/serve-static.mjs dist 4173" + }, + "devDependencies": { + "@gromlab/svg-sprites": "file:../../.." + } +} diff --git a/integration/apps/standalone/src/sprite/.gitignore b/integration/apps/standalone/src/sprite/.gitignore new file mode 100644 index 0000000..7f3344e --- /dev/null +++ b/integration/apps/standalone/src/sprite/.gitignore @@ -0,0 +1,2 @@ +# @generated by @gromlab/svg-sprites. Do not edit. +/.svg-sprite/ diff --git a/integration/apps/standalone/src/sprite/svg-sprite.config.json b/integration/apps/standalone/src/sprite/svg-sprite.config.json new file mode 100644 index 0000000..8c51c85 --- /dev/null +++ b/integration/apps/standalone/src/sprite/svg-sprite.config.json @@ -0,0 +1,6 @@ +{ + "mode": "standalone", + "name": "app-icons", + "inputFiles": ["../../../../fixtures/icons/check.svg"], + "generatedNotice": false +} diff --git a/integration/apps/vanilla-vite/src/main.ts b/integration/apps/vanilla-vite/src/main.ts deleted file mode 100644 index e176370..0000000 --- a/integration/apps/vanilla-vite/src/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -import './style.css' - -document.querySelector('#app')!.innerHTML = ` -

Vanilla + Vite

- - - -` diff --git a/integration/apps/vanilla-vite/svg-sprites.config.ts b/integration/apps/vanilla-vite/svg-sprites.config.ts deleted file mode 100644 index 24da7e0..0000000 --- a/integration/apps/vanilla-vite/svg-sprites.config.ts +++ /dev/null @@ -1,11 +0,0 @@ -export default { - output: 'public/sprites', - preview: false, - sprites: [ - { - name: 'icons', - input: '../../fixtures/icons', - format: 'symbol', - }, - ], -} diff --git a/integration/package-lock.json b/integration/package-lock.json index aae018b..ec322f1 100644 --- a/integration/package-lock.json +++ b/integration/package-lock.json @@ -34,14 +34,11 @@ "@types/react-dom": "^19.2.3", "@types/svg-sprite": "^0.0.39", "jsdom": "^26.1.0", - "next": "16.2.10", "react": "^19.2.5", "react-colorful": "^5.6.1", "react-dom": "^19.2.5", "tsup": "^8.4.0", - "typescript": "^5.8.3", - "vite": "^6.4.3", - "webpack": "^5.108.4" + "typescript": "^5.8.3" }, "engines": { "node": ">=18" @@ -2130,6 +2127,35 @@ "vite-plugin-solid": "2.11.12" } }, + "apps/standalone": { + "name": "@svg-sprites-fixtures/standalone", + "version": "0.0.0", + "devDependencies": { + "@gromlab/svg-sprites": "file:../../.." + } + }, + "apps/standalone-vite": { + "name": "@svg-sprites-fixtures/standalone-vite", + "version": "0.0.0", + "devDependencies": { + "@gromlab/svg-sprites": "file:../../..", + "typescript": "6.0.2", + "vite": "8.1.4" + } + }, + "apps/standalone-webpack": { + "name": "@svg-sprites-fixtures/standalone-webpack", + "version": "0.0.0", + "devDependencies": { + "@gromlab/svg-sprites": "file:../../..", + "html-webpack-plugin": "5.6.7", + "ts-loader": "9.6.2", + "typescript": "6.0.2", + "webpack": "5.108.4", + "webpack-cli": "7.2.1", + "webpack-dev-server": "5.2.2" + } + }, "apps/svelte-vite": { "name": "@svg-sprites-fixtures/svelte-vite", "version": "0.0.0", @@ -2160,15 +2186,6 @@ "vite": "8.1.4" } }, - "apps/vanilla-vite": { - "name": "@svg-sprites-fixtures/vanilla-vite", - "version": "0.0.0", - "devDependencies": { - "@gromlab/svg-sprites": "file:../../..", - "typescript": "6.0.2", - "vite": "8.1.4" - } - }, "apps/vue-vite": { "name": "@svg-sprites-fixtures/vue-vite", "version": "0.0.0", @@ -9372,6 +9389,18 @@ "resolved": "apps/solid-vite", "link": true }, + "node_modules/@svg-sprites-fixtures/standalone": { + "resolved": "apps/standalone", + "link": true + }, + "node_modules/@svg-sprites-fixtures/standalone-vite": { + "resolved": "apps/standalone-vite", + "link": true + }, + "node_modules/@svg-sprites-fixtures/standalone-webpack": { + "resolved": "apps/standalone-webpack", + "link": true + }, "node_modules/@svg-sprites-fixtures/svelte-vite": { "resolved": "apps/svelte-vite", "link": true @@ -9380,10 +9409,6 @@ "resolved": "apps/sveltekit", "link": true }, - "node_modules/@svg-sprites-fixtures/vanilla-vite": { - "resolved": "apps/vanilla-vite", - "link": true - }, "node_modules/@svg-sprites-fixtures/vue-vite": { "resolved": "apps/vue-vite", "link": true @@ -11906,7 +11931,7 @@ "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 12" @@ -14372,7 +14397,7 @@ "version": "5.1.9", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.9.tgz", "integrity": "sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/import-local": { diff --git a/integration/scripts/apps.mjs b/integration/scripts/apps.mjs index 7f9e064..601e0f1 100644 --- a/integration/scripts/apps.mjs +++ b/integration/scripts/apps.mjs @@ -1,4 +1,7 @@ export const apps = [ + { id: 'standalone', kind: 'static', output: 'dist', expectedSpritePath: '/app-icons/sprite.svg' }, + { id: 'standalone-vite', kind: 'static', output: 'dist' }, + { id: 'standalone-webpack', kind: 'static', output: 'dist' }, { id: 'react-vite', kind: 'static', output: 'dist' }, { id: 'react-webpack', kind: 'static', output: 'dist' }, { id: 'next-app-turbopack', kind: 'next' }, diff --git a/integration/tests/apps.spec.ts b/integration/tests/apps.spec.ts index 50d7251..79c67dc 100644 --- a/integration/tests/apps.spec.ts +++ b/integration/tests/apps.spec.ts @@ -114,6 +114,9 @@ for (const app of apps) { expect(href).not.toMatch(/^(?:blob|data|file):/) const spriteUrl = new URL(href!.split('#')[0], page.url()) + if ('expectedSpritePath' in app) { + expect(spriteUrl.pathname).toBe(app.expectedSpritePath) + } const spriteResponse = await fetch(spriteUrl) expect(spriteResponse.status).toBe(200) expect(spriteResponse.headers.get('content-type') ?? '').toMatch(/image\/svg\+xml/) diff --git a/package-lock.json b/package-lock.json index 6ede9e0..5e63fdd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,14 +22,11 @@ "@types/react-dom": "^19.2.3", "@types/svg-sprite": "^0.0.39", "jsdom": "^26.1.0", - "next": "16.2.10", "react": "^19.2.5", "react-colorful": "^5.6.1", "react-dom": "^19.2.5", "tsup": "^8.4.0", - "typescript": "^5.8.3", - "vite": "^6.4.3", - "webpack": "^5.108.4" + "typescript": "^5.8.3" }, "engines": { "node": ">=18" @@ -192,17 +189,6 @@ "kuler": "^2.0.0" } }, - "node_modules/@emnapi/runtime": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", - "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", @@ -645,497 +631,6 @@ "node": ">=18" } }, - "node_modules/@img/colour": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", - "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" - } - }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" - } - }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" - } - }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" - } - }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", - "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND LGPL-3.0-or-later", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1157,17 +652,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1186,149 +670,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@next/env": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.10.tgz", - "integrity": "sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.10.tgz", - "integrity": "sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.10.tgz", - "integrity": "sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.10.tgz", - "integrity": "sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.10.tgz", - "integrity": "sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.10.tgz", - "integrity": "sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.10.tgz", - "integrity": "sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.10.tgz", - "integrity": "sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.10.tgz", - "integrity": "sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@resvg/resvg-js": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/@resvg/resvg-js/-/resvg-js-2.6.2.tgz", @@ -1904,16 +1245,6 @@ "text-hex": "1.0.x" } }, - "node_modules/@swc/helpers": { - "version": "0.5.15", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", - "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1928,13 +1259,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/node": { "version": "22.19.17", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz", @@ -1994,167 +1318,6 @@ "@types/node": "*" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, "node_modules/@xmldom/xmldom": { "version": "0.8.13", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", @@ -2164,20 +1327,6 @@ "node": ">=10.0.0" } }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -2191,19 +1340,6 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2214,54 +1350,6 @@ "node": ">= 14" } }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2329,19 +1417,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.42", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", - "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", @@ -2358,47 +1433,6 @@ "concat-map": "0.0.1" } }, - "node_modules/browserslist": { - "version": "4.28.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.5.tgz", - "integrity": "sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.42", - "caniuse-lite": "^1.0.30001800", - "electron-to-chromium": "^1.5.387", - "node-releases": "^2.0.50", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, "node_modules/bundle-require": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", @@ -2425,27 +1459,6 @@ "node": ">=8" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001803", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001803.tgz", - "integrity": "sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chokidar": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", @@ -2462,23 +1475,6 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "dev": true, - "license": "MIT" - }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -2743,17 +1739,6 @@ "dev": true, "license": "MIT" }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -2809,13 +1794,6 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.389", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.389.tgz", - "integrity": "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg==", - "dev": true, - "license": "ISC" - }, "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", @@ -2828,20 +1806,6 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", "license": "MIT" }, - "node_modules/enhanced-resolve": { - "version": "5.24.2", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.2.tgz", - "integrity": "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/entities": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", @@ -2851,13 +1815,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/es-module-lexer": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.0.tgz", - "integrity": "sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==", - "dev": true, - "license": "MIT" - }, "node_modules/esbuild": { "version": "0.28.1", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", @@ -2909,87 +1866,6 @@ "node": ">=6" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/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" - } - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", - "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -3083,23 +1959,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/html-encoding-sniffer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", @@ -3205,21 +2064,6 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, - "node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -3301,13 +2145,6 @@ } } }, - "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true, - "license": "MIT" - }, "node_modules/kuler": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", @@ -3344,20 +2181,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/loader-runner": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", - "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/lodash.escape": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-4.0.1.tgz", @@ -3410,23 +2233,6 @@ "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "license": "CC0-1.0" }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/minimatch": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -3439,67 +2245,6 @@ "node": "*" } }, - "node_modules/minimizer-webpack-plugin": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", - "integrity": "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@minify-html/node": { - "optional": true - }, - "@swc/core": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "@swc/html": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "cssnano": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "html-minifier-terser": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "postcss": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, "node_modules/mlly": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", @@ -3540,96 +2285,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/next": { - "version": "16.2.10", - "resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz", - "integrity": "sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@next/env": "16.2.10", - "@swc/helpers": "0.5.15", - "baseline-browser-mapping": "^2.9.19", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.4.31", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=20.9.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "16.2.10", - "@next/swc-darwin-x64": "16.2.10", - "@next/swc-linux-arm64-gnu": "16.2.10", - "@next/swc-linux-arm64-musl": "16.2.10", - "@next/swc-linux-x64-gnu": "16.2.10", - "@next/swc-linux-x64-musl": "16.2.10", - "@next/swc-win32-arm64-msvc": "16.2.10", - "@next/swc-win32-x64-msvc": "16.2.10", - "sharp": "^0.34.5" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/node-releases": { - "version": "2.0.51", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", - "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -3760,35 +2415,6 @@ "pathe": "^2.0.1" } }, - "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -3940,16 +2566,6 @@ "node": ">=0.10.0" } }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -4063,86 +2679,6 @@ "dev": true, "license": "MIT" }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "@img/colour": "^1.0.0", - "detect-libc": "^2.1.2", - "semver": "^7.7.3" - }, - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -4152,27 +2688,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -4224,30 +2739,6 @@ "node": ">=8" } }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", - "dev": true, - "license": "MIT", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, "node_modules/sucrase": { "version": "3.35.1", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", @@ -4281,22 +2772,6 @@ "node": ">= 6" } }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/svg-sprite": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/svg-sprite/-/svg-sprite-2.0.4.tgz", @@ -4356,46 +2831,6 @@ "dev": true, "license": "MIT" }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser": { - "version": "5.49.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.0.tgz", - "integrity": "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, "node_modules/text-hex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", @@ -4521,13 +2956,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD" - }, "node_modules/tsup": { "version": "8.5.1", "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", @@ -4619,37 +3047,6 @@ "dev": true, "license": "MIT" }, - "node_modules/update-browserslist-db": { - "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==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4673,81 +3070,6 @@ "node": ">= 0.10" } }, - "node_modules/vite": { - "version": "6.4.3", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", - "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -4761,19 +3083,6 @@ "node": ">=18" } }, - "node_modules/watchpack": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", - "integrity": "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -4784,62 +3093,6 @@ "node": ">=12" } }, - "node_modules/webpack": { - "version": "5.108.4", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.108.4.tgz", - "integrity": "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.16.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.28.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.22.2", - "es-module-lexer": "^2.1.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "graceful-fs": "^4.2.11", - "loader-runner": "^4.3.2", - "mime-db": "^1.54.0", - "minimizer-webpack-plugin": "^5.6.1", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "watchpack": "^2.5.2", - "webpack-sources": "^3.5.0" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-sources": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.1.tgz", - "integrity": "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/whatwg-encoding": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", diff --git a/package.json b/package.json index 2b032a4..6c68403 100644 --- a/package.json +++ b/package.json @@ -100,13 +100,10 @@ "@types/react-dom": "^19.2.3", "@types/svg-sprite": "^0.0.39", "jsdom": "^26.1.0", - "next": "16.2.10", "react": "^19.2.5", "react-colorful": "^5.6.1", "react-dom": "^19.2.5", "tsup": "^8.4.0", - "typescript": "^5.8.3", - "vite": "^6.4.3", - "webpack": "^5.108.4" + "typescript": "^5.8.3" } } diff --git a/skills/svg-sprites/skill.config.mjs b/skills/svg-sprites/skill.config.mjs index 5f2bacf..5c0002e 100644 --- a/skills/svg-sprites/skill.config.mjs +++ b/skills/svg-sprites/skill.config.mjs @@ -35,7 +35,7 @@ const upstream = [ export default [ { name: 'svg-sprites', - description: 'Use only when configuring, generating, or troubleshooting @gromlab/svg-sprites. Triggers: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer, or --icon-color-N. Do NOT use for custom SVG sprites, favicons, raster images, icon fonts, choosing an icon set, or inline SVG without this package.', + description: 'Use only when configuring, generating, or troubleshooting @gromlab/svg-sprites. Triggers: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer, or --icon-color-N. Do NOT use for custom SVG sprites, favicons, raster images, icon fonts, choosing an icon set, or inline SVG without this package.', output: '../artifacts/svg-sprites', maxSkillBytes: 48_000, documents: documents('en'), @@ -43,7 +43,7 @@ export default [ }, { name: 'svg-sprites-ru', - description: 'Используй только при настройке, изменении или диагностике @gromlab/svg-sprites. Триггеры: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer и --icon-color-N. НЕ используй для самописных SVG-спрайтов, inline SVG, favicon, растровых изображений, icon fonts или выбора библиотеки иконок.', + description: 'Используй только при настройке, изменении или диагностике @gromlab/svg-sprites. Триггеры: @gromlab/svg-sprites, svg-sprite.config.ts, defineSpriteConfig, generateSprite, standalone, standalone@vite, standalone@webpack, react@vite, react@webpack, next@app, next@pages, inputFiles, SpriteViewer и --icon-color-N. НЕ используй для самописных SVG-спрайтов, inline SVG, favicon, растровых изображений, icon fonts или выбора библиотеки иконок.', output: '../artifacts/svg-sprites-ru', maxSkillBytes: 48_000, documents: documents('ru'), diff --git a/skills/svg-sprites/src/en/core/00-package-overview.md b/skills/svg-sprites/src/en/core/00-package-overview.md index 8f37578..d10fc99 100644 --- a/skills/svg-sprites/src/en/core/00-package-overview.md +++ b/skills/svg-sprites/src/en/core/00-package-overview.md @@ -1,15 +1,15 @@ ## What the package does -`@gromlab/svg-sprites` is a CLI generator that builds SVG sprites from user-provided SVG files. The package does not include its own icon set: it compiles the project's SVGs into an external cacheable sprite asset and, for React/Next.js, creates a typed component, a list of valid names, and a debug manifest. +`@gromlab/svg-sprites` is a CLI generator that builds SVG sprites from user-provided SVG files. The package does not include its own icon set: it compiles the project's SVGs into an external sprite asset, supports standalone projects, and creates typed components for React/Next.js. The package supports multiple independent sprites in one project. Each explicitly selected config file or config-less directory describes one sprite and gets its own: - SVG asset; -- icon name types; -- React component; -- production entry point `.svg-sprite/index.js` with `.d.ts`; -- debug entry point `.svg-sprite/svg-sprite.manifest.js` with `.d.ts`. +- mode-specific manifest data; +- icon name types and `.svg-sprite/index.js` for bundler modes; +- a React component only for React/Next.js; +- a deployment-neutral JSON manifest without a public URL for bare `standalone`. The project determines how many sprite directories exist and where they live. For example, `name: 'file-manager'` produces `FileManagerIcon`, while another directory with `name: 'navigation'` produces a separate `NavigationIcon`. The names `FileManagerIcon` and `fileManagerIconNames` used below are examples of the API for one possible sprite, not fixed package exports. -Generated production components do not import `@gromlab/svg-sprites` at runtime. Install the package as a development dependency so configuration helpers and the local CLI use the version recorded in the project's lockfile. +Generated production runtime does not import `@gromlab/svg-sprites` at runtime. Install the package as a development dependency so configuration helpers and the local CLI use the version recorded in the project's lockfile. diff --git a/skills/svg-sprites/src/en/core/10-mode-selection.md b/skills/svg-sprites/src/en/core/10-mode-selection.md index 509a423..52c8e72 100644 --- a/skills/svg-sprites/src/en/core/10-mode-selection.md +++ b/skills/svg-sprites/src/en/core/10-mode-selection.md @@ -4,6 +4,9 @@ Select exactly one supported mode key: | Project | Mode key | |---|---| +| Static HTML / custom publishing | `standalone` | +| Standalone + Vite | `standalone@vite` | +| Standalone + Webpack 5 | `standalone@webpack` | | React + Vite | `react@vite` | | React + Webpack 5 | `react@webpack` | | Next.js App Router + Turbopack | `next@app/turbopack` | @@ -24,4 +27,4 @@ The CLI accepts exactly one path. A `.ts`, `.js`, or `.json` file loads that exa } ``` -Do not use incomplete `react`, `next@app`, or `next@pages` keys, the removed `legacy` mode, or the not-yet-implemented `standalone` mode. Create one command per config file or directory when the project has multiple sprites. +Do not use incomplete `react`, `next@app`, `next@pages`, or `standalone@` keys, or the removed `legacy` mode. Use bare `standalone` only when the application publishes the SVG itself; use the complete Vite/Webpack key otherwise. Create one command per config file or directory when the project has multiple sprites. diff --git a/skills/svg-sprites/src/en/core/40-generated-contract.md b/skills/svg-sprites/src/en/core/40-generated-contract.md index e46359d..178d31f 100644 --- a/skills/svg-sprites/src/en/core/40-generated-contract.md +++ b/skills/svg-sprites/src/en/core/40-generated-contract.md @@ -1,6 +1,6 @@ ## Generated directory contract -After generation, the selected directory has this structure: +After generation, a React/Next directory has this structure: ```text svg-sprite/ @@ -23,6 +23,10 @@ svg-sprite/ └── react-component.module.css ``` +Standalone does not create `react/`. Bare `standalone` generates `sprite.svg` and +`svg-sprite.manifest.json`; `standalone@vite`/`standalone@webpack` additionally +generate `index.*`, `icon-data.*`, and a resolved manifest. + Edit the source SVGs, selected config, and user-owned `index.ts`. Do not manually change `.gitignore` or anything in `.svg-sprite`: the next generation will overwrite them. To import from the sprite-module root, create a barrel: ```ts @@ -36,9 +40,11 @@ The internal `index.js` exports the component from `react/react-component.js` an The sprite remains a separate content-hashed asset; SVG path data is not embedded in JavaScript: - `react@vite` generates a static `sprite.svg?no-inline` import, preventing Vite from inlining it; +- `standalone@vite` uses the same Vite asset mechanism but exports an href helper without React; +- `standalone@webpack` uses Webpack Asset Modules without React; - React Webpack 5 and all Next modes generate `new URL('./sprite.svg', import.meta.url).href`, which must be processed by the selected bundler's Asset Modules; - a custom Webpack SVG loader must not intercept the generated `sprite.svg`; - in Next mode, the generated component does not contain `'use client'` and works in Server Components, SSR, and SSG; do not add a client boundary solely for an icon; - the Next build command and mode key must agree: Turbopack with `.../turbopack`, Webpack with `.../webpack`. -Do not move the generated sprite into `public` or rewrite its URL manually. When changing the router or bundler, regenerate the sprite with the new full mode key. +For bundler modes, do not move the generated sprite into `public` or rewrite its URL manually. For bare `standalone`, do not move the managed original: the application may explicitly copy it into deploy output and owns the public URL and stale-copy cleanup. Regenerate with the new complete key when changing mode. diff --git a/skills/svg-sprites/src/ru/core/00-package-overview.md b/skills/svg-sprites/src/ru/core/00-package-overview.md index dd12b1f..cf2e412 100644 --- a/skills/svg-sprites/src/ru/core/00-package-overview.md +++ b/skills/svg-sprites/src/ru/core/00-package-overview.md @@ -1,15 +1,15 @@ ## Что делает пакет -`@gromlab/svg-sprites` — CLI-генератор SVG-спрайтов для пользовательских SVG-файлов. Пакет не содержит собственного набора иконок: он собирает SVG проекта во внешний кешируемый sprite asset и для React/Next.js создаёт типизированный компонент, список допустимых имён и debug manifest. +`@gromlab/svg-sprites` — CLI-генератор SVG-спрайтов для пользовательских SVG-файлов. Пакет не содержит собственного набора иконок: он собирает SVG проекта во внешний sprite asset, поддерживает standalone-проекты и для React/Next.js создаёт типизированный компонент. Пакет рассчитан на несколько независимых спрайтов в одном проекте. Каждый явно выбранный config-файл или config-less каталог описывает один спрайт и получает собственные: - SVG asset; -- типы имён иконок; -- React-компонент; -- production entry `.svg-sprite/index.js` с `.d.ts`; -- debug entry `.svg-sprite/svg-sprite.manifest.js` с `.d.ts`. +- mode-specific manifest data; +- для bundler modes — типы имён и production entry `.svg-sprite/index.js`; +- только для React/Next.js — React-компонент; +- для bare `standalone` — deployment-neutral JSON manifest без публичного URL. Количество и расположение каталогов определяет проект. Например, `name: 'file-manager'` создаёт `FileManagerIcon`, а другой каталог с `name: 'navigation'` создаст отдельный `NavigationIcon`. Имена `FileManagerIcon` и `fileManagerIconNames` ниже являются примерами API одного из возможных спрайтов, а не фиксированными экспортами пакета. -Generated production-компоненты не импортируют `@gromlab/svg-sprites` во время выполнения. Устанавливай пакет как development dependency, чтобы config helpers и локальный CLI использовали версию из lockfile проекта. +Generated production runtime не импортирует `@gromlab/svg-sprites` во время выполнения. Устанавливай пакет как development dependency, чтобы config helpers и локальный CLI использовали версию из lockfile проекта. diff --git a/skills/svg-sprites/src/ru/core/10-mode-selection.md b/skills/svg-sprites/src/ru/core/10-mode-selection.md index c7dceeb..bd35b6e 100644 --- a/skills/svg-sprites/src/ru/core/10-mode-selection.md +++ b/skills/svg-sprites/src/ru/core/10-mode-selection.md @@ -4,6 +4,9 @@ | Проект | Mode key | |---|---| +| Static HTML / собственная публикация | `standalone` | +| Standalone + Vite | `standalone@vite` | +| Standalone + Webpack 5 | `standalone@webpack` | | React + Vite | `react@vite` | | React + Webpack 5 | `react@webpack` | | Next.js App Router + Turbopack | `next@app/turbopack` | @@ -24,4 +27,4 @@ CLI принимает ровно один путь. Путь к файлу `.ts } ``` -Не используй неполные `react`, `next@app`, `next@pages`, удалённый `legacy` или ещё не реализованный `standalone`. Для нескольких спрайтов создай отдельную команду для каждого config-файла или каталога. +Не используй неполные `react`, `next@app`, `next@pages`, `standalone@` или удалённый `legacy`. Bare `standalone` выбирай только когда приложение само публикует SVG; для Vite/Webpack используй соответствующий полный key. Для нескольких спрайтов создай отдельную команду для каждого config-файла или каталога. diff --git a/skills/svg-sprites/src/ru/core/40-generated-contract.md b/skills/svg-sprites/src/ru/core/40-generated-contract.md index fa1cc95..13fa6a1 100644 --- a/skills/svg-sprites/src/ru/core/40-generated-contract.md +++ b/skills/svg-sprites/src/ru/core/40-generated-contract.md @@ -1,6 +1,6 @@ ## Контракт generated-каталога -После генерации выбранный каталог имеет следующий вид: +После генерации React/Next-каталог имеет следующий вид: ```text svg-sprite/ @@ -23,6 +23,10 @@ svg-sprite/ └── react-component.module.css ``` +Standalone не создаёт `react/`. Bare `standalone` генерирует `sprite.svg` и +`svg-sprite.manifest.json`; `standalone@vite`/`standalone@webpack` дополнительно +генерируют `index.*`, `icon-data.*` и resolved manifest. + Редактируй исходные SVG, config-файл и пользовательский `index.ts`. Не изменяй вручную `.gitignore` и содержимое `.svg-sprite`: повторная генерация их перезапишет. Для импорта из корня sprite-модуля создай barrel: ```ts @@ -36,9 +40,11 @@ export * from './.svg-sprite' Спрайт остаётся отдельным asset с content hash; SVG path-данные не встраиваются в JavaScript: - `react@vite` генерирует статический импорт `sprite.svg?no-inline`, запрещающий Vite inline; +- `standalone@vite` использует тот же Vite asset-механизм, но экспортирует href helper без React; +- `standalone@webpack` использует Webpack Asset Modules без React; - React Webpack 5 и все Next modes генерируют `new URL('./sprite.svg', import.meta.url).href`, который должен обработать Asset Modules соответствующего сборщика; - кастомный Webpack SVG loader не должен перехватывать generated `sprite.svg`; - в Next mode generated-компонент не содержит `'use client'` и работает в Server Components, SSR и SSG; не добавляй клиентскую границу только ради иконки; - команда сборки Next и mode key должны совпадать: Turbopack с `.../turbopack`, Webpack с `.../webpack`. -Не перемещай generated sprite в `public` и не переписывай URL вручную. При смене роутера или сборщика перегенерируй спрайт с новым полным mode key. +Для bundler modes не перемещай generated sprite в `public` и не переписывай URL вручную. Для bare `standalone` не перемещай managed original: приложение может явно копировать его в deploy output и само отвечает за публичный URL и очистку копии. При смене mode перегенерируй спрайт с новым полным key. diff --git a/src/cli/parse-args.ts b/src/cli/parse-args.ts index c7cbab5..b74a276 100644 --- a/src/cli/parse-args.ts +++ b/src/cli/parse-args.ts @@ -11,6 +11,9 @@ export const CLI_USAGE = [ ' A directory enables config-less generation from CLI options', '', 'Modes:', + ' standalone', + ' standalone@vite', + ' standalone@webpack', ' react@vite', ' react@webpack', ' next@app/turbopack', diff --git a/src/config.ts b/src/config.ts index 0da06a2..30940b1 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,6 +17,9 @@ const CONFIG_FIELDS = new Set([ ]) const TRANSFORM_FIELDS = new Set(['removeSize', 'replaceColors', 'addTransition']) const MODES = new Set([ + 'standalone', + 'standalone@vite', + 'standalone@webpack', 'react@vite', 'react@webpack', 'next@app/turbopack', diff --git a/src/core/mode-adapter.ts b/src/core/mode-adapter.ts index 7416c99..38af85b 100644 --- a/src/core/mode-adapter.ts +++ b/src/core/mode-adapter.ts @@ -3,6 +3,7 @@ import type { NextBundler, NextRouter, ReactAssetTarget, + StandaloneAssetTarget, SpriteMode, } from '../targets/types.js' import type { ResolvedSpriteConfig, SpriteFolder } from '../types.js' @@ -33,7 +34,11 @@ export type NextModeResultMetadata = { readonly bundler: NextBundler } -export type ModeResultMetadata = ReactModeResultMetadata | NextModeResultMetadata +export type StandaloneModeResultMetadata = { + readonly target: StandaloneAssetTarget +} + +export type ModeResultMetadata = ReactModeResultMetadata | NextModeResultMetadata | StandaloneModeResultMetadata export type OutputPlan = { readonly files: readonly GeneratedFile[] @@ -41,7 +46,7 @@ export type OutputPlan = { readonly generatedDir: '.svg-sprite' readonly sprite: string readonly manifest: string - readonly entry: string + readonly entry?: string } readonly result: ModeResultMetadata } diff --git a/src/generate.ts b/src/generate.ts index 3235abb..ede6935 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -33,7 +33,10 @@ export async function generateSprite( }) const plannedPaths = new Set(plan.files.map((file) => file.path.replaceAll('\\', '/'))) - for (const requiredPath of [plan.paths.entry, plan.paths.sprite, plan.paths.manifest]) { + const resultPaths = [plan.paths.sprite, plan.paths.manifest] + if (plan.paths.entry) resultPaths.push(plan.paths.entry) + + for (const requiredPath of resultPaths) { if (!plannedPaths.has(requiredPath)) { throw new Error(`Mode "${config.mode}" result path is missing from its output plan: ${requiredPath}`) } diff --git a/src/index.ts b/src/index.ts index f02f376..1e2c93f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,11 +23,21 @@ export type { NextRouter, ReactAssetTarget, ReactSpriteMode, + StandaloneAssetTarget, + StandaloneSpriteMode, + StaticAssetTarget, SpriteAssetTarget, SpriteMode, ViteAssetTarget, WebpackAssetTarget, } from './targets/types.js' +export type { + StandaloneSpriteManifest, + StandaloneSpriteManifestColor, + StandaloneSpriteManifestData, + StandaloneSpriteManifestIcon, + StandaloneTargetForMode, +} from './manifest-types.js' export type { ResolvedSpriteConfig, SpriteConfig, diff --git a/src/manifest-types.ts b/src/manifest-types.ts new file mode 100644 index 0000000..bd32140 --- /dev/null +++ b/src/manifest-types.ts @@ -0,0 +1,42 @@ +import type { StandaloneSpriteMode } from './targets/types.js' + +export type StandaloneSpriteManifestColor = { + variable: `--icon-color-${number}` + fallback: string +} + +export type StandaloneSpriteManifestIcon = { + name: string + id: string + viewBox: string | null + colors: readonly StandaloneSpriteManifestColor[] +} + +export type StandaloneTargetForMode = + M extends 'standalone' + ? 'static' + : M extends 'standalone@vite' + ? 'vite' + : 'webpack' + +/** Deployment-neutral данные standalone-спрайта. */ +export type StandaloneSpriteManifestData< + M extends StandaloneSpriteMode = StandaloneSpriteMode, +> = { + schemaVersion: 1 + generator: '@gromlab/svg-sprites' + name: string + description?: string + mode: M + target: StandaloneTargetForMode + format: 'stack' | 'symbol' + iconCount: number + icons: readonly StandaloneSpriteManifestIcon[] +} + +/** Standalone manifest с публичным URL опубликованного SVG. */ +export type StandaloneSpriteManifest< + M extends StandaloneSpriteMode = StandaloneSpriteMode, +> = StandaloneSpriteManifestData & { + spriteUrl: string +} diff --git a/src/mode-registry.ts b/src/mode-registry.ts index 61c1f51..f463b79 100644 --- a/src/mode-registry.ts +++ b/src/mode-registry.ts @@ -5,9 +5,15 @@ import { nextPagesTurbopackAdapter } from './modes/next-pages-turbopack/adapter. import { nextPagesWebpackAdapter } from './modes/next-pages-webpack/adapter.js' import { reactViteAdapter } from './modes/react-vite/adapter.js' import { reactWebpackAdapter } from './modes/react-webpack/adapter.js' +import { standaloneAdapter } from './modes/standalone/adapter.js' +import { standaloneViteAdapter } from './modes/standalone-vite/adapter.js' +import { standaloneWebpackAdapter } from './modes/standalone-webpack/adapter.js' import type { SpriteMode } from './targets/types.js' const modeRegistry: Record = { + standalone: standaloneAdapter, + 'standalone@vite': standaloneViteAdapter, + 'standalone@webpack': standaloneWebpackAdapter, 'react@vite': reactViteAdapter, 'react@webpack': reactWebpackAdapter, 'next@app/turbopack': nextAppTurbopackAdapter, diff --git a/src/modes/standalone-vite/adapter.ts b/src/modes/standalone-vite/adapter.ts new file mode 100644 index 0000000..31814c5 --- /dev/null +++ b/src/modes/standalone-vite/adapter.ts @@ -0,0 +1,27 @@ +import { compileSpriteContent } from '../../compiler.js' +import { createCompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import type { ModeAdapter } from '../../core/mode-adapter.js' +import { generateOutputFiles } from './output.js' + +export const standaloneViteAdapter: ModeAdapter<'standalone@vite'> = { + mode: 'standalone@vite', + contractVersion: 1, + + async generate(context) { + const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { + rootViewBox: false, + }) + const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack') + + return { + files: generateOutputFiles(context.config, artifact), + paths: { + generatedDir: '.svg-sprite', + sprite: '.svg-sprite/sprite.svg', + manifest: '.svg-sprite/svg-sprite.manifest.js', + entry: '.svg-sprite/index.js', + }, + result: { target: 'vite' }, + } + }, +} diff --git a/src/modes/standalone-vite/output.ts b/src/modes/standalone-vite/output.ts new file mode 100644 index 0000000..0fd8e27 --- /dev/null +++ b/src/modes/standalone-vite/output.ts @@ -0,0 +1,159 @@ +import path from 'node:path' +import type { CompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import { GENERATED_MARKER } from '../../core/generated-markers.js' +import type { GeneratedFile } from '../../core/mode-adapter.js' +import type { ResolvedSpriteConfig } from '../../types.js' + +const MODE = 'standalone@vite' +const TARGET = 'vite' +const OUTPUT_DIR = '.svg-sprite' +const NOTICE = 'АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ. Не редактируйте вручную.' + +function pascal(value: string): string { + return value.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('') +} + +function camel(value: string): string { + return value.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase()) +} + +function header(enabled: boolean): string { + return enabled + ? `/*\n * ${NOTICE}\n * ${GENERATED_MARKER}.\n */` + : `/* ${GENERATED_MARKER}. Do not edit. */` +} + +function svg(bytes: Uint8Array, enabled: boolean): string { + const marker = enabled ? `${NOTICE}\n ${GENERATED_MARKER}.` : `${GENERATED_MARKER}. Do not edit.` + const content = new TextDecoder().decode(bytes) + return content.startsWith(')\s*/, `$1\n\n`) + : `\n${content}` +} + +function description(config: ResolvedSpriteConfig): string[] { + const text = config.description ?? `Имена иконок SVG-спрайта «${config.name}».` + return ['/**', ...text.split(/\r?\n/).map((line) => ` * ${line.replace(/\*\//g, '* /')}`), ' */'] +} + +function iconData(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + const prefix = camel(config.name) + return [ + header(config.generatedNotice), + '', + ...description(config), + `export const ${prefix}IconNames = ${JSON.stringify(artifact.icons.map((icon) => icon.name), null, 2)}`, + '', + `export const ${prefix}IconIds = ${JSON.stringify(Object.fromEntries(artifact.icons.map((icon) => [icon.name, icon.id])), null, 2)}`, + '', + ].join('\n') +} + +function iconDeclarations(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + const name = `${pascal(config.name)}IconName` + const names = `${camel(config.name)}IconNames` + const ids = `${camel(config.name)}IconIds` + return [ + header(config.generatedNotice), + '', + ...description(config), + `export declare const ${names}: readonly [`, + ...artifact.icons.map((icon) => ` ${JSON.stringify(icon.name)},`), + ']', + `export type ${name} = typeof ${names}[number]`, + `export declare const ${ids}: {`, + ...artifact.icons.map((icon) => ` readonly ${JSON.stringify(icon.name)}: ${JSON.stringify(icon.id)}`), + '}', + '', + ].join('\n') +} + +function index(config: ResolvedSpriteConfig): string { + const prefix = camel(config.name) + const pascalName = pascal(config.name) + return [ + header(config.generatedNotice), + "import spriteUrl from './sprite.svg?no-inline'", + `import { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`, + '', + `export { ${prefix}IconIds, ${prefix}IconNames }`, + `export const ${prefix}SpriteUrl = spriteUrl`, + `export function get${pascalName}IconHref(icon) {`, + ` return ${prefix}SpriteUrl + '#' + ${prefix}IconIds[icon]`, + '}', + '', + ].join('\n') +} + +function indexDeclarations(config: ResolvedSpriteConfig): string { + const prefix = camel(config.name) + const pascalName = pascal(config.name) + return [ + header(config.generatedNotice), + `import type { ${pascalName}IconName } from './icon-data.js'`, + '', + `export { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`, + `export type { ${pascalName}IconName } from './icon-data.js'`, + `export declare const ${prefix}SpriteUrl: string`, + `export declare function get${pascalName}IconHref(icon: ${pascalName}IconName): string`, + '', + ].join('\n') +} + +function manifestData(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact) { + return { + schemaVersion: 1, + generator: '@gromlab/svg-sprites', + name: config.name, + ...(config.description === undefined ? {} : { description: config.description }), + mode: MODE, + target: TARGET, + format: artifact.format, + iconCount: artifact.icons.length, + icons: artifact.icons, + } +} + +function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + return [ + header(config.generatedNotice), + "import spriteUrl from './sprite.svg?no-inline'", + '', + `export const spriteManifestData = ${JSON.stringify(manifestData(config, artifact), null, 2)}`, + 'export function createSpriteManifest(publicSpriteUrl) {', + ' return { ...spriteManifestData, spriteUrl: publicSpriteUrl }', + '}', + 'export const spriteManifest = createSpriteManifest(spriteUrl)', + '', + 'export default spriteManifest', + '', + ].join('\n') +} + +function manifestTypes(config: ResolvedSpriteConfig): string { + return [ + header(config.generatedNotice), + "import type { StandaloneSpriteManifest, StandaloneSpriteManifestData } from '@gromlab/svg-sprites'", + '', + "export declare const spriteManifestData: StandaloneSpriteManifestData<'standalone@vite'>", + "export declare function createSpriteManifest(spriteUrl: string): StandaloneSpriteManifest<'standalone@vite'>", + "export declare const spriteManifest: StandaloneSpriteManifest<'standalone@vite'>", + 'export default spriteManifest', + '', + ].join('\n') +} + +export function generateOutputFiles( + config: ResolvedSpriteConfig, + artifact: CompiledSpriteArtifact, +): GeneratedFile[] { + return [ + { path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config) }, + { path: path.posix.join(OUTPUT_DIR, 'index.d.ts'), content: indexDeclarations(config) }, + { path: path.posix.join(OUTPUT_DIR, 'sprite.svg'), content: svg(artifact.bytes, config.generatedNotice) }, + { path: path.posix.join(OUTPUT_DIR, 'icon-data.js'), content: iconData(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'icon-data.d.ts'), content: iconDeclarations(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'svg-sprite.manifest.js'), content: manifest(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'svg-sprite.manifest.d.ts'), content: manifestTypes(config) }, + ] +} diff --git a/src/modes/standalone-webpack/adapter.ts b/src/modes/standalone-webpack/adapter.ts new file mode 100644 index 0000000..a0f245b --- /dev/null +++ b/src/modes/standalone-webpack/adapter.ts @@ -0,0 +1,27 @@ +import { compileSpriteContent } from '../../compiler.js' +import { createCompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import type { ModeAdapter } from '../../core/mode-adapter.js' +import { generateOutputFiles } from './output.js' + +export const standaloneWebpackAdapter: ModeAdapter<'standalone@webpack'> = { + mode: 'standalone@webpack', + contractVersion: 1, + + async generate(context) { + const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { + rootViewBox: false, + }) + const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack') + + return { + files: generateOutputFiles(context.config, artifact), + paths: { + generatedDir: '.svg-sprite', + sprite: '.svg-sprite/sprite.svg', + manifest: '.svg-sprite/svg-sprite.manifest.js', + entry: '.svg-sprite/index.js', + }, + result: { target: 'webpack' }, + } + }, +} diff --git a/src/modes/standalone-webpack/output.ts b/src/modes/standalone-webpack/output.ts new file mode 100644 index 0000000..a00afd0 --- /dev/null +++ b/src/modes/standalone-webpack/output.ts @@ -0,0 +1,158 @@ +import path from 'node:path' +import type { CompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import { GENERATED_MARKER } from '../../core/generated-markers.js' +import type { GeneratedFile } from '../../core/mode-adapter.js' +import type { ResolvedSpriteConfig } from '../../types.js' + +const MODE = 'standalone@webpack' +const TARGET = 'webpack' +const OUTPUT_DIR = '.svg-sprite' +const NOTICE = 'АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ. Не редактируйте вручную.' + +function pascal(value: string): string { + return value.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join('') +} + +function camel(value: string): string { + return value.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase()) +} + +function header(enabled: boolean): string { + return enabled + ? `/*\n * ${NOTICE}\n * ${GENERATED_MARKER}.\n */` + : `/* ${GENERATED_MARKER}. Do not edit. */` +} + +function svg(bytes: Uint8Array, enabled: boolean): string { + const marker = enabled ? `${NOTICE}\n ${GENERATED_MARKER}.` : `${GENERATED_MARKER}. Do not edit.` + const content = new TextDecoder().decode(bytes) + return content.startsWith(')\s*/, `$1\n\n`) + : `\n${content}` +} + +function description(config: ResolvedSpriteConfig): string[] { + const text = config.description ?? `Имена иконок SVG-спрайта «${config.name}».` + return ['/**', ...text.split(/\r?\n/).map((line) => ` * ${line.replace(/\*\//g, '* /')}`), ' */'] +} + +function iconData(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + const prefix = camel(config.name) + return [ + header(config.generatedNotice), + '', + ...description(config), + `export const ${prefix}IconNames = ${JSON.stringify(artifact.icons.map((icon) => icon.name), null, 2)}`, + '', + `export const ${prefix}IconIds = ${JSON.stringify(Object.fromEntries(artifact.icons.map((icon) => [icon.name, icon.id])), null, 2)}`, + '', + ].join('\n') +} + +function iconDeclarations(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + const name = `${pascal(config.name)}IconName` + const names = `${camel(config.name)}IconNames` + const ids = `${camel(config.name)}IconIds` + return [ + header(config.generatedNotice), + '', + ...description(config), + `export declare const ${names}: readonly [`, + ...artifact.icons.map((icon) => ` ${JSON.stringify(icon.name)},`), + ']', + `export type ${name} = typeof ${names}[number]`, + `export declare const ${ids}: {`, + ...artifact.icons.map((icon) => ` readonly ${JSON.stringify(icon.name)}: ${JSON.stringify(icon.id)}`), + '}', + '', + ].join('\n') +} + +function index(config: ResolvedSpriteConfig): string { + const prefix = camel(config.name) + const pascalName = pascal(config.name) + return [ + header(config.generatedNotice), + `import { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`, + '', + `export { ${prefix}IconIds, ${prefix}IconNames }`, + `export const ${prefix}SpriteUrl = new URL('./sprite.svg', import.meta.url).href`, + `export function get${pascalName}IconHref(icon) {`, + ` return ${prefix}SpriteUrl + '#' + ${prefix}IconIds[icon]`, + '}', + '', + ].join('\n') +} + +function indexDeclarations(config: ResolvedSpriteConfig): string { + const prefix = camel(config.name) + const pascalName = pascal(config.name) + return [ + header(config.generatedNotice), + `import type { ${pascalName}IconName } from './icon-data.js'`, + '', + `export { ${prefix}IconIds, ${prefix}IconNames } from './icon-data.js'`, + `export type { ${pascalName}IconName } from './icon-data.js'`, + `export declare const ${prefix}SpriteUrl: string`, + `export declare function get${pascalName}IconHref(icon: ${pascalName}IconName): string`, + '', + ].join('\n') +} + +function manifestData(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact) { + return { + schemaVersion: 1, + generator: '@gromlab/svg-sprites', + name: config.name, + ...(config.description === undefined ? {} : { description: config.description }), + mode: MODE, + target: TARGET, + format: artifact.format, + iconCount: artifact.icons.length, + icons: artifact.icons, + } +} + +function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + return [ + header(config.generatedNotice), + "const spriteUrl = new URL('./sprite.svg', import.meta.url).href", + '', + `export const spriteManifestData = ${JSON.stringify(manifestData(config, artifact), null, 2)}`, + 'export function createSpriteManifest(publicSpriteUrl) {', + ' return { ...spriteManifestData, spriteUrl: publicSpriteUrl }', + '}', + 'export const spriteManifest = createSpriteManifest(spriteUrl)', + '', + 'export default spriteManifest', + '', + ].join('\n') +} + +function manifestTypes(config: ResolvedSpriteConfig): string { + return [ + header(config.generatedNotice), + "import type { StandaloneSpriteManifest, StandaloneSpriteManifestData } from '@gromlab/svg-sprites'", + '', + "export declare const spriteManifestData: StandaloneSpriteManifestData<'standalone@webpack'>", + "export declare function createSpriteManifest(spriteUrl: string): StandaloneSpriteManifest<'standalone@webpack'>", + "export declare const spriteManifest: StandaloneSpriteManifest<'standalone@webpack'>", + 'export default spriteManifest', + '', + ].join('\n') +} + +export function generateOutputFiles( + config: ResolvedSpriteConfig, + artifact: CompiledSpriteArtifact, +): GeneratedFile[] { + return [ + { path: path.posix.join(OUTPUT_DIR, 'index.js'), content: index(config) }, + { path: path.posix.join(OUTPUT_DIR, 'index.d.ts'), content: indexDeclarations(config) }, + { path: path.posix.join(OUTPUT_DIR, 'sprite.svg'), content: svg(artifact.bytes, config.generatedNotice) }, + { path: path.posix.join(OUTPUT_DIR, 'icon-data.js'), content: iconData(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'icon-data.d.ts'), content: iconDeclarations(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'svg-sprite.manifest.js'), content: manifest(config, artifact) }, + { path: path.posix.join(OUTPUT_DIR, 'svg-sprite.manifest.d.ts'), content: manifestTypes(config) }, + ] +} diff --git a/src/modes/standalone/adapter.ts b/src/modes/standalone/adapter.ts new file mode 100644 index 0000000..e0fa5f4 --- /dev/null +++ b/src/modes/standalone/adapter.ts @@ -0,0 +1,26 @@ +import { compileSpriteContent } from '../../compiler.js' +import { createCompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import type { ModeAdapter } from '../../core/mode-adapter.js' +import { generateOutputFiles } from './output.js' + +export const standaloneAdapter: ModeAdapter<'standalone'> = { + mode: 'standalone', + contractVersion: 2, + + async generate(context) { + const bytes = await compileSpriteContent(context.prepared.folder, context.config.transform, { + rootViewBox: false, + }) + const artifact = createCompiledSpriteArtifact(bytes, context.prepared.iconNames, 'stack') + + return { + files: generateOutputFiles(context.config, artifact), + paths: { + generatedDir: '.svg-sprite', + sprite: '.svg-sprite/sprite.svg', + manifest: '.svg-sprite/svg-sprite.manifest.json', + }, + result: { target: 'static' }, + } + }, +} diff --git a/src/modes/standalone/output.ts b/src/modes/standalone/output.ts new file mode 100644 index 0000000..8dcca9b --- /dev/null +++ b/src/modes/standalone/output.ts @@ -0,0 +1,53 @@ +import path from 'node:path' +import type { CompiledSpriteArtifact } from '../../core/compiled-artifact.js' +import { GENERATED_MARKER } from '../../core/generated-markers.js' +import type { GeneratedFile } from '../../core/mode-adapter.js' +import type { ResolvedSpriteConfig } from '../../types.js' + +const MODE = 'standalone' +const TARGET = 'static' +const OUTPUT_DIR = '.svg-sprite' +const NOTICE = 'АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ. Не редактируйте вручную.' + +function generated(enabled: boolean): string { + return enabled + ? `${NOTICE} ${GENERATED_MARKER}.` + : `${GENERATED_MARKER}. Do not edit.` +} + +function svg(bytes: Uint8Array, enabled: boolean): string { + const marker = enabled ? `${NOTICE}\n ${GENERATED_MARKER}.` : `${GENERATED_MARKER}. Do not edit.` + const content = new TextDecoder().decode(bytes) + return content.startsWith(')\s*/, `$1\n\n`) + : `\n${content}` +} + +function manifestData(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact) { + return { + schemaVersion: 1, + generator: '@gromlab/svg-sprites', + generated: generated(config.generatedNotice), + name: config.name, + ...(config.description === undefined ? {} : { description: config.description }), + mode: MODE, + target: TARGET, + format: artifact.format, + iconCount: artifact.icons.length, + icons: artifact.icons, + } +} + +function manifest(config: ResolvedSpriteConfig, artifact: CompiledSpriteArtifact): string { + return `${JSON.stringify(manifestData(config, artifact), null, 2)}\n` +} + +export function generateOutputFiles( + config: ResolvedSpriteConfig, + artifact: CompiledSpriteArtifact, +): GeneratedFile[] { + return [ + { path: path.posix.join(OUTPUT_DIR, 'sprite.svg'), content: svg(artifact.bytes, config.generatedNotice) }, + { path: path.posix.join(OUTPUT_DIR, 'svg-sprite.manifest.json'), content: manifest(config, artifact) }, + ] +} diff --git a/src/react/types.ts b/src/react/types.ts index cea60f1..d709dc5 100644 --- a/src/react/types.ts +++ b/src/react/types.ts @@ -1,4 +1,4 @@ -import type { SpriteAssetTarget, SpriteMode } from '../targets/types.js' +import type { NextAssetTarget, ReactAssetTarget, ReactSpriteMode } from '../targets/types.js' export type SpriteManifestColor = { variable: `--icon-color-${number}` @@ -19,8 +19,8 @@ export type SpriteManifest = { name: string description?: string componentName: string - mode?: SpriteMode - target: SpriteAssetTarget + mode?: ReactSpriteMode | NextAssetTarget + target: ReactAssetTarget | NextAssetTarget format: 'stack' | 'symbol' iconCount: number spriteUrl: string diff --git a/src/targets/types.ts b/src/targets/types.ts index b974536..054cfc2 100644 --- a/src/targets/types.ts +++ b/src/targets/types.ts @@ -14,6 +14,9 @@ export type ViteAssetTarget = 'vite' */ export type WebpackAssetTarget = 'webpack' +/** Asset target для проекта без сборщика. */ +export type StaticAssetTarget = 'static' + /** Роутер Next.js, для которого генерируется sprite-модуль. */ export type NextRouter = 'app' | 'pages' @@ -34,11 +37,17 @@ export type ReactAssetTarget = ViteAssetTarget | WebpackAssetTarget /** Полный ключ React mode, используемый конфигом, CLI и manifest. */ export type ReactSpriteMode = `react@${ReactAssetTarget}` -/** Любая среда, для которой может быть сгенерирован React sprite-модуль. */ -export type SpriteAssetTarget = ReactAssetTarget | NextAssetTarget +/** Среда standalone sprite-модуля. */ +export type StandaloneAssetTarget = StaticAssetTarget | ViteAssetTarget | WebpackAssetTarget -/** Режим генерации sprite-модуля. В будущем расширяется standalone mode. */ -export type SpriteMode = ReactSpriteMode | NextAssetTarget +/** Полный ключ standalone mode, используемый конфигом, CLI и manifest. */ +export type StandaloneSpriteMode = 'standalone' | `standalone@${ViteAssetTarget | WebpackAssetTarget}` + +/** Любая среда, для которой может быть сгенерирован React sprite-модуль. */ +export type SpriteAssetTarget = ReactAssetTarget | NextAssetTarget | StaticAssetTarget + +/** Режим генерации sprite-модуля. */ +export type SpriteMode = ReactSpriteMode | NextAssetTarget | StandaloneSpriteMode /** Фрагменты кода, необходимые компоненту для получения URL SVG asset. */ export type SpriteAssetUrlCode = { diff --git a/test/asset-targets.test.mjs b/test/asset-targets.test.mjs deleted file mode 100644 index 5ae7ebc..0000000 --- a/test/asset-targets.test.mjs +++ /dev/null @@ -1,117 +0,0 @@ -import assert from 'node:assert/strict' -import fs from 'node:fs' -import os from 'node:os' -import path from 'node:path' -import test from 'node:test' -import { build as viteBuild } from 'vite' -import webpack from 'webpack' - -const TINY_SPRITE = [ - '', - '', - '', - '', - '', - '', -].join('') - -function getOutputFiles(directory, extension) { - const files = [] - - for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { - const entryPath = path.join(directory, entry.name) - - if (entry.isDirectory()) { - files.push(...getOutputFiles(entryPath, extension).map((filePath) => path.join(entry.name, filePath))) - } else if (entry.name.endsWith(extension)) { - files.push(entry.name) - } - } - - return files -} - -test('Vite target keeps a tiny sprite as a separate asset', async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-vite-target-')) - const outputDir = path.join(rootDir, 'dist') - - fs.writeFileSync( - path.join(rootDir, 'index.html'), - '
', - ) - fs.writeFileSync( - path.join(rootDir, 'main.js'), - [ - "import spriteUrl from './sprite.svg?no-inline'", - "document.querySelector('#root').dataset.spriteUrl = spriteUrl", - '', - ].join('\n'), - ) - fs.writeFileSync(path.join(rootDir, 'sprite.svg'), TINY_SPRITE) - - await viteBuild({ - root: rootDir, - logLevel: 'silent', - build: { - outDir: outputDir, - }, - }) - - const svgFiles = getOutputFiles(outputDir, '.svg') - const jsFiles = getOutputFiles(outputDir, '.js') - const javascript = jsFiles - .map((filePath) => fs.readFileSync(path.join(outputDir, filePath), 'utf-8')) - .join('\n') - - assert.equal(svgFiles.length, 1) - assert.match(svgFiles[0], /sprite-[A-Za-z0-9_-]+\.svg$/) - assert.doesNotMatch(javascript, /data:image\/svg\+xml/) -}) - -test('Webpack target emits new URL sprite through Asset Modules', async () => { - const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-webpack-target-')) - const outputDir = path.join(rootDir, 'dist') - - fs.writeFileSync( - path.join(rootDir, 'index.js'), - [ - "const spriteUrl = new URL('./sprite.svg', import.meta.url).href", - 'globalThis.__SPRITE_URL__ = spriteUrl', - '', - ].join('\n'), - ) - fs.writeFileSync(path.join(rootDir, 'sprite.svg'), TINY_SPRITE) - - await new Promise((resolve, reject) => { - webpack({ - context: rootDir, - entry: './index.js', - mode: 'production', - output: { - assetModuleFilename: 'assets/[name]-[contenthash][ext]', - filename: 'bundle.js', - path: outputDir, - }, - }, (error, stats) => { - if (error) { - reject(error) - return - } - - if (!stats || stats.hasErrors()) { - const errors = stats?.toJson({ all: false, errors: true }).errors ?? [] - reject(new Error(errors.map((item) => item.message).join('\n'))) - return - } - - resolve() - }) - }) - - const svgFiles = getOutputFiles(outputDir, '.svg') - const javascript = fs.readFileSync(path.join(outputDir, 'bundle.js'), 'utf-8') - - assert.equal(svgFiles.length, 1) - assert.match(svgFiles[0], /sprite-[a-f0-9]+\.svg$/) - assert.doesNotMatch(javascript, /data:image\/svg\+xml/) -}) diff --git a/test/mode-contracts.test.mjs b/test/mode-contracts.test.mjs new file mode 100644 index 0000000..048079b --- /dev/null +++ b/test/mode-contracts.test.mjs @@ -0,0 +1,243 @@ +import assert from 'node:assert/strict' +import { createHash } from 'node:crypto' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import test from 'node:test' + +import { generateNextSprite, generateReactSprite } from '../dist/index.js' + +const GENERATED_FILES = [ + 'icon-data.d.ts', + 'icon-data.js', + 'index.d.ts', + 'index.js', + 'react', + 'sprite.svg', + 'state.json', + 'svg-sprite.manifest.d.ts', + 'svg-sprite.manifest.js', +] + +function createReactFixture(config = {}) { + const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-react-')) + const rootDir = path.join(temporaryDir, 'src', 'widgets', 'file-manager', 'svg-sprite') + const iconsDir = path.join(rootDir, 'icons') + const configPath = path.join(rootDir, 'svg-sprite.config.ts') + + fs.mkdirSync(iconsDir, { recursive: true }) + fs.writeFileSync(configPath, `export default ${JSON.stringify({ + name: 'file-manager', + description: 'File manager icons', + ...config, + }, null, 2)}\n`) + fs.writeFileSync( + path.join(iconsDir, 'check.svg'), + '', + ) + fs.writeFileSync( + path.join(iconsDir, 'folder.svg'), + '', + ) + fs.writeFileSync( + path.join(iconsDir, 'folder open.svg'), + '', + ) + + return { rootDir, iconsDir, configPath } +} + +test('generates the current isolated React Vite contract', async () => { + const { rootDir, configPath } = createReactFixture() + const result = await generateReactSprite(configPath, 'vite') + const generatedDir = path.join(rootDir, '.svg-sprite') + + assert.equal(result.name, 'file-manager') + assert.equal(result.iconCount, 3) + assert.equal(result.target, 'vite') + assert.equal(result.manifestPath, path.join(generatedDir, 'svg-sprite.manifest.js')) + assert.deepEqual(fs.readdirSync(generatedDir).sort(), GENERATED_FILES) + + const gitignore = fs.readFileSync(path.join(rootDir, '.gitignore'), 'utf8') + assert.equal(gitignore, '# @generated by @gromlab/svg-sprites. Do not edit.\n/.svg-sprite/\n') + + const sprite = fs.readFileSync(result.spritePath, 'utf8') + assert.match(sprite, /АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ/) + assert.match(sprite, /]+id="check"/) + assert.match(sprite, /]+id="folder"/) + assert.doesNotMatch(sprite, /]+id="(icon-[a-f0-9]{16})"/)?.[1] + assert.ok(unsafeId) + + const component = fs.readFileSync(path.join(generatedDir, 'react', 'react-component.js'), 'utf8') + const componentTypes = fs.readFileSync(path.join(generatedDir, 'react', 'react-component.d.ts'), 'utf8') + const iconData = fs.readFileSync(path.join(generatedDir, 'icon-data.js'), 'utf8') + const manifest = fs.readFileSync(result.manifestPath, 'utf8') + + assert.match(component, /export const FileManagerIcon/) + assert.match(component, /sprite\.svg\?no-inline/) + assert.match(componentTypes, /FileManagerIconStyle/) + assert.match(componentTypes, /--icon-color-/) + assert.ok(iconData.includes(`"folder open": "${unsafeId}"`)) + assert.match(manifest, /"componentName": "FileManagerIcon"/) + assert.match(manifest, /"mode": "react@vite"/) + assert.match(manifest, /"target": "vite"/) + assert.match(manifest, /"iconCount": 3/) + + const state = JSON.parse(fs.readFileSync(path.join(generatedDir, 'state.json'), 'utf8')) + assert.deepEqual(state.owner, { mode: 'react@vite', contractVersion: 5 }) + assert.equal(state.files.includes('.svg-sprite/react/react-component.js'), true) +}) + +test('supports Webpack target and a custom icons directory', async () => { + const { rootDir, configPath } = createReactFixture({ + name: 'documents', + inputFolder: './assets', + generatedNotice: false, + }) + fs.renameSync(path.join(rootDir, 'icons'), path.join(rootDir, 'assets')) + + const result = await generateReactSprite(configPath, 'webpack') + const component = fs.readFileSync(path.join(result.generatedDir, 'react', 'react-component.js'), 'utf8') + const manifest = fs.readFileSync(result.manifestPath, 'utf8') + + assert.equal(result.name, 'documents') + assert.equal(result.iconCount, 3) + assert.equal(result.target, 'webpack') + assert.match(component, /@generated by @gromlab\/svg-sprites/) + assert.doesNotMatch(component, /АВТОМАТИЧЕСКИ/) + assert.match(component, /DocumentsIcon/) + assert.match(component, /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/) + assert.match(manifest, /"target": "webpack"/) +}) + +test('merges inputFolder and inputFiles and deduplicates identical paths', async () => { + const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-react-inputs-')) + const rootDir = path.join(temporaryDir, 'svg-sprite') + const iconsDir = path.join(rootDir, 'icons') + const sharedIcon = path.join(temporaryDir, 'shared.svg') + fs.mkdirSync(iconsDir, { recursive: true }) + fs.writeFileSync(path.join(iconsDir, 'local.svg'), '') + fs.writeFileSync(sharedIcon, '') + const configPath = path.join(rootDir, 'config.ts') + fs.writeFileSync(configPath, `export default { name: 'mixed-inputs', inputFiles: [${JSON.stringify(path.relative(rootDir, sharedIcon))}, ${JSON.stringify(path.relative(rootDir, sharedIcon))}] }`) + + const result = await generateReactSprite(configPath, 'vite') + const sprite = fs.readFileSync(result.spritePath, 'utf8') + assert.equal(result.name, 'mixed-inputs') + assert.equal(result.iconCount, 2) + assert.match(sprite, /id="local"/) + assert.match(sprite, /id="shared"/) +}) + +test('supports inputFiles without the default icons directory', async () => { + const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-react-files-')) + const rootDir = path.join(temporaryDir, 'svg-sprite') + const sharedIcon = path.join(temporaryDir, 'shared', 'only.svg') + fs.mkdirSync(rootDir, { recursive: true }) + fs.mkdirSync(path.dirname(sharedIcon), { recursive: true }) + fs.writeFileSync(sharedIcon, '') + const configPath = path.join(rootDir, 'config.ts') + fs.writeFileSync(configPath, `export default { inputFiles: [${JSON.stringify(path.relative(rootDir, sharedIcon))}] }`) + + const result = await generateReactSprite(configPath, 'webpack') + assert.equal(result.iconCount, 1) + assert.match(fs.readFileSync(result.spritePath, 'utf8'), /id="only"/) +}) + +test('rejects duplicate names and explicitly missing input folders', async () => { + const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-react-input-errors-')) + const rootDir = path.join(temporaryDir, 'svg-sprite') + const first = path.join(temporaryDir, 'one', 'shared.svg') + const second = path.join(temporaryDir, 'two', 'shared.svg') + fs.mkdirSync(rootDir, { recursive: true }) + fs.mkdirSync(path.dirname(first), { recursive: true }) + fs.mkdirSync(path.dirname(second), { recursive: true }) + fs.writeFileSync(first, '') + fs.writeFileSync(second, '') + const duplicateConfig = path.join(rootDir, 'duplicate.ts') + fs.writeFileSync(duplicateConfig, `export default { inputFiles: [${JSON.stringify(path.relative(rootDir, first))}, ${JSON.stringify(path.relative(rootDir, second))}] }`) + await assert.rejects(generateReactSprite(duplicateConfig, 'vite'), /produce the same SVG id "shared"/) + + const missingConfig = path.join(rootDir, 'missing.ts') + fs.writeFileSync(missingConfig, `export default { inputFolder: './missing', inputFiles: [${JSON.stringify(path.relative(rootDir, first))}] }`) + await assert.rejects(generateReactSprite(missingConfig, 'vite'), /Input directory does not exist/) +}) + +test('applies transform options to SVG and React styles', async () => { + const { rootDir, configPath } = createReactFixture({ + transform: { removeSize: false, replaceColors: false, addTransition: false }, + }) + fs.writeFileSync( + path.join(rootDir, 'icons', 'plain.svg'), + '', + ) + + const result = await generateReactSprite(configPath, 'vite') + const sprite = fs.readFileSync(result.spritePath, 'utf8') + const styles = fs.readFileSync(path.join(result.generatedDir, 'react', 'react-component.module.css'), 'utf8') + assert.match(sprite, /width="24"/) + assert.match(sprite, /fill="#(?:123456|123)"/) + assert.doesNotMatch(sprite, /--icon-color-/) + assert.doesNotMatch(styles, /transition-/) +}) + +test('generates every Next.js exact mode without a client boundary', async () => { + const targets = [ + { router: 'app', bundler: 'turbopack' }, + { router: 'app', bundler: 'webpack' }, + { router: 'pages', bundler: 'turbopack' }, + { router: 'pages', bundler: 'webpack' }, + ] + + for (const options of targets) { + const { configPath } = createReactFixture() + const result = await generateNextSprite(configPath, options) + const component = fs.readFileSync(path.join(result.generatedDir, 'react', 'react-component.js'), 'utf8') + const manifest = fs.readFileSync(result.manifestPath, 'utf8') + const mode = `next@${options.router}/${options.bundler}` + assert.equal(result.target, mode) + assert.equal(result.router, options.router) + assert.equal(result.bundler, options.bundler) + assert.match(component, /new URL\('\.\.\/sprite\.svg', import\.meta\.url\)\.href/) + assert.doesNotMatch(component, /['"]use client['"]/) + assert.match(manifest, new RegExp(`"target": ${JSON.stringify(mode)}`)) + } +}) + +test('writer removes only obsolete managed files and preserves user files', async () => { + const { rootDir, configPath } = createReactFixture() + await generateReactSprite(configPath, 'vite') + const statePath = path.join(rootDir, '.svg-sprite', 'state.json') + const state = JSON.parse(fs.readFileSync(statePath, 'utf8')) + state.files.push('.svg-sprite/obsolete.js') + fs.writeFileSync(statePath, `${JSON.stringify(state, null, 2)}\n`) + fs.writeFileSync(path.join(rootDir, '.svg-sprite', 'obsolete.js'), '/* @generated by @gromlab/svg-sprites. */\n') + fs.writeFileSync(path.join(rootDir, '.svg-sprite', 'notes.md'), 'Keep me\n') + fs.writeFileSync(path.join(rootDir, 'index.ts'), 'export const userCode = true\n') + + await generateReactSprite(configPath, 'vite') + assert.equal(fs.existsSync(path.join(rootDir, '.svg-sprite', 'obsolete.js')), false) + assert.equal(fs.readFileSync(path.join(rootDir, '.svg-sprite', 'notes.md'), 'utf8'), 'Keep me\n') + assert.equal(fs.readFileSync(path.join(rootDir, 'index.ts'), 'utf8'), 'export const userCode = true\n') +}) + +test('writer refuses user content and symbolic links in managed paths', async () => { + const first = createReactFixture() + fs.mkdirSync(path.join(first.rootDir, '.svg-sprite')) + fs.writeFileSync(path.join(first.rootDir, '.svg-sprite', 'index.js'), 'export const userCode = true\n') + await assert.rejects(generateReactSprite(first.configPath, 'vite'), /Refusing to overwrite a user file/) + + const second = createReactFixture() + const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-outside-')) + fs.symlinkSync(outsideDir, path.join(second.rootDir, '.svg-sprite'), 'dir') + await assert.rejects(generateReactSprite(second.configPath, 'vite'), /Symbolic links are not allowed/) + assert.deepEqual(fs.readdirSync(outsideDir), []) +}) + +test('rejects colliding SVG shape IDs', async () => { + const { iconsDir, configPath } = createReactFixture() + const id = `icon-${createHash('sha256').update('folder open').digest('hex').slice(0, 16)}` + fs.writeFileSync(path.join(iconsDir, `${id}.svg`), '') + await assert.rejects(generateReactSprite(configPath, 'vite'), /produce the same SVG id/) +}) diff --git a/test/next-targets.test.mjs b/test/next-targets.test.mjs deleted file mode 100644 index 71c47e9..0000000 --- a/test/next-targets.test.mjs +++ /dev/null @@ -1,204 +0,0 @@ -import assert from 'node:assert/strict' -import { spawn, spawnSync } from 'node:child_process' -import { once } from 'node:events' -import fs from 'node:fs' -import net from 'node:net' -import path from 'node:path' -import test from 'node:test' -import { setTimeout as delay } from 'node:timers/promises' - -import { generateNextSprite } from '../dist/index.js' - -const NEXT_BIN = path.resolve('node_modules/next/dist/bin/next') - -function writeFile(filePath, content) { - fs.mkdirSync(path.dirname(filePath), { recursive: true }) - fs.writeFileSync(filePath, content) -} - -function createNextFixture() { - const rootDir = fs.mkdtempSync(path.join(path.resolve('test'), '.next-fixture-')) - - writeFile( - path.join(rootDir, 'package.json'), - JSON.stringify({ name: 'svg-sprites-next-fixture', private: true }, null, 2), - ) - writeFile( - path.join(rootDir, 'tsconfig.json'), - JSON.stringify({ - compilerOptions: { - jsx: 'preserve', - module: 'esnext', - moduleResolution: 'bundler', - strict: true, - skipLibCheck: true, - }, - }, null, 2), - ) - writeFile( - path.join(rootDir, 'next-env.d.ts'), - '/// \n/// \n', - ) - writeFile( - path.join(rootDir, 'app', 'layout.tsx'), - `export default function Layout({ children }: { children: React.ReactNode }) { - return {children} -} -`, - ) - writeFile( - path.join(rootDir, 'app', 'page.tsx'), - `import { AppIcon } from '../sprites/app' - -export default function Page() { - return
-} -`, - ) - writeFile( - path.join(rootDir, 'pages', 'legacy.tsx'), - `import { PagesIcon } from '../sprites/pages' - -export default function LegacyPage() { - return
-} - -export function getServerSideProps() { - return { props: {} } -} -`, - ) - - for (const name of ['app', 'pages']) { - const spriteRoot = path.join(rootDir, 'sprites', name) - writeFile( - path.join(spriteRoot, 'svg-sprite.config.ts'), - `export default { name: '${name}', generatedNotice: false }\n`, - ) - writeFile( - path.join(spriteRoot, 'icons', 'check.svg'), - '', - ) - } - - return rootDir -} - -function runNext(rootDir, args) { - const result = spawnSync(process.execPath, [NEXT_BIN, ...args], { - cwd: rootDir, - encoding: 'utf-8', - env: { - ...process.env, - NEXT_TELEMETRY_DISABLED: '1', - }, - maxBuffer: 10 * 1024 * 1024, - timeout: 120_000, - }) - - assert.equal( - result.status, - 0, - [result.stdout, result.stderr, result.error?.message].filter(Boolean).join('\n'), - ) -} - -async function getFreePort() { - const server = net.createServer() - server.listen(0, '127.0.0.1') - await once(server, 'listening') - const address = server.address() - assert.ok(address && typeof address === 'object') - const { port } = address - server.close() - await once(server, 'close') - return port -} - -async function startNext(rootDir) { - const port = await getFreePort() - const child = spawn(process.execPath, [NEXT_BIN, 'start', '--hostname', '127.0.0.1', '--port', String(port)], { - cwd: rootDir, - env: { - ...process.env, - NEXT_TELEMETRY_DISABLED: '1', - }, - stdio: ['ignore', 'pipe', 'pipe'], - }) - let output = '' - child.stdout.on('data', (chunk) => { output += chunk }) - child.stderr.on('data', (chunk) => { output += chunk }) - const origin = `http://127.0.0.1:${port}` - - for (let attempt = 0; attempt < 100; attempt++) { - if (child.exitCode !== null) { - throw new Error(`Next.js exited before startup.\n${output}`) - } - - try { - const response = await fetch(origin) - if (response.ok) return { child, origin } - } catch { - // Server is still starting. - } - - await delay(100) - } - - child.kill('SIGKILL') - throw new Error(`Next.js did not start in time.\n${output}`) -} - -async function stopNext(child) { - if (child.exitCode !== null) return - child.kill('SIGTERM') - - await Promise.race([ - once(child, 'exit'), - delay(5_000).then(() => child.kill('SIGKILL')), - ]) -} - -async function assertRenderedSprite(origin, route) { - const response = await fetch(`${origin}${route}`) - assert.equal(response.status, 200) - const html = await response.text() - const href = html.match(/href="([^"]+\.svg)#check"/)?.[1] - - assert.ok(href, `Sprite href not found in ${route}: ${html}`) - assert.doesNotMatch(href, /^(?:data|file|blob):/) - - const spriteResponse = await fetch(new URL(href, origin)) - assert.equal(spriteResponse.status, 200) - assert.match(spriteResponse.headers.get('content-type') ?? '', /image\/svg\+xml/) - assert.match(await spriteResponse.text(), /id="check"/) -} - -test('Next 16.2 renders App and Pages sprites with Turbopack and Webpack', { timeout: 300_000 }, async () => { - const rootDir = createNextFixture() - - try { - for (const bundler of ['turbopack', 'webpack']) { - await generateNextSprite(path.join(rootDir, 'sprites', 'app'), { - router: 'app', - bundler, - }) - await generateNextSprite(path.join(rootDir, 'sprites', 'pages'), { - router: 'pages', - bundler, - }) - - runNext(rootDir, ['build', `--${bundler}`]) - const { child, origin } = await startNext(rootDir) - - try { - await assertRenderedSprite(origin, '/') - await assertRenderedSprite(origin, '/legacy') - } finally { - await stopNext(child) - } - } - } finally { - fs.rmSync(rootDir, { recursive: true, force: true }) - } -}) diff --git a/test/react-mode.test.mjs b/test/react-mode.test.mjs deleted file mode 100644 index e83a3d9..0000000 --- a/test/react-mode.test.mjs +++ /dev/null @@ -1,590 +0,0 @@ -import assert from 'node:assert/strict' -import { spawnSync } from 'node:child_process' -import { createHash } from 'node:crypto' -import fs from 'node:fs' -import os from 'node:os' -import path from 'node:path' -import test from 'node:test' -import ts from 'typescript' - -import { generateNextSprite, generateReactSprite } from '../dist/index.js' - -const GENERATED_FILES = [ - '.svg-sprites.manifest.json', - 'react-component.tsx', - 'sprite.svg', - 'styles.module.css', - 'types.ts', -] - -function createReactFixture() { - const temporaryDir = fs.mkdtempSync(path.join(os.tmpdir(), 'svg-sprites-react-')) - const rootDir = path.join(temporaryDir, 'src', 'widgets', 'file-manager', 'svg-sprite') - const iconsDir = path.join(rootDir, 'icons') - - fs.mkdirSync(iconsDir, { recursive: true }) - fs.writeFileSync( - path.join(rootDir, 'svg-sprite.config.ts'), - `export default { description: 'File manager icons' }\n`, - ) - fs.writeFileSync( - path.join(iconsDir, 'check.svg'), - '', - ) - fs.writeFileSync( - path.join(iconsDir, 'folder.svg'), - '', - ) - fs.writeFileSync( - path.join(iconsDir, 'folder open.svg'), - '', - ) - - return { rootDir, iconsDir } -} - -test('generates an isolated React sprite module', async () => { - const { rootDir } = createReactFixture() - const result = await generateReactSprite(rootDir, 'vite') - const generatedDir = path.join(rootDir, 'generated') - - assert.equal(result.name, 'file-manager') - assert.equal(result.iconCount, 3) - assert.equal(result.target, 'vite') - assert.equal(result.manifestPath, path.join(rootDir, 'manifest.ts')) - assert.deepEqual(fs.readdirSync(generatedDir).sort(), GENERATED_FILES) - - const gitignore = fs.readFileSync(path.join(rootDir, '.gitignore'), 'utf-8') - assert.match(gitignore, /@generated by @gromlab\/svg-sprites/) - assert.match(gitignore, /^\/generated\/$/m) - assert.match(gitignore, /^\/index\.ts$/m) - assert.match(gitignore, /^\/manifest\.ts$/m) - - const sprite = fs.readFileSync(path.join(generatedDir, 'sprite.svg'), 'utf-8') - assert.match(sprite, /АВТОМАТИЧЕСКИ СГЕНЕРИРОВАННЫЙ ФАЙЛ/) - assert.match(sprite, /Генератор: @gromlab\/svg-sprites/) - assert.match(sprite, /]+id="check"/) - assert.match(sprite, /]+id="folder"/) - assert.match(sprite, /]*>\n