dinou 6.1.1 → 7.1.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.
Files changed (135) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +9352 -1081
  3. package/cli.js +314 -91
  4. package/dinou/adapters/bun.js +62 -0
  5. package/dinou/adapters/cloudflare.js +65 -0
  6. package/dinou/adapters/deno.js +48 -0
  7. package/dinou/adapters/netlify.js +85 -0
  8. package/dinou/adapters/node.mjs +31 -0
  9. package/dinou/bun/build.mjs +1077 -0
  10. package/dinou/cloudflare/build.mjs +1121 -0
  11. package/dinou/cloudflare/shims/child_process.js +14 -0
  12. package/dinou/cloudflare/shims/empty.js +2 -0
  13. package/dinou/cloudflare/shims/fs.js +99 -0
  14. package/dinou/cloudflare/shims/os.js +29 -0
  15. package/dinou/config.js +1 -0
  16. package/dinou/config.mjs +2 -0
  17. package/dinou/constants.js +29 -4
  18. package/dinou/core/babel-esm-loader.js +66 -37
  19. package/dinou/core/bailout-proxy.js +61 -0
  20. package/dinou/core/build-static-pages.js +322 -165
  21. package/dinou/core/cache-revalidate.js +471 -144
  22. package/dinou/core/cache-slot.js +387 -0
  23. package/dinou/core/client-redirect.jsx +12 -7
  24. package/dinou/core/client-webpack.jsx +457 -82
  25. package/dinou/core/client.jsx +452 -82
  26. package/dinou/core/config.js +11 -0
  27. package/dinou/core/dinou-paths.js +52 -0
  28. package/dinou/core/edge-ssr.js +122 -0
  29. package/dinou/core/export-static.mjs +64 -0
  30. package/dinou/core/generate-route-types.js +252 -0
  31. package/dinou/core/generate-static-page.js +55 -94
  32. package/dinou/core/generate-static-rsc.js +47 -15
  33. package/dinou/core/generate-static.js +212 -10
  34. package/dinou/core/generating-isg.js +5 -0
  35. package/dinou/core/get-asset-from-manifest.js +9 -3
  36. package/dinou/core/get-error-jsx.js +221 -41
  37. package/dinou/core/get-file-path-and-dynamic-params.js +9 -13
  38. package/dinou/core/get-jsx.js +175 -96
  39. package/dinou/core/handler.js +2303 -0
  40. package/dinou/core/http-adapter.js +115 -0
  41. package/dinou/core/import-module.js +83 -53
  42. package/dinou/core/layout-functions.js +183 -0
  43. package/dinou/core/link.jsx +18 -6
  44. package/dinou/core/load-dinou-config.js +60 -0
  45. package/dinou/core/manifest-provider.js +261 -0
  46. package/dinou/core/navigation-utils.js +4 -0
  47. package/dinou/core/navigation.js +50 -39
  48. package/dinou/core/parse-exports.js +252 -2
  49. package/dinou/core/ppr-context.js +105 -0
  50. package/dinou/core/ppr-runtime.js +263 -0
  51. package/dinou/core/redirect.jsx +20 -19
  52. package/dinou/core/render-jsx-to-client-jsx.js +91 -87
  53. package/dinou/core/request-context.js +47 -13
  54. package/dinou/core/resolve-module-ppr.js +34 -0
  55. package/dinou/core/revalidating.js +28 -9
  56. package/dinou/core/route-generator.js +107 -0
  57. package/dinou/core/rsc-renderer.js +90 -0
  58. package/dinou/core/run-ssg.js +19 -0
  59. package/dinou/core/scan-dependency-components.js +218 -0
  60. package/dinou/core/server-function-proxy-webpack.js +3 -0
  61. package/dinou/core/server-function-proxy.js +3 -0
  62. package/dinou/core/slot.js +302 -0
  63. package/dinou/core/status-manifest.js +41 -1
  64. package/dinou/core/storage-adapter.js +522 -0
  65. package/dinou/core/url-resolver.js +16 -16
  66. package/dinou/core/vfs.js +139 -68
  67. package/dinou/deno/build.mjs +1112 -0
  68. package/dinou/deno/seed-kv.js +115 -0
  69. package/dinou/esbuild/build.mjs +51 -1
  70. package/dinou/esbuild/dev.mjs +290 -224
  71. package/dinou/esbuild/helpers-esbuild/get-config-esbuild-prod.mjs +3 -2
  72. package/dinou/esbuild/helpers-esbuild/get-config-esbuild.mjs +104 -16
  73. package/dinou/esbuild/helpers-esbuild/get-esbuild-entries.mjs +31 -19
  74. package/dinou/esbuild/helpers-esbuild/strip-namespace.mjs +9 -0
  75. package/dinou/esbuild/helpers-esbuild/swc-disk-cache.mjs +225 -0
  76. package/dinou/esbuild/helpers-esbuild/update-manifest-for-module.mjs +7 -0
  77. package/dinou/esbuild/helpers-esbuild/write.mjs +111 -2
  78. package/dinou/esbuild/plugins-esbuild/assets-plugin.mjs +236 -161
  79. package/dinou/esbuild/plugins-esbuild/css-processor-plugin.mjs +101 -5
  80. package/dinou/esbuild/plugins-esbuild/react-client-manifest-plugin.mjs +63 -24
  81. package/dinou/esbuild/plugins-esbuild/server-functions-plugin.mjs +177 -56
  82. package/dinou/esbuild/plugins-esbuild/skip-missing-entry-points-plugin.mjs +3 -0
  83. package/dinou/esbuild/plugins-esbuild/stable-chunk-names-and-maps-plugin.mjs +182 -137
  84. package/dinou/esbuild/plugins-postcss/postcss-extract-plugin.js +38 -11
  85. package/dinou/esbuild/react-refresh/esm-hmr/client.mjs +55 -8
  86. package/dinou/esbuild/react-refresh/esm-hmr/server.js +1 -1
  87. package/dinou/esbuild/react-refresh/esm-hmr-plugin.mjs +238 -101
  88. package/dinou/esbuild/react-refresh/is-react-refresh-boundary.mjs +18 -4
  89. package/dinou/index.d.ts +315 -11
  90. package/dinou/index.js +12 -6
  91. package/dinou/index.mjs +9 -0
  92. package/dinou/node/build.mjs +195 -0
  93. package/dinou/node/bundle-dual-engine.mjs +1012 -0
  94. package/dinou/node/dev.mjs +2589 -0
  95. package/dinou/node/port-selector.mjs +164 -0
  96. package/dinou/node/terminal-status.mjs +345 -0
  97. package/dinou/package.json +16 -14
  98. package/dinou/rollup/react-refresh/esm-hmr/client.js +66 -10
  99. package/dinou/rollup/react-refresh/esm-hmr/server.js +1 -1
  100. package/dinou/rollup/react-refresh/is-react-refresh-boundary.js +24 -5
  101. package/dinou/rollup/react-refresh/react-refresh-runtime.js +13 -6
  102. package/dinou/rollup/react-refresh/react-refresh-wrap-modules.js +56 -11
  103. package/dinou/rollup/react-refresh/rollup-plugin-esm-hmr.js +176 -48
  104. package/dinou/rollup/rollup-plugins/dinou-asset-plugin.js +9 -5
  105. package/dinou/rollup/rollup-plugins/rollup-plugin-memory.js +66 -0
  106. package/dinou/rollup/rollup-plugins/rollup-plugin-react-client-manifest.js +468 -123
  107. package/dinou/rollup/rollup-plugins/rollup-plugin-server-functions.js +44 -12
  108. package/dinou/rollup/rollup-plugins/rollup-plugin-swc.js +100 -0
  109. package/dinou/rollup/rollup.config.js +155 -43
  110. package/dinou/server.d.ts +114 -12
  111. package/dinou/server.js +35 -10
  112. package/dinou/server.mjs +30 -9
  113. package/dinou/webpack/helpers/get-webpack-entries.js +14 -19
  114. package/dinou/webpack/loaders/server-functions-loader.js +22 -1
  115. package/dinou/webpack/loaders/swc-loader.js +76 -0
  116. package/dinou/webpack/plugins/manifest-generator-plugin.js +1 -0
  117. package/dinou/webpack/plugins/server-functions-plugin.js +75 -19
  118. package/dinou/webpack/plugins/webpack-memory-plugin.js +66 -0
  119. package/dinou/webpack/webpack.config.js +227 -29
  120. package/eject.js +113 -34
  121. package/package.json +18 -16
  122. package/dinou/core/asset-require-hook.js +0 -58
  123. package/dinou/core/client-error-webpack.jsx +0 -432
  124. package/dinou/core/client-error.jsx +0 -432
  125. package/dinou/core/concurrency-manager.js +0 -48
  126. package/dinou/core/context-proxy.js +0 -68
  127. package/dinou/core/generate-static-pages.js +0 -130
  128. package/dinou/core/generate-static-rscs.js +0 -151
  129. package/dinou/core/get-ssg-metadata.js +0 -31
  130. package/dinou/core/register-loader.mjs +0 -11
  131. package/dinou/core/register-paths.js +0 -27
  132. package/dinou/core/render-app-to-html.js +0 -430
  133. package/dinou/core/render-html.js +0 -407
  134. package/dinou/core/server.js +0 -1542
  135. package/dinou/esbuild/plugins-esbuild/write-metafile-plugin.mjs +0 -16
@@ -1,151 +0,0 @@
1
- const fs = require("fs");
2
- const path = require("path");
3
- const { PassThrough } = require("stream");
4
- const url = require("url");
5
- const getJSX = require("./get-jsx.js");
6
- const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
7
- const { renderToPipeableStream } = isWebpack
8
- ? require("react-server-dom-webpack/server")
9
- : require("@roggc/react-server-dom-esm/server");
10
-
11
- // 👇 Your context storage
12
- const { requestStorage } = require("./request-context.js");
13
-
14
- const OUT_DIR = path.resolve(".dinou/dist2");
15
- // const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
16
-
17
- async function generateStaticRSCs(routes) {
18
- const manifest = JSON.parse(
19
- fs.readFileSync(
20
- path.resolve(
21
- isWebpack
22
- ? ".dinou/dist3/react-client-manifest.json"
23
- : ".dinou/react_client_manifest/react-client-manifest.json"
24
- ),
25
- "utf8"
26
- )
27
- );
28
-
29
- for (const route of routes) {
30
- const reqPath = route.endsWith("/") ? route : route + "/";
31
- const payloadPath = path.join(OUT_DIR, reqPath, "rsc.rsc");
32
-
33
- // ====================================================================
34
- // 1. MOCK RES: Fulfilling the ResponseProxy interface
35
- // ====================================================================
36
- // Although the contract says it returns void, internally we save
37
- // the state in case you want to log errors (e.g., a redirect at build time).
38
- const mockRes = {
39
- _statusCode: 200,
40
- _headers: {},
41
- _redirectUrl: null,
42
- _cookies: [], // Optional: for debug
43
-
44
- // 👇 ADD THIS METHOD
45
- cookie(name, value, options) {
46
- // In SSG we don't do anything real, but we save a record if you want to debug
47
- // console.log(`[SSG] Cookie set ignored: ${name}=${value}`);
48
- this._cookies.push({ name, value, options });
49
- },
50
-
51
- // clearCookie(name: string, options?: ...): void;
52
- clearCookie(name, options) {
53
- // In SSG we don't do anything, but we fulfill the contract avoiding crash
54
- },
55
-
56
- // setHeader(name: string, value: string | ReadonlyArray<string>): void;
57
- setHeader(name, value) {
58
- this._headers[name.toLowerCase()] = value;
59
- },
60
-
61
- // status(code: number): void;
62
- status(code) {
63
- this._statusCode = code;
64
- },
65
-
66
- // redirect(status: number, url: string): void;
67
- // redirect(url: string): void;
68
- redirect(arg1, arg2) {
69
- let status = 302;
70
- let url = "";
71
-
72
- if (typeof arg1 === "number") {
73
- status = arg1;
74
- url = arg2;
75
- } else {
76
- url = arg1;
77
- }
78
-
79
- this._statusCode = status;
80
- this._redirectUrl = url;
81
-
82
- // We log a warning because a redirect in SSG is usually problematic
83
- console.warn(
84
- `⚠️ [SSG] Redirect detected in ${reqPath} -> ${url} (${status})`
85
- );
86
- },
87
- };
88
-
89
- // ====================================================================
90
- // 2. MOCK REQ: Fulfilling RequestContextStore['req']
91
- // ====================================================================
92
- const mockReq = {
93
- query: {},
94
- cookies: {},
95
- headers: {
96
- "user-agent": "Dinou-SSG-Builder",
97
- host: "localhost",
98
- // Add here any default header you need
99
- },
100
- path: reqPath,
101
- method: "GET",
102
- };
103
-
104
- // 3. COMPLETE CONTEXT
105
- const mockContext = {
106
- req: mockReq,
107
- res: mockRes,
108
- };
109
-
110
- try {
111
- fs.mkdirSync(path.dirname(payloadPath), { recursive: true });
112
- const fileStream = fs.createWriteStream(payloadPath);
113
- const passThrough = new PassThrough();
114
-
115
- // 1. The entire lifecycle of the stream must be inside the storage
116
- await requestStorage.run(mockContext, async () => {
117
- const jsx = await getJSX(reqPath, {}, null, false);
118
- const { pipe } = isWebpack
119
- ? renderToPipeableStream(jsx, manifest)
120
- : renderToPipeableStream(jsx, url.pathToFileURL(process.cwd()).href + "/");
121
-
122
- pipe(passThrough);
123
- passThrough.pipe(fileStream);
124
-
125
- // 2. IMPORTANT: We wait for the file to be written COMPLETELY
126
- // before exiting the 'run' block.
127
- await new Promise((resolve, reject) => {
128
- fileStream.on("finish", resolve);
129
- fileStream.on("error", reject);
130
- // Optional: handle errors from passThrough as well
131
- passThrough.on("error", reject);
132
- });
133
- });
134
-
135
- // Post-generation validation
136
- if (mockRes._statusCode !== 200) {
137
- console.log(
138
- `⚠️ Generated RSC for ${reqPath} but logic set status to ${mockRes._statusCode}`
139
- );
140
- } else {
141
- console.log("✅ Generated RSC payload:", reqPath);
142
- }
143
- } catch (error) {
144
- console.error("❌ Error generating RSC payload for:", reqPath, error);
145
- }
146
- }
147
-
148
- console.log("🟢 Static RSC payload generation complete.");
149
- }
150
-
151
- module.exports = generateStaticRSCs;
@@ -1,31 +0,0 @@
1
- function processMetadata(effects) {
2
- if (!effects) return "";
3
-
4
- let scriptContent = "";
5
-
6
- if (effects.cookies && effects.cookies.length > 0) {
7
- effects.cookies.forEach((ck) => {
8
- const name = JSON.stringify(ck.name);
9
- const value = JSON.stringify(ck.value || "");
10
- const path = JSON.stringify(ck.options?.path || "/");
11
-
12
- if (ck.isClear) {
13
- scriptContent += `document.cookie = ${name} + "=; Max-Age=0; path=" + ${path} + ";";`;
14
- } else {
15
- scriptContent += `document.cookie = ${name} + "=" + ${value} + "; path=" + ${path} + ";";`;
16
- }
17
- });
18
- }
19
-
20
- if (effects.redirect) {
21
- scriptContent += `window.location.href = "${effects.redirect}";`;
22
- }
23
-
24
- if (!scriptContent) return "";
25
-
26
- return `<script>(function(){ ${scriptContent} })();</script>`;
27
- }
28
-
29
- module.exports = {
30
- processMetadata,
31
- };
@@ -1,11 +0,0 @@
1
- // dinou/register-loader.mjs
2
- import { register } from "node:module";
3
- import { pathToFileURL } from "node:url";
4
- import { createRequire } from "node:module";
5
-
6
- const require = createRequire(import.meta.url);
7
- globalThis.__dinou_require__ = require;
8
-
9
- const loaderPath = require.resolve("./babel-esm-loader.js");
10
-
11
- register(pathToFileURL(loaderPath).href, pathToFileURL("./"));
@@ -1,27 +0,0 @@
1
- const tsconfigPaths = require("tsconfig-paths");
2
- const path = require("path");
3
- const fs = require("fs");
4
-
5
- function getConfigFileIfExists() {
6
- const tsconfigPath = path.resolve(process.cwd(), "tsconfig.json");
7
- const jsconfigPath = path.resolve(process.cwd(), "jsconfig.json");
8
-
9
- if (fs.existsSync(tsconfigPath)) return tsconfigPath;
10
- if (fs.existsSync(jsconfigPath)) return jsconfigPath;
11
-
12
- return null;
13
- }
14
-
15
- const configFile = getConfigFileIfExists();
16
-
17
- if (configFile) {
18
- const config = require(configFile);
19
- const { baseUrl, paths } = config.compilerOptions || {};
20
-
21
- if (baseUrl && paths) {
22
- tsconfigPaths.register({
23
- baseUrl: path.resolve(process.cwd(), baseUrl),
24
- paths,
25
- });
26
- }
27
- }
@@ -1,430 +0,0 @@
1
- const path = require("path");
2
- const { fork } = require("child_process");
3
- const url = require("url");
4
- const fs = require("fs");
5
- const getJSX = require("./get-jsx.js");
6
- const { requestStorage } = require("./request-context.js");
7
-
8
- const isDevelopment = process.env.NODE_ENV !== "production";
9
- const isWebpack = process.env.DINOU_BUILD_TOOL === "webpack";
10
-
11
- const { renderToPipeableStream } = isWebpack
12
- ? require("react-server-dom-webpack/server")
13
- : require("@roggc/react-server-dom-esm/server");
14
-
15
- const manifestPath = path.resolve(
16
- process.cwd(),
17
- isWebpack
18
- ? (isDevelopment ? ".dinou/public/react-client-manifest.json" : ".dinou/dist3/react-client-manifest.json")
19
- : ".dinou/react_client_manifest/react-client-manifest.json"
20
- );
21
-
22
- let cachedManifest = null;
23
- function getManifest() {
24
- if (!isDevelopment && cachedManifest) return cachedManifest;
25
- try {
26
- const content = fs.readFileSync(manifestPath, "utf8");
27
- const parsed = JSON.parse(content);
28
- if (parsed && Object.keys(parsed).length > 0) {
29
- cachedManifest = parsed;
30
- }
31
- return cachedManifest || parsed;
32
- } catch (e) {
33
- if (cachedManifest) {
34
- console.warn("Using cached client manifest due to read error:", e.message);
35
- return cachedManifest;
36
- }
37
- console.error("Error reading client manifest:", e);
38
- return {};
39
- }
40
- }
41
-
42
- function toFileUrl(p) {
43
- return url.pathToFileURL(p).href;
44
- }
45
-
46
- const registerLoaderPath = toFileUrl(
47
- path.join(__dirname, "register-loader.mjs"),
48
- );
49
- const renderHtmlPath = path.resolve(__dirname, "render-html.js");
50
-
51
- const ESSENTIAL_NODE_ARGS = [];
52
- const loaderArg = `--import=${registerLoaderPath}`;
53
- const childExecArgv = ESSENTIAL_NODE_ARGS.concat(loaderArg);
54
-
55
- const { resolveRelativeUrl } = require("./url-resolver");
56
-
57
- function createParentResponseWrapper(reqPath, res, child) {
58
- let hasRedirected = false;
59
-
60
- const safeRedirect = (targetUrl) => {
61
- if (hasRedirected) return;
62
- hasRedirected = true;
63
-
64
- const resolvedUrl = resolveRelativeUrl(targetUrl, reqPath);
65
- let finalUrl = "/";
66
- if (
67
- typeof resolvedUrl === "string" &&
68
- resolvedUrl.startsWith("/") &&
69
- !resolvedUrl.startsWith("//")
70
- ) {
71
- finalUrl = resolvedUrl;
72
- } else {
73
- console.warn(
74
- `[Dinou Security] Blocked unsafe redirect to: ${targetUrl}`,
75
- );
76
- }
77
-
78
- if (res.headersSent) {
79
- console.log(
80
- `[Dinou] Streaming active. Redirecting via JavaScript to: ${finalUrl}`,
81
- );
82
- const safeUrl = JSON.stringify(finalUrl);
83
- res.write(`<script>window.location.href = ${safeUrl};</script>`);
84
- res.end();
85
- child.stdout.unpipe(res);
86
- child.kill();
87
- } else {
88
- res.redirect(302, finalUrl);
89
- child.stdout.unpipe(res);
90
- child.kill();
91
- }
92
- };
93
-
94
- return {
95
- setHeader: (name, value) => {
96
- if (res.headersSent) {
97
- console.warn(
98
- `[Dinou Warning] Cannot set header '${name}' because streaming started.`,
99
- );
100
- } else {
101
- res.setHeader(name, value);
102
- }
103
- },
104
- cookie: (name, value, options) => {
105
- if (res.headersSent) {
106
- if (options && options.httpOnly) {
107
- console.error(
108
- `[Dinou Error] Cannot set HttpOnly cookie '${name}' because streaming has already started.`,
109
- );
110
- return;
111
- }
112
- console.log(
113
- `[Dinou] Streaming active. Setting cookie '${name}' via JS.`,
114
- );
115
- let cookieStr = `${name}=${encodeURIComponent(value)}`;
116
- if (options) {
117
- if (options.path) cookieStr += `; path=${options.path}`;
118
- if (options.domain) cookieStr += `; domain=${options.domain}`;
119
- if (options.maxAge) cookieStr += `; max-age=${options.maxAge}`;
120
- if (options.expires)
121
- cookieStr += `; expires=${new Date(options.expires).toUTCString()}`;
122
- if (options.secure) cookieStr += `; secure`;
123
- if (options.sameSite)
124
- cookieStr += `; samesite=${options.sameSite}`;
125
- }
126
- const safeCookieStr = JSON.stringify(cookieStr);
127
- res.write(`<script>document.cookie = ${safeCookieStr};</script>`);
128
- } else {
129
- res.cookie(name, value, options);
130
- }
131
- },
132
- clearCookie: (name, options) => {
133
- if (res.headersSent) {
134
- console.log(
135
- `[Dinou] Streaming active. Clearing cookie '${name}' via JS.`,
136
- );
137
- let cookieStr = `${name}=; Max-Age=0`;
138
- const path = options?.path || "/";
139
- cookieStr += `; path=${path}`;
140
- if (options) {
141
- if (options.domain) cookieStr += `; domain=${options.domain}`;
142
- if (options.secure) cookieStr += `; secure`;
143
- if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
144
- }
145
- cookieStr += ";";
146
- const safeCookieStr = JSON.stringify(cookieStr);
147
- res.write(`<script>document.cookie = ${safeCookieStr};</script>`);
148
- } else {
149
- res.clearCookie(name, options);
150
- }
151
- },
152
- redirect: (arg1, arg2) => {
153
- const url = arg2 || arg1;
154
- safeRedirect(url);
155
- },
156
- status: (code) => {
157
- if (res.headersSent) {
158
- console.warn(
159
- `[Dinou Warning] HTTP status '${code}' ignored because streaming started.`,
160
- );
161
- } else {
162
- res.status(code);
163
- }
164
- },
165
- };
166
- }
167
-
168
- function renderAppToHtml(
169
- reqPath,
170
- paramsString,
171
- contextForChild,
172
- res,
173
- capturedStatus = null,
174
- isDynamic = false,
175
- forceNotFound = false,
176
- ) {
177
- const child = fork(
178
- renderHtmlPath,
179
- [
180
- reqPath,
181
- paramsString,
182
- contextForChild ? JSON.stringify(contextForChild) : JSON.stringify({}),
183
- isDynamic ? "true" : "false",
184
- ],
185
- {
186
- execArgv: childExecArgv,
187
- stdio: ["ignore", "pipe", "pipe", "ipc", "pipe"], // fd 4 is the RSC stream pipe
188
- },
189
- );
190
-
191
- const query = JSON.parse(paramsString || "{}");
192
- const rscPath = path.resolve(process.cwd(), ".dinou/dist2", reqPath.replace(/^\//, ""), "rsc.rsc");
193
- const hasStaticRsc = !isDynamic && fs.existsSync(rscPath);
194
-
195
- if (hasStaticRsc) {
196
- try {
197
- const rscBuffer = fs.readFileSync(rscPath);
198
- child.stdio[4].write(rscBuffer);
199
- child.stdio[4].end();
200
- } catch (err) {
201
- console.error(`[Dinou] Failed to read static RSC from ${rscPath}:`, err.message);
202
- if (child.stdio[4]) child.stdio[4].destroy();
203
- }
204
- } else {
205
- // Dynamic SSR render: render RSC in parent and pipe to fd 4
206
- const isNotFound = {};
207
- const parentRes = createParentResponseWrapper(reqPath, res, child);
208
- const context = {
209
- req: contextForChild ? contextForChild.req : {},
210
- res: parentRes,
211
- };
212
- requestStorage.run(context, () => {
213
- getJSX(reqPath, query, isNotFound, isDevelopment, forceNotFound)
214
- .then((jsx) => {
215
- if (isNotFound.value) {
216
- parentRes.status(404);
217
- }
218
- const manifest = getManifest();
219
- const { pipe } = isWebpack
220
- ? renderToPipeableStream(jsx, manifest)
221
- : renderToPipeableStream(jsx, url.pathToFileURL(process.cwd()).href + "/");
222
- pipe(child.stdio[4]);
223
- })
224
- .catch((err) => {
225
- console.error("Error rendering JSX in parent renderAppToHtml:", err);
226
- if (child.stdio[4]) child.stdio[4].destroy();
227
- });
228
- });
229
- }
230
-
231
- // 💡 on('message') Implementation (IPC Channel)
232
- // ----------------------------------------------------
233
- child.on("message", (message) => {
234
- if (message && message.type === "DINOU_CONTEXT_COMMAND") {
235
- const { command, args } = message;
236
- // console.log(
237
- // `[Dinou] Received context command from child: ${command}`,
238
- // args
239
- // );
240
- // List of supported commands for quick check
241
- if (
242
- command === "setHeader" ||
243
- command === "clearCookie" ||
244
- command === "cookie" || // ⬅️ ADDED
245
- command === "status" ||
246
- command === "redirect"
247
- ) {
248
- // ============================================================
249
- // SCENARIO 1: STREAMING ALREADY STARTED (Headers sent)
250
- // ============================================================
251
- if (res.headersSent) {
252
- // --- REDIRECT (JS Injection) ---
253
- if (command === "redirect") {
254
- const rawUrl = args.length === 1 ? args[0] : args[1];
255
- const resolvedUrl = resolveRelativeUrl(rawUrl, reqPath);
256
- console.log(
257
- `[Dinou] Streaming active. Redirecting via JavaScript to: ${resolvedUrl}`,
258
- );
259
- let finalUrl = "/";
260
- if (
261
- typeof resolvedUrl === "string" &&
262
- resolvedUrl.startsWith("/") &&
263
- !resolvedUrl.startsWith("//")
264
- ) {
265
- finalUrl = resolvedUrl;
266
- } else {
267
- console.warn(
268
- `[Dinou Security] Blocked unsafe streaming redirect to: ${rawUrl}`,
269
- );
270
- }
271
- const safeUrl = JSON.stringify(finalUrl);
272
- res.write(`<script>window.location.href = ${safeUrl};</script>`);
273
- res.end();
274
- child.stdout.unpipe(res);
275
- child.kill();
276
- return;
277
- }
278
-
279
- // --- STATUS (Warning) ---
280
- if (command === "status") {
281
- console.warn(
282
- `[Dinou Warning] HTTP status '${args[0]}' ignored because streaming started.`,
283
- );
284
- if (capturedStatus) capturedStatus.value = args[0];
285
- return;
286
- }
287
-
288
- // --- CLEAR COOKIE (JS Injection - Only Non-HttpOnly) ---
289
- if (command === "clearCookie") {
290
- const [name, options] = args;
291
- console.log(
292
- `[Dinou] Streaming active. Clearing cookie '${name}' via JS.`,
293
- );
294
- let cookieStr = `${name}=; Max-Age=0`;
295
- const path = options?.path || "/";
296
- cookieStr += `; path=${path}`;
297
- if (options) {
298
- if (options.domain) cookieStr += `; domain=${options.domain}`;
299
- if (options.secure) cookieStr += `; secure`;
300
- if (options.sameSite) cookieStr += `; samesite=${options.sameSite}`;
301
- }
302
- cookieStr += ";";
303
- const safeCookieStr = JSON.stringify(cookieStr);
304
- res.write(
305
- `<script>document.cookie = ${safeCookieStr};</script>`,
306
- );
307
- return;
308
- }
309
-
310
- // --- 🍪 SET COOKIE (New - JS Injection) ---
311
- if (command === "cookie") {
312
- const [name, value, options] = args;
313
-
314
- // ⚠️ CRITICAL WARNING:
315
- // If the cookie is HttpOnly, JS cannot write it.
316
- if (options && options.httpOnly) {
317
- console.error(
318
- `[Dinou Error] Cannot set HttpOnly cookie '${name}' because streaming has already started. ` +
319
- `Headers are sent, and document.cookie cannot write HttpOnly cookies.`,
320
- );
321
- return; // We do nothing because it would fail silently in the browser
322
- }
323
-
324
- console.log(
325
- `[Dinou] Streaming active. Setting cookie '${name}' via JS.`,
326
- );
327
-
328
- // We build the cookie string manually for JS
329
- // Format: name=value; path=/; max-age=...
330
- let cookieStr = `${name}=${encodeURIComponent(value)}`;
331
-
332
- if (options) {
333
- if (options.path) cookieStr += `; path=${options.path}`;
334
- if (options.domain) cookieStr += `; domain=${options.domain}`;
335
- if (options.maxAge) cookieStr += `; max-age=${options.maxAge}`;
336
- if (options.expires)
337
- cookieStr += `; expires=${new Date(
338
- options.expires,
339
- ).toUTCString()}`;
340
- if (options.secure) cookieStr += `; secure`;
341
- if (options.sameSite)
342
- cookieStr += `; samesite=${options.sameSite}`;
343
- }
344
-
345
- // console.log(`[Dinou] Cookie string to set: ${cookieStr}`);
346
- const safeCookieStr = JSON.stringify(cookieStr);
347
- res.write(`<script>document.cookie = ${safeCookieStr};</script>`);
348
- return;
349
- }
350
-
351
- // --- SET HEADER (Warning) ---
352
- if (command === "setHeader") {
353
- console.warn(
354
- `[Dinou Warning] Cannot set header '${args[0]}' because streaming started.`,
355
- );
356
- return;
357
- }
358
- }
359
- }
360
-
361
- // ============================================================
362
- // SCENARIO 2: HEADERS NOT YET SENT (Normal Express usage)
363
- // ============================================================
364
- if (typeof res[command] === "function") {
365
- // Special safe redirect handling
366
- if (command === "redirect") {
367
- // 1. Normalize arguments (Status and URL)
368
- let status = 302; // Express Default
369
- let rawUrl = "";
370
-
371
- if (args.length === 2) {
372
- // Signature: redirect(status, url)
373
- status = args[0];
374
- rawUrl = args[1];
375
- } else {
376
- // Signature: redirect(url)
377
- rawUrl = args[0];
378
- }
379
-
380
- // Resolve relative URL relative to reqPath
381
- const resolvedUrl = resolveRelativeUrl(rawUrl, reqPath);
382
-
383
- // 2. Security Logic (Safe Redirect)
384
- // We allow only relative routes starting with '/' but not '//'
385
- let finalUrl = "/";
386
-
387
- if (
388
- typeof resolvedUrl === "string" &&
389
- resolvedUrl.startsWith("/") &&
390
- !resolvedUrl.startsWith("//")
391
- ) {
392
- finalUrl = resolvedUrl;
393
- } else {
394
- console.warn(
395
- `[Dinou Security] Blocked unsafe redirect to: ${rawUrl}`,
396
- );
397
- // finalUrl remains "/"
398
- }
399
-
400
- // 3. Execute safe redirect always using (status, url)
401
- res.redirect.apply(res, [status, finalUrl]);
402
- child.stdout.unpipe(res);
403
- child.kill();
404
- return;
405
- }
406
-
407
- // Standard execution (cookie, clearCookie, status, setHeader)
408
- res[command].apply(res, args);
409
- } else {
410
- console.error(`[Dinou] Unknown context command: ${command}`);
411
- }
412
- }
413
- });
414
- // ----------------------------------------------------
415
-
416
- // Capture child errors
417
- child.on("error", (err) => {
418
- console.error("Failed to start child process:", err);
419
- });
420
-
421
- // Listen to stderr, replicating the behavior of the original function
422
- child.stderr.on("data", (chunk) => {
423
- console.error(chunk.toString());
424
- });
425
-
426
- // ⬅️ Return identical to original, returning only the HTML stream
427
- return child.stdout;
428
- }
429
-
430
- module.exports = renderAppToHtml;