diff --git a/.gitattributes b/.gitattributes index 0362972a38dc..83c2c6e9e8fb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -32,9 +32,7 @@ test/js/bun/resolve/xml/xml-utf8-bom.xml -text test/js/bun/resolve/xml/xml-latin1.xml -text .vscode/launch.json linguist-generated -src/api/schema.d.ts linguist-generated fixture.*.c linguist-generated -src/api/schema.js linguist-generated *-fixture* linguist-generated src/jsc/bindings/ZigGeneratedCode.h linguist-generated src/jsc/bindings/ZigGeneratedCode.cpp linguist-generated diff --git a/.gitignore b/.gitignore index 92d2891e3eb5..87b01fe7fa5c 100644 --- a/.gitignore +++ b/.gitignore @@ -120,7 +120,6 @@ sign.json src/bake/generated.ts src/jsc/bindings-obj src/bun.js/debug-bindings-obj -src/fallback.version src/js/out/DebugPath.h src/js/out/functions* src/js/out/modules* diff --git a/Cargo.lock b/Cargo.lock index af3697250e50..3f386b432a26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1050,7 +1050,6 @@ dependencies = [ "bun_crash_handler", "bun_dispatch", "bun_highway", - "bun_io", "bun_options_types", "bun_paths", "bun_ptr", diff --git a/bun.lock b/bun.lock index b2ee070407a1..c2b42fb768a5 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,6 @@ "esbuild": "^0.21.5", "mitata": "^0.1.14", "oxlint": "1.70.0", - "peechy": "0.4.34", "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.3.0", "react": "^18.3.1", @@ -213,32 +212,22 @@ "camel-case": ["camel-case@3.0.0", "", { "dependencies": { "no-case": "^2.2.0", "upper-case": "^1.1.1" } }, "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w=="], - "capital-case": ["capital-case@1.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A=="], - - "change-case": ["change-case@4.1.2", "", { "dependencies": { "camel-case": "^4.1.2", "capital-case": "^1.0.4", "constant-case": "^3.0.4", "dot-case": "^3.0.4", "header-case": "^2.0.4", "no-case": "^3.0.4", "param-case": "^3.0.4", "pascal-case": "^3.1.2", "path-case": "^3.0.4", "sentence-case": "^3.0.4", "snake-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A=="], - "clean-css": ["clean-css@4.2.4", "", { "dependencies": { "source-map": "~0.6.0" } }, "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A=="], "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], "commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], - "constant-case": ["constant-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case": "^2.0.2" } }, "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ=="], - "deprecation": ["deprecation@2.3.1", "", {}, "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], - "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="], "esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="], "he": ["he@1.2.0", "", { "bin": { "he": "bin/he" } }, "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="], - "header-case": ["header-case@2.0.4", "", { "dependencies": { "capital-case": "^1.0.4", "tslib": "^2.0.3" } }, "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q=="], - "html-minifier": ["html-minifier@4.0.0", "", { "dependencies": { "camel-case": "^3.0.0", "clean-css": "^4.2.1", "commander": "^2.19.0", "he": "^1.2.0", "param-case": "^2.1.1", "relateurl": "^0.2.7", "uglify-js": "^3.5.1" }, "bin": { "html-minifier": "./cli.js" } }, "sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig=="], "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], @@ -291,7 +280,7 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], + "lower-case": ["lower-case@1.1.4", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="], "lru-cache": ["@wolfy1339/lru-cache@11.0.2-patch.1", "", {}, "sha512-BgYZfL2ADCXKOw2wJtkM3slhHotawWkgIRRxq4wEybnZQPjvAp71SPX35xepMykTw8gXlzWcWPTY31hlbnRsDA=="], @@ -301,7 +290,7 @@ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], - "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + "no-case": ["no-case@2.3.2", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="], "octokit": ["octokit@3.2.2", "", { "dependencies": { "@octokit/app": "^14.0.2", "@octokit/core": "^5.0.0", "@octokit/oauth-app": "^6.0.0", "@octokit/plugin-paginate-graphql": "^4.0.0", "@octokit/plugin-paginate-rest": "11.4.4-cjs.2", "@octokit/plugin-rest-endpoint-methods": "13.3.2-cjs.1", "@octokit/plugin-retry": "^6.0.0", "@octokit/plugin-throttling": "^8.0.0", "@octokit/request-error": "^5.0.0", "@octokit/types": "^13.0.0", "@octokit/webhooks": "^12.3.1" } }, "sha512-7Abo3nADdja8l/aglU6Y3lpnHSfv0tw7gFPiqzry/yCU+2gTAX7R1roJ8hJrxIK+S1j+7iqRJXtmuHJ/UDsBhQ=="], @@ -311,12 +300,6 @@ "param-case": ["param-case@2.1.1", "", { "dependencies": { "no-case": "^2.2.0" } }, "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w=="], - "pascal-case": ["pascal-case@3.1.2", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g=="], - - "path-case": ["path-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg=="], - - "peechy": ["peechy@0.4.34", "", { "dependencies": { "change-case": "^4.1.2" }, "bin": { "peechy": "cli.js" } }, "sha512-Cpke/cCqqZHhkyxz7mdqS8ZAGJFUi5icu3ZGqxm9GC7g2VrhH0tmjPhZoWHAN5ghw1m1wq5+2YvfbDSqgC4+Zg=="], - "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], "prettier-plugin-organize-imports": ["prettier-plugin-organize-imports@4.3.0", "", { "peerDependencies": { "prettier": ">=2.0", "typescript": ">=2.9", "vue-tsc": "^2.1.0 || 3" }, "optionalPeers": ["vue-tsc"] }, "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw=="], @@ -333,16 +316,10 @@ "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], - "sentence-case": ["sentence-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3", "upper-case-first": "^2.0.2" } }, "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg=="], - - "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], - "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "typescript": ["typescript@6.0.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], @@ -355,8 +332,6 @@ "upper-case": ["upper-case@1.1.3", "", {}, "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA=="], - "upper-case-first": ["upper-case-first@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg=="], - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "@octokit/app/@octokit/plugin-paginate-rest": ["@octokit/plugin-paginate-rest@9.2.2", "", { "dependencies": { "@octokit/types": "^12.6.0" }, "peerDependencies": { "@octokit/core": "5" } }, "sha512-u3KYkGF7GcZnSD/3UP0S7K5XUFT2FkOQdcfXZGZQPGv3lm4F2Xbf71lvjldr8c1H3nNbF+33cLEkWYbokGWqiQ=="], @@ -371,24 +346,10 @@ "bun-tracestrings/typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="], - "camel-case/no-case": ["no-case@2.3.2", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="], - - "change-case/camel-case": ["camel-case@4.1.2", "", { "dependencies": { "pascal-case": "^3.1.2", "tslib": "^2.0.3" } }, "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw=="], - - "change-case/param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="], - - "constant-case/upper-case": ["upper-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg=="], - - "param-case/no-case": ["no-case@2.3.2", "", { "dependencies": { "lower-case": "^1.1.1" } }, "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ=="], - "@octokit/app/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], "@octokit/auth-unauthenticated/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], "@octokit/plugin-throttling/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], - - "camel-case/no-case/lower-case": ["lower-case@1.1.4", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="], - - "param-case/no-case/lower-case": ["lower-case@1.1.4", "", {}, "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA=="], } } diff --git a/package.json b/package.json index 918d320de959..2cad3d1c6d8a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "esbuild": "^0.21.5", "mitata": "^0.1.14", "oxlint": "1.70.0", - "peechy": "0.4.34", "prettier": "^3.6.2", "prettier-plugin-organize-imports": "^4.3.0", "react": "^18.3.1", diff --git a/packages/bun-error/index.tsx b/packages/bun-error/index.tsx index e31a9c440f56..8a1aef4bf1be 100644 --- a/packages/bun-error/index.tsx +++ b/packages/bun-error/index.tsx @@ -1,28 +1,18 @@ import type { JSX } from "preact"; import { createContext, render } from "preact"; import { useCallback, useContext, useEffect, useRef, useState } from "preact/hooks"; -import type { - FallbackMessageContainer, - JSException, - JSException as JSExceptionType, - Location, - Message, - SourceLine, - StackFrame, - WebsocketMessageBuildFailure, -} from "../../src/api/schema"; -import { messagesToMarkdown, problemsToMarkdown, withBunInfo } from "./markdown"; +import { problemsToMarkdown, withBunInfo } from "./markdown"; +import { + StackFrameScope, + type FallbackMessageContainer, + type JSException as JSExceptionType, + type Location, + type Message, + type SourceLine, + type StackFrame, +} from "./schema"; import { fetchAllMappings, remapPosition, sourceMappings } from "./sourcemap"; -export enum StackFrameScope { - Eval = 1, - Module = 2, - Function = 3, - Global = 4, - Wasm = 5, - Constructor = 6, -} - export enum JSErrorCode { Error = 0, EvalError = 1, @@ -225,7 +215,7 @@ const srcFileURL = (filename: string, line?: number, column?: number): string => }; class FancyTypeError { - constructor(exception: JSException) { + constructor(exception: JSExceptionType) { this.runtimeType = exception.runtime_type || 0; this.runtimeTypeName = RuntimeType[this.runtimeType] || "undefined"; this.message = exception.message || ""; @@ -239,7 +229,7 @@ class FancyTypeError { runtimeTypeName: string; message: string; - normalize(exception: JSException) { + normalize(exception: JSExceptionType) { if (!exception.message) return; const i = exception.message.lastIndexOf(" is "); if (i === -1) return; @@ -317,7 +307,7 @@ const AsyncSourceLines = ({ }: { highlight: number; highlightColumnStart: number; - highlightColumnEnd: number; + highlightColumnEnd?: number; children?: any; buildURL: (line?: number, column?: number) => string; sourceLines: SourceLine[]; @@ -432,7 +422,7 @@ const SourceLines = ({ sourceLines: SourceLine[]; highlight: number; highlightColumnStart: number; - highlightColumnEnd: number; + highlightColumnEnd?: number; children?: any; buildURL: (line?: number, column?: number) => string; }) => { @@ -922,11 +912,7 @@ const ResolveError = ({ message }: { message: Message }) => { ); }; -const OverlayMessageContainer = ({ - problems, - reason, - isClient = false, -}: FallbackMessageContainer & { isClient: boolean }) => { +const OverlayMessageContainer = ({ problems, isClient = false }: FallbackMessageContainer & { isClient: boolean }) => { const errorCount = problems ? problems.exceptions.length + problems.build.errors : 0; return (
@@ -982,29 +968,6 @@ const Footer = ({ toMarkdown, data }) => (
); -const BuildFailureMessageContainer = ({ messages }: { messages: Message[] }) => { - return ( -
-
-
- -
-
- {messages.map((buildMessage, index) => { - if (buildMessage.on.build) { - return ; - } else if (buildMessage.on.resolve) { - return ; - } else { - throw new Error("Unknown build message type"); - } - })} -
-
-
- ); -}; export var thisCwd = ""; const ErrorGroupContext = createContext<{ cwd?: string }>({ cwd: undefined }); var reactRoot; @@ -1034,12 +997,6 @@ function renderWithFunc(func) { fallbackStyleSheet.remove(); shadowRoot.appendChild(fallbackStyleSheet); reactRoot.classList.add("BunErrorRoot--FullPage"); - - const page = document.querySelector("style[data-bun-error-page-style]"); - if (page) { - page.remove(); - shadowRoot.appendChild(page); - } } shadowRoot.appendChild(reactRoot); @@ -1055,9 +1012,6 @@ export function renderFallbackError(fallback: FallbackMessageContainer) { if (fallback && fallback.cwd) { thisCwd = fallback.cwd; } - // Not an error - if (fallback?.problems?.name === "JSDisabled") return; - return renderWithFunc(() => ( @@ -1152,8 +1106,6 @@ export function renderRuntimeError(error: Error) { errors: 0, msgs: [], }, - code: 0, - name: error.name, exceptions: [exception], }, }; @@ -1254,15 +1206,3 @@ export function dismissError() { while (pending.length > 0) pending.shift().stopped = true; } } - -export const renderBuildFailure = (failure: WebsocketMessageBuildFailure, cwd: string) => { - thisCwd = cwd; - renderWithFunc(() => ( - - - - )); -}; - -export const clearBuildFailure = dismissError; -globalThis.__BunClearBuildFailure = dismissError; diff --git a/packages/bun-error/markdown.ts b/packages/bun-error/markdown.ts index a87970d8a715..dc6b518a3c72 100644 --- a/packages/bun-error/markdown.ts +++ b/packages/bun-error/markdown.ts @@ -1,5 +1,5 @@ -import type { JSException, JSException as JSExceptionType, Message, Problems } from "../../src/api/schema"; -import { normalizedFilename, StackFrameIdentifier, StackFrameScope, thisCwd } from "./index"; +import { normalizedFilename, StackFrameIdentifier, thisCwd } from "./index"; +import { StackFrameScope, type JSException, type Message, type Problems } from "./schema"; export function problemsToMarkdown(problems: Problems) { var markdown = ""; @@ -21,7 +21,7 @@ export function messagesToMarkdown(messages: Message[]): string { .join("\n"); } -export function exceptionsToMarkdown(exceptions: JSExceptionType[]): string { +export function exceptionsToMarkdown(exceptions: JSException[]): string { return exceptions .map(exceptionToMarkdown) .map(a => a.trim()) @@ -57,17 +57,13 @@ function exceptionToMarkdown(exception: JSException): string { markdown += `**${name}**\n`; } - if (stack.frames.length > 0) { + if (stack && stack.frames.length > 0) { var frames = stack.frames; if (stack.source_lines.length > 0) { const { file: _file = "", function_name = "", - position: { line = -1, column_start: column = -1, column_stop: columnEnd = column } = { - line: -1, - column_start: -1, - column_stop: -1, - }, + position: { line = -1, column = -1 } = { line: -1, column: -1 }, scope = 0 as any, } = stack.frames[0]; const file = normalizedFilename(_file, thisCwd); @@ -127,10 +123,7 @@ function exceptionToMarkdown(exception: JSException): string { for (let frame of framesToDisplay) { const { function_name = "", - position: { line = -1, column_start: column = -1 } = { - line: -1, - column_start: -1, - }, + position: { line = -1, column = -1 } = { line: -1, column: -1 }, scope = 0 as any, } = frame; padding = Math.max( @@ -149,10 +142,7 @@ function exceptionToMarkdown(exception: JSException): string { const { file = "", function_name = "", - position: { line = -1, column_start: column = -1 } = { - line: -1, - column_start: -1, - }, + position: { line = -1, column = -1 } = { line: -1, column: -1 }, scope = 0 as any, } = frame; diff --git a/packages/bun-error/runtime-error.ts b/packages/bun-error/runtime-error.ts index 81041129d84c..87d1ce8d0d7c 100644 --- a/packages/bun-error/runtime-error.ts +++ b/packages/bun-error/runtime-error.ts @@ -1,6 +1,6 @@ // Based on https://github.com/stacktracejs/error-stack-parser/blob/master/error-stack-parser.js -import type { StackFramePosition, StackFrameScope, StackFrame as StackFrameType } from "../../src/api/schema"; +import type { StackFramePosition, StackFrameScope, StackFrame as StackFrameType } from "./schema"; export class StackFrame implements StackFrameType { function_name: string; @@ -23,6 +23,7 @@ export class StackFrame implements StackFrameType { this.scope = 3; this.position = { line: line, + column: column, source_offset: -1, line_start: -1, line_stop: -1, diff --git a/packages/bun-error/schema.ts b/packages/bun-error/schema.ts new file mode 100644 index 000000000000..5a00dc6eac0d --- /dev/null +++ b/packages/bun-error/schema.ts @@ -0,0 +1,108 @@ +// Shape of the JSON payload the `Bun.serve({ development: true })` error page +// embeds in ` - - - - - - - - diff --git a/src/fallback.ts b/src/fallback.ts deleted file mode 100644 index 0e0f735ceaa5..000000000000 --- a/src/fallback.ts +++ /dev/null @@ -1,24 +0,0 @@ -declare var document: any; -import { ByteBuffer } from "peechy"; -import { decodeFallbackMessageContainer, FallbackMessageContainer } from "./api/schema"; - -function getFallbackInfo(): FallbackMessageContainer { - const binary_string = globalThis.atob(document.getElementById("__bunfallback").textContent.trim()); - - var len = binary_string.length; - var bytes = new Uint8Array(len); - for (var i = 0; i < len; i++) { - bytes[i] = binary_string.charCodeAt(i); - } - - return decodeFallbackMessageContainer(new ByteBuffer(bytes)); -} - -globalThis.__BUN_DATA__ = getFallbackInfo(); -// It's probably better to remove potentially large content from the DOM when not in use -if ("requestIdleCallback" in globalThis) { - globalThis.requestIdleCallback(() => { - document.getElementById("__bunfallback")?.remove(); - document.getElementById("__bun_fallback_script")?.remove(); - }); -} diff --git a/src/js_parser/Cargo.toml b/src/js_parser/Cargo.toml index 627b5502d251..c4f93074f4be 100644 --- a/src/js_parser/Cargo.toml +++ b/src/js_parser/Cargo.toml @@ -30,7 +30,6 @@ bun_ptr.workspace = true bun_collections.workspace = true bun_crash_handler.workspace = true bun_highway.workspace = true -bun_io.workspace = true bun_ast.workspace = true bun_react_compiler = { path = "../react_compiler" } bun_options_types.workspace = true diff --git a/src/js_parser/parser.rs b/src/js_parser/parser.rs index e2d35c0b52ae..cf8248bf4b40 100644 --- a/src/js_parser/parser.rs +++ b/src/js_parser/parser.rs @@ -413,110 +413,6 @@ pub mod Runtime { pub(crate) use bun_ast::runtime::{ Imports, ReactCompilerMode, ReplaceableExport, ReplaceableExportMap, ServerComponentsMode, }; - - // ───────────────────────────── Runtime / Fallback ───────────────────── - - // ───────────────────────────── Fallback ─────────────────────────────── - // REFACTOR_BUN_AST: moved here from `bun_ast::runtime` — needs - // `bun_options_types::schema`, `bun_io`, `bun_base64`, all of which would - // form a cycle inside `bun_ast`. - - use bun_options_types::schema; - use bun_options_types::schema::api; - use core::fmt; - - pub struct Fallback; - - impl Fallback { - pub(crate) const HTML_BACKEND_TEMPLATE: &'static [u8] = - include_bytes!("../fallback-backend.html"); - - #[inline] - pub(crate) fn error_js() -> &'static [u8] { - bun_core::runtime_embed_file!(Codegen, "bun-error/index.js").as_bytes() - } - - #[inline] - pub(crate) fn error_css() -> &'static [u8] { - bun_core::runtime_embed_file!(Codegen, "bun-error/bun-error.css").as_bytes() - } - - #[inline] - pub(crate) fn fallback_decoder_js() -> &'static [u8] { - bun_core::runtime_embed_file!(Codegen, "fallback-decoder.js").as_bytes() - } - - pub fn render_backend( - msg: &api::FallbackMessageContainer, - writer: &mut impl bun_io::Write, - ) -> bun_io::Result<()> { - let blob = Base64FallbackMessage { msg }; - let bun_error_css = Self::error_css(); - let bun_error = Self::error_js(); - let bun_error_page_css: &[u8] = b""; - let fallback = Self::fallback_decoder_js(); - render_named_template( - writer, - Self::HTML_BACKEND_TEMPLATE, - &mut |w, name| match name { - b"blob" => w.write_fmt(format_args!("{}", blob)), - b"bun_error_css" => w.write_all(bun_error_css), - b"bun_error" => w.write_all(bun_error), - b"bun_error_page_css" => w.write_all(bun_error_page_css), - b"fallback" => w.write_all(fallback), - _ => Ok(()), - }, - ) - } - } - - /// Tiny substitutor for `{[name]s}` / `{[name]f}` named placeholders - /// (the only specifiers used in fallback.html / fallback-backend.html). - fn render_named_template( - writer: &mut W, - template: &'static [u8], - subst: &mut dyn FnMut(&mut W, &[u8]) -> bun_io::Result<()>, - ) -> bun_io::Result<()> { - let mut i = 0usize; - let mut last = 0usize; - let bytes = template; - while i + 1 < bytes.len() { - if bytes[i] == b'{' && bytes[i + 1] == b'[' { - let mut j = i + 2; - while j < bytes.len() && bytes[j] != b']' { - j += 1; - } - if j + 2 < bytes.len() && bytes[j] == b']' && bytes[j + 2] == b'}' { - writer.write_all(&bytes[last..i])?; - let name = &bytes[i + 2..j]; - subst(writer, name)?; - i = j + 3; - last = i; - continue; - } - } - i += 1; - } - writer.write_all(&bytes[last..]) - } - - pub(crate) struct Base64FallbackMessage<'a> { - pub msg: &'a api::FallbackMessageContainer, - } - - impl fmt::Display for Base64FallbackMessage<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut bb: Vec = Vec::new(); - let mut encoder = schema::Writer::new(&mut bb); - self.msg.encode(&mut encoder); // catch {} - // Standard alphabet, no '=' padding. - let enc = &bun_base64::zig_base64::STANDARD_NO_PAD.encoder; - let mut out = vec![0u8; enc.calc_size(bb.len())]; - let s = enc.encode(&mut out, &bb); // catch {} - // SAFETY: STANDARD_ALPHABET_CHARS is pure ASCII; encoder output contains only those bytes. - f.write_str(unsafe { core::str::from_utf8_unchecked(s) }) - } - } } pub type RuntimeFeatures = Runtime::Features; pub(crate) type RuntimeImports = Runtime::Imports; diff --git a/src/jsc/VirtualMachine.rs b/src/jsc/VirtualMachine.rs index 9012d07194bb..a33a8cc85096 100644 --- a/src/jsc/VirtualMachine.rs +++ b/src/jsc/VirtualMachine.rs @@ -58,11 +58,7 @@ pub use bun_core::STRING_ALLOCATION_LIMIT; pub(crate) type OnUnhandledRejection = fn(&mut VirtualMachine, &JSGlobalObject, JSValue); pub(crate) type MacroMap = bun_collections::ArrayHashMap; -/// `api::JsException` lives in -/// [`crate::schema_api`] (not `bun_options_types::schema::api`) because its -/// `stack: StackTrace` field transitively names `ZigStackFramePosition` from -/// this crate — see the `schema_api` module doc in lib.rs. -pub type ExceptionList = Vec; +pub type ExceptionList = Vec; // ────────────────────────────────────────────────────────────────────────── // VirtualMachine struct (file-level @This()) @@ -4842,9 +4838,8 @@ impl VirtualMachine { let _ = Self::print_stack_trace(writer, &zig_exception.stack, allow_ansi_color); } if let Some(list) = exception_list { - let top_level_dir = self.top_level_dir(); - let _ = - zig_exception.add_to_error_list(list, top_level_dir, Some(&self.origin)); + let origin = self.is_from_devserver.then_some(&self.origin); + zig_exception.add_to_error_list(list, self.top_level_dir(), origin); } holder.deinit(self); } @@ -5147,13 +5142,8 @@ impl VirtualMachine { let _ = (self.enable_source_code_preview, self.source_code_slice); } if let Some(list) = self.exception_list.take() { - let top_level_dir = this.top_level_dir(); - // OOM-only. - bun_core::handle_oom(exception.add_to_error_list( - list, - top_level_dir, - Some(&this.origin), - )); + let origin = this.is_from_devserver.then_some(&this.origin); + exception.add_to_error_list(list, this.top_level_dir(), origin); } } } @@ -5670,15 +5660,7 @@ impl VirtualMachine { last_pad = pad; splat_space(writer, pad)?; - let text = source.text.slice(); - let _trimmed = text - .trim_ascii_start() - .strip_prefix(b"\n") - .unwrap_or(text) - .trim_ascii_end(); - // Trim newlines on both sides, then trailing tab/space. - let trimmed = bun_core::trim(text, b"\n"); - let trimmed = bun_core::trim_right(trimmed, b"\t "); + let trimmed = source.trimmed_text(); let clamped = &trimmed[..trimmed.len().min(MAX_LINE_LENGTH)]; let hl = bun_core::fmt::fmt_javascript( @@ -5782,9 +5764,7 @@ impl VirtualMachine { } } - let text = source.text.slice(); - let trimmed = bun_core::trim(text, b"\n"); - let trimmed = bun_core::trim_right(trimmed, b"\t "); + let trimmed = source.trimmed_text(); if top_frame.is_none() || top_frame.unwrap().position.is_invalid() { did_print_name = true; diff --git a/src/jsc/ZigException.rs b/src/jsc/ZigException.rs index 25cf0b6ca14c..7852d6f5d457 100644 --- a/src/jsc/ZigException.rs +++ b/src/jsc/ZigException.rs @@ -2,7 +2,7 @@ use core::ffi::{c_int, c_void}; use core::mem::MaybeUninit; use core::ptr; -use crate::schema_api as api; +use crate::exception_list; use bun_core::String; use bun_url::URL as ZigURL; @@ -86,45 +86,24 @@ impl ZigException { pub(crate) fn add_to_error_list( &mut self, - error_list: &mut Vec, + error_list: &mut Vec, root_path: &[u8], origin: Option<&ZigURL>, - ) -> Result<(), bun_alloc::AllocError> { - let name_slice = self.name.to_utf8(); - let message_slice = self.message.to_utf8(); - - let name = name_slice.slice(); - let message = message_slice.slice(); - - let mut is_empty = true; - let mut api_exception = api::JsException { - // JSRuntimeType/JSErrorCode are transparent newtypes over u16/u8 - // (non-exhaustive enums). - runtime_type: self.runtime_type.0, - code: u16::from(self.r#type.0), - ..Default::default() - }; - - if !name.is_empty() { - api_exception.name = Box::<[u8]>::from(name); - is_empty = false; - } - - if !message.is_empty() { - api_exception.message = Box::<[u8]>::from(message); - is_empty = false; - } - - if self.stack.frames_len > 0 { - api_exception.stack = self.stack.to_api(root_path, origin)?; - is_empty = false; - } + ) { + let name = self.name.to_utf8(); + let message = self.message.to_utf8(); - if !is_empty { - error_list.push(api_exception); + if name.slice().is_empty() && message.slice().is_empty() && self.stack.frames_len == 0 { + return; } - Ok(()) + error_list.push(exception_list::JsException { + name: Box::from(name.slice()), + message: Box::from(message.slice()), + runtime_type: self.runtime_type, + code: self.r#type, + stack: self.stack.snapshot(root_path, origin), + }); } } diff --git a/src/jsc/ZigStackFrame.rs b/src/jsc/ZigStackFrame.rs index 704de2f690ff..4909e063006a 100644 --- a/src/jsc/ZigStackFrame.rs +++ b/src/jsc/ZigStackFrame.rs @@ -8,7 +8,7 @@ use bun_core::String as BunString; use bun_paths::strings; use bun_url::URL as ZigURL; -use crate::schema_api as api; +use crate::exception_list; use crate::{ZigStackFrameCode, ZigStackFramePosition}; /// Represents a single frame in a stack trace @@ -42,38 +42,27 @@ impl ZigStackFrame { self.source_url.deref(); } - pub(crate) fn to_api( + pub(crate) fn snapshot( &self, root_path: &[u8], origin: Option<&ZigURL<'_>>, - ) -> Result { - let mut frame: api::StackFrame = api::StackFrame::default(); - if !self.function_name.is_empty() { - let slicer = self.function_name.to_utf8(); - // TODO: Memory leak? `frame.function_name` may have just been allocated by this - // function, but it doesn't seem like we ever free it. Changing to `toUTF8Owned` would - // make the ownership clearer, but would also make the memory leak worse without an - // additional free. - frame.function_name = Box::<[u8]>::from(slicer.slice()); - } - + ) -> exception_list::StackFrame { + let mut file = Vec::::new(); if !self.source_url.is_empty() { - let mut buf = Vec::::new(); write!( - &mut buf, + &mut file, "{}", self.source_url_formatter(root_path, origin, true, false) ) .expect("Vec write is infallible"); - frame.file = buf.into_boxed_slice(); } - frame.position = self.position; - // api::StackFrameScope is a #[repr(transparent)] u8 newtype with the same - // discriminants as ZigStackFrameCode. - frame.scope = api::StackFrameScope(self.code_type.0); - - Ok(frame) + exception_list::StackFrame { + function_name: Box::from(self.function_name.to_utf8().slice()), + file: file.into_boxed_slice(), + position: self.position, + code_type: self.code_type, + } } pub const ZERO: ZigStackFrame = ZigStackFrame { @@ -175,7 +164,8 @@ impl<'a> fmt::Display for SourceURLFormatter<'a> { } } - if !source_slice.is_empty() + if !self.exclude_line_column + && !source_slice.is_empty() && (self.position.line.is_valid() || self.position.column.is_valid()) { if self.enable_color { diff --git a/src/jsc/ZigStackTrace.rs b/src/jsc/ZigStackTrace.rs index fba64163b548..aea7915a306f 100644 --- a/src/jsc/ZigStackTrace.rs +++ b/src/jsc/ZigStackTrace.rs @@ -1,7 +1,7 @@ use core::ptr; use core::ptr::NonNull; -use crate::schema_api as api; +use crate::exception_list; use bun_core::String as BunString; use bun_core::ZigStringSlice; use bun_url::URL as ZigURL; @@ -45,40 +45,32 @@ impl ZigStackTrace { } } - pub(crate) fn to_api( + /// Owned copy of the frames and source lines, with source URLs remapped + /// relative to `root_path` / `origin`. + pub(crate) fn snapshot( &self, root_path: &[u8], origin: Option<&ZigURL<'_>>, - ) -> Result { - let mut stack_trace = api::StackTrace::default(); - { - let mut source_lines_iter = self.source_line_iterator(); - - let source_line_len = source_lines_iter.get_length(); - - if source_line_len > 0 { - let n_lines = usize::try_from((source_lines_iter.i + 1).max(0)).expect("int cast"); - let mut source_lines: Vec = Vec::with_capacity(n_lines); - source_lines_iter = self.source_line_iterator(); - while let Some(source) = source_lines_iter.next() { - source_lines.push(api::SourceLine { line: source.line }); - } - stack_trace.source_lines = source_lines; - } - } - { - let frames = self.frames(); - if !frames.is_empty() { - let mut stack_frames: Vec = Vec::with_capacity(frames.len()); - - for frame in frames { - stack_frames.push(frame.to_api(root_path, origin)?); - } - stack_trace.frames = stack_frames; + ) -> exception_list::StackTrace { + let mut source_lines = Vec::new(); + let mut iter = self.source_line_iterator(); + if iter.get_length() > 0 { + while let Some(source) = iter.next() { + source_lines.push(exception_list::SourceLine { + line: source.line, + text: Box::from(source.trimmed_text()), + }); } } - Ok(stack_trace) + exception_list::StackTrace { + source_lines, + frames: self + .frames() + .iter() + .map(|frame| frame.snapshot(root_path, origin)) + .collect(), + } } pub fn frames(&self) -> &[ZigStackFrame] { @@ -131,6 +123,15 @@ pub(crate) struct SourceLine { pub text: ZigStringSlice, } +impl SourceLine { + /// The line as it should be displayed: surrounding newlines and trailing + /// indentation removed. + pub(crate) fn trimmed_text(&self) -> &[u8] { + let text = bun_core::trim(self.text.slice(), b"\n"); + bun_core::trim_right(text, b"\t ") + } +} + impl<'a> SourceLineIterator<'a> { fn get_length(&mut self) -> usize { let mut count: usize = 0; diff --git a/src/jsc/lib.rs b/src/jsc/lib.rs index e57d39e81823..a4ddb912d61b 100644 --- a/src/jsc/lib.rs +++ b/src/jsc/lib.rs @@ -93,73 +93,39 @@ pub mod zig_stack_frame_code; #[path = "ZigStackFramePosition.rs"] pub mod zig_stack_frame_position; -/// `bun.schema.api` types that reference `ZigStackFramePosition` (this crate) -/// and so cannot live in `bun_options_types::schema::api` without a dep cycle. -pub mod schema_api { - use crate::ZigStackFramePosition; +/// Owned snapshots of a [`ZigException`] (see `ZigException::add_to_error_list`), +/// collected into an [`ExceptionList`](virtual_machine::ExceptionList) so callers +/// such as the `Bun.serve` development error page can report errors after the +/// JSC exception itself is gone. +pub mod exception_list { + use crate::{JSErrorCode, JSRuntimeType, ZigStackFrameCode, ZigStackFramePosition}; - /// Non-exhaustive stack-frame scope tag. Newtype keeps any-u8 FFI-safe. - #[repr(transparent)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub struct StackFrameScope(pub(crate) u8); - - impl StackFrameScope { - pub(crate) const NONE: Self = Self(0); - } - - /// Line/column position of a stack frame (FFI layout shared with C++). - pub type StackFramePosition = ZigStackFramePosition; - - /// One captured stack frame: function name, file, position, and scope (FFI layout shared with C++). - #[derive(Clone)] pub struct StackFrame { - /// function_name - pub(crate) function_name: Box<[u8]>, - /// file + pub function_name: Box<[u8]>, + /// Source URL, remapped relative to the project root / origin. pub file: Box<[u8]>, - /// position - pub(crate) position: StackFramePosition, - /// scope - pub scope: StackFrameScope, - } - - impl Default for StackFrame { - fn default() -> Self { - Self { - function_name: Box::default(), - file: Box::default(), - position: StackFramePosition::INVALID, - scope: StackFrameScope::NONE, - } - } + pub position: ZigStackFramePosition, + pub code_type: ZigStackFrameCode, } - /// A line of source text with its line number, used for error previews. - #[derive(Clone, Default)] pub struct SourceLine { - /// line + /// 0-based. pub line: i32, + pub text: Box<[u8]>, } - /// A captured stack trace: frames plus the source lines used to render previews. - #[derive(Clone, Default)] + #[derive(Default)] pub struct StackTrace { - /// source_lines - pub(crate) source_lines: Vec, - /// frames - pub(crate) frames: Vec, + pub source_lines: Vec, + pub frames: Vec, } - /// Lives here (not `bun_options_types::schema::api`) because `stack`'s - /// [`StackTrace`] transitively names `ZigStackFramePosition` from this - /// crate; the `bun_options_types` copy omits `stack` to avoid the cycle. - #[derive(Clone, Default)] pub struct JsException { pub name: Box<[u8]>, pub message: Box<[u8]>, - pub runtime_type: u16, - pub code: u16, - pub(crate) stack: StackTrace, + pub runtime_type: JSRuntimeType, + pub code: JSErrorCode, + pub stack: StackTrace, } } #[path = "array_buffer.rs"] diff --git a/src/options_types/schema.rs b/src/options_types/schema.rs index 14c46ebbdd52..8ab5f8950398 100644 --- a/src/options_types/schema.rs +++ b/src/options_types/schema.rs @@ -1,57 +1,5 @@ //! Option/config structs shared by the CLI, bunfig, bundler and runtime -//! (`TransformOptions`, `BunInstall`, …) plus the wire types for the -//! `Bun.serve` development-mode error page (`FallbackMessageContainer`). - -/// Binary writer for the dev-mode error page payload -/// (`js_parser::runtime::Base64FallbackMessage::fmt`), decoded by -/// `src/fallback.ts` in the browser. -pub struct Writer<'a> { - writable: &'a mut Vec, -} - -impl<'a> Writer<'a> { - #[inline] - pub fn new(writable: &'a mut Vec) -> Self { - Self { writable } - } - #[inline] - pub(crate) fn write(&mut self, bytes: &[u8]) { - self.writable.extend_from_slice(bytes); - } - #[inline] - pub(crate) fn write_byte(&mut self, byte: u8) { - self.writable.push(byte); - } - /// Writes the int's native-endian raw bytes. - #[inline] - pub(crate) fn write_int(&mut self, int: I) { - // SAFETY: `int` is a live stack local, so `&raw const int` is valid for reads of - // `size_of::()` initialized bytes; `u8` has align 1 so the cast pointer is always - // aligned; the slice is consumed by `extend_from_slice` before `int` leaves scope. - let bytes = unsafe { - core::slice::from_raw_parts((&raw const int).cast::(), core::mem::size_of::()) - }; - self.writable.extend_from_slice(bytes); - } - #[inline] - pub(crate) fn write_field_id(&mut self, id: u8) { - self.write_byte(id); - } - #[inline] - pub(crate) fn write_enum(&mut self, val: E) { - self.write_int(val); - } - /// Length-prefixed byte slice. - #[inline] - pub(crate) fn write_array_u8(&mut self, slice: &[u8]) { - self.write_int(u32::try_from(slice.len()).unwrap()); - self.write(slice); - } - #[inline] - pub(crate) fn end_message(&mut self) { - self.write_byte(0); - } -} +//! (`TransformOptions`, `BunInstall`, …). pub mod api { /// Canonical definition lives in bun_dotenv (lower tier). @@ -429,152 +377,4 @@ pub mod api { Bundle = 0, External = 1, } - - // ── Fallback error-page wire types ────────────────────────────────────── - // Encoded by `Writer` above; the layout must stay in sync with the decoder - // in `src/api/schema.js` (bundled into `src/fallback.ts`). - - #[repr(u8)] - #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] - pub enum FallbackStep { - #[default] - _none = 0, - ssr_disabled = 1, - create_vm = 2, - configure_router = 3, - configure_defines = 4, - resolve_entry_point = 5, - load_entry_point = 6, - eval_entry_point = 7, - fetch_event_handler = 8, - } - - /// peechy `struct Router`. - #[derive(Clone, Debug, Default)] - pub struct Router { - pub(crate) routes: StringMap, - pub(crate) route: i32, - pub(crate) params: StringMap, - } - impl Router { - pub(crate) fn encode(&self, w: &mut super::Writer<'_>) { - self.routes.encode(w); - w.write_int(self.route); - self.params.encode(w); - } - } - - /// peechy `struct Problems`. - #[derive(Clone, Debug, Default)] - pub struct Problems { - pub code: u16, - pub name: Box<[u8]>, - pub exceptions: Vec, - pub build: Log, - } - impl Problems { - pub(crate) fn encode(&self, w: &mut super::Writer<'_>) { - w.write_int(self.code); - w.write_array_u8(&self.name); - w.write_int(u32::try_from(self.exceptions.len()).unwrap()); - for ex in &self.exceptions { - ex.encode(w); - } - self.build.encode(w); - } - } - - /// peechy `message JsException` (all fields optional). - #[derive(Clone, Debug, Default)] - pub struct JsException { - pub name: Option>, - pub message: Option>, - pub runtime_type: Option, - pub code: Option, - // `stack: ?StackTrace` — omitted until StackTrace is ported. - } - impl JsException { - pub(crate) fn encode(&self, w: &mut super::Writer<'_>) { - if let Some(ref v) = self.name { - w.write_field_id(1); - w.write_array_u8(v); - } - if let Some(ref v) = self.message { - w.write_field_id(2); - w.write_array_u8(v); - } - if let Some(v) = self.runtime_type { - w.write_field_id(3); - w.write_int(v); - } - if let Some(v) = self.code { - w.write_field_id(4); - w.write_int(v); - } - w.end_message(); - } - } - - impl StringMap { - pub(crate) fn encode(&self, w: &mut super::Writer<'_>) { - w.write_int(u32::try_from(self.keys.len()).unwrap()); - for k in &self.keys { - w.write_array_u8(k); - } - w.write_int(u32::try_from(self.values.len()).unwrap()); - for v in &self.values { - w.write_array_u8(v); - } - } - } - - /// peechy `struct Log` (minimal: `warnings`, `errors`, `msgs`). - #[derive(Copy, Clone, Debug, Default)] - pub struct Log { - pub warnings: u32, - pub errors: u32, - // `msgs: []Message` — omitted until `Message` is ported. - } - impl Log { - pub(crate) fn encode(self, w: &mut super::Writer<'_>) { - w.write_int(self.warnings); - w.write_int(self.errors); - w.write_int(0u32); // msgs.len - } - } - - /// peechy `message FallbackMessageContainer`. - #[derive(Clone, Debug, Default)] - pub struct FallbackMessageContainer { - pub message: Option>, - pub router: Option, - pub reason: Option, - pub problems: Option, - pub cwd: Option>, - } - impl FallbackMessageContainer { - pub fn encode(&self, w: &mut super::Writer<'_>) { - if let Some(ref message) = self.message { - w.write_field_id(1); - w.write_array_u8(message); - } - if let Some(ref router) = self.router { - w.write_field_id(2); - router.encode(w); - } - if let Some(reason) = self.reason { - w.write_field_id(3); - w.write_enum(reason); - } - if let Some(ref problems) = self.problems { - w.write_field_id(4); - problems.encode(w); - } - if let Some(ref cwd) = self.cwd { - w.write_field_id(5); - w.write_array_u8(cwd); - } - w.end_message(); - } - } } diff --git a/src/runtime/cli/init_command.rs b/src/runtime/cli/init_command.rs index 0830f5af7f4a..142e3db5f694 100644 --- a/src/runtime/cli/init_command.rs +++ b/src/runtime/cli/init_command.rs @@ -982,32 +982,6 @@ impl Assets { Self::create_full_with_contents(asset_name, contents, "", is_template, args) } - /// Substitutes named placeholders `{[key]s}` in `template` with the - /// corresponding value from `args`. - fn substitute(template: &[u8], args: &[(&[u8], &[u8])]) -> Vec { - let mut out = Vec::with_capacity(template.len()); - let mut i = 0; - 'outer: while i < template.len() { - if template[i] == b'{' && template.get(i + 1) == Some(&b'[') { - for &(key, value) in args { - // "{[" + key + "]s}" - let placeholder_len = 2 + key.len() + 3; - if i + placeholder_len <= template.len() - && &template[i + 2..i + 2 + key.len()] == key - && &template[i + 2 + key.len()..i + placeholder_len] == b"]s}" - { - out.extend_from_slice(value); - i += placeholder_len; - continue 'outer; - } - } - } - out.push(template[i]); - i += 1; - } - out - } - fn create_new(filename: &ZStr, contents: &[u8]) -> Result<(), Error> { // Create parent dirs then open. if let Some(dir) = bun_core::dirname(filename.as_bytes()) { @@ -1058,7 +1032,7 @@ impl Assets { // Write contents of known assets to the new file. Template assets get formatted. if is_template { - let buf = Self::substitute(asset, args); + let buf = bun_fmt::substitute_named(asset, args); file.write_all(&buf)?; } else { file.write_all(asset)?; @@ -1091,7 +1065,7 @@ impl Assets { )?; if is_template { - let buf = Self::substitute(contents, args); + let buf = bun_fmt::substitute_named(contents, args); file.write_all(&buf)?; } else { file.write_all(contents)?; diff --git a/src/runtime/error.rs b/src/runtime/error.rs index 37884590a6a4..c10ffc0125cc 100644 --- a/src/runtime/error.rs +++ b/src/runtime/error.rs @@ -362,8 +362,6 @@ pub enum Error { WatchFailed, #[error("Unsupported")] Unsupported, - #[error("ExceptionOcurred")] - ExceptionOcurred, #[error("EscapeCalledTwice")] EscapeCalledTwice, #[error("UnsupportedAlgorithm")] @@ -764,7 +762,6 @@ impl Error { Self::ChromeNotFound => "ChromeNotFound", Self::WatchFailed => "WatchFailed", Self::Unsupported => "Unsupported", - Self::ExceptionOcurred => "ExceptionOcurred", Self::EscapeCalledTwice => "EscapeCalledTwice", Self::UnsupportedAlgorithm => "UnsupportedAlgorithm", Self::PasswordVerificationFailed => "PasswordVerificationFailed", diff --git a/src/runtime/server/DevErrorPage.rs b/src/runtime/server/DevErrorPage.rs new file mode 100644 index 000000000000..a471d707633f --- /dev/null +++ b/src/runtime/server/DevErrorPage.rs @@ -0,0 +1,207 @@ +//! The HTML error page `Bun.serve({ development: true })` responds with when a +//! request handler throws. The page embeds the captured errors as JSON and is +//! rendered in the browser by `packages/bun-error`. + +use std::io::Write as _; + +use bun_ast::{Kind, Location, Log, Metadata, Msg}; +use bun_core::fmt::{JSONFormatterUTF8Options, format_json_string_utf8, substitute_named}; +use bun_core::strings; +use bun_jsc::exception_list::{JsException, StackTrace}; + +const HTML_TEMPLATE: &[u8] = include_bytes!("dev-error-page.html"); + +pub struct DevErrorPage<'a> { + /// One-line summary, e.g. `GET /foo failed`. + pub message: &'a [u8], + pub cwd: &'a [u8], + pub exceptions: &'a [JsException], + /// Build/resolve errors logged while handling the request. + pub log: Option<&'a Log>, +} + +impl DevErrorPage<'_> { + pub fn render(&self) -> Vec { + let mut json = Vec::new(); + self.write_json(&mut json); + substitute_named( + HTML_TEMPLATE, + &[ + (b"error_json", &escape_for_script_element(&json)), + ( + b"bun_error_css", + bun_core::runtime_embed_file!(Codegen, "bun-error/bun-error.css").as_bytes(), + ), + ( + b"bun_error_js", + bun_core::runtime_embed_file!(Codegen, "bun-error/index.js").as_bytes(), + ), + ], + ) + } + + /// Field names and enum values follow `packages/bun-error/schema.ts`. + fn write_json(&self, w: &mut Vec) { + w.extend_from_slice(b"{\"message\":"); + write_string(w, self.message); + w.extend_from_slice(b",\"cwd\":"); + write_string(w, self.cwd); + + w.extend_from_slice(b",\"problems\":{\"exceptions\":["); + for (i, exception) in self.exceptions.iter().enumerate() { + if i > 0 { + w.push(b','); + } + w.extend_from_slice(b"{\"name\":"); + write_string(w, &exception.name); + w.extend_from_slice(b",\"message\":"); + write_string(w, &exception.message); + write!( + w, + ",\"runtime_type\":{},\"code\":{}", + exception.runtime_type.0, exception.code.0 + ) + .unwrap(); + if !exception.stack.frames.is_empty() { + w.extend_from_slice(b",\"stack\":"); + write_stack_trace(w, &exception.stack); + } + w.push(b'}'); + } + + // `Log.errors` / `Log.warnings` don't count messages added via + // `add_msg`, which is how build/resolve errors reach this log. + let msgs: &[Msg] = match self.log { + Some(log) => &log.msgs, + None => &[], + }; + let (mut errors, mut warnings) = (0u32, 0u32); + for msg in msgs { + errors += (msg.kind == Kind::Err) as u32; + warnings += (msg.kind == Kind::Warn) as u32; + } + write!( + w, + "],\"build\":{{\"errors\":{errors},\"warnings\":{warnings},\"msgs\":[" + ) + .unwrap(); + for (i, msg) in msgs.iter().enumerate() { + if i > 0 { + w.push(b','); + } + write_message(w, msg); + } + w.extend_from_slice(b"]}}}"); + } +} + +fn write_stack_trace(w: &mut Vec, stack: &StackTrace) { + w.extend_from_slice(b"{\"frames\":["); + for (i, frame) in stack.frames.iter().enumerate() { + if i > 0 { + w.push(b','); + } + w.extend_from_slice(b"{\"function_name\":"); + write_string(w, &frame.function_name); + w.extend_from_slice(b",\"file\":"); + write_string(w, &frame.file); + write!( + w, + ",\"scope\":{},\"position\":{{\"line\":{},\"column\":{}}}}}", + frame.code_type.0, + one_based_or_missing(frame.position.line), + one_based_or_missing(frame.position.column), + ) + .unwrap(); + } + w.extend_from_slice(b"],\"source_lines\":["); + for (i, source_line) in stack.source_lines.iter().enumerate() { + if i > 0 { + w.push(b','); + } + write!(w, "{{\"line\":{},\"text\":", source_line.line + 1).unwrap(); + write_string(w, &source_line.text); + w.push(b'}'); + } + w.extend_from_slice(b"]}"); +} + +/// bun-error treats `-1` as "no line/column" (e.g. frames without a source position). +fn one_based_or_missing(ordinal: bun_core::Ordinal) -> core::ffi::c_int { + if ordinal.is_valid() { + ordinal.one_based() + } else { + -1 + } +} + +fn write_message(w: &mut Vec, msg: &Msg) { + let level: u8 = match msg.kind { + Kind::Err => 1, + Kind::Warn => 2, + Kind::Note => 3, + Kind::Debug | Kind::Verbose => 5, + }; + write!(w, "{{\"level\":{level},\"data\":").unwrap(); + write_message_data(w, &msg.data.text, msg.data.location.as_ref()); + w.extend_from_slice(b",\"notes\":["); + for (i, note) in msg.notes.iter().enumerate() { + if i > 0 { + w.push(b','); + } + write_message_data(w, ¬e.text, note.location.as_ref()); + } + // bun-error renders each message either as a resolve error (`on.resolve` + // holds the specifier) or as a build error (`on.build` is set). + w.extend_from_slice(b"],\"on\":{\"resolve\":"); + let specifier: &[u8] = match &msg.metadata { + Metadata::Resolve(resolve) => resolve.specifier.slice(&msg.data.text), + Metadata::Build => b"", + }; + write_string(w, specifier); + write!(w, ",\"build\":{}}}}}", specifier.is_empty()).unwrap(); +} + +fn write_message_data(w: &mut Vec, text: &[u8], location: Option<&Location>) { + w.extend_from_slice(b"{\"text\":"); + write_string(w, text); + if let Some(location) = location { + w.extend_from_slice(b",\"location\":{\"file\":"); + write_string(w, &location.file); + w.extend_from_slice(b",\"namespace\":"); + write_string(w, location.namespace); + w.extend_from_slice(b",\"line_text\":"); + write_string(w, location.line_text.as_deref().unwrap_or(b"")); + write!( + w, + ",\"line\":{},\"column\":{},\"offset\":{}}}", + location.line, location.column, location.offset + ) + .unwrap(); + } + w.push(b'}'); +} + +fn write_string(w: &mut Vec, bytes: &[u8]) { + write!( + w, + "{}", + format_json_string_utf8(bytes, JSONFormatterUTF8Options::default()) + ) + .unwrap(); +} + +/// The JSON is embedded in a ` + + + + + + + + diff --git a/src/runtime/server/mod.rs b/src/runtime/server/mod.rs index 1f5baac3e14a..5ad8aa534e3f 100644 --- a/src/runtime/server/mod.rs +++ b/src/runtime/server/mod.rs @@ -81,6 +81,10 @@ pub use file_route::FileRoute; pub mod directory_route; pub use directory_route::DirectoryRoute; +#[path = "DevErrorPage.rs"] +pub mod dev_error_page; +pub use dev_error_page::DevErrorPage; + #[path = "FileResponseStream.rs"] pub mod file_response_stream; pub use file_response_stream::FileResponseStream; diff --git a/test/bake/dev/react-response.test.ts b/test/bake/dev/react-response.test.ts index 7e95f7a6dbde..1c8a5c91e0b8 100644 --- a/test/bake/dev/react-response.test.ts +++ b/test/bake/dev/react-response.test.ts @@ -1,23 +1,10 @@ import { expect } from "bun:test"; -import { ByteBuffer } from "peechy"; -import { decodeFallbackMessageContainer } from "../../../src/api/schema"; import { devTest } from "../bake-harness"; +// The dev error page embeds its payload as JSON (see src/runtime/server/DevErrorPage.rs). function getFallbackMessageContainer(text: string) { - const regex = /\s*\