feat: Implement monorepo-like structure and Cloudflare Pages deployment

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.
This commit is contained in:
Coffee In 2025-12-13 10:32:40 +00:00
parent 8ed79cbd0b
commit 74388a1669
29 changed files with 1745 additions and 10951 deletions

1
.wrangler Symbolic link
View File

@ -0,0 +1 @@
/home/ddalex/.config/.wrangler/

15
GEMINI.md Normal file
View File

@ -0,0 +1,15 @@
# Project Context
This app has been modified to be on the skeleton of a cloudflare app so it can be deployed on cloudflare.
## Status
- Fixed `wrangler deploy` error: "Worker written as a service-worker".
- Removed Durable Objects and Workers logic to convert to a **Static Cloudflare Pages** site.
- Restructured project:
- `src/apps/`: Contains all sub-applications (hexarow, german-trainer, snapmark).
- `src/landing/`: Contains the main landing page.
- `dist/`: Generated build output served by Cloudflare.
- Created `scripts/build.js` to manage builds, inject a standard Navigation Bar, and generate static redirects.
- Updated `wrangler.jsonc` and `package.json` for Cloudflare Pages deployment.
- Successfully deployed to `https://cffyin.pages.dev` (and alias `master.cffyin.pages.dev`).

4
PLAN.md Normal file
View File

@ -0,0 +1,4 @@
# Plan
- [x] Analyze Logs and Create Documentation <!-- id: 0 -->
- [x] Fix Deployment Issues <!-- id: 1 -->

1552
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,16 @@
{ {
"name": "muddy-frost-c06d", "name": "cffy.in",
"version": "0.0.0", "version": "0.0.1",
"private": true, "private": true,
"scripts": { "scripts": {
"deploy": "wrangler deploy", "deploy": "wrangler pages deploy dist --project-name cffyin",
"dev": "wrangler dev", "dev": "wrangler pages dev dist",
"start": "wrangler dev", "start": "wrangler pages dev dist",
"cf-typegen": "wrangler types" "cf-typegen": "wrangler types",
"build": "node scripts/build.js"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^5.5.2", "typescript": "^5.5.2",
"wrangler": "^4.54.0" "wrangler": "^4.54.0"
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,24 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

149
scripts/build.js Normal file
View File

@ -0,0 +1,149 @@
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();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
src/landing/index.html Normal file

File diff suppressed because one or more lines are too long

10850
worker-configuration.d.ts vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +1,9 @@
/**
* For more details on how to configure Wrangler, refer to:
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{ {
"$schema": "node_modules/wrangler/config-schema.json", "$schema": "node_modules/wrangler/config-schema.json",
"name": "muddy-frost-c06d", "name": "cffyin",
"main": "src/index.ts", "pages_build_output_dir": "./dist",
"compatibility_date": "2025-12-13", "compatibility_date": "2025-12-13",
"migrations": [
{
"new_sqlite_classes": [
"MyDurableObject"
],
"tag": "v1"
}
],
"assets": {
// The path to the directory containing the `index.html` file to be served at `/`
"directory": "./public"
},
"durable_objects": {
"bindings": [
{
"class_name": "MyDurableObject",
"name": "MY_DURABLE_OBJECT"
}
]
},
"observability": { "observability": {
"enabled": true "enabled": true
} }
/** }
* Smart Placement
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
*/
// "placement": { "mode": "smart" }
/**
* Bindings
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
* databases, object storage, AI inference, real-time communication and more.
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
*/
/**
* Environment Variables
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
*/
// "vars": { "MY_VARIABLE": "production_value" }
/**
* Note: Use secrets to store sensitive data.
* https://developers.cloudflare.com/workers/configuration/secrets/
*/
/**
* Static Assets
* https://developers.cloudflare.com/workers/static-assets/binding/
*/
// "assets": { "directory": "./public/", "binding": "ASSETS" }
/**
* Service Bindings (communicate between multiple Workers)
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
*/
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
}