mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-05-28 10:01:31 +02:00
ef6b8ec181
build-frontend-export.cjs stages a desktop-only frontend export tree and
strips the ``force-dynamic`` + ``revalidate`` directives from
``frontend/src/app/layout.tsx`` so Next's ``output: "export"`` can
prerender every route.
The strip regexes only matched LF (``\n``). Any Windows checkout without
``core.autocrlf=input`` has CRLF line endings, the strip silently
no-op'd, and the desktop build failed at the static-export step:
Error: Page with `dynamic = "force-dynamic"` couldn't be exported.
`output: "export"` requires all pages be renderable statically
because there is no runtime server to dynamically render routes
in this output format.
Export encountered an error on /_not-found/page: /_not-found
Reaches every Windows contributor who hasn't normalized line endings
locally. Replacing each ``\n`` in the strip regexes with ``\r?\n``
makes the strip CRLF-tolerant; LF behavior is unchanged.
Verified by running both regexes against the actual layout.tsx (302
bytes removed, force-dynamic + revalidate both gone) and against a
synthetic LF input (296 bytes removed, same outcome).
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
120 lines
3.6 KiB
JavaScript
120 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { spawnSync } = require('node:child_process');
|
|
|
|
const scriptDir = __dirname;
|
|
const tauriDir = path.resolve(scriptDir, '..');
|
|
const repoRoot = path.resolve(tauriDir, '..', '..');
|
|
const frontendDir = path.join(repoRoot, 'frontend');
|
|
const buildRoot = path.join(repoRoot, '.desktop-export-build');
|
|
const buildFrontendDir = path.join(buildRoot, 'frontend');
|
|
const buildOutDir = path.join(buildFrontendDir, 'out');
|
|
const liveOutDir = path.join(frontendDir, 'out');
|
|
const excludedPaths = [
|
|
'node_modules',
|
|
'.next',
|
|
'out',
|
|
'src/app/api',
|
|
'src/middleware.ts',
|
|
];
|
|
|
|
function normalizeRelativePath(target) {
|
|
return target.split(path.sep).join('/');
|
|
}
|
|
|
|
function shouldCopy(srcPath) {
|
|
const relativePath = path.relative(frontendDir, srcPath);
|
|
if (!relativePath) {
|
|
return true;
|
|
}
|
|
|
|
const normalized = normalizeRelativePath(relativePath);
|
|
return !excludedPaths.some(
|
|
(excluded) => normalized === excluded || normalized.startsWith(`${excluded}/`),
|
|
);
|
|
}
|
|
|
|
function prepareBuildTree() {
|
|
fs.rmSync(buildRoot, { recursive: true, force: true });
|
|
fs.cpSync(frontendDir, buildFrontendDir, {
|
|
recursive: true,
|
|
filter: shouldCopy,
|
|
});
|
|
|
|
const stagedLayoutPath = path.join(buildFrontendDir, 'src', 'app', 'layout.tsx');
|
|
if (fs.existsSync(stagedLayoutPath)) {
|
|
const layoutSource = fs.readFileSync(stagedLayoutPath, 'utf8');
|
|
// CRLF compatibility: on Windows checkouts without ``core.autocrlf=input``
|
|
// (the default) layout.tsx has CRLF line endings, but the original regexes
|
|
// only matched LF. The strip silently no-op'd, ``force-dynamic`` stayed,
|
|
// and Next's static-export refused to render ``/_not-found`` ("Page with
|
|
// `dynamic = \"force-dynamic\"` couldn't be exported"). Use ``\r?\n`` so
|
|
// the strip works regardless of line-ending normalization.
|
|
fs.writeFileSync(
|
|
stagedLayoutPath,
|
|
layoutSource
|
|
.replace(/\r?\n\/\/ The dashboard is a live local runtime[\s\S]*?client polling ever hydrates\.\r?\n/g, '\n')
|
|
.replace(/\r?\nexport const dynamic = ['"]force-dynamic['"];\r?\n/g, '\n')
|
|
.replace(/\r?\nexport const revalidate = 0;\r?\n/g, '\n'),
|
|
);
|
|
}
|
|
|
|
const liveNodeModules = path.join(frontendDir, 'node_modules');
|
|
const stagedNodeModules = path.join(buildFrontendDir, 'node_modules');
|
|
if (!fs.existsSync(liveNodeModules)) {
|
|
throw new Error(`Missing frontend/node_modules at ${liveNodeModules}`);
|
|
}
|
|
fs.symlinkSync(liveNodeModules, stagedNodeModules, 'junction');
|
|
}
|
|
|
|
function runExportBuild() {
|
|
const env = {
|
|
...process.env,
|
|
NEXT_OUTPUT: 'export',
|
|
};
|
|
|
|
const result =
|
|
process.platform === 'win32'
|
|
? spawnSync(
|
|
process.env.ComSpec || 'cmd.exe',
|
|
['/d', '/s', '/c', 'npm.cmd run build -- --webpack'],
|
|
{
|
|
cwd: buildFrontendDir,
|
|
env,
|
|
stdio: 'inherit',
|
|
},
|
|
)
|
|
: spawnSync('npm', ['run', 'build', '--', '--webpack'], {
|
|
cwd: buildFrontendDir,
|
|
env,
|
|
stdio: 'inherit',
|
|
});
|
|
|
|
if (result.error) {
|
|
throw result.error;
|
|
}
|
|
if (typeof result.status === 'number' && result.status !== 0) {
|
|
throw new Error(`Frontend export build failed with exit code ${result.status}.`);
|
|
}
|
|
}
|
|
|
|
function syncBuildOutput() {
|
|
if (!fs.existsSync(buildOutDir)) {
|
|
throw new Error(`Desktop export did not produce ${buildOutDir}`);
|
|
}
|
|
fs.rmSync(liveOutDir, { recursive: true, force: true });
|
|
fs.cpSync(buildOutDir, liveOutDir, {
|
|
recursive: true,
|
|
});
|
|
}
|
|
|
|
try {
|
|
prepareBuildTree();
|
|
runExportBuild();
|
|
syncBuildOutput();
|
|
} finally {
|
|
fs.rmSync(buildRoot, { recursive: true, force: true });
|
|
}
|