mirror of
https://github.com/wahyd4/heygo.git
synced 2026-08-09 05:15:52 +10:00
feat: scaffold cloudflare shortlinks app
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.wrangler/
|
||||
.dev.vars
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
.DS_Store
|
||||
npm-debug.log*
|
||||
@@ -0,0 +1,31 @@
|
||||
# Heygo
|
||||
|
||||
Heygo is an independent Cloudflare React + Workers scaffold for a future shortlinks app.
|
||||
|
||||
This repository lives at `/home/ai-bot/code/heygo` and is intentionally separate from the old Linux/Django Links repository. The old Links repository under `/home/ai-bot/code/links` is read-only reference material only and must not be modified by this project.
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run dev:worker
|
||||
```
|
||||
|
||||
- `npm run dev` starts the Vite React SPA dev server.
|
||||
- `npm run dev:worker` starts the Cloudflare Worker locally with Wrangler.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
- `npm run build` type-checks the TypeScript project and builds the React SPA into `dist/client` for Workers Assets.
|
||||
- `npm test` runs Vitest tests against the Worker API handler.
|
||||
|
||||
## Current API skeleton
|
||||
|
||||
- `GET /api/health` returns JSON health status from the Worker.
|
||||
- Unknown `/api/*` routes return JSON `404` responses.
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Heygo</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+2793
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "heygo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:worker": "wrangler dev",
|
||||
"build": "tsc --noEmit && vite build",
|
||||
"preview": "vite preview",
|
||||
"test": "vitest run"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.2.7",
|
||||
"react-dom": "^19.2.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20260619.1",
|
||||
"@types/react": "^19.2.17",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"typescript": "^6.0.3",
|
||||
"vite": "^8.0.16",
|
||||
"vitest": "^4.1.9",
|
||||
"wrangler": "^4.103.0"
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
export default function App() {
|
||||
return (
|
||||
<main className="app-shell">
|
||||
<section className="hero">
|
||||
<p className="eyebrow">Cloudflare Workers + React</p>
|
||||
<h1>Heygo shortlinks</h1>
|
||||
<p>
|
||||
A fresh TypeScript scaffold for a React SPA backed by a Cloudflare
|
||||
Worker API.
|
||||
</p>
|
||||
<a href="/api/health">Check the Worker API health endpoint</a>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './styles.css';
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
@@ -0,0 +1,47 @@
|
||||
:root {
|
||||
color: #172033;
|
||||
background: #f6f8fb;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
justify-content: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.hero {
|
||||
background: white;
|
||||
border: 1px solid #dde4ef;
|
||||
border-radius: 24px;
|
||||
box-shadow: 0 24px 80px rgb(23 32 51 / 12%);
|
||||
max-width: 720px;
|
||||
padding: 3rem;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
color: #5b6ee1;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.5rem, 8vw, 5rem);
|
||||
line-height: 0.95;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #4354d8;
|
||||
font-weight: 700;
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import worker from '../worker/index';
|
||||
|
||||
function fetchWorker(path: string) {
|
||||
const request = new Request(`https://heygo.test${path}`);
|
||||
|
||||
return worker.fetch(request as unknown as Parameters<typeof worker.fetch>[0]);
|
||||
}
|
||||
|
||||
describe('worker API scaffold', () => {
|
||||
it('responds to the health endpoint', async () => {
|
||||
const response = await fetchWorker('/api/health');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.headers.get('content-type')).toContain('application/json');
|
||||
await expect(response.json()).resolves.toEqual({
|
||||
ok: true,
|
||||
service: 'heygo-worker',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns 404 JSON for unknown API routes', async () => {
|
||||
const response = await fetchWorker('/api/missing');
|
||||
|
||||
expect(response.status).toBe(404);
|
||||
await expect(response.json()).resolves.toEqual({ error: 'Not found' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022", "WebWorker"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["@cloudflare/workers-types", "vitest/globals"]
|
||||
},
|
||||
"include": ["src", "worker", "tests", "vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import react from '@vitejs/plugin-react';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
build: {
|
||||
outDir: 'dist/client',
|
||||
emptyOutDir: true,
|
||||
},
|
||||
test: {
|
||||
environment: 'node',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
export interface Env {}
|
||||
|
||||
const jsonHeaders = {
|
||||
'content-type': 'application/json; charset=utf-8',
|
||||
};
|
||||
|
||||
function json(body: unknown, init: ResponseInit = {}) {
|
||||
return Response.json(body, {
|
||||
...init,
|
||||
headers: {
|
||||
...jsonHeaders,
|
||||
...init.headers,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
|
||||
if (url.pathname === '/api/health') {
|
||||
return json({ ok: true, service: 'heygo-worker' });
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith('/api/')) {
|
||||
return json({ error: 'Not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
return json({ error: 'Not found' }, { status: 404 });
|
||||
},
|
||||
} satisfies ExportedHandler<Env>;
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "node_modules/wrangler/config-schema.json",
|
||||
"name": "heygo",
|
||||
"main": "worker/index.ts",
|
||||
"compatibility_date": "2026-06-20",
|
||||
"assets": {
|
||||
"directory": "./dist/client",
|
||||
"not_found_handling": "single-page-application"
|
||||
},
|
||||
"observability": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user