Use Cloudflare workers to host + sync with Spelling Bee.
This commit is contained in:
parent
57c05a265a
commit
a9d9c1a3af
22 changed files with 9505 additions and 18413 deletions
|
@ -1 +0,0 @@
|
|||
module.exports = {};
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,5 +1,7 @@
|
|||
dist
|
||||
public
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
@ -67,3 +69,6 @@ node_modules/
|
|||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# Workers
|
||||
transpiled
|
||||
|
|
6
.parcelrc
Normal file
6
.parcelrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"extends": "@parcel/config-default",
|
||||
"transformers": {
|
||||
"*.{ts,tsx}": ["@parcel/transformer-typescript-tsc"]
|
||||
}
|
||||
}
|
21720
package-lock.json
generated
21720
package-lock.json
generated
File diff suppressed because it is too large
Load diff
36
package.json
36
package.json
|
@ -2,10 +2,9 @@
|
|||
"name": "built-to-spell",
|
||||
"version": "0.0.1",
|
||||
"description": "NYT Spelling Bee simulator",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"lint": "eslint src",
|
||||
"build": "rm -r .cache ; parcel build -d public src/index.html",
|
||||
"build": "parcel build --no-cache --dist-dir=public src/index.html",
|
||||
"start": "rm -r .cache ; parcel -d public src/index.html"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -19,20 +18,27 @@
|
|||
},
|
||||
"homepage": "https://gitlab.com/brentschroeter/built-to-spell#readme",
|
||||
"dependencies": {
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1"
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.10",
|
||||
"@types/node": "^14.14.20",
|
||||
"@types/react": "^17.0.0",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
||||
"eslint": "^7.17.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-react": "^7.22.0",
|
||||
"parcel-bundler": "^1.12.4",
|
||||
"typescript": "^4.1.3"
|
||||
"@babel/core": "^7.17.8",
|
||||
"@parcel/packager-raw-url": "^2.4.0",
|
||||
"@parcel/transformer-typescript-tsc": "^2.4.0",
|
||||
"@parcel/transformer-webmanifest": "^2.4.0",
|
||||
"@types/node": "^17.0.23",
|
||||
"@types/react": "^17.0.43",
|
||||
"@types/react-dom": "^17.0.14",
|
||||
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
||||
"@typescript-eslint/parser": "^5.16.0",
|
||||
"eslint": "^8.12.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.29.4",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint_d": "^11.1.1",
|
||||
"parcel": "^2.4.0",
|
||||
"typescript": "^4.6.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,6 @@
|
|||
</head>
|
||||
<body class="bg-gray-50">
|
||||
<div id="app-root" class="w-screen h-screen"></div>
|
||||
<script type="text/javascript" src="./index.tsx"></script>
|
||||
<script type="module" src="./index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2,12 +2,15 @@ import React from 'react';
|
|||
import { render } from 'react-dom';
|
||||
|
||||
import App from './app';
|
||||
import { NytProvider } from './nyt';
|
||||
import { StoreProvider } from './store';
|
||||
|
||||
render(
|
||||
(
|
||||
<StoreProvider>
|
||||
<App />
|
||||
<NytProvider>
|
||||
<App />
|
||||
</NytProvider>
|
||||
</StoreProvider>
|
||||
),
|
||||
document.getElementById('app-root'),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"icons": [
|
||||
{"src": "/icon-192.png", "type": "image/png", "sizes": "192x192"},
|
||||
{"src": "/icon-512.png", "type": "image/png", "sizes": "512x512"}
|
||||
{"src": "./icon-192.png", "type": "image/png", "sizes": "192x192"},
|
||||
{"src": "./icon-512.png", "type": "image/png", "sizes": "512x512"}
|
||||
]
|
||||
}
|
||||
|
|
37
src/nyt.tsx
Normal file
37
src/nyt.tsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React, {
|
||||
createContext,
|
||||
FC,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
interface GameData {
|
||||
today: {
|
||||
validLetters: string[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface NytData {
|
||||
letters: string[];
|
||||
}
|
||||
|
||||
export const Context = createContext<NytData>({ letters: [] });
|
||||
|
||||
export const NytProvider: FC = ({ children }) => {
|
||||
const [nytData, setNytData] = useState({ letters: [] } as NytData);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/game-data/latest')
|
||||
.then((res) => res.json() as Promise<GameData>)
|
||||
.then((body) => {
|
||||
setNytData({ letters: body.today.validLetters.map((x) => x.toLocaleUpperCase('en')) });
|
||||
})
|
||||
.catch(console.error);
|
||||
});
|
||||
|
||||
return (
|
||||
<Context.Provider value={nytData}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
|
@ -5,6 +5,7 @@ import React, {
|
|||
useEffect,
|
||||
} from 'react';
|
||||
|
||||
import { Context as NytContext } from './nyt';
|
||||
import { StoreContext } from './store';
|
||||
|
||||
const range7 = [0, 1, 2, 3, 4, 5, 6];
|
||||
|
@ -12,8 +13,12 @@ const range7 = [0, 1, 2, 3, 4, 5, 6];
|
|||
const isValidLetter = (ch: string): boolean => ch.length === 1 && ch >= 'A' && ch <= 'Z';
|
||||
|
||||
const SetupView: FunctionComponent = () => {
|
||||
const { dispatch, state } = useContext(StoreContext);
|
||||
const { letters } = state;
|
||||
const {
|
||||
dispatch,
|
||||
state: { letters },
|
||||
} = useContext(StoreContext);
|
||||
|
||||
const { letters: nytLetters } = useContext(NytContext);
|
||||
|
||||
useEffect(() => {
|
||||
const firstInput = document.getElementById('setup-view-letter-selector-0');
|
||||
|
@ -82,20 +87,30 @@ const SetupView: FunctionComponent = () => {
|
|||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<button
|
||||
className={`
|
||||
my-6 px-4 py-2 rounded font-bold text-sm
|
||||
bg-yellow-400 hover:bg-yellow-500 transition-all
|
||||
${letters.length < 7 ? 'opacity-0 cursor-default' : ''}
|
||||
`}
|
||||
disabled={letters.length !== 7}
|
||||
onClick={() => dispatch({ type: 'SWITCH_VIEW', payload: 'GAME' })}
|
||||
type="button"
|
||||
>
|
||||
Play Built to Spell
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className={`
|
||||
w-full my-6 px-4 py-2 rounded font-bold text-sm
|
||||
bg-yellow-400 hover:bg-yellow-500 transition-all
|
||||
${nytLetters.length === 7 ? 'bg-yellow-400 cursor-pointer' : 'bg-gray-400 cursor-default'}
|
||||
`}
|
||||
disabled={nytLetters.length !== 7}
|
||||
onClick={() => dispatch({ type: 'SET_LETTERS', payload: nytLetters })}
|
||||
type="button"
|
||||
>
|
||||
{nytLetters.length === 7 ? 'Populate from NYT' : 'Loading from NYT...'}
|
||||
</button>
|
||||
<button
|
||||
className={`
|
||||
w-full my-6 px-4 py-2 rounded font-bold text-sm
|
||||
bg-yellow-400 hover:bg-yellow-500 transition-all
|
||||
${letters.length < 7 ? 'opacity-0 cursor-default' : 'cursor-pointer'}
|
||||
`}
|
||||
disabled={letters.length !== 7}
|
||||
onClick={() => dispatch({ type: 'SWITCH_VIEW', payload: 'GAME' })}
|
||||
type="button"
|
||||
>
|
||||
Play Built to Spell
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*",
|
||||
"src/",
|
||||
".eslintrc.js",
|
||||
],
|
||||
"exclude": [
|
||||
|
|
0
workers-site/.cargo-ok
Normal file
0
workers-site/.cargo-ok
Normal file
170
workers-site/.eslintrc.js
Normal file
170
workers-site/.eslintrc.js
Normal file
|
@ -0,0 +1,170 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaFeatures: { jsx: true },
|
||||
ecmaVersion: 12,
|
||||
project: ['./tsconfig.json'],
|
||||
sourceType: 'module',
|
||||
},
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:jsx-a11y/recommended',
|
||||
'plugin:react/recommended',
|
||||
'airbnb',
|
||||
],
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'import',
|
||||
'jsx-a11y',
|
||||
'react',
|
||||
'react-hooks',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/brace-style': ['error'],
|
||||
'@typescript-eslint/comma-dangle': ['error', 'always-multiline'],
|
||||
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
|
||||
'@typescript-eslint/keyword-spacing': ['error'],
|
||||
'@typescript-eslint/member-delimiter-style': ['error'],
|
||||
'@typescript-eslint/method-signature-style': ['warn', 'method'],
|
||||
'@typescript-eslint/naming-convention': [
|
||||
'warn',
|
||||
{
|
||||
selector: 'default',
|
||||
format: ['camelCase', 'PascalCase'],
|
||||
leadingUnderscore: 'forbid',
|
||||
trailingUnderscore: 'forbid',
|
||||
filter: {
|
||||
// skip names requiring quotes, e.g. HTTP headers
|
||||
regex: '[- ]',
|
||||
match: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: ['enumMember'],
|
||||
format: ['UPPER_CASE'],
|
||||
},
|
||||
{
|
||||
selector: ['property', 'parameter', 'parameterProperty'],
|
||||
format: ['camelCase', 'PascalCase'],
|
||||
leadingUnderscore: 'allow',
|
||||
trailingUnderscore: 'forbid',
|
||||
filter: {
|
||||
// skip names requiring quotes, e.g. HTTP headers
|
||||
regex: '[- ]',
|
||||
match: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
selector: 'typeLike',
|
||||
format: ['PascalCase'],
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/no-duplicate-imports': ['error'],
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-floating-promises': ['error'],
|
||||
'@typescript-eslint/no-misused-promises': ['error'],
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-shadow': ['error'],
|
||||
'@typescript-eslint/no-unnecessary-condition': ['error', { allowConstantLoopConditions: true }],
|
||||
'@typescript-eslint/no-unnecessary-boolean-literal-compare': ['error'],
|
||||
'@typescript-eslint/no-unused-expressions': ['error'],
|
||||
'@typescript-eslint/no-unused-vars': ['error'],
|
||||
'@typescript-eslint/no-use-before-define': ['error'],
|
||||
'@typescript-eslint/prefer-nullish-coalescing': ['warn'],
|
||||
'@typescript-eslint/quotes': ['error', 'single', { avoidEscape: true }],
|
||||
'@typescript-eslint/semi': ['error'],
|
||||
'@typescript-eslint/space-before-function-paren': ['error', {
|
||||
anonymous: 'never',
|
||||
named: 'never',
|
||||
asyncArrow: 'always',
|
||||
}],
|
||||
'@typescript-eslint/switch-exhaustiveness-check': ['error'],
|
||||
'@typescript-eslint/type-annotation-spacing': ['warn', {
|
||||
before: false,
|
||||
after: true,
|
||||
overrides: {
|
||||
arrow: { before: true },
|
||||
},
|
||||
}],
|
||||
'brace-style': 'off',
|
||||
camelcase: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'comma-dangle': 'off',
|
||||
'keyword-spacing': 'off',
|
||||
'no-duplicate-imports': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
quotes: 'off',
|
||||
semi: 'off',
|
||||
'space-before-function-paren': 'off',
|
||||
'implicit-arrow-linebreak': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
'ignorePackages',
|
||||
{
|
||||
js: 'never',
|
||||
jsx: 'never',
|
||||
ts: 'never',
|
||||
tsx: 'never',
|
||||
},
|
||||
],
|
||||
indent: ['error', 2, {
|
||||
CallExpression: { arguments: 'first' },
|
||||
FunctionDeclaration: { parameters: 'first' },
|
||||
FunctionExpression: { parameters: 'first' },
|
||||
SwitchCase: 1,
|
||||
VariableDeclarator: 'first',
|
||||
offsetTernaryExpressions: true,
|
||||
}],
|
||||
'jsx-a11y/label-has-associated-control': ['warn', {
|
||||
assert: 'either',
|
||||
}],
|
||||
'max-classes-per-file': 'off',
|
||||
'max-len': ['warn', { code: 100, tabWidth: 2 }],
|
||||
'no-console': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'prefer-arrow-callback': ['error'],
|
||||
'react/function-component-definition': ['error', {
|
||||
namedComponents: 'arrow-function',
|
||||
unnamedComponents: 'arrow-function',
|
||||
}],
|
||||
'react/jsx-curly-newline': ['error', { multiline: 'forbid', singleline: 'forbid' }],
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
|
||||
'react/jsx-first-prop-new-line': ['error', 'multiline'],
|
||||
'react/jsx-fragments': 'off',
|
||||
'react/jsx-indent': ['error', 2, { checkAttributes: true, indentLogicalExpressions: true }],
|
||||
'react/jsx-indent-props': ['error', 2],
|
||||
'react/jsx-key': ['error', { checkFragmentShorthand: true }],
|
||||
'react/jsx-max-props-per-line': ['error', { maximum: 3, when: 'always' }],
|
||||
'react/jsx-no-target-blank': ['error'],
|
||||
'react/jsx-wrap-multilines': ['error', {
|
||||
declaration: 'parens-new-line',
|
||||
assignment: 'parens-new-line',
|
||||
return: 'parens-new-line',
|
||||
arrow: 'parens-new-line',
|
||||
condition: 'parens-new-line',
|
||||
logical: 'parens-new-line',
|
||||
prop: 'parens-new-line',
|
||||
}],
|
||||
'react/prop-types': 'off',
|
||||
'react/no-deprecated': ['error'],
|
||||
'react/no-multi-comp': ['error'],
|
||||
'react/no-unused-prop-types': ['error'],
|
||||
'react/prefer-stateless-function': ['error'],
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react-hooks/rules-of-hooks': ['error'],
|
||||
'react-hooks/exhaustive-deps': ['warn'],
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
node: { extensions: ['.js', '.jsx', '.ts', '.tsx'] },
|
||||
},
|
||||
},
|
||||
};
|
2
workers-site/.gitignore
vendored
Normal file
2
workers-site/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules
|
||||
worker
|
7
workers-site/jestconfig.json
Normal file
7
workers-site/jestconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"transform": {
|
||||
"^.+\\.(t|j)sx?$": "ts-jest"
|
||||
},
|
||||
"testRegex": "/test/.*\\.test\\.ts$",
|
||||
"collectCoverageFrom": ["src/**/*.{ts,js}"]
|
||||
}
|
5662
workers-site/package-lock.json
generated
Normal file
5662
workers-site/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
36
workers-site/package.json
Normal file
36
workers-site/package.json
Normal file
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "worker",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/worker.js",
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "webpack",
|
||||
"format": "prettier --write '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
|
||||
"lint": "eslint --max-warnings=0 src && prettier --check '*.{json,js}' 'src/**/*.{js,ts}' 'test/**/*.{js,ts}'",
|
||||
"test": "jest --config jestconfig.json --verbose"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cloudflare/kv-asset-handler": "~0.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^3.0.0",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/service-worker-mock": "^2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.16.1",
|
||||
"@typescript-eslint/parser": "^4.16.1",
|
||||
"eslint": "^7.21.0",
|
||||
"eslint-config-prettier": "^8.1.0",
|
||||
"eslint-config-typescript": "^3.0.0",
|
||||
"eslint_d": "^11.1.1",
|
||||
"jest": "^27.0.1",
|
||||
"prettier": "^2.3.0",
|
||||
"service-worker-mock": "^2.0.5",
|
||||
"ts-jest": "^27.0.1",
|
||||
"ts-loader": "^9.2.8",
|
||||
"typescript": "^4.6.3",
|
||||
"webpack": "^5.70.0",
|
||||
"webpack-cli": "^4.9.2"
|
||||
}
|
||||
}
|
5
workers-site/src/bindings.d.ts
vendored
Normal file
5
workers-site/src/bindings.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
export {};
|
||||
|
||||
declare global {
|
||||
const NYT_GAME_DATA: KVNamespace;
|
||||
}
|
102
workers-site/src/index.ts
Normal file
102
workers-site/src/index.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
import {
|
||||
getAssetFromKV,
|
||||
Options as GetAssetFromKVOptions,
|
||||
} from '@cloudflare/kv-asset-handler';
|
||||
|
||||
/**
|
||||
* The DEBUG flag will do two things that help during development:
|
||||
* 1. we will skip caching on the edge, which makes it easier to
|
||||
* debug.
|
||||
* 2. we will return an error message on exception in your Response rather
|
||||
* than the default 404.html page.
|
||||
*/
|
||||
const DEBUG = false;
|
||||
|
||||
addEventListener('fetch', (event) => {
|
||||
try {
|
||||
event.respondWith(handleFetch(event));
|
||||
} catch (e) {
|
||||
if (e instanceof Error && DEBUG) {
|
||||
return event.respondWith(new Response(e.message || e.toString(), { status: 500 }));
|
||||
}
|
||||
event.respondWith(new Response('Internal Error', { status: 500 }));
|
||||
}
|
||||
});
|
||||
|
||||
addEventListener('scheduled', (event) => {
|
||||
event.waitUntil(handleCron());
|
||||
});
|
||||
|
||||
interface DailyGameData {
|
||||
printDate: string;
|
||||
centerLetter: string;
|
||||
outerLetters: string[];
|
||||
answers: string[];
|
||||
}
|
||||
|
||||
interface GameData {
|
||||
today: DailyGameData;
|
||||
}
|
||||
|
||||
async function handleFetch(event: FetchEvent) {
|
||||
const url = new URL(event.request.url);
|
||||
const options: Partial<GetAssetFromKVOptions> = {};
|
||||
try {
|
||||
if (DEBUG) {
|
||||
options.cacheControl = { bypassCache: true };
|
||||
}
|
||||
let response = new Response('', {});
|
||||
if (url.pathname.toLocaleLowerCase('en') === '/api/game-data/latest') {
|
||||
const gameData = await NYT_GAME_DATA.get('latest');
|
||||
response = new Response(gameData || '', { status: gameData ? 200 : 500 });
|
||||
response.headers.set('Content-Type', 'application/json');
|
||||
} else {
|
||||
const page = await getAssetFromKV(event, options);
|
||||
response = new Response(page.body, page);
|
||||
}
|
||||
response.headers.set('X-XSS-Protection', '1; mode=block');
|
||||
response.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
response.headers.set('X-Frame-Options', 'DENY');
|
||||
response.headers.set('Referrer-Policy', 'unsafe-url');
|
||||
response.headers.set('Feature-Policy', 'none');
|
||||
return response;
|
||||
} catch (e) {
|
||||
// if an error is thrown try to serve the asset at 404.html
|
||||
if (e instanceof Error && DEBUG) {
|
||||
return new Response(e.message || e.toString(), { status: 500 });
|
||||
}
|
||||
try {
|
||||
let notFoundResponse = await getAssetFromKV(event, {
|
||||
mapRequestToAsset: (req) => new Request(`${new URL(req.url).origin}/404.html`, req),
|
||||
});
|
||||
return new Response(notFoundResponse.body, { ...notFoundResponse, status: 404 });
|
||||
} catch (e) { /* no-op */ }
|
||||
return new Response('Not found.', { status: 404 });
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCron() {
|
||||
console.log('Starting cron trigger...');
|
||||
const resp = await fetch('https://www.nytimes.com/puzzles/spelling-bee');
|
||||
console.log('Fetched from NYT.');
|
||||
let text = await resp.text();
|
||||
const startStr = '<script type="text/javascript">window.gameData = {';
|
||||
const startIdx = text.indexOf(startStr);
|
||||
if (startIdx === -1) {
|
||||
throw new Error('Unable to locate gameData.');
|
||||
}
|
||||
text = text.slice(startIdx + startStr.length - 1);
|
||||
const endIdx = text.indexOf('</script>');
|
||||
if (endIdx === -1) {
|
||||
throw new Error('Unable to locate end of gameData.');
|
||||
}
|
||||
text = text.slice(0, endIdx);
|
||||
console.log('Found gameData.');
|
||||
const gameData = JSON.parse(text) as GameData;
|
||||
console.log('Parsed gameData.');
|
||||
await Promise.all([
|
||||
NYT_GAME_DATA.put('latest', text),
|
||||
NYT_GAME_DATA.put(gameData.today.printDate, text),
|
||||
]);
|
||||
console.log('Saved gameData.');
|
||||
}
|
21
workers-site/tsconfig.json
Normal file
21
workers-site/tsconfig.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"lib": ["esnext"],
|
||||
"alwaysStrict": true,
|
||||
"strict": true,
|
||||
"preserveConstEnums": true,
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"types": [
|
||||
"@cloudflare/workers-types",
|
||||
"@types/jest",
|
||||
"@types/service-worker-mock"
|
||||
]
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
22
workers-site/webpack.config.js
Normal file
22
workers-site/webpack.config.js
Normal file
|
@ -0,0 +1,22 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: path.join(__dirname, 'src/index.ts'),
|
||||
output: {
|
||||
filename: 'worker.js',
|
||||
path: path.join(__dirname, 'dist'),
|
||||
},
|
||||
devtool: 'cheap-module-source-map',
|
||||
mode: 'development',
|
||||
resolve: {
|
||||
extensions: ['.ts', '.tsx', '.js'],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
26
workers-site/wrangler.toml
Normal file
26
workers-site/wrangler.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
name = "built-to-spell"
|
||||
type = "javascript"
|
||||
route = 'bts.brentsch.com/*'
|
||||
account_id = '7c81d50e4fc6ec51eecd802aacd6304b'
|
||||
zone_id = '23121cb221cde0d0fbcb10798c01f8ba'
|
||||
usage_model = ''
|
||||
compatibility_flags = []
|
||||
workers_dev = false
|
||||
compatibility_date = "2022-03-27"
|
||||
|
||||
[triggers]
|
||||
crons = ["0 * * * *"]
|
||||
|
||||
[build]
|
||||
command = "npm --prefix=../ run build && npm run build"
|
||||
|
||||
[build.upload]
|
||||
format = "service-worker"
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "NYT_GAME_DATA"
|
||||
id = "ad58b382cc884b889f42ed94cf4dbb6b"
|
||||
|
||||
[site]
|
||||
bucket = "../public"
|
||||
entry-point = "./"
|
Loading…
Add table
Reference in a new issue