added a way to bundle the whole app
This commit is contained in:
parent
872d29f3e1
commit
249c88da83
5 changed files with 119 additions and 116 deletions
208
server.js
208
server.js
|
|
@ -7,7 +7,9 @@ import { fileURLToPath } from 'url';
|
|||
import { parsePatchFiles, DEFAULT_THEMES } from '@pierre/diffs';
|
||||
import { preloadFileDiff } from '@pierre/diffs/ssr';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const __dirname = typeof __filename !== 'undefined'
|
||||
? dirname(__filename)
|
||||
: dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
// --- arg parsing ---
|
||||
|
||||
|
|
@ -86,76 +88,75 @@ if (files.length === 0) {
|
|||
process.exit(0);
|
||||
}
|
||||
|
||||
// --- SSR diffs ---
|
||||
// --- SSR diffs + helpers + mode dispatch ---
|
||||
|
||||
const ssrOptions = {
|
||||
theme: DEFAULT_THEMES,
|
||||
themeType: 'system',
|
||||
diffStyle: 'unified',
|
||||
useTokenTransformer: true,
|
||||
};
|
||||
(async () => {
|
||||
const ssrOptions = {
|
||||
theme: DEFAULT_THEMES,
|
||||
themeType: 'system',
|
||||
diffStyle: 'unified',
|
||||
useTokenTransformer: true,
|
||||
};
|
||||
|
||||
const renderedFiles = [];
|
||||
for (const fileDiff of files) {
|
||||
try {
|
||||
const result = await preloadFileDiff({ fileDiff, options: ssrOptions });
|
||||
renderedFiles.push({
|
||||
name: fileDiff.name || 'unknown',
|
||||
prerenderedHTML: result.prerenderedHTML,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(`Failed to SSR-render ${fileDiff.name || 'unknown'}: ${err.message}`);
|
||||
renderedFiles.push({
|
||||
name: fileDiff.name || 'unknown',
|
||||
prerenderedHTML: null,
|
||||
});
|
||||
const renderedFiles = [];
|
||||
for (const fileDiff of files) {
|
||||
try {
|
||||
const result = await preloadFileDiff({ fileDiff, options: ssrOptions });
|
||||
renderedFiles.push({
|
||||
name: fileDiff.name || 'unknown',
|
||||
prerenderedHTML: result.prerenderedHTML,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(`Failed to SSR-render ${fileDiff.name || 'unknown'}: ${err.message}`);
|
||||
renderedFiles.push({
|
||||
name: fileDiff.name || 'unknown',
|
||||
prerenderedHTML: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
const finalPatch = patch;
|
||||
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function fileId(name) {
|
||||
return 'DF-' + name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
function fileId(name) {
|
||||
return 'DF-' + name.replace(/[^a-zA-Z0-9_-]/g, '_');
|
||||
}
|
||||
|
||||
// --- HTML template ---
|
||||
function pageHtml(opts) {
|
||||
const { mode, sidebarHtml, extraScript } = opts;
|
||||
|
||||
function pageHtml(opts) {
|
||||
const { mode, sidebarHtml, extraScript } = opts;
|
||||
|
||||
const fileSections = renderedFiles.map((f) => {
|
||||
const escapedName = escapeHtml(f.name);
|
||||
const id = fileId(f.name);
|
||||
if (f.prerenderedHTML !== null) {
|
||||
return `
|
||||
const fileSections = renderedFiles.map((f) => {
|
||||
const escapedName = escapeHtml(f.name);
|
||||
const id = fileId(f.name);
|
||||
if (f.prerenderedHTML !== null) {
|
||||
return `
|
||||
<div class="diff-file" id="${id}">
|
||||
<div class="diff-file-header">${escapedName}</div>
|
||||
<diffs-container>
|
||||
<template shadowrootmode="open">${f.prerenderedHTML}</template>
|
||||
</diffs-container>
|
||||
</div>`;
|
||||
}
|
||||
return `
|
||||
}
|
||||
return `
|
||||
<div class="diff-file" id="${id}">
|
||||
<div class="diff-file-header">${escapedName}</div>
|
||||
<div class="diff-fallback"><a href="/patch">View raw patch</a></div>
|
||||
</div>`;
|
||||
}).join('\n');
|
||||
}).join('\n');
|
||||
|
||||
const hasLocal = stagedDiff || unstagedDiff || untrackedPatch;
|
||||
const subtitle = hasLocal
|
||||
? `${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)} (includes local changes)`
|
||||
: `${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)}`;
|
||||
const title = `Diff: ${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)}`;
|
||||
const rawLink = mode === 'server'
|
||||
? '<a class="raw-link" href="/patch">View raw patch \u2192</a>'
|
||||
: '';
|
||||
const hasLocal = stagedDiff || unstagedDiff || untrackedPatch;
|
||||
const subtitle = hasLocal
|
||||
? `${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)} (includes local changes)`
|
||||
: `${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)}`;
|
||||
const title = `Diff: ${escapeHtml(mainBranch)} \u2192 ${escapeHtml(featureBranch)}`;
|
||||
const rawLink = mode === 'server'
|
||||
? '<a class="raw-link" href="/patch">View raw patch \u2192</a>'
|
||||
: '';
|
||||
|
||||
return `<!DOCTYPE html>
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
|
@ -262,21 +263,19 @@ function pageHtml(opts) {
|
|||
${extraScript}
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
}
|
||||
|
||||
// --- static HTML generation (shared by --static and --out) ---
|
||||
|
||||
function buildStaticHtml() {
|
||||
const sidebarHtml = `
|
||||
function buildStaticHtml() {
|
||||
const sidebarHtml = `
|
||||
<div class="sidebar">
|
||||
<div class="file-list">
|
||||
${renderedFiles.map(f => ` <a class="file-item" href="#${fileId(f.name)}">${escapeHtml(f.name)}</a>`).join('\n')}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
const escapedPatch = escapeHtml(patch);
|
||||
const escapedPatch = escapeHtml(finalPatch);
|
||||
|
||||
const extraScript = ` <script>
|
||||
const extraScript = ` <script>
|
||||
document.querySelectorAll('.file-item').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
const id = el.getAttribute('href').slice(1);
|
||||
|
|
@ -293,64 +292,61 @@ ${renderedFiles.map(f => ` <a class="file-item" href="#${fileId(f.name)
|
|||
});
|
||||
</script>`;
|
||||
|
||||
return pageHtml({ mode: 'static', sidebarHtml, extraScript })
|
||||
.replace('</body>', ` <details style="padding:0 24px 16px">
|
||||
return pageHtml({ mode: 'static', sidebarHtml, extraScript })
|
||||
.replace('</body>', ` <details style="padding:0 24px 16px">
|
||||
<summary id="patch-toggle" style="cursor:pointer;font-size:0.8125rem;color:#2563eb">View raw patch</summary>
|
||||
<pre id="patch-content" class="raw-patch">${escapedPatch}</pre>
|
||||
</details>\n</body>`);
|
||||
}
|
||||
|
||||
// --- mode dispatch ---
|
||||
|
||||
if (outFile) {
|
||||
// -- out mode: write HTML to specified path, exit --
|
||||
const html = buildStaticHtml();
|
||||
writeFileSync(outFile, html, 'utf-8');
|
||||
console.log(`Written to ${outFile}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (isStatic) {
|
||||
// -- static mode: generate self-contained HTML file, open in browser, exit --
|
||||
const html = buildStaticHtml();
|
||||
const tmpFile = join(tmpdir(), `difftool-${Date.now()}.html`);
|
||||
writeFileSync(tmpFile, html, 'utf-8');
|
||||
|
||||
const openCmd = process.platform === 'darwin' ? 'open'
|
||||
: process.platform === 'win32' ? 'start'
|
||||
: 'xdg-open';
|
||||
console.log(`Opening ${tmpFile}`);
|
||||
try {
|
||||
spawn(openCmd, [tmpFile], { detached: true, stdio: 'ignore' }).unref();
|
||||
} catch {
|
||||
console.log(`HTML file saved to ${tmpFile}`);
|
||||
}
|
||||
} else {
|
||||
// -- server mode: start Express --
|
||||
|
||||
const app = express();
|
||||
app.use(express.static(join(__dirname, 'public')));
|
||||
// --- mode dispatch ---
|
||||
|
||||
app.get('/patch', (req, res) => {
|
||||
res.type('text/plain').send(patch);
|
||||
});
|
||||
if (outFile) {
|
||||
const html = buildStaticHtml();
|
||||
writeFileSync(outFile, html, 'utf-8');
|
||||
console.log(`Written to ${outFile}`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const fileDataJson = JSON.stringify(renderedFiles.map(f => ({ name: f.name })))
|
||||
.replace(/<\/script>/gi, '<\\/script>');
|
||||
if (isStatic) {
|
||||
const html = buildStaticHtml();
|
||||
const tmpFile = join(tmpdir(), `difftool-${Date.now()}.html`);
|
||||
writeFileSync(tmpFile, html, 'utf-8');
|
||||
|
||||
const sidebarHtml = '<div class="sidebar"><div id="file-tree"></div></div>';
|
||||
const openCmd = process.platform === 'darwin' ? 'open'
|
||||
: process.platform === 'win32' ? 'start'
|
||||
: 'xdg-open';
|
||||
console.log(`Opening ${tmpFile}`);
|
||||
try {
|
||||
spawn(openCmd, [tmpFile], { detached: true, stdio: 'ignore' }).unref();
|
||||
} catch {
|
||||
console.log(`HTML file saved to ${tmpFile}`);
|
||||
}
|
||||
} else {
|
||||
const app = express();
|
||||
app.use(express.static(join(__dirname, 'public')));
|
||||
|
||||
const extraScript = `
|
||||
app.get('/patch', (req, res) => {
|
||||
res.type('text/plain').send(finalPatch);
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
const fileDataJson = JSON.stringify(renderedFiles.map(f => ({ name: f.name })))
|
||||
.replace(/<\/script>/gi, '<\\/script>');
|
||||
|
||||
const sidebarHtml = '<div class="sidebar"><div id="file-tree"></div></div>';
|
||||
|
||||
const extraScript = `
|
||||
<script id="diff-files-data" type="application/json">${fileDataJson}</script>
|
||||
<script type="module" src="/client.js"></script>`;
|
||||
|
||||
const html = pageHtml({ mode: 'server', sidebarHtml, extraScript });
|
||||
res.type('html').send(html);
|
||||
});
|
||||
const html = pageHtml({ mode: 'server', sidebarHtml, extraScript });
|
||||
res.type('html').send(html);
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Difftool running at http://localhost:${port}`);
|
||||
console.log(`Patch endpoint: http://localhost:${port}/patch`);
|
||||
});
|
||||
}
|
||||
app.listen(port, () => {
|
||||
console.log(`Difftool running at http://localhost:${port}`);
|
||||
console.log(`Patch endpoint: http://localhost:${port}/patch`);
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue