From 7313d6b024dbcd666c1925073197ebed67ffa509 Mon Sep 17 00:00:00 2001 From: Clay Good Date: Fri, 16 Oct 2026 15:48:43 -0500 Subject: [PATCH] fix(website): preview the built site without a server dependency `pnpm start` ran `serve`, a dev-only tool whose transitive tree kept publishing advisories into the docs site's graph. Four of them had to be pinned by hand in #2019 (brace-expansion x3, fast-uri) after the Security workflow went red on main, for code that never reached the published site. Replace it with a first-party static preview script, which has no tree to audit, and drop the three pins that existed only to patch serve's tree (brace-expansion, fast-uri, compression). The build-time pins (postcss, sharp, nanoid) stay. Co-Authored-By: Claude Opus 5 --- test/pnpm-workspace-config.test.ts | 18 + test/website-preview.test.ts | 130 ++++++ website/package.json | 3 +- website/pnpm-lock.yaml | 627 ----------------------------- website/pnpm-workspace.yaml | 10 - website/scripts/preview.mjs | 135 +++++++ 6 files changed, 284 insertions(+), 639 deletions(-) create mode 100644 test/website-preview.test.ts create mode 100644 website/scripts/preview.mjs diff --git a/test/pnpm-workspace-config.test.ts b/test/pnpm-workspace-config.test.ts index 50b2ce47..fa9b255d 100644 --- a/test/pnpm-workspace-config.test.ts +++ b/test/pnpm-workspace-config.test.ts @@ -54,6 +54,24 @@ describe('pnpm workspace configuration', () => { expect(workspace.overrides).toEqual(lockfile.overrides); }); + it('previews the built site without a server dependency', () => { + const packageJson = readJson('website/package.json'); + const lockfile = readYaml('website/pnpm-lock.yaml'); + + // `pnpm start` ran `serve`, whose dev-only tree kept publishing advisories + // into the site's graph — four of them (brace-expansion x3, fast-uri) turned + // the Security workflow red on `main` and had to be pinned by hand, for code + // that never reached the published site. A first-party preview script has no + // tree to audit. Re-adding a server dependency means taking that back on + // purpose, so it should update this test too. + expect(packageJson.scripts.start).toBe('node scripts/preview.mjs'); + expect(fs.existsSync(path.join(projectRoot, 'website/scripts/preview.mjs'))).toBe(true); + const serverPackages = Object.keys(lockfile.packages).filter((key) => + /^(serve|serve-handler)@/.test(key) + ); + expect(serverPackages).toEqual([]); + }); + it('includes install policy changes in Nix and security validation', () => { const flake = fs.readFileSync(path.join(projectRoot, 'flake.nix'), 'utf8'); const ci = fs.readFileSync(path.join(projectRoot, '.github/workflows/ci.yml'), 'utf8'); diff --git a/test/website-preview.test.ts b/test/website-preview.test.ts new file mode 100644 index 00000000..579cc315 --- /dev/null +++ b/test/website-preview.test.ts @@ -0,0 +1,130 @@ +import fs from 'fs'; +import os from 'os'; +import path from 'path'; +import type { AddressInfo } from 'net'; +import { afterAll, beforeAll, describe, expect, it } from 'vitest'; + +const projectRoot = process.cwd(); +const scriptPath = path.join(projectRoot, 'website', 'scripts', 'preview.mjs'); + +type PreviewModule = { + resolveStaticFile(root: string, requestPath: string): string | null; + contentTypeFor(filePath: string): string; + createPreviewServer(root: string): import('http').Server; +}; + +const preview: PreviewModule = await import(scriptPath); + +/** + * Mirrors a Next static export: a route is written as `docs/cli.html`, and a + * directory of the same name holds its data files but no index.html. + */ +function buildFixture(): string { + const root = fs.mkdtempSync(path.join(os.tmpdir(), 'openspec-preview-')); + fs.writeFileSync(path.join(root, 'index.html'), 'home'); + fs.writeFileSync(path.join(root, '404.html'), 'missing'); + fs.writeFileSync(path.join(root, 'robots.txt'), 'User-agent: *\n'); + fs.mkdirSync(path.join(root, 'docs', 'cli'), { recursive: true }); + fs.writeFileSync(path.join(root, 'docs', 'cli.html'), 'cli'); + fs.writeFileSync(path.join(root, 'docs', 'cli', 'data.txt'), 'data'); + fs.mkdirSync(path.join(root, 'guide'), { recursive: true }); + fs.writeFileSync(path.join(root, 'guide', 'index.html'), 'guide'); + return root; +} + +describe('website preview server', () => { + let root: string; + + beforeAll(() => { + root = buildFixture(); + }); + + afterAll(() => { + fs.rmSync(root, { recursive: true, force: true }); + }); + + describe('resolveStaticFile', () => { + it('serves the root index', () => { + expect(preview.resolveStaticFile(root, '/')).toBe(path.join(root, 'index.html')); + }); + + it('falls back to the exported .html file for a route', () => { + expect(preview.resolveStaticFile(root, '/docs/cli')).toBe( + path.join(root, 'docs', 'cli.html') + ); + }); + + it('resolves a route the same way with a trailing slash', () => { + expect(preview.resolveStaticFile(root, '/docs/cli/')).toBe( + path.join(root, 'docs', 'cli.html') + ); + }); + + it('prefers a directory index when one exists', () => { + expect(preview.resolveStaticFile(root, '/guide')).toBe( + path.join(root, 'guide', 'index.html') + ); + }); + + it('serves a file by its exact path', () => { + expect(preview.resolveStaticFile(root, '/robots.txt')).toBe(path.join(root, 'robots.txt')); + }); + + it('returns null for a path with no match', () => { + expect(preview.resolveStaticFile(root, '/nope')).toBeNull(); + }); + + it('refuses to escape the export directory', () => { + const outside = path.join(root, '..', 'escaped.txt'); + fs.writeFileSync(outside, 'secret'); + try { + expect(preview.resolveStaticFile(root, '/../escaped.txt')).toBeNull(); + expect(preview.resolveStaticFile(root, '/%2e%2e/escaped.txt')).toBeNull(); + expect(preview.resolveStaticFile(root, '/docs/../../escaped.txt')).toBeNull(); + } finally { + fs.rmSync(outside, { force: true }); + } + }); + + it('rejects malformed encoding and null bytes', () => { + expect(preview.resolveStaticFile(root, '/%E0%A4%A')).toBeNull(); + expect(preview.resolveStaticFile(root, '/index.html%00.txt')).toBeNull(); + }); + }); + + describe('contentTypeFor', () => { + it('names the common static types', () => { + expect(preview.contentTypeFor('a/b.html')).toBe('text/html; charset=utf-8'); + expect(preview.contentTypeFor('a/b.css')).toBe('text/css; charset=utf-8'); + expect(preview.contentTypeFor('a/b.svg')).toBe('image/svg+xml'); + expect(preview.contentTypeFor('a/b.woff2')).toBe('font/woff2'); + }); + + it('falls back to a byte stream for anything else', () => { + expect(preview.contentTypeFor('a/b.unknown')).toBe('application/octet-stream'); + }); + }); + + describe('over HTTP', () => { + it('serves pages and answers a miss with the 404 page', async () => { + const server = preview.createPreviewServer(root); + await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve)); + const { port } = server.address() as AddressInfo; + + try { + const page = await fetch(`http://127.0.0.1:${port}/docs/cli`); + expect(page.status).toBe(200); + expect(page.headers.get('content-type')).toBe('text/html; charset=utf-8'); + expect(await page.text()).toBe('cli'); + + const missing = await fetch(`http://127.0.0.1:${port}/nope`); + expect(missing.status).toBe(404); + expect(await missing.text()).toBe('missing'); + } finally { + await new Promise((resolve, reject) => + server.close((error) => (error ? reject(error) : resolve())) + ); + } + }); + }); +}); diff --git a/website/package.json b/website/package.json index 81d1a8cb..1f1e0561 100644 --- a/website/package.json +++ b/website/package.json @@ -8,7 +8,7 @@ "sync:docs:watch": "node --watch-path=../docs --watch-path=../docs-lab --watch-path=docs.sync.config.mjs --watch-preserve-output scripts/sync-docs.mjs", "build": "pnpm run sync:docs && fumadocs-mdx && next build", "dev": "pnpm run sync:docs && (pnpm run sync:docs:watch & next dev)", - "start": "serve out", + "start": "node scripts/preview.mjs", "types:check": "pnpm run sync:docs && fumadocs-mdx && next typegen && tsc --noEmit" }, "dependencies": { @@ -29,7 +29,6 @@ "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", "postcss": "^8.5.29", - "serve": "^14.2.6", "tailwindcss": "^4.3.1", "typescript": "^6.0.3" } diff --git a/website/pnpm-lock.yaml b/website/pnpm-lock.yaml index d09ff01b..97c8ce23 100644 --- a/website/pnpm-lock.yaml +++ b/website/pnpm-lock.yaml @@ -7,10 +7,7 @@ settings: overrides: postcss: ^8.5.28 sharp: ^0.35.3 - brace-expansion@<5.0.12: '>=5.0.12 <6' - fast-uri@<3.1.8: ^3.1.8 nanoid@<3.3.17: '>=3.3.17 <4' - compression@<1.8.2: '>=1.8.2 <2' importers: @@ -62,9 +59,6 @@ importers: postcss: specifier: ^8.5.28 version: 8.5.29 - serve: - specifier: ^14.2.6 - version: 14.2.6 tailwindcss: specifier: ^4.3.1 version: 4.3.3 @@ -1116,9 +1110,6 @@ packages: '@yuku-toolchain/types@0.10.2': resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} - '@zeit/schemas@2.36.0': - resolution: {integrity: sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1129,34 +1120,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - - ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -1168,10 +1131,6 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.27: resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==} engines: {node: '>=6.0.0'} @@ -1180,44 +1139,12 @@ packages: beautiful-mermaid@1.1.3: resolution: {integrity: sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg==} - boxen@7.0.0: - resolution: {integrity: sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==} - engines: {node: '>=14.16'} - - brace-expansion@5.0.12: - resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} - engines: {node: 20 || >=22} - - bytes@3.0.0: - resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} - engines: {node: '>= 0.8'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - camelcase@7.0.1: - resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} - engines: {node: '>=14.16'} - caniuse-lite@1.0.30001814: resolution: {integrity: sha512-/Uaf1lAzr59XcMpW0o96WoEfr+VXK2OX4U9AgFoiSHsVJ4HppnIFUjtYzsyDH2+tgANaQb2/oxYGwCPapN1FpA==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk-template@0.4.0: - resolution: {integrity: sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==} - engines: {node: '>=12'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - chalk@5.0.1: - resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1237,17 +1164,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - cli-boxes@3.0.0: - resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} - engines: {node: '>=10'} - client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - clipboardy@3.0.0: - resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -1260,46 +1179,15 @@ packages: collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} - - compression@1.8.2: - resolution: {integrity: sha512-o8vI5RE5A6EVVOd9o41jKp41aJom+QTEO/Bx8MYNjexMo/Bv2WOjUfZr+aL0WnYSgymUy6zeguqLTsIhV0gMvQ==} - engines: {node: '>= 0.8.0'} - compute-scroll-into-view@3.1.1: resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - content-disposition@0.5.2: - resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} - engines: {node: '>= 0.6'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1312,18 +1200,10 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1334,18 +1214,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - elkjs@0.11.1: resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - enhanced-resolve@5.24.3: resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} engines: {node: '>=10.13.0'} @@ -1397,19 +1268,9 @@ packages: estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.8: - resolution: {integrity: sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1556,20 +1417,12 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} @@ -1600,13 +1453,6 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1619,15 +1465,6 @@ packages: is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} @@ -1635,28 +1472,10 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-port-reachable@4.0.0: - resolution: {integrity: sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -1804,9 +1623,6 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - micromark-core-commonmark@2.0.4: resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} @@ -1915,28 +1731,6 @@ packages: micromark@4.0.3: resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} - mime-db@1.33.0: - resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.18: - resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} - engines: {node: '>= 0.6'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - motion-dom@14.0.0: resolution: {integrity: sha512-aU3ApXo1yTpeUi3JU+jbDk2TELjxPE3MbBMUP7nZHlARb6RweJK+ojmLjpuB0YsY6zhbBl1YWGo8LMsI1/Llqw==} @@ -1954,9 +1748,6 @@ packages: react-dom: optional: true - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1965,10 +1756,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} - next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -1996,22 +1783,10 @@ packages: sass: optional: true - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - npm-to-yarn@3.2.0: resolution: {integrity: sha512-K1HmQeZT2HrjpsR6KgqbN2FAXL2NrJJmNUSD9ck7HGTVu1JKXox8n9SB+tjbU8m8JGLF4OscrroPepew/L7/Xw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - on-headers@1.1.0: - resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} - engines: {node: '>= 0.8'} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - oniguruma-parser@0.12.2: resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} @@ -2024,16 +1799,6 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - path-is-inside@1.0.2: - resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2048,14 +1813,6 @@ packages: property-information@7.2.0: resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - range-parser@1.2.0: - resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} - engines: {node: '>= 0.6'} - - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-dom@19.3.0: resolution: {integrity: sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==} peerDependencies: @@ -2122,13 +1879,6 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - registry-auth-token@3.3.2: - resolution: {integrity: sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==} - - registry-url@3.1.0: - resolution: {integrity: sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==} - engines: {node: '>=0.10.0'} - rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} @@ -2153,13 +1903,6 @@ packages: remark@15.0.1: resolution: {integrity: sha512-Eht5w30ruCXgFmxVUSlNWQ9iiimq07URKeFS3hNc8cUWy1llX4KDWfyEDZRycMc+znsN9Ux5/tJ/BFdgdOwA3A==} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - scheduler@0.28.0: resolution: {integrity: sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==} @@ -2171,14 +1914,6 @@ packages: engines: {node: '>=10'} hasBin: true - serve-handler@6.1.7: - resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} - - serve@14.2.6: - resolution: {integrity: sha512-QEjUSA+sD4Rotm1znR8s50YqA3kYpRGPmtd5GlFxbaL9n/FdUNbqMhxClqdditSk0LlZyA/dhud6XNRTOC9x2Q==} - engines: {node: '>= 14'} - hasBin: true - sharp@0.35.5: resolution: {integrity: sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==} engines: {node: '>=20.9.0'} @@ -2188,21 +1923,10 @@ packages: '@types/node': optional: true - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - shiki@4.5.0: resolution: {integrity: sha512-OcRW72ngQvc+MSVWBRIovC/BTW589iUE/Vxav7VeM4RcY9MUjqB+IR6ILG1k9nuW6LiG/nct9uma4/MBtnZbmQ==} engines: {node: '>=20'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - source-map-js@1.2.2: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} @@ -2214,33 +1938,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -2260,10 +1960,6 @@ packages: babel-plugin-macros: optional: true - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - tailwindcss@4.3.3: resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} @@ -2288,10 +1984,6 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - type-fest@2.19.0: - resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} - engines: {node: '>=12.20'} - typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} @@ -2324,9 +2016,6 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - update-check@1.5.4: - resolution: {integrity: sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==} - use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -2347,10 +2036,6 @@ packages: '@types/react': optional: true - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -2363,19 +2048,6 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - widest-line@4.0.1: - resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==} - engines: {node: '>=12'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - yaml@2.9.1: resolution: {integrity: sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==} engines: {node: '>= 14.6'} @@ -3240,39 +2912,12 @@ snapshots: '@yuku-toolchain/types@0.10.2': {} - '@zeit/schemas@2.36.0': {} - acorn-jsx@5.3.2(acorn@8.18.0): dependencies: acorn: 8.18.0 acorn@8.18.0: {} - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.8 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-align@3.0.1: - dependencies: - string-width: 4.2.3 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - arch@2.2.0: {} - - arg@5.0.2: {} - aria-hidden@1.2.6: dependencies: tslib: 2.8.1 @@ -3281,8 +2926,6 @@ snapshots: bail@2.0.2: {} - balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.27: {} beautiful-mermaid@1.1.3: @@ -3290,42 +2933,10 @@ snapshots: elkjs: 0.11.1 entities: 7.0.1 - boxen@7.0.0: - dependencies: - ansi-align: 3.0.1 - camelcase: 7.0.1 - chalk: 5.0.1 - cli-boxes: 3.0.0 - string-width: 5.1.2 - type-fest: 2.19.0 - widest-line: 4.0.1 - wrap-ansi: 8.1.0 - - brace-expansion@5.0.12: - dependencies: - balanced-match: 4.0.4 - - bytes@3.0.0: {} - - bytes@3.1.2: {} - - camelcase@7.0.1: {} - caniuse-lite@1.0.30001814: {} ccount@2.0.1: {} - chalk-template@0.4.0: - dependencies: - chalk: 4.1.2 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - chalk@5.0.1: {} - character-entities-html4@2.1.0: {} character-entities-legacy@3.0.0: {} @@ -3342,63 +2953,20 @@ snapshots: dependencies: clsx: 2.1.1 - cli-boxes@3.0.0: {} - client-only@0.0.1: {} - clipboardy@3.0.0: - dependencies: - arch: 2.2.0 - execa: 5.1.1 - is-wsl: 2.2.0 - clsx@2.1.1: {} cn@0.4.0: {} collapse-white-space@2.1.0: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - comma-separated-tokens@2.0.3: {} - compressible@2.0.18: - dependencies: - mime-db: 1.54.0 - - compression@1.8.2: - dependencies: - bytes: 3.1.2 - compressible: 2.0.18 - debug: 2.6.9 - destroy: 1.2.0 - negotiator: 0.6.4 - on-headers: 1.1.0 - safe-buffer: 5.2.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - compute-scroll-into-view@3.1.1: {} - content-disposition@0.5.2: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - csstype@3.2.3: {} - debug@2.6.9: - dependencies: - ms: 2.0.0 - debug@4.4.3: dependencies: ms: 2.1.3 @@ -3407,12 +2975,8 @@ snapshots: dependencies: character-entities: 2.0.2 - deep-extend@0.6.0: {} - dequal@2.0.3: {} - destroy@1.2.0: {} - detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -3421,14 +2985,8 @@ snapshots: dependencies: dequal: 2.0.3 - eastasianwidth@0.2.0: {} - elkjs@0.11.1: {} - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - enhanced-resolve@5.24.3: dependencies: graceful-fs: 4.2.11 @@ -3520,24 +3078,8 @@ snapshots: dependencies: '@types/estree': 1.0.9 - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - extend@3.0.2: {} - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.8: {} - fdir@6.5.0(picomatch@4.0.7): optionalDependencies: picomatch: 4.0.7 @@ -3654,14 +3196,10 @@ snapshots: get-nonce@1.0.1: {} - get-stream@6.0.1: {} - github-slugger@2.0.0: {} graceful-fs@4.2.11: {} - has-flag@4.0.0: {} - hast-util-from-parse5@8.0.3: dependencies: '@types/hast': 3.0.5 @@ -3772,10 +3310,6 @@ snapshots: html-void-elements@3.0.0: {} - human-signals@2.1.0: {} - - ini@1.3.8: {} - inline-style-parser@0.2.7: {} is-alphabetical@2.0.1: {} @@ -3787,28 +3321,12 @@ snapshots: is-decimal@2.0.1: {} - is-docker@2.2.1: {} - - is-fullwidth-code-point@3.0.0: {} - is-hexadecimal@2.0.1: {} is-plain-obj@4.1.0: {} - is-port-reachable@4.0.0: {} - - is-stream@2.0.1: {} - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - isexe@2.0.0: {} - jiti@2.7.0: {} - json-schema-traverse@1.0.0: {} - lightningcss-android-arm64@1.32.0: optional: true @@ -4043,8 +3561,6 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - merge-stream@2.0.0: {} - micromark-core-commonmark@2.0.4: dependencies: decode-named-character-reference: 1.3.0 @@ -4315,22 +3831,6 @@ snapshots: transitivePeerDependencies: - supports-color - mime-db@1.33.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.18: - dependencies: - mime-db: 1.33.0 - - mimic-fn@2.1.0: {} - - minimatch@3.1.5: - dependencies: - brace-expansion: 5.0.12 - - minimist@1.2.8: {} - motion-dom@14.0.0: dependencies: motion-utils: 14.0.0 @@ -4345,14 +3845,10 @@ snapshots: react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - ms@2.0.0: {} - ms@2.1.3: {} nanoid@3.3.20: {} - negotiator@0.6.4: {} - next-themes@0.4.6(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: react: 19.3.0 @@ -4383,18 +3879,8 @@ snapshots: - '@types/node' - babel-plugin-macros - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - npm-to-yarn@3.2.0: {} - on-headers@1.1.0: {} - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - oniguruma-parser@0.12.2: {} oniguruma-to-es@4.3.6: @@ -4417,12 +3903,6 @@ snapshots: dependencies: entities: 6.0.1 - path-is-inside@1.0.2: {} - - path-key@3.1.1: {} - - path-to-regexp@3.3.0: {} - picocolors@1.1.1: {} picomatch@4.0.7: {} @@ -4435,15 +3915,6 @@ snapshots: property-information@7.2.0: {} - range-parser@1.2.0: {} - - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-dom@19.3.0(react@19.3.0): dependencies: react: 19.3.0 @@ -4519,15 +3990,6 @@ snapshots: dependencies: regex-utilities: 2.3.0 - registry-auth-token@3.3.2: - dependencies: - rc: 1.2.8 - safe-buffer: 5.2.1 - - registry-url@3.1.0: - dependencies: - rc: 1.2.8 - rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.5 @@ -4592,10 +4054,6 @@ snapshots: transitivePeerDependencies: - supports-color - require-from-string@2.0.2: {} - - safe-buffer@5.2.1: {} - scheduler@0.28.0: {} scroll-into-view-if-needed@3.1.0: @@ -4605,32 +4063,6 @@ snapshots: semver@7.8.5: optional: true - serve-handler@6.1.7: - dependencies: - bytes: 3.0.0 - content-disposition: 0.5.2 - mime-types: 2.1.18 - minimatch: 3.1.5 - path-is-inside: 1.0.2 - path-to-regexp: 3.3.0 - range-parser: 1.2.0 - - serve@14.2.6: - dependencies: - '@zeit/schemas': 2.36.0 - ajv: 8.18.0 - arg: 5.0.2 - boxen: 7.0.0 - chalk: 5.0.1 - chalk-template: 0.4.0 - clipboardy: 3.0.0 - compression: 1.8.2 - is-port-reachable: 4.0.0 - serve-handler: 6.1.7 - update-check: 1.5.4 - transitivePeerDependencies: - - supports-color - sharp@0.35.5(@types/node@26.6.4): dependencies: '@img/colour': 1.1.0 @@ -4665,12 +4097,6 @@ snapshots: '@types/node': 26.6.4 optional: true - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - shiki@4.5.0: dependencies: '@shikijs/core': 4.5.0 @@ -4682,43 +4108,17 @@ snapshots: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.5 - signal-exit@3.0.7: {} - source-map-js@1.2.2: {} source-map@0.7.6: {} space-separated-tokens@2.0.2: {} - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - strip-final-newline@2.0.0: {} - - strip-json-comments@2.0.1: {} - style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -4732,10 +4132,6 @@ snapshots: client-only: 0.0.1 react: 19.3.0 - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - tailwindcss@4.3.3: {} tapable@2.3.3: {} @@ -4753,8 +4149,6 @@ snapshots: tslib@2.8.1: {} - type-fest@2.19.0: {} - typescript@6.0.3: {} undici-types@8.9.0: {} @@ -4801,11 +4195,6 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - update-check@1.5.4: - dependencies: - registry-auth-token: 3.3.2 - registry-url: 3.1.0 - use-callback-ref@1.3.3(@types/react@19.3.0)(react@19.3.0): dependencies: react: 19.3.0 @@ -4821,8 +4210,6 @@ snapshots: optionalDependencies: '@types/react': 19.3.0 - vary@1.1.2: {} - vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -4840,20 +4227,6 @@ snapshots: web-namespaces@2.0.1: {} - which@2.0.2: - dependencies: - isexe: 2.0.0 - - widest-line@4.0.1: - dependencies: - string-width: 5.1.2 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - yaml@2.9.1: {} yuku-analyzer@0.10.2: diff --git a/website/pnpm-workspace.yaml b/website/pnpm-workspace.yaml index 67632bb3..ee4cbb2d 100644 --- a/website/pnpm-workspace.yaml +++ b/website/pnpm-workspace.yaml @@ -12,17 +12,7 @@ allowBuilds: overrides: postcss: ^8.5.28 sharp: ^0.35.3 - # GHSA-6j4f-fj2g-mc7p, GHSA-qhr7-859c-m2p7, GHSA-q2hr-2g5m-vwhr — brace-expansion - # DoS via nested or comma-heavy brace groups. Dev-only (serve > serve-handler > - # minimatch); never in the static site. - brace-expansion@<5.0.12: '>=5.0.12 <6' - # GHSA-hrr3-gc8f-f4qj — fast-uri. Dev-only (serve > ajv). - fast-uri@<3.1.8: ^3.1.8 # GHSA-2v37-7h3g-55p8 / CVE-2026-67213 — nanoid infinite loop on size=0. Build-time # only (transitive via postcss); this is a statically exported site with no server # runtime. Remove once transitive nanoid is >=3.3.17 (check: pnpm why nanoid). nanoid@<3.3.17: '>=3.3.17 <4' - # GHSA-vc2v-76pw-4v95 — compression memory-leak DoS on premature response close. - # Dev-only (serve > compression); never in the static site. Remove once - # transitive compression is >=1.8.2 (check: pnpm why compression). - compression@<1.8.2: '>=1.8.2 <2' diff --git a/website/scripts/preview.mjs b/website/scripts/preview.mjs new file mode 100644 index 00000000..21e7cb5a --- /dev/null +++ b/website/scripts/preview.mjs @@ -0,0 +1,135 @@ +#!/usr/bin/env node +// Serve the built static site in `out/` for a local look at what Cloudflare +// Pages will publish. Backs `pnpm start`. +// +// Deliberately dependency-free. This replaced `serve`, whose transitive tree +// kept publishing advisories into a dev-only tool: four of them (brace-expansion +// x3, fast-uri) turned the Security workflow red on `main` and had to be pinned +// by hand, and none of that code ever reached the published site. A preview +// server for a static export is small enough to own outright. +// +// Routing approximates Cloudflare Pages for a Next static export: +// / -> out/index.html +// /docs/cli -> out/docs/cli.html (or out/docs/cli/index.html) +// /docs/cli/ -> the same, so links work with or without a trailing slash +// anything else -> out/404.html with status 404 +// +// `out/_redirects` is a Cloudflare feature and is not applied here, same as +// before: the preview serves the exported files as they are. + +import { createServer } from 'node:http'; +import { createReadStream, existsSync, statSync } from 'node:fs'; +import { dirname, extname, join, normalize, resolve, sep } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const websiteRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const outDir = join(websiteRoot, 'out'); + +const CONTENT_TYPES = { + '.html': 'text/html; charset=utf-8', + '.js': 'text/javascript; charset=utf-8', + '.mjs': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.json': 'application/json; charset=utf-8', + '.map': 'application/json; charset=utf-8', + '.txt': 'text/plain; charset=utf-8', + '.xml': 'application/xml; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.gif': 'image/gif', + '.webp': 'image/webp', + '.avif': 'image/avif', + '.ico': 'image/x-icon', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', + '.otf': 'font/otf', +}; + +export function contentTypeFor(filePath) { + return CONTENT_TYPES[extname(filePath).toLowerCase()] ?? 'application/octet-stream'; +} + +/** + * Resolves a request path to a file inside `root`, or null when nothing matches. + * + * Escaping `root` is never a match: the decoded path is normalized and the + * result is checked to still sit under `root`, so `..` segments and an + * encoded `%2e%2e` both fall through to the 404 page. + */ +export function resolveStaticFile(root, requestPath) { + let decoded; + try { + decoded = decodeURIComponent(requestPath); + } catch { + return null; // Malformed percent-encoding. + } + if (decoded.includes('\0')) return null; + + // Normalize in POSIX terms first: a request path is URL-shaped, so its + // separator is `/` on every platform. + // Trailing separators are dropped so a directory request and the `.html` + // fallback for it build the same base path. + const relativePath = normalize(decoded.replace(/^\/+/, '').split('/').join(sep)).replace( + new RegExp(`\\${sep}+$`), + '' + ); + const base = relativePath === '' || relativePath === '.' ? root : join(root, relativePath); + if (base !== root && !base.startsWith(root + sep)) return null; + + // Next's export writes a route as `docs/cli.html`, not `docs/cli/index.html`, + // so both spellings have to resolve either way the link was written. + const candidates = decoded.endsWith('/') + ? [join(base, 'index.html'), `${base}.html`] + : [base, join(base, 'index.html'), `${base}.html`]; + + for (const candidate of candidates) { + if (candidate !== root && !candidate.startsWith(root + sep)) continue; + if (existsSync(candidate) && statSync(candidate).isFile()) return candidate; + } + return null; +} + +/** The preview server for a built export directory. Exported for tests. */ +export function createPreviewServer(root) { + return createServer((request, response) => { + const requestPath = new URL(request.url ?? '/', 'http://localhost').pathname; + const filePath = resolveStaticFile(root, requestPath); + + if (!filePath) { + const notFound = join(root, '404.html'); + if (existsSync(notFound)) { + response.writeHead(404, { 'content-type': 'text/html; charset=utf-8' }); + createReadStream(notFound).pipe(response); + return; + } + response.writeHead(404, { 'content-type': 'text/plain; charset=utf-8' }); + response.end('Not found\n'); + return; + } + + response.writeHead(200, { 'content-type': contentTypeFor(filePath) }); + createReadStream(filePath).pipe(response); + }); +} + +function main() { + if (!existsSync(outDir)) { + console.error(`No build found at ${outDir}. Run "pnpm build" first.`); + process.exit(1); + } + + const port = Number(process.env.PORT ?? 3000); + const host = process.env.HOST ?? 'localhost'; + + createPreviewServer(outDir).listen(port, host, () => { + console.log(`Serving ${outDir} at http://${host}:${port}`); + }); +} + +// Importing this file for its helpers must not start a server. +if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) { + main(); +}