Refactor the project to support multiple applications, centralized build process,
and deployment to Cloudflare Pages.
- **Restructuring:**
- Moved static HTML pages (game.html, german.html) and the SnapMark React app source into .
- Moved the main landing page (index.html) into .
- Consolidated output to a directory.
- **Build System:**
- Created to automate:
- Building Vite-based apps (e.g., SnapMark).
- Processing static HTML apps (injecting dynamic navigation).
- Generating static client-side redirects for old URLs (/game.html -> /hexarow/).
- **Navigation:**
- Implemented a dynamic navigation bar across all pages.
- **Deployment:**
- Removed Durable Objects and worker-specific configurations.
- Configured and for Cloudflare Pages deployment.
- Updated
> cffy.in@0.0.1 deploy
> wrangler pages deploy dist --project-name cffyin
⛅️ wrangler 4.54.0
───────────────────
Uploading... (7/7)
✨ Success! Uploaded 0 files (7 already uploaded) (0.34 sec)
🌎 Deploying...
✨ Deployment complete! Take a peek over at https://a00cadbf.cffyin.pages.dev
✨ Deployment alias URL: https://master.cffyin.pages.dev to target the Cloudflare Pages project.
150 lines
5.0 KiB
JavaScript
150 lines
5.0 KiB
JavaScript
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const { execSync } = require('node:child_process');
|
|
|
|
const SRC_DIR = path.join(__dirname, '../src');
|
|
const DIST_DIR = path.join(__dirname, '../dist');
|
|
const APPS_DIR = path.join(SRC_DIR, 'apps');
|
|
|
|
// Standard Navigation Template
|
|
// Note: We use absolute paths (/) so they work from any depth, assuming Cloudflare Pages root.
|
|
const NAV_TEMPLATE = `
|
|
<nav class="bg-white/70 backdrop-blur-sm border-b-2 border-amber-800 p-3 sticky top-0 z-20">
|
|
<div class="max-w-6xl mx-auto flex justify-between items-center font-impact text-amber-900">
|
|
<a href="/" class="text-2xl font-black hover:text-amber-700 transition-colors">cffyin</a>
|
|
<div class="flex gap-6 text-lg items-center">
|
|
<a href="/" class="hover:underline">Home</a>
|
|
<a href="/german-trainer/" class="hover:underline">German Trainer</a>
|
|
<a href="/hexarow/" class="hover:underline">Hex-a-Row</a>
|
|
<a href="/snapmark/" class="hover:underline">SnapMark</a>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
`;
|
|
|
|
function cleanDist() {
|
|
if (fs.existsSync(DIST_DIR)) {
|
|
fs.rmSync(DIST_DIR, { recursive: true, force: true });
|
|
}
|
|
fs.mkdirSync(DIST_DIR);
|
|
}
|
|
|
|
function processHtml(filePath, outPath) {
|
|
let content = fs.readFileSync(filePath, 'utf-8');
|
|
|
|
// Simple regex-based injection.
|
|
// 1. Remove existing <nav>...</nav> (if any) to avoid duplication.
|
|
// 2. Inject new NAV_TEMPLATE at the start of <body>.
|
|
|
|
// Remove existing nav
|
|
content = content.replace(/<nav[\s\S]*?<\/nav>/i, '');
|
|
|
|
// Inject new nav
|
|
if (content.includes('<body')) {
|
|
content = content.replace(/<body([^>]*)>/i, `<body$1>
|
|
${NAV_TEMPLATE}`);
|
|
} else {
|
|
// Fallback if no body tag found (unlikely for valid HTML)
|
|
content = NAV_TEMPLATE + content;
|
|
}
|
|
|
|
fs.writeFileSync(outPath, content);
|
|
console.log(`Processed ${filePath} -> ${outPath}`);
|
|
}
|
|
|
|
function buildLanding() {
|
|
const landingSrc = path.join(__dirname, '..', 'src', 'landing', 'index.html');
|
|
const landingDist = path.join(DIST_DIR, 'index.html');
|
|
if (fs.existsSync(landingSrc)) {
|
|
processHtml(landingSrc, landingDist);
|
|
} else {
|
|
console.error("Landing page not found!");
|
|
}
|
|
}
|
|
|
|
function buildApps() {
|
|
if (!fs.existsSync(APPS_DIR)) return;
|
|
|
|
const apps = fs.readdirSync(APPS_DIR);
|
|
|
|
apps.forEach(app => {
|
|
const appSrc = path.join(APPS_DIR, app);
|
|
const appDist = path.join(DIST_DIR, app);
|
|
|
|
if (!fs.statSync(appSrc).isDirectory()) return;
|
|
|
|
fs.mkdirSync(appDist, { recursive: true });
|
|
|
|
// Check if it's a Vite app (has package.json and vite.config.ts)
|
|
if (fs.existsSync(path.join(appSrc, 'package.json')) && fs.existsSync(path.join(appSrc, 'vite.config.ts'))) {
|
|
console.log(`Building Vite app: ${app}...`);
|
|
try {
|
|
// Install dependencies if needed
|
|
if (!fs.existsSync(path.join(appSrc, 'node_modules'))) {
|
|
console.log(`Installing dependencies for ${app}...`);
|
|
execSync('npm install', { cwd: appSrc, stdio: 'inherit' });
|
|
}
|
|
|
|
// Build
|
|
execSync('npm run build', { cwd: appSrc, stdio: 'inherit' });
|
|
|
|
// Copy dist contents
|
|
const viteDist = path.join(appSrc, 'dist');
|
|
if (fs.existsSync(viteDist)) {
|
|
fs.cpSync(viteDist, appDist, { recursive: true });
|
|
} else {
|
|
console.error(`Vite build failed to produce 'dist' for ${app}`);
|
|
}
|
|
} catch (e) {
|
|
console.error(`Failed to build ${app}:`, e);
|
|
}
|
|
} else {
|
|
// Standard HTML app
|
|
console.log(`Processing HTML app: ${app}...`);
|
|
const files = fs.readdirSync(appSrc);
|
|
files.forEach(file => {
|
|
const srcFile = path.join(appSrc, file);
|
|
const distFile = path.join(appDist, file);
|
|
|
|
if (file.endsWith('.html')) {
|
|
processHtml(srcFile, distFile);
|
|
} else {
|
|
fs.cpSync(srcFile, distFile, { recursive: true });
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
function createRedirect(filename, target) {
|
|
const content = `<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="refresh" content="0; url=${target}" />
|
|
<script>window.location.href = "${target}";</script>
|
|
<title>Redirecting...</title>
|
|
</head>
|
|
<body>
|
|
<p>If you are not redirected automatically, follow this <a href="${target}">link</a>.</p>
|
|
</body>
|
|
</html>`;
|
|
const filePath = path.join(DIST_DIR, filename);
|
|
fs.writeFileSync(filePath, content);
|
|
console.log(`Created static redirect: ${filename} -> ${target}`);
|
|
}
|
|
|
|
function main() {
|
|
console.log("Starting Build...");
|
|
cleanDist();
|
|
buildLanding();
|
|
buildApps();
|
|
|
|
// Create static redirects for legacy paths
|
|
createRedirect('game.html', '/hexarow/');
|
|
createRedirect('german.html', '/german-trainer/');
|
|
|
|
console.log("Build Complete!");
|
|
}
|
|
|
|
main();
|