@nattyjs/express 0.0.1-beta.55 → 0.0.1-beta.57

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.
package/dist/index.cjs CHANGED
@@ -6,6 +6,7 @@ const cors = require('cors');
6
6
  const compression = require('compression');
7
7
  const core = require('@nattyjs/core');
8
8
  const common = require('@nattyjs/common');
9
+ const fs = require('node:fs');
9
10
 
10
11
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
11
12
 
@@ -13,6 +14,7 @@ const express__default = /*#__PURE__*/_interopDefaultCompat(express);
13
14
  const path__default = /*#__PURE__*/_interopDefaultCompat(path);
14
15
  const cors__default = /*#__PURE__*/_interopDefaultCompat(cors);
15
16
  const compression__default = /*#__PURE__*/_interopDefaultCompat(compression);
17
+ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
16
18
 
17
19
  async function getRequestBodyInfo(request) {
18
20
  const contentType = request.headers["content-type"];
@@ -83,6 +85,80 @@ function requestHandler(config) {
83
85
  };
84
86
  }
85
87
 
88
+ function hasExtension(p) {
89
+ return /\.[a-z0-9]+$/i.test(p);
90
+ }
91
+ function acceptsJson(req) {
92
+ const a = String(req.headers.accept ?? "");
93
+ return a.includes("application/json") || a.includes("+json");
94
+ }
95
+ function acceptsHtml(req) {
96
+ const a = String(req.headers.accept ?? "");
97
+ return a.includes("text/html") || a.includes("application/xhtml+xml");
98
+ }
99
+ function fileExists(p) {
100
+ try {
101
+ return fs__default.existsSync(p) && fs__default.statSync(p).isFile();
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+ function dirExists(p) {
107
+ try {
108
+ return fs__default.existsSync(p) && fs__default.statSync(p).isDirectory();
109
+ } catch {
110
+ return false;
111
+ }
112
+ }
113
+ function staticPrettyRoutes(options) {
114
+ const allowExt = new Set(["html", "json"].map((x) => x.toLowerCase()));
115
+ const absBase = path__default.resolve(options.staticDir);
116
+ return (req, res, next) => {
117
+ if (req.method !== "GET" && req.method !== "HEAD")
118
+ return next();
119
+ const originalUrl = req.url;
120
+ const urlPathRaw = req.path || "/";
121
+ const urlPath = urlPathRaw !== "/" ? urlPathRaw.replace(/\/+$/, "") : "/";
122
+ if (urlPath.startsWith(options.apiPrefix))
123
+ return next();
124
+ if (hasExtension(urlPath))
125
+ return next();
126
+ if (urlPath.includes(".."))
127
+ return res.status(400).send("Bad request");
128
+ if (/(^|\/)\./.test(urlPath))
129
+ return res.status(404).end();
130
+ let rel = urlPath;
131
+ try {
132
+ rel = decodeURIComponent(urlPath);
133
+ } catch {
134
+ }
135
+ const absCandidateBase = path__default.resolve(options.staticDir, "." + rel);
136
+ if (!absCandidateBase.startsWith(absBase))
137
+ return res.status(400).send("Bad request");
138
+ const qs = originalUrl.includes("?") ? originalUrl.slice(originalUrl.indexOf("?")) : "";
139
+ if (dirExists(absCandidateBase)) {
140
+ const idx = path__default.join(absCandidateBase, "index.html");
141
+ if (fileExists(idx)) {
142
+ req.url = rel + "/index.html" + qs;
143
+ return next();
144
+ }
145
+ }
146
+ const wantJson = acceptsJson(req);
147
+ const wantHtml = acceptsHtml(req);
148
+ const extOrder = wantJson && !wantHtml ? ["json", "html"] : ["html", "json"];
149
+ for (const ext of extOrder) {
150
+ if (!allowExt.has(ext))
151
+ continue;
152
+ const fileAbs = absCandidateBase + "." + ext;
153
+ if (fileExists(fileAbs)) {
154
+ req.url = rel + "." + ext + qs;
155
+ return next();
156
+ }
157
+ }
158
+ return next();
159
+ };
160
+ }
161
+
86
162
  const app = express__default();
87
163
  const ExpressModule = {
88
164
  init(config) {
@@ -92,13 +168,13 @@ const ExpressModule = {
92
168
  if (config.cors)
93
169
  app.use(cors__default(config.cors));
94
170
  app.use(express__default.json({ limit: config.payload?.limit || "1mb" }));
95
- if (staticCfg?.dir) {
96
- const staticDir = path__default.resolve(staticCfg.dir);
171
+ if (staticCfg.enabled) {
172
+ const staticDir = path__default.resolve("public");
173
+ app.use(staticPrettyRoutes({ staticDir, apiPrefix }));
97
174
  app.use(
98
175
  express__default.static(staticDir, {
99
176
  index: false,
100
- // we'll control SPA index fallback manually
101
- maxAge: staticCfg.maxAge ?? "1d"
177
+ maxAge: "1d"
102
178
  })
103
179
  );
104
180
  if (staticCfg.spaFallback !== false) {
package/dist/index.mjs CHANGED
@@ -4,6 +4,7 @@ import cors from 'cors';
4
4
  import compression from 'compression';
5
5
  import { HttpHandler, HttpContext } from '@nattyjs/core';
6
6
  import { GET, commonContainer, FrameworkType, getPort } from '@nattyjs/common';
7
+ import fs from 'node:fs';
7
8
 
8
9
  async function getRequestBodyInfo(request) {
9
10
  const contentType = request.headers["content-type"];
@@ -74,6 +75,80 @@ function requestHandler(config) {
74
75
  };
75
76
  }
76
77
 
78
+ function hasExtension(p) {
79
+ return /\.[a-z0-9]+$/i.test(p);
80
+ }
81
+ function acceptsJson(req) {
82
+ const a = String(req.headers.accept ?? "");
83
+ return a.includes("application/json") || a.includes("+json");
84
+ }
85
+ function acceptsHtml(req) {
86
+ const a = String(req.headers.accept ?? "");
87
+ return a.includes("text/html") || a.includes("application/xhtml+xml");
88
+ }
89
+ function fileExists(p) {
90
+ try {
91
+ return fs.existsSync(p) && fs.statSync(p).isFile();
92
+ } catch {
93
+ return false;
94
+ }
95
+ }
96
+ function dirExists(p) {
97
+ try {
98
+ return fs.existsSync(p) && fs.statSync(p).isDirectory();
99
+ } catch {
100
+ return false;
101
+ }
102
+ }
103
+ function staticPrettyRoutes(options) {
104
+ const allowExt = new Set(["html", "json"].map((x) => x.toLowerCase()));
105
+ const absBase = path.resolve(options.staticDir);
106
+ return (req, res, next) => {
107
+ if (req.method !== "GET" && req.method !== "HEAD")
108
+ return next();
109
+ const originalUrl = req.url;
110
+ const urlPathRaw = req.path || "/";
111
+ const urlPath = urlPathRaw !== "/" ? urlPathRaw.replace(/\/+$/, "") : "/";
112
+ if (urlPath.startsWith(options.apiPrefix))
113
+ return next();
114
+ if (hasExtension(urlPath))
115
+ return next();
116
+ if (urlPath.includes(".."))
117
+ return res.status(400).send("Bad request");
118
+ if (/(^|\/)\./.test(urlPath))
119
+ return res.status(404).end();
120
+ let rel = urlPath;
121
+ try {
122
+ rel = decodeURIComponent(urlPath);
123
+ } catch {
124
+ }
125
+ const absCandidateBase = path.resolve(options.staticDir, "." + rel);
126
+ if (!absCandidateBase.startsWith(absBase))
127
+ return res.status(400).send("Bad request");
128
+ const qs = originalUrl.includes("?") ? originalUrl.slice(originalUrl.indexOf("?")) : "";
129
+ if (dirExists(absCandidateBase)) {
130
+ const idx = path.join(absCandidateBase, "index.html");
131
+ if (fileExists(idx)) {
132
+ req.url = rel + "/index.html" + qs;
133
+ return next();
134
+ }
135
+ }
136
+ const wantJson = acceptsJson(req);
137
+ const wantHtml = acceptsHtml(req);
138
+ const extOrder = wantJson && !wantHtml ? ["json", "html"] : ["html", "json"];
139
+ for (const ext of extOrder) {
140
+ if (!allowExt.has(ext))
141
+ continue;
142
+ const fileAbs = absCandidateBase + "." + ext;
143
+ if (fileExists(fileAbs)) {
144
+ req.url = rel + "." + ext + qs;
145
+ return next();
146
+ }
147
+ }
148
+ return next();
149
+ };
150
+ }
151
+
77
152
  const app = express();
78
153
  const ExpressModule = {
79
154
  init(config) {
@@ -83,13 +158,13 @@ const ExpressModule = {
83
158
  if (config.cors)
84
159
  app.use(cors(config.cors));
85
160
  app.use(express.json({ limit: config.payload?.limit || "1mb" }));
86
- if (staticCfg?.dir) {
87
- const staticDir = path.resolve(staticCfg.dir);
161
+ if (staticCfg.enabled) {
162
+ const staticDir = path.resolve("public");
163
+ app.use(staticPrettyRoutes({ staticDir, apiPrefix }));
88
164
  app.use(
89
165
  express.static(staticDir, {
90
166
  index: false,
91
- // we'll control SPA index fallback manually
92
- maxAge: staticCfg.maxAge ?? "1d"
167
+ maxAge: "1d"
93
168
  })
94
169
  );
95
170
  if (staticCfg.spaFallback !== false) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nattyjs/express",
3
- "version": "0.0.1-beta.55",
3
+ "version": "0.0.1-beta.57",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "author": "ajayojha <ojhaajay@outlook.com>",
@@ -19,9 +19,9 @@
19
19
  "chokidar": "4.0.3",
20
20
  "cors": "2.8.5",
21
21
  "compression": "1.7.4",
22
- "@nattyjs/core": "0.0.1-beta.55",
23
- "@nattyjs/common": "0.0.1-beta.55",
24
- "@nattyjs/types": "0.0.1-beta.55"
22
+ "@nattyjs/core": "0.0.1-beta.57",
23
+ "@nattyjs/common": "0.0.1-beta.57",
24
+ "@nattyjs/types": "0.0.1-beta.57"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/node": "20.3.1",