bun-dev-server 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { type BuildConfig, type TLSOptions } from "bun";
2
- export interface BunDevServerConfig extends Partial<BunDevServerSocketConfig> {
2
+ export interface BunDevServerConfig extends Partial<BunServeConfig> {
3
3
  port: number;
4
4
  buildConfig: BuildConfig;
5
5
  watchDir: string;
@@ -14,6 +14,7 @@ export interface BunDevServerConfig extends Partial<BunDevServerSocketConfig> {
14
14
  manifestName?: string;
15
15
  manifestWithHash?: boolean;
16
16
  hotReload?: "plugin" | "footer";
17
+ logRequests?: boolean;
17
18
  reloadOnChange?: boolean;
18
19
  /**
19
20
  * The path to the directory to serve files from.
@@ -25,8 +26,9 @@ export interface BunDevServerConfig extends Partial<BunDevServerSocketConfig> {
25
26
  serveOutputEjs?: string;
26
27
  serveOutputHtml?: string;
27
28
  }
28
- export interface BunDevServerSocketConfig {
29
+ export interface BunServeConfig {
29
30
  port: number;
30
31
  tls?: TLSOptions;
31
32
  websocketPath?: string;
33
+ static?: Record<`/${string}`, Response>;
32
34
  }
package/dist/index.js CHANGED
@@ -966,7 +966,7 @@ var indexHTMLTemplate_default = `<!DOCTYPE html>\r
966
966
  `;
967
967
 
968
968
  // src/server.ts
969
- import { watch, readdir, exists, readFile as readFile2 } from "fs/promises";
969
+ import { watch, readdir, access, readFile as readFile2, constants } from "fs/promises";
970
970
 
971
971
  // src/bunClientHmr.ts
972
972
  function hotReload() {
@@ -1187,82 +1187,27 @@ async function startBunDevServer(serverConfig) {
1187
1187
  port: finalConfig.port,
1188
1188
  development: true,
1189
1189
  tls: finalConfig.tls,
1190
+ static: {
1191
+ "/favicon.ico": withCORSHeaders(new Response("", { status: 404 })),
1192
+ ...finalConfig.static
1193
+ },
1190
1194
  async fetch(req, server) {
1191
1195
  if (req.method === "OPTIONS") {
1192
- const response = new Response("", { status: 200 });
1193
- augumentHeaders(req, response);
1194
- return response;
1195
- }
1196
- if (req.url.toLowerCase().endsWith("/favicon.ico")) {
1197
- const response = new Response("", { status: 404 });
1198
- augumentHeaders(req, response);
1199
- return response;
1196
+ finalConfig.logRequests && console.log(`${200} ${req.url} OPTIONS`);
1197
+ return withCORSHeaders(new Response("", { status: 200 }), req);
1200
1198
  }
1201
1199
  if (req.url.toLowerCase().endsWith(finalConfig.websocketPath)) {
1200
+ finalConfig.logRequests && console.log(`${req.url} Socket Upgrade`);
1202
1201
  if (server.upgrade(req)) {
1203
1202
  return;
1204
1203
  }
1205
1204
  }
1206
1205
  const url = new URL(req.url);
1207
- const requestPath = url.pathname;
1208
- const objThere = await exists(destinationPath + requestPath);
1209
- let isDirectory = false;
1210
- if (objThere) {
1211
- try {
1212
- await readFile2(destinationPath + requestPath);
1213
- } catch (e) {
1214
- if (e.code === "EISDIR") {
1215
- isDirectory = true;
1216
- } else {
1217
- throw e;
1218
- }
1219
- }
1220
- } else {
1221
- const response = new Response("", { status: 404 });
1222
- augumentHeaders(req, response);
1223
- return response;
1224
- }
1225
- if (!isDirectory) {
1226
- try {
1227
- const fl = Bun.file(destinationPath + requestPath);
1228
- const response = new Response(fl);
1229
- augumentHeaders(req, response);
1230
- return response;
1231
- } catch (e) {
1232
- if (e.code === "ENOENT") {
1233
- const response = new Response("", { status: 404 });
1234
- augumentHeaders(req, response);
1235
- return response;
1236
- } else {
1237
- throw e;
1238
- }
1239
- }
1240
- }
1241
- try {
1242
- const allEntries = await readdir(destinationPath + requestPath, {
1243
- withFileTypes: true
1244
- });
1245
- const dirs = allEntries.filter((entry) => entry.isDirectory()).map((entry) => {
1246
- return {
1247
- requestPath: requestPath === "/" ? "" : requestPath,
1248
- name: entry.name
1249
- };
1250
- });
1251
- const files = allEntries.filter((entry) => entry.isFile()).map((entry) => {
1252
- return {
1253
- requestPath: requestPath === "/" ? "" : requestPath,
1254
- name: entry.name
1255
- };
1256
- });
1257
- const rnd = import_ejs.render(finalConfig.serveOutputEjs, { dirs, files });
1258
- const response = new Response(rnd, { headers: { "Content-Type": "text/html" } });
1259
- augumentHeaders(req, response);
1260
- return response;
1261
- } catch {
1262
- const response = new Response("Not Found", { status: 404 });
1263
- augumentHeaders(req, response);
1264
- return response;
1206
+ let requestPath = url.pathname;
1207
+ if (requestPath.toLowerCase() === "/index.html") {
1208
+ requestPath = "/";
1265
1209
  }
1210
+ return handlePathRequest(requestPath, req, finalConfig, destinationPath);
1266
1211
  },
1267
1212
  websocket: {
1268
1213
  open(ws) {
@@ -1294,6 +1239,11 @@ var debouncedbuildAndNotify = import_debounce.default(async (finalConfig, destin
1294
1239
  bunServer.publish("message", JSON.stringify({ type: "reload" }));
1295
1240
  }
1296
1241
  }, watchDelay, { immediate: true });
1242
+ function handleErrorResponse(req, err) {
1243
+ const msg = `Error while processing request ${req.url}`;
1244
+ console.error(msg, err);
1245
+ return withCORSHeaders(new Response(msg, { status: 500 }), req);
1246
+ }
1297
1247
  function publishOutputLogs(bunServer, output, event) {
1298
1248
  output.logs.forEach(console.log);
1299
1249
  bunServer.publish("message", JSON.stringify({ type: "message", message: `[Bun HMR] ${event.filename} ${event.eventType}` }));
@@ -1320,10 +1270,11 @@ function publishIndexHTML(destinationPath, template, output, _event) {
1320
1270
  }
1321
1271
  Bun.write(destinationPath + "/index.html", import_ejs.render(template, { hashedImports }));
1322
1272
  }
1323
- function augumentHeaders(request, response) {
1324
- response.headers.set("Access-Control-Allow-Origin", request.headers.get("origin") ?? "*");
1325
- response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
1273
+ function withCORSHeaders(response, request) {
1274
+ response.headers.set("Access-Control-Allow-Origin", request?.headers.get("origin") ?? "*");
1275
+ response.headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
1326
1276
  response.headers.set("Access-Control-Allow-Credentials", "true");
1277
+ return response;
1327
1278
  }
1328
1279
  async function cleanDirectory(dst) {
1329
1280
  const { stderr, exitCode } = await $2`rm -rf ${dst}/*`.nothrow();
@@ -1347,6 +1298,74 @@ function convertBytes(bytes) {
1347
1298
  }
1348
1299
  return (bytes / Math.pow(1024, i)).toFixed(1) + " " + sizes[i];
1349
1300
  }
1301
+ async function handlePathRequest(requestPath, req, finalConfig, destinationPath) {
1302
+ const fsPath = destinationPath + requestPath;
1303
+ const objThere = await checkObjectExists(fsPath, req);
1304
+ let isDirectory = false;
1305
+ if (objThere) {
1306
+ try {
1307
+ await readFile2(fsPath);
1308
+ } catch (e) {
1309
+ if (e.code === "EISDIR") {
1310
+ isDirectory = true;
1311
+ } else {
1312
+ throw e;
1313
+ }
1314
+ }
1315
+ } else {
1316
+ finalConfig.logRequests && console.log(`${404} ${req.url}`);
1317
+ return withCORSHeaders(new Response("", { status: 404 }), req);
1318
+ }
1319
+ if (!isDirectory) {
1320
+ try {
1321
+ const fl = Bun.file(fsPath);
1322
+ finalConfig.logRequests && console.log(`${200} ${req.url}`);
1323
+ return withCORSHeaders(new Response(fl), req);
1324
+ } catch (e) {
1325
+ if (e?.code === "ENOENT") {
1326
+ finalConfig.logRequests && console.log(`${404} ${req.url}`);
1327
+ return withCORSHeaders(new Response("", { status: 404 }), req);
1328
+ } else {
1329
+ return handleErrorResponse(req, e);
1330
+ }
1331
+ }
1332
+ }
1333
+ try {
1334
+ const allEntries = await readdir(fsPath, {
1335
+ withFileTypes: true
1336
+ });
1337
+ const dirs = allEntries.filter((entry) => entry.isDirectory()).map((entry) => {
1338
+ return {
1339
+ requestPath: requestPath === "/" ? "" : requestPath,
1340
+ name: entry.name
1341
+ };
1342
+ });
1343
+ const files = allEntries.filter((entry) => entry.isFile()).map((entry) => {
1344
+ return {
1345
+ requestPath: requestPath === "/" ? "" : requestPath,
1346
+ name: entry.name
1347
+ };
1348
+ });
1349
+ const rnd = import_ejs.render(finalConfig.serveOutputEjs, { dirs, files });
1350
+ finalConfig.logRequests && console.log(`${200} ${req.url}`);
1351
+ return withCORSHeaders(new Response(rnd, { headers: { "Content-Type": "text/html" } }), req);
1352
+ } catch (err) {
1353
+ return handleErrorResponse(req, err);
1354
+ }
1355
+ }
1356
+ async function checkObjectExists(fsPath, req) {
1357
+ try {
1358
+ await access(fsPath, constants.R_OK);
1359
+ return true;
1360
+ } catch (e) {
1361
+ if (e?.code === "ENOENT") {
1362
+ return false;
1363
+ }
1364
+ const msg = `Error while accessing path ${fsPath}`;
1365
+ console.error(msg, e);
1366
+ return false;
1367
+ }
1368
+ }
1350
1369
  export {
1351
1370
  startBunDevServer,
1352
1371
  getBunHMRFooter,
package/package.json CHANGED
@@ -21,18 +21,18 @@
21
21
  "exports": {
22
22
  ".": "./dist/index.js"
23
23
  },
24
- "version": "0.5.0",
24
+ "version": "0.6.0",
25
25
  "module": "index.ts",
26
26
  "type": "module",
27
27
  "license": "MIT",
28
28
  "scripts": {
29
- "serve": "bun --hot ./serve.ts"
29
+ "build": "bun run ./build.ts"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/bun": "latest"
33
33
  },
34
34
  "peerDependencies": {
35
- "typescript": "^5.7.2"
35
+ "typescript": "^5.7.3"
36
36
  },
37
37
  "dependencies": {
38
38
  "@types/ejs": "^3.1.5",