From d50b3650d6a5602e77e6f9ace9861f6363d67381 Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Sun, 1 Nov 2026 23:59:43 -0600 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=94=92=20Patch=20transitive=20glob=20?= =?UTF-8?q?and=20follow-redirects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve affected glob releases to 14.0.2 and follow-redirects to 1.16.2 across the workspace. These overrides address the remaining glob and redirect advisories while preserving older minimatch APIs still required by upstream tooling. --- pnpm-lock.yaml | 178 ++++---------------------------------------- pnpm-workspace.yaml | 4 + 2 files changed, 17 insertions(+), 165 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a73d6c1..eca82c7a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,8 @@ overrides: zod@>=4.0.0 <4.6.6: 4.6.7 body-parser@>=2.0.0 <2.4.0: 2.4.0 fast-uri@>=3.0.0 <3.1.9: 3.1.9 + glob@>=9.0.0 <14.0.2: 14.0.2 + follow-redirects@>=1.16.0 <1.16.2: 1.16.2 importers: @@ -2139,10 +2141,6 @@ packages: '@types/node': optional: true - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - '@isaacs/nonrecursive-glob-match@1.0.3': resolution: {integrity: sha512-SI7kyKHq4XXDRozw+r9GlRZkia1ZkFrpLW98r2IC2Bpr8YkFGE3fvjzmxCG0x78A++69HmDNrDvSMYBZmHGpKA==} @@ -2474,10 +2472,6 @@ packages: cpu: [x64] os: [win32] - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@playwright/test@1.64.1': resolution: {integrity: sha512-IaYGml/nSOiZWROfPtuIL7neuu9qDOZ3TNvNljUIrhDMxiGTgPjzj/g9p9RxpS2tSepPDRY+UwAaEeiKtUvIAg==} engines: {node: '>=20'} @@ -4342,9 +4336,6 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - editions@1.3.4: resolution: {integrity: sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==} engines: {node: '>=0.8'} @@ -4470,9 +4461,6 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - emojis-list@3.0.0: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} @@ -4810,8 +4798,8 @@ packages: resolution: {integrity: sha512-kWyh8ADvHBFz6ua5xYOPnUroZTT/bwWfrCeL0Wj1dzG4/YOmOcfJ99W8dOVyyynJN35rZ9aCOtHChqQovV7yog==} engines: {node: '>=6'} - follow-redirects@1.16.1: - resolution: {integrity: sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==} + follow-redirects@1.16.2: + resolution: {integrity: sha512-/kVP2U8z596Ia0gwFrwyIeo74ta5aVmSeU1FfAUMAmcEP5EP7zydmag3SLiN64NaRKef3lxsq7i/g7ltJQDGow==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -4823,10 +4811,6 @@ packages: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - form-data@4.0.6: resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} @@ -4963,15 +4947,6 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} - engines: {node: 18 || 20 || >=22} - glob@14.0.2: resolution: {integrity: sha512-vfMgTR2djMHPUchsJMLpZlYQvtjVL+mHpEr5wPpoCbQs2vr/FBNK+3Gnl9Ara1DSs4Aeg4eG9HiADb5/rkY/ZA==} engines: {node: 18 || 20 || >=22} @@ -4980,11 +4955,6 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - glob@9.3.5: - resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} - engines: {node: '>=16 || 14 >=14.17'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - global-modules@1.0.0: resolution: {integrity: sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==} engines: {node: '>=0.10.0'} @@ -5487,9 +5457,6 @@ packages: resolution: {integrity: sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==} engines: {node: '>=0.12'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} @@ -5997,14 +5964,6 @@ packages: resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@8.0.7: - resolution: {integrity: sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -6067,10 +6026,6 @@ packages: uglify-js: optional: true - minipass@4.2.8: - resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} - engines: {node: '>=8'} - minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -6368,14 +6323,6 @@ packages: resolution: {integrity: sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==} engines: {node: '>=0.10.0'} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} - engines: {node: 18 || 20 || >=22} - path-scurry@3.0.0: resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} engines: {node: 18 || 20 || >=22} @@ -7114,10 +7061,6 @@ packages: 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'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} @@ -7797,14 +7740,6 @@ packages: peerDependencies: react: '>=16.8.0' - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} @@ -7937,7 +7872,7 @@ snapshots: chokidar: 5.0.0 commander: 14.0.3 convert-source-map: 2.0.0 - glob: 13.0.6 + glob: 14.0.2 slash: 5.1.0 '@babel/code-frame@7.29.7': @@ -10060,15 +9995,6 @@ snapshots: optionalDependencies: '@types/node': 26.6.5 - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - '@isaacs/nonrecursive-glob-match@1.0.3': {} '@jest/schemas@29.6.3': @@ -10077,7 +10003,7 @@ snapshots: '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@7.0.2)(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.1))': dependencies: - glob: 13.0.6 + glob: 14.0.2 react-docgen-typescript: 2.4.0(typescript@7.0.2) vite: 8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.1) optionalDependencies: @@ -10324,9 +10250,6 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.21.2': optional: true - '@pkgjs/parseargs@0.11.0': - optional: true - '@playwright/test@1.64.1': dependencies: playwright: 1.64.1 @@ -11238,7 +11161,7 @@ snapshots: babel-plugin-module-resolver@5.0.3: dependencies: find-babel-config: 2.1.2 - glob: 9.3.5 + glob: 14.0.2 pkg-up: 3.1.0 reselect: 4.1.8 resolve: 1.22.13 @@ -12146,8 +12069,6 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - eastasianwidth@0.2.0: {} - editions@1.3.4: {} editions@2.3.1: @@ -12335,7 +12256,7 @@ snapshots: fs-tree-diff: 2.0.1 get-caller-file: 2.0.5 git-repo-info: 2.1.1 - glob: 13.0.6 + glob: 14.0.2 heimdalljs: 0.2.6 heimdalljs-fs-monitor: 1.1.2 heimdalljs-graph: 1.0.0 @@ -12531,8 +12452,6 @@ snapshots: emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - emojis-list@3.0.0: {} empathic@2.1.0: {} @@ -13012,17 +12931,12 @@ snapshots: flru@1.0.2: {} - follow-redirects@1.16.1: {} + follow-redirects@1.16.2: {} for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - form-data@4.0.6: dependencies: asynckit: 0.4.0 @@ -13202,21 +13116,6 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@13.0.6: - dependencies: - minimatch: 10.2.6 - minipass: 7.1.3 - path-scurry: 2.0.2 - glob@14.0.2: dependencies: minimatch: 11.0.4 @@ -13232,13 +13131,6 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@9.3.5: - dependencies: - fs.realpath: 1.0.0 - minimatch: 8.0.7 - minipass: 4.2.8 - path-scurry: 1.11.1 - global-modules@1.0.0: dependencies: global-prefix: 1.0.2 @@ -13431,7 +13323,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.16.1 + follow-redirects: 1.16.2 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -13730,12 +13622,6 @@ snapshots: editions: 2.3.1 textextensions: 2.6.0 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - jake@10.9.4: dependencies: async: 3.2.6 @@ -14220,14 +14106,6 @@ snapshots: dependencies: brace-expansion: 2.1.7 - minimatch@8.0.7: - dependencies: - brace-expansion: 2.1.7 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.1.7 - minimist-options@4.1.0: dependencies: arrify: 1.0.1 @@ -14247,8 +14125,6 @@ snapshots: esbuild: 0.28.2 postcss: 8.5.31 - minipass@4.2.8: {} - minipass@7.1.3: {} mkdirp-classic@0.5.3: @@ -14554,16 +14430,6 @@ snapshots: dependencies: path-root-regex: 0.1.2 - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - - path-scurry@2.0.2: - dependencies: - lru-cache: 11.5.3 - minipass: 7.1.3 - path-scurry@3.0.0: dependencies: lru-cache: 11.5.3 @@ -14989,11 +14855,11 @@ snapshots: rimraf@5.0.10: dependencies: - glob: 10.5.0 + glob: 14.0.2 rimraf@6.1.3: dependencies: - glob: 13.0.6 + glob: 14.0.2 package-json-from-dist: 1.0.1 rolldown@1.2.15: @@ -15414,12 +15280,6 @@ snapshots: 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 - string-width@7.2.0: dependencies: emoji-regex: 10.6.0 @@ -15689,7 +15549,7 @@ snapshots: consolidate: 1.0.4(@babel/core@7.29.7)(ejs@3.1.10)(handlebars@4.7.10)(lodash@4.18.1)(mustache@4.2.0)(react-dom@19.3.1(react@19.3.1))(react@19.3.1)(underscore@1.13.8) execa: 9.6.1 express: 5.3.0 - glob: 13.0.6 + glob: 14.0.2 httpxy: 0.5.5 js-yaml: 5.5.0 lodash: 4.18.1 @@ -16279,18 +16139,6 @@ snapshots: regexparam: 3.0.0 use-sync-external-store: 1.7.0(react@19.3.1) - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd1e2c62..d43f680d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -15,6 +15,10 @@ overrides: zod@>=4.0.0 <4.6.6: 4.6.7 body-parser@>=2.0.0 <2.4.0: 2.4.0 fast-uri@>=3.0.0 <3.1.9: 3.1.9 + # Stop affected glob walks when their AbortSignal is cancelled. + glob@>=9.0.0 <14.0.2: 14.0.2 + # Fix credential forwarding in http-proxy's redirect dependency. + follow-redirects@>=1.16.0 <1.16.2: 1.16.2 minimumReleaseAge: 10080 minimumReleaseAgeStrict: true From ad8e22b990f760c08262f6614f4458db2f246a88 Mon Sep 17 00:00:00 2001 From: Robert DeLuca Date: Mon, 2 Nov 2026 00:17:31 -0600 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=92=20Remove=20minimatch=20from=20?= =?UTF-8?q?SDK=20static=20servers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace serve-handler with serve-static in the published Storybook and static-site plugins and the Vitest browser fixture. Verify real HTTP behavior and screenshot workflows, and leave the remaining incompatible minimatch consumers confined to the Ember test app. --- clients/static-site/package.json | 2 +- clients/static-site/src/server.js | 16 +-- clients/static-site/tests/server.test.js | 36 +------ clients/storybook/package.json | 2 +- clients/storybook/src/server.js | 16 +-- clients/storybook/tests/server.test.js | 40 +------- clients/vitest/package.json | 2 +- clients/vitest/vitest.e2e.config.js | 13 ++- pnpm-lock.yaml | 73 ++------------ tests/helpers/static-server-contract.js | 123 +++++++++++++++++++++++ 10 files changed, 170 insertions(+), 153 deletions(-) create mode 100644 tests/helpers/static-server-contract.js diff --git a/clients/static-site/package.json b/clients/static-site/package.json index 54f8e3d7..6da993c1 100644 --- a/clients/static-site/package.json +++ b/clients/static-site/package.json @@ -69,7 +69,7 @@ "dependencies": { "fast-xml-parser": "^5.11.2", "playwright-core": "^1.64.1", - "serve-handler": "^6.1.7", + "serve-static": "^2.2.1", "zod": "^4.6.7" }, "devDependencies": { diff --git a/clients/static-site/src/server.js b/clients/static-site/src/server.js index 189573c4..e2b6af97 100644 --- a/clients/static-site/src/server.js +++ b/clients/static-site/src/server.js @@ -1,11 +1,11 @@ /** - * HTTP server for serving static site files using serve-handler + * HTTP server for serving static site files * Needed because file:// protocol doesn't load assets properly */ import { createServer } from 'node:http'; import { resolve } from 'node:path'; -import handler from 'serve-handler'; +import serveStatic from 'serve-static'; /** * Start an HTTP server to serve static files @@ -16,12 +16,16 @@ import handler from 'serve-handler'; export async function startStaticServer(rootDir, port = 0) { let absoluteRoot = resolve(rootDir); let sockets = new Set(); + let serve = serveStatic(absoluteRoot, { fallthrough: false }); let server = createServer((req, res) => { - return handler(req, res, { - public: absoluteRoot, - cleanUrls: false, - trailingSlash: false, + serve(req, res, error => { + if (res.headersSent) { + res.destroy(); + return; + } + res.writeHead(error?.statusCode || 404, error?.headers); + res.end(); }); }); diff --git a/clients/static-site/tests/server.test.js b/clients/static-site/tests/server.test.js index b2a14b5e..9c0d748e 100644 --- a/clients/static-site/tests/server.test.js +++ b/clients/static-site/tests/server.test.js @@ -1,36 +1,4 @@ -import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { afterEach, beforeEach, describe, it } from 'node:test'; - +import { testStaticServer } from '../../../tests/helpers/static-server-contract.js'; import { startStaticServer, stopStaticServer } from '../src/server.js'; -describe('static-site server', () => { - let workspace; - let serverInfo; - - beforeEach(async () => { - workspace = await mkdtemp(join(tmpdir(), 'vizzly-static-server-')); - await mkdir(join(workspace, 'site')); - await writeFile(join(workspace, 'site', 'index.html'), '

Hello

'); - }); - - afterEach(async () => { - await stopStaticServer(serverInfo); - await rm(workspace, { recursive: true, force: true }); - }); - - it('serves files and stops accepting requests after shutdown', async () => { - serverInfo = await startStaticServer(join(workspace, 'site')); - - let response = await fetch(`${serverInfo.url}/index.html`); - assert.equal(response.status, 200); - assert.match(await response.text(), /Hello/); - - await stopStaticServer(serverInfo); - serverInfo = null; - - await assert.rejects(fetch(response.url)); - }); -}); +testStaticServer('static-site server', { startStaticServer, stopStaticServer }); diff --git a/clients/storybook/package.json b/clients/storybook/package.json index 7ab71118..c762c96e 100644 --- a/clients/storybook/package.json +++ b/clients/storybook/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "playwright-core": "^1.64.1", - "serve-handler": "^6.1.7" + "serve-static": "^2.2.1" }, "devDependencies": { "@babel/cli": "^8.0.6", diff --git a/clients/storybook/src/server.js b/clients/storybook/src/server.js index af31f1e1..ef4a7e9f 100644 --- a/clients/storybook/src/server.js +++ b/clients/storybook/src/server.js @@ -1,11 +1,11 @@ /** - * HTTP server for serving static Storybook files using serve-handler + * HTTP server for serving static Storybook files * Needed because file:// protocol doesn't load assets properly */ import { createServer } from 'node:http'; import { resolve } from 'node:path'; -import handler from 'serve-handler'; +import serveStatic from 'serve-static'; /** * Start an HTTP server to serve static files @@ -16,12 +16,16 @@ import handler from 'serve-handler'; export async function startStaticServer(rootDir, port = 0) { let absoluteRoot = resolve(rootDir); let sockets = new Set(); + let serve = serveStatic(absoluteRoot, { fallthrough: false }); let server = createServer((req, res) => { - return handler(req, res, { - public: absoluteRoot, - cleanUrls: false, - trailingSlash: false, + serve(req, res, error => { + if (res.headersSent) { + res.destroy(); + return; + } + res.writeHead(error?.statusCode || 404, error?.headers); + res.end(); }); }); diff --git a/clients/storybook/tests/server.test.js b/clients/storybook/tests/server.test.js index cb840654..944f35b4 100644 --- a/clients/storybook/tests/server.test.js +++ b/clients/storybook/tests/server.test.js @@ -1,39 +1,7 @@ -import assert from 'node:assert/strict'; -import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { tmpdir } from 'node:os'; -import { join } from 'node:path'; -import { afterEach, beforeEach, describe, it } from 'node:test'; - +import { testStaticServer } from '../../../tests/helpers/static-server-contract.js'; import { startStaticServer, stopStaticServer } from '../src/server.js'; -describe('storybook static server', () => { - let workspace; - let serverInfo; - - beforeEach(async () => { - workspace = await mkdtemp(join(tmpdir(), 'vizzly-storybook-server-')); - await mkdir(join(workspace, 'dist')); - await writeFile( - join(workspace, 'dist', 'index.html'), - '

Storybook

' - ); - }); - - afterEach(async () => { - await stopStaticServer(serverInfo); - await rm(workspace, { recursive: true, force: true }); - }); - - it('serves files and stops accepting requests after shutdown', async () => { - serverInfo = await startStaticServer(join(workspace, 'dist')); - - let response = await fetch(`${serverInfo.url}/index.html`); - assert.equal(response.status, 200); - assert.match(await response.text(), /Storybook/); - - await stopStaticServer(serverInfo); - serverInfo = null; - - await assert.rejects(fetch(response.url)); - }); +testStaticServer('storybook static server', { + startStaticServer, + stopStaticServer, }); diff --git a/clients/vitest/package.json b/clients/vitest/package.json index 686c0e02..68962ec4 100644 --- a/clients/vitest/package.json +++ b/clients/vitest/package.json @@ -69,7 +69,7 @@ "@vitest/browser-playwright": "^5.0.5", "@vizzly-testing/cli": "workspace:*", "playwright": "^1.64.1", - "serve-handler": "^6.1.7", + "serve-static": "^2.2.1", "vitest": "^5.0.5" } } diff --git a/clients/vitest/vitest.e2e.config.js b/clients/vitest/vitest.e2e.config.js index e2131305..18a79599 100644 --- a/clients/vitest/vitest.e2e.config.js +++ b/clients/vitest/vitest.e2e.config.js @@ -2,7 +2,7 @@ import { existsSync } from 'node:fs'; import { createServer } from 'node:http'; import { resolve } from 'node:path'; import { playwright } from '@vitest/browser-playwright'; -import handler from 'serve-handler'; +import serveStatic from 'serve-static'; import { defineConfig } from 'vitest/config'; import { vizzlyPlugin } from './src/index.js'; @@ -15,10 +15,15 @@ if (!existsSync(resolve(testSitePath, 'index.html'))) { } // Start static server for test-site assets +let serve = serveStatic(testSitePath, { fallthrough: false }); let server = createServer((req, res) => { - return handler(req, res, { - public: testSitePath, - cleanUrls: false, + serve(req, res, error => { + if (res.headersSent) { + res.destroy(); + return; + } + res.writeHead(error?.statusCode || 404, error?.headers); + res.end(); }); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eca82c7a..82681d66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -282,9 +282,9 @@ importers: playwright-core: specifier: ^1.64.1 version: 1.64.1 - serve-handler: - specifier: ^6.1.7 - version: 6.1.7 + serve-static: + specifier: ^2.2.1 + version: 2.2.1 zod: specifier: ^4.6.7 version: 4.6.7 @@ -313,9 +313,9 @@ importers: playwright-core: specifier: ^1.64.1 version: 1.64.1 - serve-handler: - specifier: ^6.1.7 - version: 6.1.7 + serve-static: + specifier: ^2.2.1 + version: 2.2.1 devDependencies: '@babel/cli': specifier: ^8.0.6 @@ -386,9 +386,9 @@ importers: playwright: specifier: ^1.64.1 version: 1.64.1 - serve-handler: - specifier: ^6.1.7 - version: 6.1.7 + serve-static: + specifier: ^2.2.1 + version: 2.2.1 vitest: specifier: ^5.0.5 version: 5.0.5(@types/node@26.6.5)(@vitest/browser-playwright@5.0.5)(@vitest/ui@5.0.5)(jsdom@26.1.0(canvas@3.2.3))(vite@8.3.5(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(terser@5.51.2)(yaml@2.9.1)) @@ -3686,10 +3686,6 @@ packages: bytes@1.0.0: resolution: {integrity: sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==} - 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'} @@ -4068,10 +4064,6 @@ packages: whiskers: optional: true - content-disposition@0.5.2: - resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} - engines: {node: '>= 0.6'} - content-disposition@2.0.1: resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} engines: {node: '>=18'} @@ -5899,10 +5891,6 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - mime-db@1.33.0: - resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} - engines: {node: '>= 0.6'} - mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -5911,10 +5899,6 @@ packages: 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'} - mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} @@ -6298,9 +6282,6 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} - 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'} @@ -6327,9 +6308,6 @@ packages: resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} engines: {node: 18 || 20 || >=22} - path-to-regexp@3.3.0: - resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@8.4.2: resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} @@ -6541,10 +6519,6 @@ packages: engines: {node: '>=10'} hasBin: true - range-parser@1.2.0: - resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} - engines: {node: '>= 0.6'} - range-parser@1.3.0: resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} engines: {node: '>= 0.6'} @@ -6858,9 +6832,6 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} - serve-handler@6.1.7: - resolution: {integrity: sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==} - serve-static@2.2.1: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} @@ -11598,8 +11569,6 @@ snapshots: bytes@1.0.0: {} - bytes@3.0.0: {} - bytes@3.1.2: {} cacheable@2.5.0: @@ -11841,8 +11810,6 @@ snapshots: react-dom: 19.3.1(react@19.3.1) underscore: 1.13.8 - content-disposition@0.5.2: {} - content-disposition@2.0.1: {} content-tag@3.1.3: {} @@ -14054,16 +14021,10 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - mime-db@1.33.0: {} - mime-db@1.52.0: {} mime-db@1.54.0: {} - mime-types@2.1.18: - dependencies: - mime-db: 1.33.0 - mime-types@2.1.35: dependencies: mime-db: 1.52.0 @@ -14414,8 +14375,6 @@ snapshots: path-is-absolute@1.0.1: {} - path-is-inside@1.0.2: {} - path-key@3.1.1: {} path-key@4.0.0: {} @@ -14435,8 +14394,6 @@ snapshots: lru-cache: 11.5.3 minipass: 7.1.3 - path-to-regexp@3.3.0: {} - path-to-regexp@8.4.2: {} path-type@4.0.0: {} @@ -14635,8 +14592,6 @@ snapshots: node-watch: 0.7.3 tiny-glob: 0.2.9 - range-parser@1.2.0: {} - range-parser@1.3.0: {} raw-body@1.1.7: @@ -15018,16 +14973,6 @@ snapshots: transitivePeerDependencies: - supports-color - 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-static@2.2.1: dependencies: encodeurl: 2.0.0 diff --git a/tests/helpers/static-server-contract.js b/tests/helpers/static-server-contract.js new file mode 100644 index 00000000..6eeebf9f --- /dev/null +++ b/tests/helpers/static-server-contract.js @@ -0,0 +1,123 @@ +import assert from 'node:assert/strict'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, beforeEach, describe, it } from 'node:test'; + +export function testStaticServer( + name, + { startStaticServer, stopStaticServer } +) { + describe(name, () => { + let workspace; + let serverInfo; + let binary = Buffer.from([0, 1, 2, 3, 254, 255]); + + beforeEach(async () => { + workspace = await mkdtemp(join(tmpdir(), 'vizzly-static-server-')); + let root = join(workspace, 'site'); + await mkdir(join(root, 'assets'), { recursive: true }); + await mkdir(join(root, 'docs')); + await Promise.all([ + writeFile(join(root, 'index.html'), '

Home

'), + writeFile(join(root, 'iframe.html'), '

Story

'), + writeFile(join(root, 'index.json'), '{"entries":{}}'), + writeFile(join(root, 'docs', 'index.html'), '

Docs

'), + writeFile(join(root, 'about.html'), '

About

'), + writeFile(join(root, 'assets', 'app.js'), 'window.loaded = true;'), + writeFile(join(root, 'assets', 'space name.bin'), binary), + writeFile(join(root, '.env'), 'PRIVATE_TOKEN=secret'), + writeFile(join(workspace, 'private.txt'), 'outside-root-secret'), + ]); + serverInfo = await startStaticServer(root); + }); + + afterEach(async () => { + await stopStaticServer(serverInfo); + await rm(workspace, { recursive: true, force: true }); + }); + + it('serves index pages, Storybook routes and assets without stripping extensions', async () => { + for (let [path, content] of [ + ['/', '

Home

'], + ['/index.html', '

Home

'], + ['/iframe.html?id=button--primary&viewMode=story', '

Story

'], + ['/docs/', '

Docs

'], + ['/index.json', '{"entries":{}}'], + ]) { + let response = await fetch(`${serverInfo.url}${path}`, { + redirect: 'manual', + }); + assert.equal(response.status, 200, path); + assert.equal(await response.text(), content, path); + } + + let script = await fetch(`${serverInfo.url}/assets/app.js`); + assert.equal(script.status, 200); + assert.match(script.headers.get('content-type'), /javascript/); + assert.equal(await script.text(), 'window.loaded = true;'); + + let image = await fetch(`${serverInfo.url}/assets/space%20name.bin`); + assert.equal(image.status, 200); + assert.deepEqual(Buffer.from(await image.arrayBuffer()), binary); + }); + + it('supports HEAD, conditional requests and byte ranges', async () => { + let url = `${serverInfo.url}/assets/space%20name.bin`; + let head = await fetch(url, { method: 'HEAD' }); + assert.equal(head.status, 200); + assert.equal(head.headers.get('content-length'), String(binary.length)); + assert.equal(await head.text(), ''); + let etag = head.headers.get('etag'); + assert.ok(etag); + + let cached = await fetch(url, { + cache: 'no-cache', + headers: { 'if-none-match': etag }, + }); + assert.equal(cached.status, 304); + assert.equal(await cached.text(), ''); + + let range = await fetch(url, { headers: { range: 'bytes=1-3' } }); + assert.equal(range.status, 206); + assert.equal(range.headers.get('content-range'), 'bytes 1-3/6'); + assert.deepEqual( + Buffer.from(await range.arrayBuffer()), + binary.subarray(1, 4) + ); + + let invalid = await fetch(url, { headers: { range: 'bytes=100-200' } }); + assert.equal(invalid.status, 416); + assert.equal(invalid.headers.get('content-range'), 'bytes */6'); + }); + + it('rejects missing files, hidden files, traversal and unsupported methods', async () => { + for (let path of ['/missing.js', '/about', '/.env']) { + let response = await fetch(`${serverInfo.url}${path}`); + assert.equal(response.status, 404, path); + assert.doesNotMatch(await response.text(), /PRIVATE_TOKEN/); + } + + let traversal = await fetch(`${serverInfo.url}/%2e%2e%2fprivate.txt`); + assert.ok([403, 404].includes(traversal.status)); + assert.doesNotMatch(await traversal.text(), /outside-root-secret/); + + let malformed = await fetch(`${serverInfo.url}/%E0%A4%A`); + assert.equal(malformed.status, 400); + + let post = await fetch(`${serverInfo.url}/index.html`, { + method: 'POST', + }); + assert.equal(post.status, 405); + assert.equal(post.headers.get('allow'), 'GET, HEAD'); + }); + + it('stops accepting requests after shutdown', async () => { + let url = `${serverInfo.url}/index.html`; + assert.equal((await fetch(url)).status, 200); + await stopStaticServer(serverInfo); + serverInfo = null; + await assert.rejects(fetch(url)); + }); + }); +}