diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4501830 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: pnpm/action-setup@v4 + with: + version: 10.27.0 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm run lint + + - run: pnpm test diff --git a/.gitignore b/.gitignore index e51b913..009c263 100644 --- a/.gitignore +++ b/.gitignore @@ -71,6 +71,10 @@ build/ build-standalone-report/ storybook-build/ +dist/ + +tests/fixtures-output/ + # Covers JetBrains IDE PyCharm .idea/ diff --git a/WARP.md b/WARP.md new file mode 100644 index 0000000..a8c8242 --- /dev/null +++ b/WARP.md @@ -0,0 +1,131 @@ +# WARP.md + +This file provides guidance to WARP (warp.dev) when working with code in this repository. + +## Commands + +### Environment and setup + +- Use Node.js version **18 or newer** (see `package.json` `engines.node`). +- Install dependencies with your preferred package manager, for example: + - `pnpm install` + +### Build and type-check + +- `pnpm run build` — compile TypeScript in `src/` to CommonJS JavaScript in `dist/` via `tsc` and `tsconfig.json`. +- `pnpm run clean` — remove the `dist/` directory before a fresh build. + +### Linting and formatting + +- `pnpm run lint` — run ESLint across the project. +- `pnpm run format` — format the codebase with Prettier. + +### Tests + +- `pnpm test` — run the Jest test suite (Node test environment, tests under `tests/**/*.test.ts`). +- Run a single test file (example using the existing test file): + - `pnpm jest tests/css-layering-plugin.integration.test.ts` + - or `pnpm test -- tests/css-layering-plugin.integration.test.ts` + +## Architecture overview + +### High-level structure + +This repository implements a **webpack plugin and loader** that wrap CSS (and Sass) modules in named CSS cascade layers, driven by glob patterns. The project is written in TypeScript and compiled to JavaScript in `dist/` for publishing. + +Key pieces: +- `src/index.ts` — main webpack plugin implementation (`CSSLayeringPlugin`). +- `src/loader.ts` — webpack loader that wraps matched stylesheets in `@layer` blocks. +- `tests/css-layering-plugin.integration.test.ts` — Jest integration tests covering the plugin's HTML injection behavior using real webpack compilations. +- `tests/css-layering-plugin.css.integration.test.ts` — Jest integration tests covering CSS transformation with various layer configurations. +- `jest.config.js` — Jest configuration. +- `tsconfig.json` — TypeScript compiler configuration targeting `dist/`. + +### CSSLayeringPlugin (`src/index.ts`) + +The `CSSLayeringPlugin` class is the primary integration point with webpack and `html-webpack-plugin`: + +- **Options and validation** + - Accepts an `Options` object with: + - `layers: Layer[]` (required, shared with the loader's `Layer` type). + - `nonce?: string` — optional CSP nonce for injected ``, + ); + }); + + it("injects link tag with default publicPath when not provided", async () => { + const outputName = "link-default-public-path"; + + const compiler = createCompiler("basic", outputName, { + layers, + injectOrderAs: "link", + }); + + await runCompiler(compiler); + + const { outDir } = getPaths("basic", outputName); + const html = await fs.readFile(path.join(outDir, "index.html"), "utf8"); + + expect(html).toContain( + ``, + ); + }); + + it("does not inject order or emit asset when injectOrderAs='none'", async () => { + const outputName = "none"; + const compiler = createCompiler("basic", outputName, { + layers, + injectOrderAs: "none", + }); + + const stats = await runCompiler(compiler); + + const { outDir } = getPaths("basic", outputName); + const html = await fs.readFile(path.join(outDir, "index.html"), "utf8"); + + expect(html).not.toContain("@layer base, components;"); + expect(html).not.toContain(LAYER_ASSET_PATH); + + // The layer asset should not be present in the compilation when injectOrderAs is 'none' + expect(stats.compilation.assets[LAYER_ASSET_PATH]).toBeUndefined(); + }); +}); diff --git a/tests/fixtures/basic/entry.ts b/tests/fixtures/basic/entry.ts new file mode 100644 index 0000000..6caa6c8 --- /dev/null +++ b/tests/fixtures/basic/entry.ts @@ -0,0 +1,3 @@ +export function main() { + // Intentionally empty: we just need an entry for HtmlWebpackPlugin +} diff --git a/tests/fixtures/basic/index.html b/tests/fixtures/basic/index.html new file mode 100644 index 0000000..3a97938 --- /dev/null +++ b/tests/fixtures/basic/index.html @@ -0,0 +1,10 @@ + + + + + CSS Layering Plugin Test + + +
+ + diff --git a/tests/fixtures/css-basic/entry.ts b/tests/fixtures/css-basic/entry.ts new file mode 100644 index 0000000..ba6c61a --- /dev/null +++ b/tests/fixtures/css-basic/entry.ts @@ -0,0 +1,5 @@ +import "./styles.css"; + +export function main() { + // Entry is only here to make webpack process styles.css +} diff --git a/tests/fixtures/css-basic/expected/styles.css b/tests/fixtures/css-basic/expected/styles.css new file mode 100644 index 0000000..a857cc1 --- /dev/null +++ b/tests/fixtures/css-basic/expected/styles.css @@ -0,0 +1,10 @@ +@use "sass:color"; +@use "my:module"; +@layer components { + .button { + color: red; +} +body { + margin: 0; +} +} diff --git a/tests/fixtures/css-basic/index.html b/tests/fixtures/css-basic/index.html new file mode 100644 index 0000000..e9e4423 --- /dev/null +++ b/tests/fixtures/css-basic/index.html @@ -0,0 +1,10 @@ + + + + + CSS Layering Plugin CSS Fixture + + +
+ + diff --git a/tests/fixtures/css-basic/styles.css b/tests/fixtures/css-basic/styles.css new file mode 100644 index 0000000..c5dc67f --- /dev/null +++ b/tests/fixtures/css-basic/styles.css @@ -0,0 +1,8 @@ +@use "sass:color"; +.button { + color: red; +} +@use "my:module"; +body { + margin: 0; +} diff --git a/tests/fixtures/css-exclude/entry.ts b/tests/fixtures/css-exclude/entry.ts new file mode 100644 index 0000000..6601398 --- /dev/null +++ b/tests/fixtures/css-exclude/entry.ts @@ -0,0 +1,6 @@ +import "./styles.css"; +import "./styles.ignore.css"; + +export function main() { + // Entry is only here to make webpack process CSS files +} diff --git a/tests/fixtures/css-exclude/expected/styles.css b/tests/fixtures/css-exclude/expected/styles.css new file mode 100644 index 0000000..645d2d0 --- /dev/null +++ b/tests/fixtures/css-exclude/expected/styles.css @@ -0,0 +1,5 @@ +@layer components { + .included { + color: green; + } +} diff --git a/tests/fixtures/css-exclude/expected/styles.ignore.css b/tests/fixtures/css-exclude/expected/styles.ignore.css new file mode 100644 index 0000000..dcc3389 --- /dev/null +++ b/tests/fixtures/css-exclude/expected/styles.ignore.css @@ -0,0 +1,3 @@ +.ignored { + color: blue; +} diff --git a/tests/fixtures/css-exclude/index.html b/tests/fixtures/css-exclude/index.html new file mode 100644 index 0000000..324cb2e --- /dev/null +++ b/tests/fixtures/css-exclude/index.html @@ -0,0 +1,10 @@ + + + + + CSS Exclude Fixture + + +
+ + diff --git a/tests/fixtures/css-exclude/styles.css b/tests/fixtures/css-exclude/styles.css new file mode 100644 index 0000000..e70e8cf --- /dev/null +++ b/tests/fixtures/css-exclude/styles.css @@ -0,0 +1,3 @@ +.included { + color: green; +} diff --git a/tests/fixtures/css-exclude/styles.ignore.css b/tests/fixtures/css-exclude/styles.ignore.css new file mode 100644 index 0000000..dcc3389 --- /dev/null +++ b/tests/fixtures/css-exclude/styles.ignore.css @@ -0,0 +1,3 @@ +.ignored { + color: blue; +} diff --git a/tests/fixtures/css-multi-file/entry.ts b/tests/fixtures/css-multi-file/entry.ts new file mode 100644 index 0000000..21a0e78 --- /dev/null +++ b/tests/fixtures/css-multi-file/entry.ts @@ -0,0 +1,6 @@ +import "./one.css"; +import "./two.css"; + +export function main() { + // Entry is only here to make webpack process both CSS files +} diff --git a/tests/fixtures/css-multi-file/expected/one.css b/tests/fixtures/css-multi-file/expected/one.css new file mode 100644 index 0000000..d7a180a --- /dev/null +++ b/tests/fixtures/css-multi-file/expected/one.css @@ -0,0 +1,5 @@ +@layer shared { + .one { + color: red; + } +} diff --git a/tests/fixtures/css-multi-file/expected/two.css b/tests/fixtures/css-multi-file/expected/two.css new file mode 100644 index 0000000..5e71372 --- /dev/null +++ b/tests/fixtures/css-multi-file/expected/two.css @@ -0,0 +1,5 @@ +@layer shared { + .two { + color: blue; + } +} diff --git a/tests/fixtures/css-multi-file/index.html b/tests/fixtures/css-multi-file/index.html new file mode 100644 index 0000000..029b694 --- /dev/null +++ b/tests/fixtures/css-multi-file/index.html @@ -0,0 +1,10 @@ + + + + + CSS Multi-File Fixture + + +
+ + diff --git a/tests/fixtures/css-multi-file/one.css b/tests/fixtures/css-multi-file/one.css new file mode 100644 index 0000000..7ee3e9d --- /dev/null +++ b/tests/fixtures/css-multi-file/one.css @@ -0,0 +1,3 @@ +.one { + color: red; +} diff --git a/tests/fixtures/css-multi-file/two.css b/tests/fixtures/css-multi-file/two.css new file mode 100644 index 0000000..68036db --- /dev/null +++ b/tests/fixtures/css-multi-file/two.css @@ -0,0 +1,3 @@ +.two { + color: blue; +} diff --git a/tests/fixtures/css-multi-layers/components.css b/tests/fixtures/css-multi-layers/components.css new file mode 100644 index 0000000..1b837da --- /dev/null +++ b/tests/fixtures/css-multi-layers/components.css @@ -0,0 +1,3 @@ +.button { + padding: 4px; +} diff --git a/tests/fixtures/css-multi-layers/entry.ts b/tests/fixtures/css-multi-layers/entry.ts new file mode 100644 index 0000000..fea83a4 --- /dev/null +++ b/tests/fixtures/css-multi-layers/entry.ts @@ -0,0 +1,6 @@ +import "./reset.css"; +import "./components.css"; + +export function main() { + // Entry is only here to make webpack process CSS files +} diff --git a/tests/fixtures/css-multi-layers/expected/components.css b/tests/fixtures/css-multi-layers/expected/components.css new file mode 100644 index 0000000..06349e7 --- /dev/null +++ b/tests/fixtures/css-multi-layers/expected/components.css @@ -0,0 +1,5 @@ +@layer components { + .button { + padding: 4px; + } +} diff --git a/tests/fixtures/css-multi-layers/expected/reset.css b/tests/fixtures/css-multi-layers/expected/reset.css new file mode 100644 index 0000000..aadbdb3 --- /dev/null +++ b/tests/fixtures/css-multi-layers/expected/reset.css @@ -0,0 +1,5 @@ +@layer base { + body { + margin: 0; + } +} diff --git a/tests/fixtures/css-multi-layers/index.html b/tests/fixtures/css-multi-layers/index.html new file mode 100644 index 0000000..4b6a288 --- /dev/null +++ b/tests/fixtures/css-multi-layers/index.html @@ -0,0 +1,10 @@ + + + + + CSS Multi-layers Fixture + + +
+ + diff --git a/tests/fixtures/css-multi-layers/reset.css b/tests/fixtures/css-multi-layers/reset.css new file mode 100644 index 0000000..293d3b1 --- /dev/null +++ b/tests/fixtures/css-multi-layers/reset.css @@ -0,0 +1,3 @@ +body { + margin: 0; +} diff --git a/tests/fixtures/css-multi-match/entry.ts b/tests/fixtures/css-multi-match/entry.ts new file mode 100644 index 0000000..ba6c61a --- /dev/null +++ b/tests/fixtures/css-multi-match/entry.ts @@ -0,0 +1,5 @@ +import "./styles.css"; + +export function main() { + // Entry is only here to make webpack process styles.css +} diff --git a/tests/fixtures/css-multi-match/expected/styles.css b/tests/fixtures/css-multi-match/expected/styles.css new file mode 100644 index 0000000..5dbed40 --- /dev/null +++ b/tests/fixtures/css-multi-match/expected/styles.css @@ -0,0 +1,5 @@ +@layer base { + .multi { + color: red; + } +} diff --git a/tests/fixtures/css-multi-match/index.html b/tests/fixtures/css-multi-match/index.html new file mode 100644 index 0000000..e8d35d1 --- /dev/null +++ b/tests/fixtures/css-multi-match/index.html @@ -0,0 +1,10 @@ + + + + + CSS Multi-Match Fixture + + +
+ + diff --git a/tests/fixtures/css-multi-match/styles.css b/tests/fixtures/css-multi-match/styles.css new file mode 100644 index 0000000..aee5a09 --- /dev/null +++ b/tests/fixtures/css-multi-match/styles.css @@ -0,0 +1,3 @@ +.multi { + color: red; +} diff --git a/tests/fixtures/css-no-match/entry.ts b/tests/fixtures/css-no-match/entry.ts new file mode 100644 index 0000000..9f08f14 --- /dev/null +++ b/tests/fixtures/css-no-match/entry.ts @@ -0,0 +1,5 @@ +import "./plain.css"; + +export function main() { + // Entry is only here to make webpack process CSS files +} diff --git a/tests/fixtures/css-no-match/expected/plain.css b/tests/fixtures/css-no-match/expected/plain.css new file mode 100644 index 0000000..c7f1aec --- /dev/null +++ b/tests/fixtures/css-no-match/expected/plain.css @@ -0,0 +1,3 @@ +.plain { + border: 1px solid black; +} diff --git a/tests/fixtures/css-no-match/index.html b/tests/fixtures/css-no-match/index.html new file mode 100644 index 0000000..a8ac8f4 --- /dev/null +++ b/tests/fixtures/css-no-match/index.html @@ -0,0 +1,10 @@ + + + + + CSS No Match Fixture + + +
+ + diff --git a/tests/fixtures/css-no-match/plain.css b/tests/fixtures/css-no-match/plain.css new file mode 100644 index 0000000..c7f1aec --- /dev/null +++ b/tests/fixtures/css-no-match/plain.css @@ -0,0 +1,3 @@ +.plain { + border: 1px solid black; +} diff --git a/tests/fixtures/css-preexisting-layer/entry.ts b/tests/fixtures/css-preexisting-layer/entry.ts new file mode 100644 index 0000000..2978a22 --- /dev/null +++ b/tests/fixtures/css-preexisting-layer/entry.ts @@ -0,0 +1,5 @@ +import "./styles.css"; + +export function main() { + // Entry is only here to make webpack process CSS files +} diff --git a/tests/fixtures/css-preexisting-layer/expected/styles.css b/tests/fixtures/css-preexisting-layer/expected/styles.css new file mode 100644 index 0000000..9c0d055 --- /dev/null +++ b/tests/fixtures/css-preexisting-layer/expected/styles.css @@ -0,0 +1,5 @@ +@layer components { + .content { + font-size: 14px; + } +} diff --git a/tests/fixtures/css-preexisting-layer/index.html b/tests/fixtures/css-preexisting-layer/index.html new file mode 100644 index 0000000..65ce561 --- /dev/null +++ b/tests/fixtures/css-preexisting-layer/index.html @@ -0,0 +1,10 @@ + + + + + CSS Preexisting Layer Fixture + + +
+ + diff --git a/tests/fixtures/css-preexisting-layer/styles.css b/tests/fixtures/css-preexisting-layer/styles.css new file mode 100644 index 0000000..60ba41a --- /dev/null +++ b/tests/fixtures/css-preexisting-layer/styles.css @@ -0,0 +1,3 @@ +.content { + font-size: 14px; +} diff --git a/tests/fixtures/css-scss-complex/entry.ts b/tests/fixtures/css-scss-complex/entry.ts new file mode 100644 index 0000000..cb2bbd3 --- /dev/null +++ b/tests/fixtures/css-scss-complex/entry.ts @@ -0,0 +1,5 @@ +import "./styles-complex.scss"; + +export function main() { + // Entry is only here to make webpack process complex SCSS file +} diff --git a/tests/fixtures/css-scss-complex/expected/styles-complex.scss b/tests/fixtures/css-scss-complex/expected/styles-complex.scss new file mode 100644 index 0000000..b91e416 --- /dev/null +++ b/tests/fixtures/css-scss-complex/expected/styles-complex.scss @@ -0,0 +1,50 @@ +@use "sass:color"; +@use "sass:math"; +@layer components { + // Global comment about the theme + + $primary: #0d6efd; + $secondary: #6c757d; + + %button-base { + display: inline-block; + padding: 0.5rem 1rem; + border-radius: 0.25rem; + } + + .button { + @extend %button-base; + + &--primary { + $primary-dark: color.adjust($primary, $lightness: -5%); + + color: $primary-dark; + + &:hover { + color: color.scale($primary-dark, $lightness: 5%); + } + } + + &--secondary { + color: $secondary; + } + } + + .container { + $columns: 3; + + width: math.div(100%, $columns); + + @media (min-width: 768px) { + max-width: 720px; + + .inner { + padding: 1rem; + + &__title { + font-weight: bold; + } + } + } + } +} diff --git a/tests/fixtures/css-scss-complex/index.html b/tests/fixtures/css-scss-complex/index.html new file mode 100644 index 0000000..12cca06 --- /dev/null +++ b/tests/fixtures/css-scss-complex/index.html @@ -0,0 +1,10 @@ + + + + + CSS SCSS Complex Fixture + + +
+ + diff --git a/tests/fixtures/css-scss-complex/styles-complex.scss b/tests/fixtures/css-scss-complex/styles-complex.scss new file mode 100644 index 0000000..07e57da --- /dev/null +++ b/tests/fixtures/css-scss-complex/styles-complex.scss @@ -0,0 +1,49 @@ +// Global comment about the theme +@use "sass:color"; + +$primary: #0d6efd; +$secondary: #6c757d; + +%button-base { + display: inline-block; + padding: 0.5rem 1rem; + border-radius: 0.25rem; +} + +.button { + @extend %button-base; + + &--primary { + $primary-dark: color.adjust($primary, $lightness: -5%); + + color: $primary-dark; + + &:hover { + color: color.scale($primary-dark, $lightness: 5%); + } + } + + &--secondary { + color: $secondary; + } +} + +@use "sass:math"; + +.container { + $columns: 3; + + width: math.div(100%, $columns); + + @media (min-width: 768px) { + max-width: 720px; + + .inner { + padding: 1rem; + + &__title { + font-weight: bold; + } + } + } +} diff --git a/tests/fixtures/css-scss/entry.ts b/tests/fixtures/css-scss/entry.ts new file mode 100644 index 0000000..5432925 --- /dev/null +++ b/tests/fixtures/css-scss/entry.ts @@ -0,0 +1,5 @@ +import "./styles.scss"; + +export function main() { + // Entry is only here to make webpack process SCSS files +} diff --git a/tests/fixtures/css-scss/expected/styles.scss b/tests/fixtures/css-scss/expected/styles.scss new file mode 100644 index 0000000..4e3cc60 --- /dev/null +++ b/tests/fixtures/css-scss/expected/styles.scss @@ -0,0 +1,6 @@ +@use "sass:color"; +@layer components { + .button { + color: red; + } +} diff --git a/tests/fixtures/css-scss/index.html b/tests/fixtures/css-scss/index.html new file mode 100644 index 0000000..a082202 --- /dev/null +++ b/tests/fixtures/css-scss/index.html @@ -0,0 +1,10 @@ + + + + + CSS SCSS Fixture + + +
+ + diff --git a/tests/fixtures/css-scss/styles.scss b/tests/fixtures/css-scss/styles.scss new file mode 100644 index 0000000..8d62828 --- /dev/null +++ b/tests/fixtures/css-scss/styles.scss @@ -0,0 +1,4 @@ +@use "sass:color"; +.button { + color: red; +} diff --git a/tests/fixtures/css-use-ordering/entry.ts b/tests/fixtures/css-use-ordering/entry.ts new file mode 100644 index 0000000..2978a22 --- /dev/null +++ b/tests/fixtures/css-use-ordering/entry.ts @@ -0,0 +1,5 @@ +import "./styles.css"; + +export function main() { + // Entry is only here to make webpack process CSS files +} diff --git a/tests/fixtures/css-use-ordering/expected/styles.css b/tests/fixtures/css-use-ordering/expected/styles.css new file mode 100644 index 0000000..457744f --- /dev/null +++ b/tests/fixtures/css-use-ordering/expected/styles.css @@ -0,0 +1,12 @@ +@use "sass:color"; +@use "my:module"; +@layer components { + /* top comment */ + .main { + color: red; + } + // mid comment + .footer { + margin: 0; + } +} diff --git a/tests/fixtures/css-use-ordering/index.html b/tests/fixtures/css-use-ordering/index.html new file mode 100644 index 0000000..bd59824 --- /dev/null +++ b/tests/fixtures/css-use-ordering/index.html @@ -0,0 +1,10 @@ + + + + + CSS Use Ordering Fixture + + +
+ + diff --git a/tests/fixtures/css-use-ordering/styles.css b/tests/fixtures/css-use-ordering/styles.css new file mode 100644 index 0000000..8470713 --- /dev/null +++ b/tests/fixtures/css-use-ordering/styles.css @@ -0,0 +1,10 @@ +/* top comment */ +@use "sass:color"; +.main { + color: red; +} +// mid comment +@use "my:module"; +.footer { + margin: 0; +} diff --git a/tests/helpers/webpack.ts b/tests/helpers/webpack.ts new file mode 100644 index 0000000..24d418d --- /dev/null +++ b/tests/helpers/webpack.ts @@ -0,0 +1,90 @@ +import path from "node:path"; +import webpack, { + type Compiler, + type Configuration, + type Stats, +} from "webpack"; +import HtmlWebpackPlugin from "html-webpack-plugin"; +import { CSSLayeringPlugin } from "../../dist"; + +type PluginOptions = ConstructorParameters[0]; + +export function getPaths(fixtureName: string, outputName: string) { + const fixturesDir = path.resolve(__dirname, "../fixtures", fixtureName); + const outDir = path.resolve( + __dirname, + "../fixtures-output", + `${fixtureName}-${outputName}`, + ); + + return { fixturesDir, outDir }; +} + +export function createCompiler( + fixtureName: string, + outputName: string, + pluginOptions: PluginOptions, + extraConfig: Configuration = {}, +): Compiler { + const { fixturesDir, outDir } = getPaths(fixtureName, outputName); + + const baseConfig: Configuration = { + mode: "production", + devtool: false, + context: fixturesDir, + entry: path.join(fixturesDir, "entry.ts"), + output: { + path: outDir, + filename: "bundle.js", + publicPath: "", + }, + module: { + rules: [], + }, + plugins: [ + new CSSLayeringPlugin(pluginOptions), + new HtmlWebpackPlugin({ + template: path.join(fixturesDir, "index.html"), + filename: "index.html", + }), + ], + }; + + return webpack({ ...baseConfig, ...extraConfig }); +} + +export function runCompiler(compiler: Compiler): Promise { + return new Promise((resolve, reject) => { + compiler.run((err, stats) => { + compiler.close(() => {}); + + if (err) { + reject(err); + return; + } + + if (!stats) { + reject(new Error("No stats returned")); + return; + } + + if (stats.hasErrors()) { + reject(new Error(stats.toString("errors-only"))); + return; + } + + resolve(stats); + }); + }); +} + +export function getAssetSource( + stats: Stats, + filename: string, +): string | undefined { + const asset = stats.compilation.assets[filename]; + if (!asset) return undefined; + + const source = asset.source(); + return typeof source === "string" ? source : source.toString(); +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 0000000..172dae2 --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "esModuleInterop": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "types": ["node", "jest"] + }, + "include": ["tests/**/*"] +}