From b485e9b707791a93a3bdfb77218d74298b7037e9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 31 Mar 2026 01:47:51 +0000 Subject: [PATCH] Add Cloudflare Pages deployment support Agent-Logs-Url: https://github.com/cogpy/influent/sessions/bc1ef1c1-5540-466b-9792-8ffff21c5d4b Co-authored-by: drzo <15202748+drzo@users.noreply.github.com> --- .gitignore | 3 +- _headers | 29 +++++++++++++++ build-cloudflare.sh | 56 +++++++++++++++++++++++++++++ functions/influent/rest/[[path]].js | 34 ++++++++++++++++++ functions/influent/rpc/[[path]].js | 34 ++++++++++++++++++ wrangler.toml | 29 +++++++++++++++ 6 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 _headers create mode 100755 build-cloudflare.sh create mode 100644 functions/influent/rest/[[path]].js create mode 100644 functions/influent/rpc/[[path]].js create mode 100644 wrangler.toml diff --git a/.gitignore b/.gitignore index 66ba896..08fada2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ target .idea .settings .project -dependency-reduced-pom.xml \ No newline at end of file +dependency-reduced-pom.xml +dist/ \ No newline at end of file diff --git a/_headers b/_headers new file mode 100644 index 0000000..768305b --- /dev/null +++ b/_headers @@ -0,0 +1,29 @@ +# Cloudflare Pages – HTTP response headers +# https://developers.cloudflare.com/pages/configuration/headers/ + +# Default security headers for all responses +/* + X-Content-Type-Options: nosniff + X-Frame-Options: SAMEORIGIN + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: geolocation=(), microphone=(), camera=() + +# Long-lived cache for versioned static assets (scripts, styles, images) +/scripts/* + Cache-Control: public, max-age=31536000, immutable + +/theme/* + Cache-Control: public, max-age=31536000, immutable + +/img/* + Cache-Control: public, max-age=31536000, immutable + +/aperture/* + Cache-Control: public, max-age=31536000, immutable + +/bootstrap/* + Cache-Control: public, max-age=31536000, immutable + +# Never cache the main HTML entry point +/index.html + Cache-Control: no-cache, no-store, must-revalidate diff --git a/build-cloudflare.sh b/build-cloudflare.sh new file mode 100755 index 0000000..8004000 --- /dev/null +++ b/build-cloudflare.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# build-cloudflare.sh – Build an Influent app WAR and extract its static +# assets into dist/ for deployment to Cloudflare Pages. +# +# Usage: +# ./build-cloudflare.sh [APP] +# +# APP – Maven module to build. One of: influent-app, bitcoin, kiva, walker +# Defaults to influent-app. +# +# After this script completes, deploy with: +# wrangler pages deploy dist/ --project-name influent +# +# Prerequisites: Java 8+, Maven 3.1+, unzip + +set -euo pipefail + +APP="${1:-influent-app}" + +VALID_APPS="influent-app bitcoin kiva walker" +if ! echo "$VALID_APPS" | tr ' ' '\n' | grep -qx "$APP"; then + echo "ERROR: Unknown app '$APP'. Choose one of: $VALID_APPS" >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +echo "==> Building $APP (this may take a few minutes)..." +mvn clean package \ + --projects "$APP" \ + --also-make \ + --define skipTests \ + --define environment=deployment \ + --batch-mode \ + --quiet + +WAR_FILE=$(find "$APP/target" -maxdepth 1 -name "${APP}-*.war" 2>/dev/null | head -n 1) +if [ -z "$WAR_FILE" ]; then + echo "ERROR: No WAR file found in $APP/target/" >&2 + exit 1 +fi + +echo "==> Extracting $WAR_FILE → dist/" +rm -rf dist +mkdir -p dist +unzip -q "$WAR_FILE" -d dist + +# Remove Java-specific server directories – not needed for static hosting. +rm -rf dist/WEB-INF dist/META-INF + +echo "==> dist/ is ready for Cloudflare Pages." +echo "" +echo " Deploy with:" +echo " wrangler pages project create influent # first time only" +echo " wrangler pages deploy dist/ --project-name influent" diff --git a/functions/influent/rest/[[path]].js b/functions/influent/rest/[[path]].js new file mode 100644 index 0000000..4e0e850 --- /dev/null +++ b/functions/influent/rest/[[path]].js @@ -0,0 +1,34 @@ +/** + * Cloudflare Pages Function – proxy /influent/rest/* to the Java backend. + * + * Set the BACKEND_URL environment variable in the Cloudflare Pages dashboard + * to the base URL of your Java backend (e.g. https://api.example.com). + * Requests are forwarded as-is, preserving method, headers, and body. + */ +export async function onRequest(context) { + const { request, env } = context; + + const backendUrl = env.BACKEND_URL; + if (!backendUrl) { + return new Response( + JSON.stringify({ error: 'BACKEND_URL environment variable is not configured' }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ); + } + + const url = new URL(request.url); + const target = `${backendUrl.replace(/\/$/, '')}${url.pathname}${url.search}`; + + const proxyHeaders = new Headers(request.headers); + proxyHeaders.set('X-Forwarded-Host', url.host); + proxyHeaders.set('X-Forwarded-Proto', url.protocol.replace(':', '')); + + // Clone the request before reading its body to avoid consuming the stream. + const cloned = request.clone(); + return fetch(target, { + method: cloned.method, + headers: proxyHeaders, + body: ['GET', 'HEAD'].includes(cloned.method) ? undefined : cloned.body, + redirect: 'follow', + }); +} diff --git a/functions/influent/rpc/[[path]].js b/functions/influent/rpc/[[path]].js new file mode 100644 index 0000000..96cb913 --- /dev/null +++ b/functions/influent/rpc/[[path]].js @@ -0,0 +1,34 @@ +/** + * Cloudflare Pages Function – proxy /influent/rpc/* to the Java backend. + * + * Set the BACKEND_URL environment variable in the Cloudflare Pages dashboard + * to the base URL of your Java backend (e.g. https://api.example.com). + * Requests are forwarded as-is, preserving method, headers, and body. + */ +export async function onRequest(context) { + const { request, env } = context; + + const backendUrl = env.BACKEND_URL; + if (!backendUrl) { + return new Response( + JSON.stringify({ error: 'BACKEND_URL environment variable is not configured' }), + { status: 500, headers: { 'Content-Type': 'application/json' } } + ); + } + + const url = new URL(request.url); + const target = `${backendUrl.replace(/\/$/, '')}${url.pathname}${url.search}`; + + const proxyHeaders = new Headers(request.headers); + proxyHeaders.set('X-Forwarded-Host', url.host); + proxyHeaders.set('X-Forwarded-Proto', url.protocol.replace(':', '')); + + // Clone the request before reading its body to avoid consuming the stream. + const cloned = request.clone(); + return fetch(target, { + method: cloned.method, + headers: proxyHeaders, + body: ['GET', 'HEAD'].includes(cloned.method) ? undefined : cloned.body, + redirect: 'follow', + }); +} diff --git a/wrangler.toml b/wrangler.toml new file mode 100644 index 0000000..c0d436d --- /dev/null +++ b/wrangler.toml @@ -0,0 +1,29 @@ +# Cloudflare Pages configuration for Influent +# https://developers.cloudflare.com/pages/ +# +# Deployment steps: +# 1. Build static assets locally: +# ./build-cloudflare.sh influent-app +# 2. Deploy to Cloudflare Pages (first time): +# wrangler pages project create influent +# 3. Deploy build output: +# wrangler pages deploy dist/ --project-name influent +# +# Required environment variable (set in Cloudflare Pages dashboard): +# BACKEND_URL – base URL of the running Java backend, e.g. +# https://api.example.com +# The backend must be reachable from Cloudflare's network. +# +# Tip: for CI/CD, add the build command and output directory in the +# Cloudflare Pages dashboard or in a GitHub Actions workflow. The build +# environment must have Java 8+ and Maven 3.1+ available; install them +# via the dashboard's "Install command" if needed. + +name = "influent" +pages_build_output_dir = "dist" +compatibility_date = "2024-01-01" + +[vars] +# Default placeholder – override this in the Cloudflare Pages dashboard +# under Settings > Environment variables. +BACKEND_URL = "http://localhost:8080"