85 lines
No EOL
2.9 KiB
JavaScript
85 lines
No EOL
2.9 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Patch Next 16's dev-only cross-site blocker so the dev server accepts
|
|
* requests from any host / IP / tunneled domain (e.g. `coolify.mywire.org`).
|
|
*
|
|
* Why this exists
|
|
* ---------------
|
|
* Next 16's dev server calls `blockCrossSiteDEV(...)` on every internal
|
|
* request (`/_next/hmr`, etc.) and blocks the request unless the
|
|
* `Origin` hostname matches one of:
|
|
* - `localhost`
|
|
* - `**.localhost`
|
|
* - any literal entry in `next.config.js` → `allowedDevOrigins`
|
|
* - the `hostname` flag passed to `next dev -H <host>`
|
|
*
|
|
* The matcher in `next/dist/server/app-render/csrf-protection.js`
|
|
* (`matchWildcardDomain`) **rejects the literal `*` and `**` as the only
|
|
* segment** of a pattern, so we cannot wildcard the allowlist through the
|
|
* documented config. To make the dev server reachable from any host —
|
|
* which is what we want when running behind Coolify, a tunnel, or just
|
|
* any LAN device — we replace the function body with a no-op.
|
|
*
|
|
* Production behaviour is unaffected: this module is only loaded by
|
|
* `next dev`, never by `next build` / `next start`.
|
|
*
|
|
* Idempotent: running this script repeatedly is safe; already-patched
|
|
* files are detected and skipped.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
|
|
const MARKER = "/* PATCHED: allow any host in dev (scripts/patch-next-dev-blocker.cjs) */";
|
|
|
|
const FILES = [
|
|
"node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js",
|
|
"node_modules/next/dist/esm/server/lib/router-utils/block-cross-site-dev.js",
|
|
];
|
|
|
|
const REPLACEMENT = `${MARKER}
|
|
const blockCrossSiteDEV = (req, res, allowedDevOrigins, hostname) => {
|
|
// Patched: allow any host / IP / tunneled domain in development.
|
|
// Original implementation is preserved above in source maps for debugging.
|
|
return false;
|
|
};`;
|
|
|
|
// Match `const blockCrossSiteDEV = (req, res, ...) => { ... };` — greedy match
|
|
// to the closing brace + semicolon at the start of a line.
|
|
const FUNCTION_REGEX = /const blockCrossSiteDEV\s*=\s*\(req[^)]*\)\s*=>\s*\{[\s\S]*?\n\};/;
|
|
|
|
let patched = 0;
|
|
let already = 0;
|
|
let missing = 0;
|
|
let failed = 0;
|
|
|
|
for (const rel of FILES) {
|
|
const abs = path.resolve(process.cwd(), rel);
|
|
if (!fs.existsSync(abs)) {
|
|
console.warn(` ! ${rel} (not found, skipping)`);
|
|
missing++;
|
|
continue;
|
|
}
|
|
const original = fs.readFileSync(abs, "utf8");
|
|
if (original.includes(MARKER)) {
|
|
console.log(` = ${rel} (already patched)`);
|
|
already++;
|
|
continue;
|
|
}
|
|
if (!FUNCTION_REGEX.test(original)) {
|
|
console.warn(` ! ${rel} (could not locate function, skipping — Next version may have changed)`);
|
|
failed++;
|
|
continue;
|
|
}
|
|
const updated = original.replace(FUNCTION_REGEX, REPLACEMENT);
|
|
fs.writeFileSync(abs, updated, "utf8");
|
|
console.log(` ✓ ${rel}`);
|
|
patched++;
|
|
}
|
|
|
|
console.log(
|
|
`\nDone. ${patched} patched · ${already} already patched · ${failed} skipped · ${missing} not found.`,
|
|
);
|
|
process.exit(failed > 0 ? 1 : 0); |