Skip to content

Commit 6e48a3f

Browse files
committed
Merge branch 'develop'
Brings the Cloudflare Pages migration to master: modernized build toolchain, wrangler.toml (project + R2 FEEDS binding), functions/_middleware.js for serving RSS/sitemap feeds from R2, updated deploy workflow, and relative landing-page game links.
2 parents 6d4957a + bdff2f3 commit 6e48a3f

14 files changed

Lines changed: 9593 additions & 16001 deletions

File tree

.github/workflows/nodejs.yml

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build
1+
name: Build & Deploy
22

33
on:
44
workflow_dispatch:
@@ -7,22 +7,29 @@ on:
77
- master
88

99
jobs:
10-
build:
10+
deploy:
1111

1212
runs-on: ubuntu-latest
1313

1414
steps:
15-
- uses: actions/checkout@v1
16-
- name: Use Node.js 18.x
17-
uses: actions/setup-node@v1
15+
- uses: actions/checkout@v4
16+
- name: Use Node.js 20.x
17+
uses: actions/setup-node@v4
1818
with:
19-
node-version: 18.x
19+
node-version: 20.x
2020

2121
- run: npm ci
2222

23-
- run: npm run build --if-present
23+
- run: npm run build
24+
25+
- name: Generate static site
26+
run: node scripts/build.js
2427
env:
25-
CI: true
2628
API_TOKEN: ${{ secrets.API_TOKEN }}
27-
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
28-
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
29+
30+
- name: Deploy to Cloudflare Pages
31+
uses: cloudflare/wrangler-action@v3
32+
with:
33+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
34+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
35+
command: pages deploy

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,11 @@ config/cron/
1212
Developers - *.csv
1313
*.log
1414
web/scripts/app\.js\.map
15+
web/scripts/*.LICENSE.txt
1516
config/keys.json
1617
.env
1718
.idea/
1819
stage
1920
dev/assets/theme*
21+
.wrangler/
22+
.dev.vars

app/actions.js

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,10 @@ export const SET_SEARCH_TERM = 'SET_SEARCH_TERM';
99
export const TOGGLE_SERVICE = 'TOGGLE_SERVICE';
1010

1111
const FETCH_DEBOUNCE_INTERVAL = 250;
12-
let API_HOSTNAME = 'api.developertracker.com';
13-
let API_PORT = 443;
12+
const API_HOSTNAME = 'api.developertracker.com';
13+
const API_PORT = 443;
1414
let trackTiming = true;
1515

16-
if ( window.location.hostname === 'localhost' ) {
17-
API_HOSTNAME = 'lvh.me';
18-
// eslint-disable-next-line no-magic-numbers
19-
API_PORT = 3000;
20-
}
21-
2216
const setSearchTerm = function setSearchTerm ( term ) {
2317
const currentQuery = queryString.parse( location.search.substr( 1 ) );
2418

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,12 @@ services:
190190
API_TOKEN: ${API_TOKEN:-}
191191
AWS_ACCESS_KEY: ${AWS_ACCESS_KEY:-}
192192
AWS_SECRET_KEY: ${AWS_SECRET_KEY:-}
193+
# R2 credentials enable the dual-write of RSS/sitemaps to the Cloudflare
194+
# R2 bucket the Pages site serves feeds from. Without these two, turtle
195+
# falls back to S3-only and the R2 feeds go stale. Bucket + account id
196+
# default correctly in turtle/r2.js, so only the keys are needed here.
197+
R2_ACCESS_KEY_ID: ${R2_ACCESS_KEY_ID:-}
198+
R2_SECRET_ACCESS_KEY: ${R2_SECRET_ACCESS_KEY:-}
193199
restart: unless-stopped
194200
depends_on:
195201
rest-api:

eslint.config.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
const js = require( '@eslint/js' );
2+
const globals = require( 'globals' );
3+
const reactPlugin = require( 'eslint-plugin-react' );
4+
5+
module.exports = [
6+
{
7+
ignores: [
8+
'node_modules/**',
9+
'web/**',
10+
'dev/**',
11+
'stage/**',
12+
'games/**',
13+
],
14+
},
15+
js.configs.recommended,
16+
{
17+
// React frontend — ES modules + JSX, browser globals.
18+
files: [ 'app/**/*.{js,jsx}' ],
19+
plugins: {
20+
react: reactPlugin,
21+
},
22+
languageOptions: {
23+
ecmaVersion: 'latest',
24+
sourceType: 'module',
25+
parserOptions: {
26+
ecmaFeatures: {
27+
jsx: true,
28+
},
29+
},
30+
globals: {
31+
...globals.browser,
32+
},
33+
},
34+
settings: {
35+
react: {
36+
version: 'detect',
37+
},
38+
},
39+
rules: {
40+
...reactPlugin.configs.flat.recommended.rules,
41+
'react/prop-types': 'off',
42+
},
43+
},
44+
{
45+
// Build scripts + config — CommonJS, node globals.
46+
files: [ 'scripts/**/*.js', '*.js' ],
47+
languageOptions: {
48+
ecmaVersion: 'latest',
49+
sourceType: 'commonjs',
50+
globals: {
51+
...globals.node,
52+
},
53+
},
54+
},
55+
{
56+
// Cloudflare Pages Functions — ES modules, Workers runtime globals.
57+
files: [ 'functions/**/*.js' ],
58+
languageOptions: {
59+
ecmaVersion: 'latest',
60+
sourceType: 'module',
61+
globals: {
62+
...globals.serviceworker,
63+
Response: 'readonly',
64+
Request: 'readonly',
65+
URL: 'readonly',
66+
},
67+
},
68+
},
69+
];

functions/_middleware.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Serves the RSS + sitemap feeds that the `turtle` service writes to the R2
2+
// bucket bound as FEEDS (see wrangler.toml). The static site is generated by
3+
// scripts/build.js and is immutable per Pages deploy, so these dynamic feeds
4+
// can't live in stage/ — they're fetched from R2 on request instead.
5+
//
6+
// _headers rules do NOT apply to Function responses, so we set Cache-Control
7+
// here (600s, matching turtle's S3 CacheControl). Every other request falls
8+
// through via next() to the static asset pipeline, where _headers still applies.
9+
10+
export async function onRequest( context ) {
11+
const { request, env, next } = context;
12+
const path = new URL( request.url ).pathname;
13+
14+
let key = null;
15+
let fallbackType = 'application/xml';
16+
17+
if ( /^\/sitemap(\.[^/]+)?\.xml$/.test( path ) ) {
18+
// /sitemap.xml or /sitemap.<game>.xml
19+
key = path.slice( 1 );
20+
} else if ( /^\/[^/]+\/rss$/.test( path ) ) {
21+
// /<game>/rss
22+
key = path.slice( 1 );
23+
fallbackType = 'application/rss+xml';
24+
}
25+
26+
if ( !key ) {
27+
return next();
28+
}
29+
30+
const object = await env.FEEDS.get( key );
31+
32+
if ( !object ) {
33+
return new Response( 'Not found', { status: 404 } );
34+
}
35+
36+
return new Response( object.body, {
37+
headers: {
38+
'Content-Type': object.httpMetadata?.contentType || fallbackType,
39+
'Cache-Control': 'public, max-age=600',
40+
},
41+
} );
42+
}

0 commit comments

Comments
 (0)