dinou 5.0.1 → 5.0.3

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.
@@ -5,6 +5,8 @@ const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
5
5
  globalThis.__dinou_require__ = require;
6
6
  const path = require("path");
7
7
 
8
+ const { normalizePathCase } = require("./path-utils.js");
9
+
8
10
  let reactServerPath, reactDomServerPath, reactJsxRuntimePath, reactJsxDevRuntimePath;
9
11
 
10
12
  if (!isWebpack) {
@@ -124,11 +126,10 @@ if (isDevelopment) {
124
126
  while (attempts < maxRetries) {
125
127
  try {
126
128
  // console.log(`Attempting to load manifest (try ${attempts + 1})...`);
127
- return JSON.parse(readFileSync(manifestPath, "utf8"));
129
+ const text = readFileSync(manifestPath, "utf8");
130
+ if (!text.trim()) throw new Error("Empty JSON");
131
+ return JSON.parse(text);
128
132
  } catch (err) {
129
- if (err.code !== "ENOENT") {
130
- throw err; // Rethrow if it's not a file not found error
131
- }
132
133
  attempts++;
133
134
  if (attempts >= maxRetries) {
134
135
  throw err; // Rethrow after max retries
@@ -621,11 +622,28 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
621
622
  // reqPath
622
623
  // );
623
624
  if ((!isDevelopment && !dynamicState.value) || isStatic) {
625
+ let currentGeneratedAt = null;
626
+ try {
627
+ const metadataPath = path.join("dist2", reqPath, "metadata.json");
628
+ if (existsSync(metadataPath)) {
629
+ const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
630
+ currentGeneratedAt = metaObj.generatedAt || null;
631
+ }
632
+ } catch (e) { }
633
+
634
+ const useOld =
635
+ isOld ||
636
+ regenerating.has(reqPath) ||
637
+ (req.query.buildId &&
638
+ currentGeneratedAt &&
639
+ req.query.buildId !== String(currentGeneratedAt));
640
+
624
641
  const payloadPath = path.resolve(
625
642
  "dist2",
626
643
  reqPath.replace(/^\//, ""),
627
- isOld || regenerating.has(reqPath) ? "rsc._old.rsc" : "rsc.rsc",
644
+ useOld ? "rsc._old.rsc" : "rsc.rsc",
628
645
  );
646
+
629
647
  const distDir = path.resolve("dist2");
630
648
 
631
649
  if (!payloadPath.startsWith(distDir)) {
@@ -633,6 +651,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
633
651
  }
634
652
  if (existsSync(payloadPath)) {
635
653
  res.setHeader("Content-Type", "application/octet-stream");
654
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
636
655
  try {
637
656
  const buffer = readFileSync(payloadPath);
638
657
  return res.send(buffer);
@@ -642,6 +661,103 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
642
661
  }
643
662
  }
644
663
  }
664
+ let isPathBlocked = false;
665
+ const reqSegments = reqPath.split("/").filter(Boolean);
666
+ const srcFolder = path.resolve(process.cwd(), "src");
667
+ const [pagePath, dynamicParams] = getFilePathAndDynamicParams(
668
+ reqSegments,
669
+ req.query,
670
+ srcFolder,
671
+ );
672
+
673
+ if (pagePath) {
674
+ let cachedConfig = pageFunctionsConfigCache.get(pagePath);
675
+ if (!cachedConfig) {
676
+ const pageFolder = path.dirname(pagePath);
677
+ const [pageFunctionsPath] = getFilePathAndDynamicParams(
678
+ reqSegments,
679
+ req.query,
680
+ pageFolder,
681
+ "page_functions",
682
+ true,
683
+ true,
684
+ undefined,
685
+ reqSegments.length,
686
+ );
687
+
688
+ if (pageFunctionsPath) {
689
+ const pageFunctionsModule = await importModule(pageFunctionsPath);
690
+ const allowISGValue = pageFunctionsModule.allowISG
691
+ ? await pageFunctionsModule.allowISG()
692
+ : true;
693
+
694
+ let staticPathsSet = null;
695
+ if (pageFunctionsModule.getStaticPaths) {
696
+ const paths = await pageFunctionsModule.getStaticPaths();
697
+ staticPathsSet = new Set(
698
+ (paths || []).map((pathObj) => {
699
+ const sortedEntries = Object.entries(pathObj).sort((a, b) =>
700
+ a[0].localeCompare(b[0])
701
+ );
702
+ return JSON.stringify(sortedEntries);
703
+ })
704
+ );
705
+ }
706
+
707
+ cachedConfig = {
708
+ allowISG: allowISGValue,
709
+ staticPathsSet,
710
+ validateParams: pageFunctionsModule.validateParams || null,
711
+ };
712
+ } else {
713
+ cachedConfig = {
714
+ allowISG: true,
715
+ staticPathsSet: null,
716
+ validateParams: null,
717
+ };
718
+ }
719
+
720
+ if (!isDevelopment) {
721
+ pageFunctionsConfigCache.set(pagePath, cachedConfig);
722
+ }
723
+ }
724
+
725
+ const { allowISG: allowISGValue, staticPathsSet, validateParams: validateParamsFn } = cachedConfig;
726
+ const hasParams = Object.keys(dynamicParams || {}).length > 0;
727
+ if (hasParams) {
728
+ if (validateParamsFn) {
729
+ const isValid = await validateParamsFn(dynamicParams);
730
+ if (!isValid) {
731
+ isPathBlocked = true;
732
+ }
733
+ }
734
+
735
+ if (!isPathBlocked && allowISGValue === false) {
736
+ let isPathAllowed = false;
737
+ if (staticPathsSet) {
738
+ const sortedQueryEntries = Object.entries(dynamicParams)
739
+ .sort((a, b) => a[0].localeCompare(b[0]))
740
+ .map(([k, v]) => {
741
+ if (Array.isArray(v)) return [k, v.join(",")];
742
+ return [k, String(v)];
743
+ });
744
+ const serializedQuery = JSON.stringify(sortedQueryEntries);
745
+ isPathAllowed = staticPathsSet.has(serializedQuery);
746
+ }
747
+ if (!isPathAllowed) {
748
+ if (isDevelopment) {
749
+ isPathBlocked = true;
750
+ } else {
751
+ const htmlPath = path.join("dist2", reqPath, "index.html");
752
+ if (!existsSync(htmlPath)) {
753
+ isPathBlocked = true;
754
+ }
755
+ }
756
+ }
757
+ }
758
+ }
759
+ }
760
+
645
761
  const context = getContext(req, res);
646
762
  const isNotFound = {};
647
763
  await requestStorage.run(context, async () => {
@@ -650,6 +766,7 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
650
766
  { ...req.query },
651
767
  isNotFound,
652
768
  isDevelopment,
769
+ isPathBlocked,
653
770
  );
654
771
 
655
772
  if (isNotFound.value) {
@@ -681,7 +798,43 @@ async function serveRSCPayload(req, res, isOld = false, isStatic = false) {
681
798
  });
682
799
  } catch (error) {
683
800
  console.error("Error rendering RSC:", error);
684
- res.status(500).send("Internal Server Error");
801
+ try {
802
+ const serializedError = {
803
+ message: error.message || "Unknown Error",
804
+ name: error.name,
805
+ stack: isDevelopment ? error.stack : undefined,
806
+ };
807
+ const context = getContext(req, res);
808
+ await requestStorage.run(context, async () => {
809
+ const jsx = await getErrorJSX(
810
+ reqPath,
811
+ { ...req.query },
812
+ serializedError,
813
+ isDevelopment,
814
+ );
815
+ const manifest = isDevelopment
816
+ ? JSON.parse(
817
+ readFileSync(
818
+ path.resolve(
819
+ process.cwd(),
820
+ isWebpack
821
+ ? `${outputFolder}/react-client-manifest.json`
822
+ : `react_client_manifest/react-client-manifest.json`,
823
+ ),
824
+ "utf8",
825
+ ),
826
+ )
827
+ : cachedClientManifest;
828
+ const { pipe } = isWebpack
829
+ ? renderToPipeableStream(jsx, manifest)
830
+ : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
831
+ res.status(500);
832
+ pipe(res);
833
+ });
834
+ } catch (innerError) {
835
+ console.error("Fatal error rendering fallback error RSC:", innerError);
836
+ res.status(500).send("Internal Server Error");
837
+ }
685
838
  }
686
839
  }
687
840
 
@@ -706,29 +859,33 @@ app.post(/^\/____rsc_payload_error____\/.*\/?$/, async (req, res) => {
706
859
  const reqPath = (
707
860
  req.path.endsWith("/") ? req.path : req.path + "/"
708
861
  ).replace("/____rsc_payload_error____", "");
709
- const jsx = await getErrorJSX(
710
- reqPath,
711
- { ...req.query },
712
- req.body.error,
713
- isDevelopment,
714
- );
715
- const manifest = isDevelopment
716
- ? JSON.parse(
717
- readFileSync(
718
- path.resolve(
719
- process.cwd(),
720
- isWebpack
721
- ? `${outputFolder}/react-client-manifest.json`
722
- : `react_client_manifest/react-client-manifest.json`,
862
+
863
+ const context = getContext(req, res);
864
+ await requestStorage.run(context, async () => {
865
+ const jsx = await getErrorJSX(
866
+ reqPath,
867
+ { ...req.query },
868
+ req.body.error,
869
+ isDevelopment,
870
+ );
871
+ const manifest = isDevelopment
872
+ ? JSON.parse(
873
+ readFileSync(
874
+ path.resolve(
875
+ process.cwd(),
876
+ isWebpack
877
+ ? `${outputFolder}/react-client-manifest.json`
878
+ : `react_client_manifest/react-client-manifest.json`,
879
+ ),
880
+ "utf8",
723
881
  ),
724
- "utf8",
725
- ),
726
- )
727
- : cachedClientManifest;
728
- const { pipe } = isWebpack
729
- ? renderToPipeableStream(jsx, manifest)
730
- : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
731
- pipe(res);
882
+ )
883
+ : cachedClientManifest;
884
+ const { pipe } = isWebpack
885
+ ? renderToPipeableStream(jsx, manifest)
886
+ : renderToPipeableStream(jsx, pathToFileURL(process.cwd()).href + "/");
887
+ pipe(res);
888
+ });
732
889
  } catch (error) {
733
890
  console.error("Error rendering RSC:", error);
734
891
  res.status(500).send("Internal Server Error");
@@ -868,6 +1025,7 @@ app.get(/^\/.*\/?$/, async (req, res) => {
868
1025
 
869
1026
  if (existsSync(fileToRead) && !dynamicState.value) {
870
1027
  res.setHeader("Content-Type", "text/html");
1028
+ res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
871
1029
  const meta = getStatus[reqPath];
872
1030
 
873
1031
  if (meta && meta.status) {
@@ -876,13 +1034,26 @@ app.get(/^\/.*\/?$/, async (req, res) => {
876
1034
  res.statusCode = 200; // Default
877
1035
  }
878
1036
  try {
879
- const htmlContent = readFileSync(fileToRead);
880
- res.write(htmlContent);
1037
+ let htmlContent = readFileSync(fileToRead, "utf8");
1038
+ let buildId = "";
1039
+ try {
1040
+ const metadataPath = path.join("dist2", reqPath, "metadata.json");
1041
+ if (existsSync(metadataPath)) {
1042
+ const metaObj = JSON.parse(readFileSync(metadataPath, "utf8"));
1043
+ buildId = metaObj.generatedAt || "";
1044
+ }
1045
+ } catch (e) { }
1046
+
1047
+ let scripts = `<script>window.__DINOU_USE_STATIC__=true;</script>`;
881
1048
  if (htmlPathOld) {
882
- res.write("<script>window.__DINOU_USE_OLD_RSC__=true;</script>");
1049
+ scripts += `<script>window.__DINOU_USE_OLD_RSC__=true;</script>`;
883
1050
  }
884
- res.write("<script>window.__DINOU_USE_STATIC__=true;</script>");
885
- res.end();
1051
+ if (buildId) {
1052
+ scripts += `<script>window.__DINOU_BUILD_ID__="${buildId}";</script>`;
1053
+ }
1054
+
1055
+ htmlContent = htmlContent.replace("</head>", `${scripts}</head>`);
1056
+ res.send(htmlContent);
886
1057
  } catch (err) {
887
1058
  console.error("Error reading HTML file:", err);
888
1059
  if (!res.headersSent) res.status(500).send("Server Error");
@@ -1078,8 +1249,27 @@ app.post("/____server_function____", async (req, res) => {
1078
1249
  return res.status(400).json({ error: "Invalid file URL format" });
1079
1250
  }
1080
1251
 
1081
- // Extract relativePath and normalize (remove 'file://' and potential '/')
1082
- let relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
1252
+ let relativePath;
1253
+
1254
+ // Check if the URL is a relative reference (e.g. file:///src/...)
1255
+ // If so, extract it directly without using fileURLToPath (which throws on Windows without a drive letter)
1256
+ const isRelativeSrc = fileUrl.startsWith("file:///src/") || fileUrl.startsWith("file:///src\\");
1257
+
1258
+ if (isRelativeSrc) {
1259
+ relativePath = fileUrl.replace(/^file:\/\/\/?/, "").trim();
1260
+ } else {
1261
+ const resolvedPath = fileURLToPath(fileUrl);
1262
+ relativePath = resolvedPath;
1263
+
1264
+ const normalizedCwd = normalizePathCase(process.cwd());
1265
+ const normalizedResolved = normalizePathCase(resolvedPath);
1266
+
1267
+ if (normalizedResolved.startsWith(normalizedCwd)) {
1268
+ relativePath = path.relative(normalizedCwd, normalizedResolved);
1269
+ } else {
1270
+ relativePath = relativePath.replace(/^[\\/]+/, "");
1271
+ }
1272
+ }
1083
1273
  const normalizedRelative = relativePath.replace(/\\/g, "/");
1084
1274
  if (
1085
1275
  normalizedRelative.startsWith("/") ||
@@ -1090,7 +1280,7 @@ app.post("/____server_function____", async (req, res) => {
1090
1280
  .status(400)
1091
1281
  .json({ error: "Invalid path: no absolute, traversal, or drive letter allowed" });
1092
1282
  }
1093
- // console.log("relPath", relativePath);
1283
+
1094
1284
  // Restrict to 'src/' folder: prepend 'src/' if missing, and resolve absolutePath
1095
1285
  if (!relativePath.startsWith("src/") && !relativePath.startsWith("src\\")) {
1096
1286
  relativePath = path.join("src", relativePath);
@@ -1104,9 +1294,10 @@ app.post("/____server_function____", async (req, res) => {
1104
1294
  .status(403)
1105
1295
  .json({ error: "Access denied: file outside src directory" });
1106
1296
  }
1107
- // console.log("absPath", absolutePath);
1297
+
1108
1298
  // Verify that the file exists
1109
1299
  if (!existsSync(absolutePath)) {
1300
+ // console.error("❌ [Dinou Server Function 404] File not found! id:", id, "relativePath:", relativePath, "absolutePath:", absolutePath);
1110
1301
  return res.status(404).json({ error: "File not found" });
1111
1302
  }
1112
1303
 
@@ -1127,8 +1318,8 @@ app.post("/____server_function____", async (req, res) => {
1127
1318
  .status(403)
1128
1319
  .json({ error: "Not a valid server function file" });
1129
1320
  }
1130
- // Parse exports (you need to implement parseExports on server if not present)
1131
- const exports = parseExports(fileContent); // Assume you move parseExports to a shared util
1321
+ // Parse exports
1322
+ const exports = parseExports(fileContent);
1132
1323
  allowedExports = new Set(exports);
1133
1324
  }
1134
1325
 
@@ -1141,10 +1332,10 @@ app.post("/____server_function____", async (req, res) => {
1141
1332
  return res.status(400).json({ error: "Invalid export name" });
1142
1333
  }
1143
1334
 
1144
- // Proceed with import (using your importModule)
1335
+ // Proceed with import
1145
1336
  const mod = await importModule(absolutePath);
1146
1337
 
1147
- // Validate exportName: only allow 'default' or others if you define a whitelist
1338
+ // Validate exportName
1148
1339
  if (!exportName || (exportName !== "default" && !mod[exportName])) {
1149
1340
  return res.status(400).json({ error: "Invalid export name" });
1150
1341
  }
@@ -56,6 +56,9 @@ export default function getConfigEsbuildProd({
56
56
  metafile: true,
57
57
  logLevel: "warning",
58
58
  minify: true,
59
+ define: {
60
+ "process.env.NODE_ENV": JSON.stringify("production"),
61
+ },
59
62
  external: [
60
63
  "/__SERVER_FUNCTION_PROXY__",
61
64
  "/serverFunctionProxy.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou-ejected",
3
- "version": "5.0.1",
3
+ "version": "5.0.3",
4
4
  "main": "index.js",
5
5
  "private": true,
6
6
  "exports": {
@@ -1,4 +1,6 @@
1
1
  const path = require("path");
2
+
3
+ const { normalizePathCase } = require("../../core/path-utils.js");
2
4
  const parseExports = require("../../core/parse-exports.js");
3
5
  const { useServerRegex } = require("../../constants.js");
4
6
 
@@ -16,7 +18,10 @@ module.exports = function (source) {
16
18
 
17
19
  // Build IDs
18
20
  const moduleId = this.resourcePath;
19
- const relativePath = path.relative(process.cwd(), moduleId);
21
+ const relativePath = path.relative(
22
+ normalizePathCase(process.cwd()),
23
+ normalizePathCase(moduleId)
24
+ );
20
25
  const normalizedPath = relativePath.replace(/\\/g, "/");
21
26
 
22
27
  const fileUrl = `file:///${normalizedPath}`;
@@ -14,7 +14,7 @@ class ManifestGeneratorPlugin {
14
14
  compilation.hooks.processAssets.tap(
15
15
  {
16
16
  name: pluginName,
17
- stage: Compilation.PROCESS_ASSETS_STAGE_REPORT,
17
+ stage: Compilation.PROCESS_ASSETS_STAGE_ANALYSE,
18
18
  },
19
19
  (assets) => {
20
20
  // Traverse chunks to generate manifest
@@ -45,7 +45,17 @@ const outputDirs = [
45
45
 
46
46
  function cleanDir(dir) {
47
47
  if (fs.existsSync(dir)) {
48
- fs.rmSync(dir, { recursive: true, force: true });
48
+ try {
49
+ const files = fs.readdirSync(dir);
50
+ for (const file of files) {
51
+ const fullPath = path.join(dir, file);
52
+ fs.rmSync(fullPath, { recursive: true, force: true });
53
+ }
54
+ } catch (e) {
55
+ try {
56
+ fs.rmSync(dir, { recursive: true, force: true });
57
+ } catch (err) {}
58
+ }
49
59
  }
50
60
  }
51
61
 
@@ -61,6 +71,7 @@ module.exports = async () => {
61
71
  maxEntrypointSize: 512000,
62
72
  maxAssetSize: 512000,
63
73
  },
74
+ cache: false,
64
75
  mode: isDevelopment ? "development" : "production",
65
76
  entry: {
66
77
  main: [path.resolve(__dirname, "../core/client-webpack.jsx")].filter(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dinou",
3
- "version": "5.0.1",
3
+ "version": "5.0.3",
4
4
  "description": "Lightweight-Ejectable Full-Stack React 19 Framework with support for React Server Components (RSC), Server Functions, Server-Side Rendering (SSR), Static Generation (SSG), Incremental Static Generation (ISG), and Incremental Static Regeneration (ISR)",
5
5
  "main": "index.js",
6
6
  "bin": {