diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..166bac86 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,26 @@ +# Editor defaults for files Biome does not format (Markdown, YAML, SQL, shell, +# and Python). Biome reads this file too, but biome.json's settings win. +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.py] +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +# The license texts stay as REUSE downloaded them. +[LICENSES/**] +charset = unset +end_of_line = unset +indent_style = unset +indent_size = unset +insert_final_newline = unset +trim_trailing_whitespace = unset diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..d7c20339 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +# Store text files with LF endings, on every platform. +* text=auto eol=lf + +# The license texts stay byte for byte as REUSE downloaded them. +LICENSES/** -text + +# Generated or vendored: collapse them in diffs and leave them out of the +# language statistics. +bun.lock linguist-generated +src/vendor/** linguist-vendored diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..a13c5851 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ +## What changes + + + +## How it was checked + + + +- [ ] Every commit is signed off (`git commit -s`), for the DCO check. +- [ ] `bun run check:ci` passes. +- [ ] Each new image or other asset has an annotation in `REUSE.toml`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7d8219c8..790f5e46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,12 @@ jobs: - name: Type check run: bun run typecheck + # knip finds unused files, exports and dependencies. It runs after the + # build for the same reason tsc does: it resolves `#site/content` to + # .velite/, which the build writes. + - name: Unused code and dependencies + run: bun run knip + - name: Unit tests run: bun run test diff --git a/.github/workflows/links.yml b/.github/workflows/links.yml new file mode 100644 index 00000000..9be4450e --- /dev/null +++ b/.github/workflows/links.yml @@ -0,0 +1,65 @@ +name: Links + +# Checks the external links in the blog: the posts in src/content/blog and the +# posts on other sites that the blog index lists (src/content/links.ts). +# `bun run verify` already checks the site's internal links on every PR. +# +# Not a required check, so a broken link never blocks a merge. It runs: +# - weekly, because a page on another site can disappear at any time +# - on a PR that changes the blog's content, to catch a mistyped link +# - by hand (workflow_dispatch) +# The job summary lists each broken link. + +on: + schedule: + - cron: "0 9 * * 1" + pull_request: + paths: + - "src/content/**" + - ".github/workflows/links.yml" + workflow_dispatch: + +concurrency: + group: links-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lychee: + name: External links + runs-on: ubuntu-26.04 + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + # The action downloads the lychee binary named by lycheeVersion from + # lychee's GitHub releases. The job has read-only contents access and no + # secrets, so that binary can read only the public repository. + # + # Excluded hosts block automated requests, so they fail when the page is + # there: LinkedIn answers 999, and the ACM Digital Library (doi.org + # 10.1145) answers 403. The action passes the job's GitHub token, which + # raises GitHub's rate limit. lychee 0.24.2 checks a file named here + # whatever its extension (the run counts 106 links with or without + # --extensions). The flag names `ts` anyway, because lychee's default + # extension list, which filters what it finds in directories, has no + # `ts`. + - name: Check links + uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0 + with: + lycheeVersion: v0.24.2 + args: >- + --no-progress + --max-retries 3 + --timeout 30 + --accept 200..=299,429 + --extensions mdx,ts + --exclude '^https://(www\.)?linkedin\.com/' + --exclude '^https://doi\.org/10\.1145/' + --exclude '^https://dl\.acm\.org/' + 'src/content/**/*.mdx' + src/content/links.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index d2224138..699ed733 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,3 @@ { - "recommendations": ["tailwindcss.tailwindcss-intellisense", "biomejs.biome"] + "recommendations": ["biomejs.biome"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index 8bd28082..296f0ac6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,12 +6,5 @@ }, "[css]": { "editor.formatOnSave": false - }, - "tailwindCSS.classAttributes": [ - "class", - "className", - ".*ClassName", - "ngClass", - ".*Styles" - ] + } } diff --git a/CLAUDE.md b/CLAUDE.md index 34d3b655..354fbe10 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `bun run dev` - Start development server with Turbopack - `bun run build` - Build production application (a bare `bun build` runs Bun's bundler, not this script) - `bun run lint` - Run Biome lint +- `bun run knip` - Find unused files, exports and dependencies (run `bun run build` first) - `bun run start` - Start production server - `bun run test` - The site's unit tests (`bun test tests/unit`) @@ -27,7 +28,7 @@ How the two sites share the app: - **Root layouts.** Each site has its own, so Next.js has none to draw an unmatched URL's 404 in. `src/app/global-not-found.tsx` (`experimental.globalNotFound`) is the site's 404, in the site's layout, prerendered for every unmatched URL; a catch-all route (`src/app/(bench)/bench/[...notFound]`) keeps bench's unmatched paths in bench's own layout. `robots.ts` stays at the app root, where Next.js reads it; the favicon is `public/favicon.ico`, named in the site's metadata, because the `app/favicon.ico` convention would also put it on bench's pages. Next.js serves a metadata image route inside a route group with a hash suffix (`/opengraph-image-12o0cb`), and the og:image tags name that URL; rewrites in `next.config.mjs` keep the plain `/opengraph-image` URLs (which the posts' JSON-LD names) serving the same images. - **Headers.** The CSP and the other security headers in `next.config.mjs` cover both sites: one policy, since both self-host their fonts. bench now sends the site's security headers, including `X-Frame-Options: DENY` and `frame-ancestors 'none'`, so no other site can embed bench's pages in a frame. `vercel.json` holds bench's cron (`/bench/api/health`) and bench's CDN rule for its landing, chart, and methodology pages on bench.vortex.dev (header rules match the path as requested, before the rewrites). bench's HTML differs between its two URL forms, so the `/bench` form gets the CDN rule only outside production (`benchPrefixCdnRules` in `next.config.mjs`): a cached `/bench` page must never answer for bench.vortex.dev. - **Toolchain.** One package: one `package.json`, one `bun.lock`, one `bunfig.toml`, one `tsconfig.json`. Next.js 16.4.1, React 19.3 and TypeScript 7. `tsconfig.json` has every strict flag either app had, including bench's `noFallthroughCasesInSwitch` and `noUncheckedSideEffectImports`; `src/css.d.ts` types the CSS side-effect imports. The root `biome.json` lints and formats both sites: an override keeps bench's files (`src/bench`, `src/app/(bench)`, the parity and deploy checks, `vitest.config.ts`) in bench's old Prettier style (single quotes, trailing commas, 100 columns) with bench's lint rules (see "bench" below), and `bun run check:ci` checks all of it. bench's tests run on Vitest from the root `vitest.config.ts`, scoped to bench's files; the site's run on `bun test`. bench's PostCSS config sits beside its stylesheets (`src/bench/styles/postcss.config.mjs`), and Turbopack applies it to those alone: with no config at the root, the site's CSS gets Turbopack's defaults. -- **CI.** Every check covers both sites and runs on every change; nothing is split by which site a change touches. `ci.yml`'s `Lint, build, verify` runs Biome on the whole repository, one build, the typecheck, the site's unit tests, bench's Vitest suite with its Postgres integration tests (testcontainers, so the runner needs Docker), the migration-runner (`db/tests`), infrastructure (`infra/tests`), parity and deploy-check tests, `bun run verify`, and the Playwright smoke specs. Dependency review, the secret scan, REUSE and the non-blocking `bun audit` run beside it. +- **CI.** Every check covers both sites and runs on every change; nothing is split by which site a change touches. `ci.yml`'s `Lint, build, verify` runs Biome on the whole repository, one build, the typecheck, knip, the site's unit tests, bench's Vitest suite with its Postgres integration tests (testcontainers, so the runner needs Docker), the migration-runner (`db/tests`), infrastructure (`infra/tests`), parity and deploy-check tests, `bun run verify`, and the Playwright smoke specs. Dependency review, the secret scan, REUSE and the non-blocking `bun audit` run beside it. `links.yml` checks the blog's external links with lychee each week and on a PR that changes `src/content/`; it is not a required check, so a broken link on another site never blocks a merge. - **Lighthouse and axe.** `lighthouse.yml` audits both sites on the pull request's Vercel preview, because bench renders from its database and only a deployment has one. Its jobs run in parallel, each on its own runner, since a second audit or browser on the same CPU would skew Lighthouse's timings. Each one waits for the preview (Vercel's GitHub deployment in the `Preview` environment, through the `.github/actions/preview-proxy` action) and requests its page once to get past the cold start. Five Lighthouse jobs each audit one page, named by `LHCI_PAGE`: `/`, `/blog`, the newest post, `/bench` and one bench chart page (`lighthouserc.cjs` holds the pages, the thresholds and their rationale). The `axe` job runs `tests/a11y.spec.ts` (`--grep @axe`: WCAG 2.1 A and AA, and a serious or critical violation fails). The `report` job, the required check `Lighthouse and axe`, posts one score table on the PR from the Lighthouse jobs' reports and fails unless every job passed. The preview is reached through `scripts/preview-proxy.mjs`, which adds the `VERCEL_AUTOMATION_BYPASS_SECRET` header only to requests for the preview, so no other origin a page loads from sees it. Fork and Dependabot PRs get no secrets, so the jobs are skipped for them, which counts as passing. The smoke run in `ci.yml` leaves the axe specs out (`--grep-invert @axe`). To run axe yourself: `BASE_URL=https://vortex.dev BENCH_URL=https://bench.vortex.dev bunx playwright test --grep @axe`. - **Deploys.** The `vortex` Vercel project builds and deploys the app, both sites, through the git integration, on every commit. `bench-verify-deploy.yml` runs `scripts/verify-web-deploy.mjs` on each production deployment Vercel reports, against bench under `/bench` on the deployment's own URL. Schema migrations go through `bench-schema-deploy.yml`, which only runs manually from `main` because the AWS role trusts nothing else. @@ -111,12 +112,20 @@ To see bench as bench.vortex.dev serves it, send its host: `curl -H 'Host: bench bench uses Biome, not ESLint or Prettier. The bench override in the root `biome.json` keeps the style bench had under Prettier (single quotes, trailing commas, 100 columns, 2-space indent) for bench's files, and `bun run check:ci` checks bench along with the site. `biome.json` cannot hold comments, so the reasons for each difference from the site's rules are here: -- `correctness/useExhaustiveDependencies` is on (the site has it off). It replaces ESLint's `react-hooks/exhaustive-deps`. An effect that omits dependencies on purpose carries a `biome-ignore` comment that says why. - The site's `a11y` rules apply to bench unchanged. Seven places in existing markup break one of them on purpose, and each carries an inline `biome-ignore` comment that says why: `useAriaPropsSupportedByRole` for `aria-label` on a plain `div` (the header nav controls, the chart toolbar), `useSemanticElements` for `role="group"` on a `div` in the toolbars (a `fieldset` would change their layout), `useFocusableInteractive` for the pointer-driven range strip, and `noNoninteractiveTabindex` for the group info icon, which takes focus so its tooltip shows for keyboard users. New markup follows the rules, or carries its own `biome-ignore` with a reason. -- `complexity/noImportantStyles` is off: `!important` overrides Chart.js's inline canvas size and the offscreen prebuild layout in `src/bench/styles/globals.css`. - `organizeImports` is off: Prettier never sorted bench's imports, and reordering them would rewrite 27 files and could change the order CSS loads in. -Next.js's `core-web-vitals` lint rules have no Biome equivalent; the site accepts that too. The site's config also turns off `suspicious/noExplicitAny`, so bench's old typescript-eslint `no-explicit-any` check is gone too. +Next.js's `core-web-vitals` lint rules have no Biome equivalent; the site accepts that too. Two rules replace bench's old ESLint checks for both sites: `correctness/useExhaustiveDependencies` replaces `react-hooks/exhaustive-deps`, and `suspicious/noExplicitAny` replaces typescript-eslint's `no-explicit-any`. An effect that omits dependencies on purpose carries a `biome-ignore` comment that says why. + +Both sites run Biome's recommended rules, with the rules in `biome.json` set to `error` so that `check:ci` fails on them (Biome's default for some is a warning, which does not fail it). Each deliberate exception carries a `biome-ignore` comment with its reason: the inline scripts and JSON-LD (`noDangerouslySetInnerHtml`; `jsonLdScript` escapes `<`), and the two lists whose index is the only key (`noArrayIndexKey`). One rule stays off: `performance/noImgElement`, because the logos and marks are SVGs and small PNGs with fixed sizes, which `next/image` would not make smaller. + +Three rules that were off until 2026-11-04 are now on, for both sites: + +- `style/noNonNullAssertion`. Production code handles a missing value. Tests use `defined()` from `src/lib/defined.ts`, which throws with a message when a value is null or undefined, in place of `value!`. +- `style/noDescendingSpecificity`. Moving a CSS rule can change which declaration wins, so a fix that reorders rules needs proof that no element's computed style changes. The proof for the first fix compared every element's computed style on bench's landing, methodology and chart pages, with and without JavaScript, under the old and the new stylesheet. +- `complexity/noImportantStyles`. Each `!important` carries a `biome-ignore` with its reason: the no-JavaScript rule that hides `[data-requires-js]` controls, and the two that beat Chart.js's inline canvas size, in `src/bench/styles/globals.css`. + +knip (`knip.json`) finds unused files, exports and dependencies, and CI fails on any. Entry points that no import reaches (configs, and scripts that CI or a person runs) are listed in `knip.json`; add a new one there. It runs after the build, because it resolves `#site/content` to `.velite/`. Export a symbol only when another file imports it. ## Licensing @@ -143,11 +152,11 @@ Defense in depth against the npm-worm class (Shai-Hulud, mini-Shai-Hulud, the Ma 5. **`actions/dependency-review-action`** runs on every PR and **hard-fails** the build if the PR introduces a new advisory at severity `high` or above in a runtime-scope package. With `bun audit` informational, this is the only advisory check that blocks merge — deliberately scoped to what the PR itself adds, not the whole pre-existing tree. - **It sees `bun.lock` through a submitted snapshot.** GitHub's dependency graph does not read `bun.lock`, so `.github/workflows/dependency-snapshot.yml` runs `scripts/dependency-snapshot.ts` on every push to main and on the head commit of every same-repository PR. It posts one manifest per workspace (one, `bun.lock (vortex)`, since bench joined the root package) to the Dependency Submission API, listing every package that workspace reaches with a scope: `runtime` if a workspace's `dependencies` or `optionalDependencies` reach it, `development` otherwise. An optional peer, like Next.js's Playwright, does not pull its package into `runtime`. - **A guard fails the review when the snapshot is missing.** The action reviews whatever graph it finds, so without the snapshot it would pass without `bun.lock`'s packages. Before the action, the job's "Require the bun.lock snapshot" step waits up to 10 minutes for the snapshot job (`Submit bun.lock`) on the PR's head commit, and fails unless that job succeeded. A fork PR gets no snapshot, because its token cannot write, so the guard fails a fork PR that changes `bun.lock` (make that change from a branch in this repository) and passes one that does not, because it adds no locked package. After the guard, the action still waits up to 300 seconds (`retry-on-snapshot-warnings`) while GitHub processes the snapshot. If you rename the snapshot job, change the guard's `SNAPSHOT_CHECK` with it. - - **It fails on runtime scope only.** `fail-on-scopes` is left at the action's default, `runtime` (`action.yml` declares no default; the action's schema and README give `runtime`). An advisory in a `development`-scope package does not block merge. That includes bench's development tools (Vitest, testcontainers, jsdom and what they pull in), whose advisories blocked bench PRs under pnpm. `bun audit` is where those advisories show: it reports the whole tree on every run, and the "Audit advisories" list below records the accepted ones. Runtime means reachable from `dependencies`, which includes build-time tools the site lists there (velite, tailwindcss, `@tailwindcss/typography`). + - **It fails on runtime scope only.** `fail-on-scopes` is left at the action's default, `runtime` (`action.yml` declares no default; the action's schema and README give `runtime`). An advisory in a `development`-scope package does not block merge. That includes bench's development tools (Vitest, testcontainers, jsdom and what they pull in), whose advisories blocked bench PRs under pnpm. `bun audit` is where those advisories show: it reports the whole tree on every run, and the "Audit advisories" list below records the accepted ones. Runtime means reachable from `dependencies`, which includes build-time tools the site lists there, such as velite. 6. **`trufflesecurity/trufflehog`** secret scan runs on every PR and push to main. Defense-in-depth on top of GitHub's push protection; catches verified secrets that slipped past push protection (low-entropy formats, detector patterns added after the secret was committed, or push-protection bypass). 7. **OpenSSF Scorecard** (`.github/workflows/scorecard.yml`) grades the repo weekly on supply-chain hygiene (pinned actions, branch protection, token permissions, dangerous workflow patterns). SARIF posts to the Security tab; aggregate score publishes to https://scorecard.dev. 8. **All third-party GitHub Actions are SHA-pinned**, not tag-pinned. A tag can be moved to point at a malicious commit (and has been, in prior supply-chain incidents); a SHA can't. Renovate's `helpers:pinGitHubActionDigests` preset enforces this on auto-bump PRs. When bumping an action manually, update both the SHA and the trailing `# vX.Y.Z` comment in the same diff. -9. **Pinned runner images** (never `ubuntu-latest`), so runner-image bumps are deliberate PRs, not silent infrastructure drift. The checks (`ci.yml`, `lighthouse.yml`) and the repository-wide workflows (`dependency-snapshot.yml`, `scorecard.yml`, `claude.yml`) run on `ubuntu-26.04`, including bench's Docker (testcontainers) Postgres suite. bench's operational workflows run on `ubuntu-24.04`: `bench-verify-deploy.yml`, `bench-keep-warm.yml` and `bench-schema-deploy.yml`. Move them to 26.04 in a PR of their own. The Node version inside the runner image can still drift; `.nvmrc` pins that separately. +9. **Pinned runner images** (never `ubuntu-latest`), so runner-image bumps are deliberate PRs, not silent infrastructure drift. The checks (`ci.yml`, `lighthouse.yml`) and the repository-wide workflows (`dependency-snapshot.yml`, `scorecard.yml`, `claude.yml`, `links.yml`) run on `ubuntu-26.04`, including bench's Docker (testcontainers) Postgres suite. bench's operational workflows run on `ubuntu-24.04`: `bench-verify-deploy.yml`, `bench-keep-warm.yml` and `bench-schema-deploy.yml`. Move them to 26.04 in a PR of their own. The Node version inside the runner image can still drift; `.nvmrc` pins that separately. ### Node version diff --git a/README.md b/README.md index 8abb952a..d553406e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,11 @@ Source for [vortex.dev](https://vortex.dev), the marketing site for [Vortex](https://github.com/vortex-data/vortex) — an extensible, state-of-the-art columnar file format. Vortex is a [Linux Foundation](https://www.linuxfoundation.org/) incubating project, a Series of LF Projects, LLC. +The same Next.js app also serves [bench.vortex.dev](https://bench.vortex.dev), the benchmark results site. Its code is in `src/bench/` and `src/app/(bench)/`, and its docs start at [docs/bench/README.md](docs/bench/README.md). + ## Local development -Prerequisites: [Bun](https://bun.sh) ≥ 1.3.13 and Node ≥ 22. Bun is the package manager and script runner; Next.js itself runs on Node. +Prerequisites: [Bun](https://bun.sh) 1.4 (the `packageManager` in `package.json`) and Node 26 (`.nvmrc`). Bun is the package manager and script runner; Next.js itself runs on Node. ```bash git clone git@github.com:vortex-data/vortex.dev.git @@ -15,6 +17,8 @@ bun install bun dev # Turbopack dev server on http://localhost:3000 ``` +bench is at http://localhost:3000/bench. It needs the `BENCH_DB_*` variables for a database ([docs/bench/web.md](docs/bench/web.md)). + ## Scripts | Command | What it does | @@ -25,11 +29,14 @@ bun dev # Turbopack dev server on http://localhost:3000 | `bun run check` | Biome lint + format + import-organize, with auto-fix | | `bun run check:ci` | Biome no-write check — what CI runs | | `bun run typecheck` | `tsc --noEmit`. Must run after `bun run build` because velite populates `.velite/` and Next emits `next-env.d.ts` during build | +| `bun run knip` | Unused files, exports and dependencies. Like `typecheck`, it needs `bun run build` first, for `.velite/` | +| `bun run test` | The marketing site's unit tests (`bun test tests/unit`) | +| `bun run test:bench` | bench's tests on Vitest. Its Postgres integration suite needs Docker or `BENCH_TEST_DATABASE_URL`, and skips without either | | `bun run verify` | HTTP suite against a running server: sitemap, robots, RSS, OG, canonicals, JSON-LD, theme parity, `/api/subscribe`, internal links | | `bun run test:e2e` | Playwright browser smoke (Chromium desktop + WebKit iPhone-13). One-time setup: `bun run test:e2e:install` | | `bun run lighthouse` | Local Lighthouse CI run against a server you started, at `BASE_URL` (default `http://localhost:3000`; bench's pages need its `BENCH_DB_*` variables). Fetches a pinned `@lhci/cli` with `bunx`; it is not a dependency | -CI runs `check:ci → build → typecheck → unit and bench tests → start → verify → playwright` for both sites on every PR via [.github/workflows/ci.yml](.github/workflows/ci.yml), with a non-blocking `bun audit`. [.github/workflows/lighthouse.yml](.github/workflows/lighthouse.yml) runs Lighthouse and axe on both sites on the PR's Vercel preview and posts a sticky score table. +CI runs `check:ci → build → typecheck → knip → unit and bench tests → start → verify → playwright` for both sites on every PR via [.github/workflows/ci.yml](.github/workflows/ci.yml), with a non-blocking `bun audit`. [.github/workflows/lighthouse.yml](.github/workflows/lighthouse.yml) runs Lighthouse and axe on both sites on the PR's Vercel preview and posts a sticky score table. ## Contributing diff --git a/REUSE.toml b/REUSE.toml index d2680e6f..c5c45697 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -21,6 +21,8 @@ path = [ "tests/**", ".github/**", ".vscode/**", + ".editorconfig", + ".gitattributes", ".gitignore", ".nvmrc", "*.cjs", diff --git a/biome.json b/biome.json index 0555bce4..15713a00 100644 --- a/biome.json +++ b/biome.json @@ -57,16 +57,16 @@ "rules": { "preset": "recommended", "suspicious": { - "noExplicitAny": "off", - "noArrayIndexKey": "off", - "noAssignInExpressions": "off", - "noShadowRestrictedNames": "off" + "noExplicitAny": "error", + "noArrayIndexKey": "error", + "noAssignInExpressions": "error", + "noShadowRestrictedNames": "error" }, "style": { - "useImportType": "warn", - "noNonNullAssertion": "off", - "noParameterAssign": "off", - "noDescendingSpecificity": "off" + "useImportType": "error", + "noParameterAssign": "error", + "noDescendingSpecificity": "error", + "noNonNullAssertion": "error" }, "correctness": { "noUnusedVariables": { @@ -76,7 +76,7 @@ } }, "noUnusedImports": "error", - "useExhaustiveDependencies": "off", + "useExhaustiveDependencies": "error", "useHookAtTopLevel": "error" }, "a11y": { @@ -86,14 +86,15 @@ "noSvgWithoutTitle": "error" }, "complexity": { - "noForEach": "off", - "noUselessFragments": "off" + "noForEach": "error", + "noUselessFragments": "error", + "noImportantStyles": "error" }, "performance": { "noImgElement": "off" }, "security": { - "noDangerouslySetInnerHtml": "off" + "noDangerouslySetInnerHtml": "error" } } }, @@ -123,16 +124,6 @@ "quoteStyle": "single" } }, - "linter": { - "rules": { - "correctness": { - "useExhaustiveDependencies": "error" - }, - "complexity": { - "noImportantStyles": "off" - } - } - }, "assist": { "actions": { "source": { diff --git a/bun.lock b/bun.lock index 429fe72c..55b0b086 100644 --- a/bun.lock +++ b/bun.lock @@ -30,6 +30,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "jsdom": "^30.1.2", + "knip": "^6.43.0", "typescript": "^7.0.2", "vite": "^8.3.4", "vitest": "^5.0.4", @@ -91,7 +92,11 @@ "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.2", "", {}, "sha512-OoKoR0f76dCY666JlcbhmVTs2drYj1GUXZTYTcbUgJjh9Nv41aFfZ21bPQTERm5+L5cBDo466NltB2lplS5GBw=="], - "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + "@emnapi/core": ["@emnapi/core@1.11.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], @@ -225,6 +230,8 @@ "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || 2.0.0-beta.2", "@emnapi/runtime": "^1.7.1 || 2.0.0-beta.2" } }, "sha512-mRyDmEw9/fCDXCGfL1sbgIw/GUK0PVxRW4YwgPN61F3LQ+fSyoBFarJVzgGyhF6kRzEQZ0FejDqm4WLXSeqQmw=="], + "@next/env": ["@next/env@16.4.1", "", {}, "sha512-5sUUrrSvs/2/2LnLoesybMGx12EtPB46fdZ6RhAw0XDGSPxU2LFyQBB7yuEiOFFJPKmBY6+q2jTr1/AMbnp0/g=="], "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.4.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-U+hQ5Gj3fpzf4fveTIF3dpUFYhaSW8vHhdAnbiIomzXzg7boreYFgBqprepXFtW4sw+aTttiNmg0gew/iFNhbg=="], @@ -243,8 +250,84 @@ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.4.1", "", { "os": "win32", "cpu": "x64" }, "sha512-6ICqPo9awCkrtYNPhwkx1uU7rlFWrZRrIboDHS2LU+jv088UBYQtmJ25h6pCp27/+FZjF6BQ4Te4ErugyMeuBg=="], + "@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.155.0", "", { "os": "android", "cpu": "arm" }, "sha512-A6xv7hDtnTm+xlZ7utn6rRQpRMJHRhVr3gjRGqlx0RUX84mNk0pAsy6YN/wWhuOTkaZhRrrzaLxf0NfbH77j2w=="], + + "@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.155.0", "", { "os": "android", "cpu": "arm64" }, "sha512-T1wWwH9/JI5g7Z4kEQwDOwhToxdDhRyOTklH+uuNpJ16m3MZFmGl+l2rKc0NHQdbVRG2LTOBsNjpW3h1BWytPA=="], + + "@oxc-parser/binding-darwin-arm64": ["@oxc-parser/binding-darwin-arm64@0.155.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZoRB3qJxJ8DWA41jHja+YeB0lLfjBy4T+Kr15vnN1k65X3zbJt3rPZftEwN4Ju4tmA4K/T9dWWD7Z8ewQLnrVA=="], + + "@oxc-parser/binding-darwin-x64": ["@oxc-parser/binding-darwin-x64@0.155.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-vxtL86BtM+IgrpejA86nbTk5j0HKTIMeuaJ1fQciw6p9RfavPtIPjI7gN34owap0yG9txXBaAWpGdxokNwdqIw=="], + + "@oxc-parser/binding-freebsd-x64": ["@oxc-parser/binding-freebsd-x64@0.155.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tN6Ru5e0UVnWaIuiH8jEwnvxuxg7FvhWwSd3oe5oDhOD4ObsSr9/N6WfHLjilUWlPPj65ptwrSDH0Y7lzwTzmw=="], + + "@oxc-parser/binding-linux-arm-gnueabihf": ["@oxc-parser/binding-linux-arm-gnueabihf@0.155.0", "", { "os": "linux", "cpu": "arm" }, "sha512-h4ozA5n6L6lwolsz9iwunEhKWnIVbmFRots5QxEix0H6nJ5ICpuDa7PuShuhfpIDFe3+Qvyscioyiv+9g+4Syg=="], + + "@oxc-parser/binding-linux-arm-musleabihf": ["@oxc-parser/binding-linux-arm-musleabihf@0.155.0", "", { "os": "linux", "cpu": "arm" }, "sha512-058/+LardpfuUlsKIvBGk/CYoakEVcMgqYx8GgyUZeOxFWTojYLlCc1Gb60/y21v3esDhHidgiY/Zbq9LpBhaQ=="], + + "@oxc-parser/binding-linux-arm64-gnu": ["@oxc-parser/binding-linux-arm64-gnu@0.155.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nrOt2Y3xIAzu+lSPzH0864ieO+iCCXgoo9qFlGANE8Xh0cQ+RP8Wstfp+/wXN2YCyb98cAGzX0VruKYw7it/jw=="], + + "@oxc-parser/binding-linux-arm64-musl": ["@oxc-parser/binding-linux-arm64-musl@0.155.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-KYAayyN5hQbJPtGKtRxlyc8XYmyntOplyXSQzTDrmbz7l/GCrzQbSswvyJFBTH1LGtsZrbXOAfN52i/Kz6kLKg=="], + + "@oxc-parser/binding-linux-ppc64-gnu": ["@oxc-parser/binding-linux-ppc64-gnu@0.155.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-CSuSY5hfjtsBnHXydOj8jiNfS1qHbdPZuPz1YL1cSgK7VmNYP+QlZIbG0BBYYBgX/vYEgZwbG9mBdW4YairZ5w=="], + + "@oxc-parser/binding-linux-riscv64-gnu": ["@oxc-parser/binding-linux-riscv64-gnu@0.155.0", "", { "os": "linux", "cpu": "none" }, "sha512-7KCfOQm+Nz2N8trAXbQkal4TQ5756WVEmhm35y816YHvyFnEfVsICsiV5k0pEszNf/fRm4cm2i6i18SJ6/iCWg=="], + + "@oxc-parser/binding-linux-riscv64-musl": ["@oxc-parser/binding-linux-riscv64-musl@0.155.0", "", { "os": "linux", "cpu": "none" }, "sha512-JSlhgoK+/g1TGJI5NuADx2+6syhxnQNLCWLXyO5QLFyetomcr/vIxSdiNlmyAv963YXUzCuwAwRG6npZ5xyJ1A=="], + + "@oxc-parser/binding-linux-s390x-gnu": ["@oxc-parser/binding-linux-s390x-gnu@0.155.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-WFIX+dPokWp64mi434Md3U3lJaTwNxAphvGjpltoyQjGUsdXavOIRrnp9dOS34Hd75HOAwDxAI+YwWOwqmJbtg=="], + + "@oxc-parser/binding-linux-x64-gnu": ["@oxc-parser/binding-linux-x64-gnu@0.155.0", "", { "os": "linux", "cpu": "x64" }, "sha512-g6xSDC/JpbnVOz5uafMGyK3txBVRFMQTx8opv/yHXGxqV3ZMVl3bf7W8XrSnF68dmy3gM4dgReNryuE53+WkeQ=="], + + "@oxc-parser/binding-linux-x64-musl": ["@oxc-parser/binding-linux-x64-musl@0.155.0", "", { "os": "linux", "cpu": "x64" }, "sha512-6/4xlxKlZJWrQ0uwDFPc0H9+xLu7068CKhX3vIDW3SbvP/lVgnVuT5v6dyugeWskWpuxqF/WJ4WKwhxjU3pG2w=="], + + "@oxc-parser/binding-openharmony-arm64": ["@oxc-parser/binding-openharmony-arm64@0.155.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tltklL0J0XjDxIRTBXWOBhvc2mitcmES45pIxhwDWfxRyqz7Vkeu0NekTZEIxGE4f/lXgq3vZcjjffteQOVorg=="], + + "@oxc-parser/binding-win32-arm64-msvc": ["@oxc-parser/binding-win32-arm64-msvc@0.155.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-alAb9TzjatUEsOeoncAziUnfdTDxfyswhalFZ9sC6plUgATIUx2NIZ0+bMn6aJ28AduS1OpG/OAisr/YISn4Ig=="], + + "@oxc-parser/binding-win32-ia32-msvc": ["@oxc-parser/binding-win32-ia32-msvc@0.155.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-yc5cbSfcGmRv/KpxDFuyk8adEBaWd/9HilWvBbQ4bFbus1V2PUh2mGB2Cfq9rqZTqLeiMT8x0skV+LKp9hOMgQ=="], + + "@oxc-parser/binding-win32-x64-msvc": ["@oxc-parser/binding-win32-x64-msvc@0.155.0", "", { "os": "win32", "cpu": "x64" }, "sha512-8heq6jGq+rfpTDRDjoQiBHw0maQZf3IXkS99pdHQZPoTL5kyF5qyPS0RidUlSdnWDIqxXb5nux7GBD9cT+imUw=="], + "@oxc-project/types": ["@oxc-project/types@0.155.0", "", {}, "sha512-SeE8JPwW1xs1tHeNycjUtUBdhorRa8o4jlvjwk49R7/Z291i+4DQa7JJIgOm/TaYQZx+QrPQbVJ/9c8dMIZN8Q=="], + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.24.2", "", { "os": "android", "cpu": "arm" }, "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.24.2", "", { "os": "android", "cpu": "arm64" }, "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.24.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.24.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.24.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.24.2", "", { "os": "linux", "cpu": "arm" }, "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.24.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.24.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.24.2", "", { "os": "linux", "cpu": "none" }, "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.24.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.24.2", "", { "os": "linux", "cpu": "x64" }, "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw=="], + + "@oxc-resolver/binding-openharmony-arm64": ["@oxc-resolver/binding-openharmony-arm64@11.24.2", "", { "os": "none", "cpu": "arm64" }, "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.24.2", "", { "dependencies": { "@emnapi/core": "1.11.2", "@emnapi/runtime": "1.11.2", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.24.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.24.2", "", { "os": "win32", "cpu": "x64" }, "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw=="], + "@playwright/test": ["@playwright/test@1.64.1", "", { "dependencies": { "playwright": "1.64.1" }, "bin": { "playwright": "cli.js" } }, "sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg=="], "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], @@ -319,6 +402,8 @@ "@testcontainers/postgresql": ["@testcontainers/postgresql@12.2.1", "", { "dependencies": { "testcontainers": "^12.2.1" } }, "sha512-MIbI5KjZ9M4SY4+xzxg/w4ZLBeV+Jd6ognXoWn7dh/mhIhBbn7ss0MiZHNg2XbCbu03BW9YQ9+H9xW8PFkJWaA=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.4", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A=="], + "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], @@ -593,10 +678,14 @@ "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], + "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "feed": ["feed@6.0.0", "", { "dependencies": { "xml-js": "^1.6.11" } }, "sha512-rvFOF1POce0ijAKy3tZN5T8PuprgIit/VbMyXJWcoYwcMOTSIJ7NqZb8DTLPJr77yiEO27kidm/fw0kcSIV+ww=="], + "formatly": ["formatly@0.11.4", "", { "dependencies": { "fd-package-json": "^2.0.0", "package-manager-detector": "^1.8.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-TeCC10lP8KCgoX+Fe5lryILIT0/Dy+nP0OSVnsuB5CwXoLuOdqxCYlPFF1inR8S/zTy1ec7TjhS2TeRKWzzImA=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], @@ -607,6 +696,8 @@ "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="], + "get-tsconfig": ["get-tsconfig@4.14.3", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA=="], + "glob": ["glob@14.0.2", "", { "dependencies": { "minimatch": "^11.0.0", "minipass": "^7.1.3", "path-scurry": "^3.0.0" } }, "sha512-vfMgTR2djMHPUchsJMLpZlYQvtjVL+mHpEr5wPpoCbQs2vr/FBNK+3Gnl9Ara1DSs4Aeg4eG9HiADb5/rkY/ZA=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -663,10 +754,14 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], + "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], "jsdom": ["jsdom@30.1.3", "", { "dependencies": { "@asamuzakjp/css-color": "^7.1.3", "@asamuzakjp/dom-selector": "^10.0.3", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.15", "@exodus/bytes": "^1.16.0", "css-tree": "^3.2.1", "data-urls": "^8.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^7.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.3", "parse5": "^8.0.1", "saxes": "^6.0.0", "tough-cookie": "^6.0.2", "undici": "^8.12.1", "w3c-xmlserializer": "^6.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^17.2.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.2.3" }, "optionalPeers": ["canvas"] }, "sha512-hQMUPvRgN7KxsfIytDQji7bimuJeRQa6Qo5+iuiZQk5CafeRxLIA10/cG5YCBFNpD9RO3oqIiN4CO1Y44kxqXg=="], + "knip": ["knip@6.43.0", "", { "dependencies": { "formatly": "^0.11.4", "get-tsconfig": "4.14.3", "jiti": "^2.7.0", "oxc-parser": "^0.155.0", "oxc-resolver": "11.24.2", "picomatch": "^4.0.7", "smol-toml": "^1.9.0", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.17", "unbash": "^6.0.1", "yaml": "^2.9.1", "zod": "^4.6.5" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-8l3cCOg/EXoILO0qcSoMra49yQM91+lEIR4fu5Ko8rrArGX8xKdKZiGI9psPiY6rhLmfLERcMfFEByF0wli5CA=="], + "lazystream": ["lazystream@1.0.1", "", { "dependencies": { "readable-stream": "^2.0.5" } }, "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw=="], "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="], @@ -823,6 +918,12 @@ "os-paths": ["os-paths@4.4.0", "", {}, "sha512-wrAwOeXp1RRMFfQY8Sy7VaGVmPocaLwSFOYCGKSyo8qmJ+/yaafCl5BCA1IQZWqFSRBrKDYFeR9d/VyQzfH/jg=="], + "oxc-parser": ["oxc-parser@0.155.0", "", { "dependencies": { "@oxc-project/types": "^0.155.0" }, "optionalDependencies": { "@oxc-parser/binding-android-arm-eabi": "0.155.0", "@oxc-parser/binding-android-arm64": "0.155.0", "@oxc-parser/binding-darwin-arm64": "0.155.0", "@oxc-parser/binding-darwin-x64": "0.155.0", "@oxc-parser/binding-freebsd-x64": "0.155.0", "@oxc-parser/binding-linux-arm-gnueabihf": "0.155.0", "@oxc-parser/binding-linux-arm-musleabihf": "0.155.0", "@oxc-parser/binding-linux-arm64-gnu": "0.155.0", "@oxc-parser/binding-linux-arm64-musl": "0.155.0", "@oxc-parser/binding-linux-ppc64-gnu": "0.155.0", "@oxc-parser/binding-linux-riscv64-gnu": "0.155.0", "@oxc-parser/binding-linux-riscv64-musl": "0.155.0", "@oxc-parser/binding-linux-s390x-gnu": "0.155.0", "@oxc-parser/binding-linux-x64-gnu": "0.155.0", "@oxc-parser/binding-linux-x64-musl": "0.155.0", "@oxc-parser/binding-openharmony-arm64": "0.155.0", "@oxc-parser/binding-win32-arm64-msvc": "0.155.0", "@oxc-parser/binding-win32-ia32-msvc": "0.155.0", "@oxc-parser/binding-win32-x64-msvc": "0.155.0" } }, "sha512-C/ReSf1JUFukK/zvk6jhcNibSssvas5+ktIzK7fbKVd3CedWe59C3QwHEOrEZj/4Rt9+xg0Jeis/Jv1F/KM1mw=="], + + "oxc-resolver": ["oxc-resolver@11.24.2", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.24.2", "@oxc-resolver/binding-android-arm64": "11.24.2", "@oxc-resolver/binding-darwin-arm64": "11.24.2", "@oxc-resolver/binding-darwin-x64": "11.24.2", "@oxc-resolver/binding-freebsd-x64": "11.24.2", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", "@oxc-resolver/binding-linux-x64-musl": "11.24.2", "@oxc-resolver/binding-openharmony-arm64": "11.24.2", "@oxc-resolver/binding-wasm32-wasi": "11.24.2", "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw=="], + + "package-manager-detector": ["package-manager-detector@1.9.0", "", {}, "sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA=="], + "parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^2.0.0", "character-entities-legacy": "^3.0.0", "character-reference-invalid": "^2.0.0", "decode-named-character-reference": "^1.0.0", "is-alphanumerical": "^2.0.0", "is-decimal": "^2.0.0", "is-hexadecimal": "^2.0.0" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], "parse-numeric-range": ["parse-numeric-range@1.3.0", "", {}, "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="], @@ -925,6 +1026,8 @@ "resend": ["resend@6.34.1", "", { "dependencies": { "postal-mime": "2.7.6", "standardwebhooks": "1.1.1" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-3jo/j1WAW7CBkE6pzK4qLjZCfHRLIhY3ph3x/wzU+MN51cSw3EtReGce8S1cA1R0ipNDOdFYU5Lmt5r563lvYw=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], "rolldown": ["rolldown@1.2.15", "", { "dependencies": { "@oxc-project/types": "=0.155.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm-eabi": "1.2.15", "@rolldown/binding-android-arm64": "1.2.15", "@rolldown/binding-darwin-arm64": "1.2.15", "@rolldown/binding-darwin-x64": "1.2.15", "@rolldown/binding-freebsd-x64": "1.2.15", "@rolldown/binding-linux-arm-gnueabihf": "1.2.15", "@rolldown/binding-linux-arm64-gnu": "1.2.15", "@rolldown/binding-linux-arm64-musl": "1.2.15", "@rolldown/binding-linux-ppc64-gnu": "1.2.15", "@rolldown/binding-linux-s390x-gnu": "1.2.15", "@rolldown/binding-linux-x64-gnu": "1.2.15", "@rolldown/binding-linux-x64-musl": "1.2.15", "@rolldown/binding-openharmony-arm64": "1.2.15", "@rolldown/binding-win32-arm64-msvc": "1.2.15", "@rolldown/binding-win32-x64-msvc": "1.2.15" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-6odND+fel5zVw3I4Yc2e69gvguDnX0CAAGiNDZSq4ik5WUcaLrkD2GLuog0XcODbor31hZ5ayEMgVDno+KPV5g=="], @@ -951,6 +1054,8 @@ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "smol-toml": ["smol-toml@1.9.1", "", {}, "sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.2", "", {}, "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw=="], @@ -983,6 +1088,8 @@ "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "style-to-js": ["style-to-js@1.1.21", "", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], @@ -1029,6 +1136,8 @@ "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + "unbash": ["unbash@6.0.1", "", {}, "sha512-0db9eS5iqybux41zLkX69nk90KTci7F7eZ+gXzEG/Q7E0tAqEBhRh5fkZvk1N7jT7QkGWqq34i8SFGPFEe1V8A=="], + "undici": ["undici@8.12.3", "", {}, "sha512-+fdndUTwBP86cVjihTp21hZHo7d6oj1c/47XPA3lL8mM0XHs+C4EkcjmgRsFnFk2f/UONGDy9ItBItmXsLOQHw=="], "undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="], @@ -1063,6 +1172,8 @@ "w3c-xmlserializer": ["w3c-xmlserializer@6.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-4Nsy8K5Tr6SPDH9jhKJOHf7ChDrc1zufZTVSF7x72hwuEXBqxqk9G6cK+K2NRUtB3iELRJqjXb4JPDMBjMTl2Q=="], + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], @@ -1107,6 +1218,8 @@ "@grpc/grpc-js/@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="], + "@img/sharp-wasm32/@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], + "@types/ssh2/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], diff --git a/knip.json b/knip.json new file mode 100644 index 00000000..485204aa --- /dev/null +++ b/knip.json @@ -0,0 +1,13 @@ +{ + "$schema": "./node_modules/knip/schema.json", + "entry": [ + "lighthouserc.cjs", + "velite.config.ts", + "src/bench/styles/postcss.config.mjs", + "scripts/*.{ts,mjs}", + "scripts/parity/**/*.{ts,mjs}" + ], + "ignore": ["src/vendor/**"], + "ignoreDependencies": ["server-only"], + "ignoreBinaries": ["uv"] +} diff --git a/package.json b/package.json index 46a36d49..508ccbf5 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "vortex", "version": "0.1.0", "private": true, + "license": "Apache-2.0", "packageManager": "bun@1.4.4", "engines": { "node": "26.x", @@ -17,6 +18,7 @@ "check": "biome check --write", "check:ci": "biome check", "typecheck": "tsc --noEmit", + "knip": "knip", "verify": "bun scripts/verify.ts", "test": "bun test tests/unit", "test:bench": "vitest run", @@ -51,6 +53,7 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "jsdom": "^30.1.2", + "knip": "^6.43.0", "typescript": "^7.0.2", "vite": "^8.3.4", "vitest": "^5.0.4" diff --git a/renovate.json b/renovate.json index 6ecfda4c..176454de 100644 --- a/renovate.json +++ b/renovate.json @@ -49,10 +49,6 @@ "@types/react-dom" ] }, - { - "groupName": "TailwindCSS", - "matchPackageNames": ["tailwindcss", "@tailwindcss/**"] - }, { "groupName": "MDX content pipeline", "matchPackageNames": [ diff --git a/scripts/verify.ts b/scripts/verify.ts index 8535ff91..8fbb20e1 100644 --- a/scripts/verify.ts +++ b/scripts/verify.ts @@ -120,8 +120,7 @@ function jsonLd(html: string): JsonLdResult { const blocks: Array> = []; const re = /]+type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi; - let m: RegExpExecArray | null; - while ((m = re.exec(html)) !== null) { + for (const m of html.matchAll(re)) { const raw = m[1] ?? ""; try { blocks.push(JSON.parse(raw)); @@ -135,8 +134,7 @@ function jsonLd(html: string): JsonLdResult { function internalHrefs(html: string): string[] { const hrefs = new Set(); const re = /]+href="(\/[^"#]*)(?:#[^"]*)?"/gi; - let m: RegExpExecArray | null; - while ((m = re.exec(html)) !== null) { + for (const m of html.matchAll(re)) { const href = m[1]; if (!href) continue; if (href.startsWith("//")) continue; @@ -162,7 +160,9 @@ async function checkSitemap(): Promise { fail("sitemap.xml", `content-type=${r.ctype}`); return []; } - const locs = [...r.body.matchAll(/([^<]+)<\/loc>/g)].map((m) => m[1]!); + const locs = [...r.body.matchAll(/([^<]+)<\/loc>/g)].flatMap((m) => + m[1] ? [m[1]] : [] + ); const required = ["/", "/blog"]; const paths = locs.map((l) => l.replace(/^https?:\/\/[^/]+/, "") || "/"); const missing = required.filter((p) => !paths.includes(p)); @@ -384,8 +384,8 @@ async function checkInternalLinks( // generation paints with one shade and the site renders with another — // exactly the bug this check is here to catch. Currently only // `THEME.background` has a corresponding CSS variable (`--background` in -// `:root`); `THEME.foreground` is white-on-dark and uses Tailwind's -// built-in `text-white`, so there's nothing to assert against in CSS. +// `:root`); `THEME.foreground` has no variable in `globals.css`, so there's +// nothing to assert against in CSS. async function checkThemeParity(): Promise { let css: string; try { diff --git a/src/app/(bench)/bench/layout.tsx b/src/app/(bench)/bench/layout.tsx index 3bcbdbf5..12b5b85e 100644 --- a/src/app/(bench)/bench/layout.tsx +++ b/src/app/(bench)/bench/layout.tsx @@ -85,6 +85,7 @@ export default async function RootLayout({ children }: { children: ReactNode }) suppressHydrationWarning > + {/* biome-ignore lint/security/noDangerouslySetInnerHtml: a constant script that sets the theme before first paint. */}