diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0c4063a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,56 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + name: Build and Test + timeout-minutes: 15 + runs-on: ubuntu-latest + + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - uses: oven-sh/setup-bun@v2 + + - name: Create Service Account file + run: | + echo "${{ secrets.FIRESTORE_SERVICE_ACCOUNT_BASE64 }}" | base64 -d > apps/server/service-account.json + + - name: Cache bun dependencies + uses: actions/cache@v4 + with: + path: | + ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }} + + + - name: Install dependencies + run: bun install + + - name: Install Playwright Browsers + run: bunx playwright install --with-deps chromium + + - name: Lint + run: bun run lint + + - name: Test + run: | + bun run dev & # Start in background + bunx wait-on http://localhost:5173 + bun run test + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: playwright-report + path: apps/web/playwright-report + retention-days: 30 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..974124e --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +node_modules +.pnp +.pnp.js + +# Local env files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Testing +coverage + +# Turbo +.turbo + +# Vercel +.vercel + +# Build Outputs +.next/ +out/ +build +dist + + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.DS_Store +*.pem + +# IDE +.code +.idea + +# React Router +.react-router/ +.vite/ +/build/ \ No newline at end of file diff --git a/README.md b/README.md index 6dd13e7..5af4fc9 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,34 @@ -To install dependencies: -```sh +# React Router + Hono Monorepo Template + +### Setup + +```shell +git clone https://github.com/barclayd/ +cd repo-name bun install ``` -To run: -```sh -bun run dev -``` +### Technologies + +| Category | Technology | Version | +|-------------------|------------|---------| +| Monorepo | [Turborepo](https://turbo.build/repo) [Turborepo](https://turbo.build/repo) | 2.3.3 | +| Workspace/Runtime | [Bun](https://bun.sh) [Bun](https://bun.sh) | 1.1.38 | +| Linting | [Biome](https://biomejs.dev/) [Biome](https://biomejs.dev/) | 1.9.4 | +| Language | [TypeScript](https://www.typescriptlang.org/) [TypeScript](https://www.typescriptlang.org/) | 5.5.4 | +| Server | [Hono](https://hono.dev) [Hono](https://hono.dev) | 4.6.13 | +| API | [tRPC](https://trpc.io) [tRPC](https://trpc.io) | 11.0.0-rc.660 | +| Schema Validation | [Zod](https://zod.dev) [Zod](https://zod.dev) | 3.23.8 | +| UI Framework | [React Router](https://reactrouter.com) [React Router](https://reactrouter.com) | 7.0.2 | +| UI Library | [React](https://react.dev) [React](https://react.dev) | 19.0.0 | +| Styling | [Tailwind CSS](https://tailwindcss.com) [Tailwind CSS](https://tailwindcss.com) | 4.0.0-beta.6 | +| E2E Testing | [Playwright](https://playwright.dev) [Playwright](https://playwright.dev) | 1.49.0 | +| Bundler | [Vite](https://vitejs.dev) [Vite](https://vitejs.dev) | 6.0.3 | +| CI | [GitHub Actions](https://github.com/features/actions) [GitHub Actions](https://github.com/features/actions) | N/A | + +### Still to come -open http://localhost:3000 +* Database connectivity - SQL and NoSQL +* Vitest + Vitest Browser Mode +* Docker +* Deployment via CD, powered by Github Actions \ No newline at end of file diff --git a/apps/server/README.md b/apps/server/README.md new file mode 100644 index 0000000..6dd13e7 --- /dev/null +++ b/apps/server/README.md @@ -0,0 +1,11 @@ +To install dependencies: +```sh +bun install +``` + +To run: +```sh +bun run dev +``` + +open http://localhost:3000 diff --git a/biome.json b/apps/server/biome.json similarity index 100% rename from biome.json rename to apps/server/biome.json diff --git a/apps/server/package.json b/apps/server/package.json new file mode 100644 index 0000000..98110e3 --- /dev/null +++ b/apps/server/package.json @@ -0,0 +1,20 @@ +{ + "name": "server", + "scripts": { + "dev": "bun run --hot src/index.ts", + "lint": "bunx biome check --write .", + "test": "bun test", + "types": "tsc --noEmit" + }, + "dependencies": { + "@trpc/server": "^11.0.0-rc.660", + "hono": "^4.6.13", + "uuid": "^11.0.3", + "zod": "^3.23.8" + }, + "devDependencies": { + "@biomejs/biome": "^1.9.4", + "@hono/trpc-server": "0.3.4", + "@types/bun": "latest" + } +} diff --git a/src/context.ts b/apps/server/src/context.ts similarity index 100% rename from src/context.ts rename to apps/server/src/context.ts diff --git a/src/index.test.ts b/apps/server/src/index.test.ts similarity index 100% rename from src/index.test.ts rename to apps/server/src/index.test.ts diff --git a/src/index.ts b/apps/server/src/index.ts similarity index 100% rename from src/index.ts rename to apps/server/src/index.ts diff --git a/src/router.ts b/apps/server/src/router.ts similarity index 100% rename from src/router.ts rename to apps/server/src/router.ts diff --git a/src/schemas.ts b/apps/server/src/schemas.ts similarity index 100% rename from src/schemas.ts rename to apps/server/src/schemas.ts diff --git a/src/types.ts b/apps/server/src/types.ts similarity index 100% rename from src/types.ts rename to apps/server/src/types.ts diff --git a/tsconfig.json b/apps/server/tsconfig.json similarity index 100% rename from tsconfig.json rename to apps/server/tsconfig.json diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..9b8d514 --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,4 @@ +.react-router +build +node_modules +README.md \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore new file mode 100644 index 0000000..68c5d18 --- /dev/null +++ b/apps/web/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..973038e --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,25 @@ +FROM oven/bun:1 AS dependencies-env +COPY . /app + +FROM dependencies-env AS development-dependencies-env +COPY ./package.json bun.lockb /app/ +WORKDIR /app +RUN bun i --frozen-lockfile + +FROM dependencies-env AS production-dependencies-env +COPY ./package.json bun.lockb /app/ +WORKDIR /app +RUN bun i --production + +FROM dependencies-env AS build-env +COPY ./package.json bun.lockb /app/ +COPY --from=development-dependencies-env /app/node_modules /app/node_modules +WORKDIR /app +RUN bun run build + +FROM dependencies-env +COPY ./package.json bun.lockb /app/ +COPY --from=production-dependencies-env /app/node_modules /app/node_modules +COPY --from=build-env /app/build /app/build +WORKDIR /app +CMD ["bun", "run", "start"] \ No newline at end of file diff --git a/apps/web/README.md b/apps/web/README.md new file mode 100644 index 0000000..f28b6d2 --- /dev/null +++ b/apps/web/README.md @@ -0,0 +1,100 @@ +# Welcome to React Router! + +A modern, production-ready template for building full-stack React applications using React Router. + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/remix-run/react-router-templates/tree/main/default) + +## Features + +- 🚀 Server-side rendering +- ⚡️ Hot Module Replacement (HMR) +- 📦 Asset bundling and optimization +- 🔄 Data loading and mutations +- 🔒 TypeScript by default +- 🎉 TailwindCSS for styling +- 📖 [React Router docs](https://reactrouter.com/) + +## Getting Started + +### Installation + +Install the dependencies: + +```bash +npm install +``` + +### Development + +Start the development server with HMR: + +```bash +npm run dev +``` + +Your application will be available at `http://localhost:5173`. + +## Building for Production + +Create a production build: + +```bash +npm run build +``` + +## Deployment + +### Docker Deployment + +This template includes three Dockerfiles optimized for different package managers: + +- `Dockerfile` - for npm +- `Dockerfile.pnpm` - for pnpm +- `Dockerfile.bun` - for bun + +To build and run using Docker: + +```bash +# For npm +docker build -t my-app . + +# For pnpm +docker build -f Dockerfile.pnpm -t my-app . + +# For bun +docker build -f Dockerfile -t my-app . + +# Run the container +docker run -p 3000:3000 my-app +``` + +The containerized application can be deployed to any platform that supports Docker, including: + +- AWS ECS +- Google Cloud Run +- Azure Container Apps +- Digital Ocean App Platform +- Fly.io +- Railway + +### DIY Deployment + +If you're familiar with deploying Node applications, the built-in app server is production-ready. + +Make sure to deploy the output of `npm run build` + +``` +├── package.json +├── package-lock.json (or pnpm-lock.yaml, or bun.lockb) +├── build/ +│ ├── client/ # Static assets +│ └── server/ # Server-side code +``` + +## Styling + +This template comes with [Tailwind CSS](https://tailwindcss.com/) already configured for a simple default starting experience. You can use whatever CSS framework you prefer. + +--- + +Built with ❤️ using React Router. diff --git a/apps/web/app/app.css b/apps/web/app/app.css new file mode 100644 index 0000000..74b0b55 --- /dev/null +++ b/apps/web/app/app.css @@ -0,0 +1,9 @@ +@import "tailwindcss"; + +@layer theme, base, components, utilities; + +@layer theme { + :root { + --font-sans: '"Inter"', ui-sans-serif, system-ui, sans-serif; + } +} diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx new file mode 100644 index 0000000..e8b258f --- /dev/null +++ b/apps/web/app/root.tsx @@ -0,0 +1,76 @@ +import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, + isRouteErrorResponse, +} from 'react-router'; + +import type { Route } from './+types/root'; +import stylesheet from './app.css?url'; + +export const links: Route.LinksFunction = () => [ + { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, + { + rel: 'preconnect', + href: 'https://fonts.gstatic.com', + crossOrigin: 'anonymous', + }, + { + rel: 'stylesheet', + href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + }, + { rel: 'stylesheet', href: stylesheet }, +]; + +export function Layout({ children }: { children: React.ReactNode }) { + return ( + + + + + + + + + {children} + + + + + ); +} + +export default function App() { + return ; +} + +export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { + let message = 'Oops!'; + let details = 'An unexpected error occurred.'; + let stack: string | undefined; + + if (isRouteErrorResponse(error)) { + message = error.status === 404 ? '404' : 'Error'; + details = + error.status === 404 + ? 'The requested page could not be found.' + : error.statusText || details; + } else if (import.meta.env.DEV && error && error instanceof Error) { + details = error.message; + stack = error.stack; + } + + return ( +
+

{message}

+

{details}

+ {stack && ( +
+          {stack}
+        
+ )} +
+ ); +} diff --git a/apps/web/app/routes.ts b/apps/web/app/routes.ts new file mode 100644 index 0000000..6fb6b54 --- /dev/null +++ b/apps/web/app/routes.ts @@ -0,0 +1,4 @@ +import type { RouteConfig } from '@react-router/dev/routes'; +import { flatRoutes } from '@react-router/fs-routes'; + +export default flatRoutes() satisfies RouteConfig; diff --git a/apps/web/app/routes/_index.tsx b/apps/web/app/routes/_index.tsx new file mode 100644 index 0000000..7d29c16 --- /dev/null +++ b/apps/web/app/routes/_index.tsx @@ -0,0 +1,19 @@ +import { client } from 'index'; +import type { Route } from './+types/_index'; + +export function meta({}: Route.MetaArgs) { + return [ + { title: 'New React Router App' }, + { name: 'description', content: 'Welcome to React Router!' }, + ]; +} + +export async function loader() { + return { + data: await client.get.query(), + }; +} + +export default function Home({ loaderData }: Route.ComponentProps) { + return

{loaderData.data.id}

; +} diff --git a/apps/web/biome.json b/apps/web/biome.json new file mode 100644 index 0000000..bfb166d --- /dev/null +++ b/apps/web/biome.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "space" + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noForEach": "off" + }, + "correctness": { + "noUnusedVariables": "error", + "noUnusedImports": "error", + "noEmptyPattern": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single" + } + } +} diff --git a/apps/web/index.ts b/apps/web/index.ts new file mode 100644 index 0000000..7b96533 --- /dev/null +++ b/apps/web/index.ts @@ -0,0 +1,10 @@ +import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'; +import type { AppRouter } from '../server/src/router'; + +export const client = createTRPCProxyClient({ + links: [ + httpBatchLink({ + url: 'http://localhost:3000/trpc', + }), + ], +}); diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..54b7125 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,41 @@ +{ + "name": "web", + "private": true, + "type": "module", + "scripts": { + "build": "react-router build", + "dev": "react-router dev", + "lint": "bunx biome check --write .", + "start": "react-router-serve ./build/server/index.js", + "test": "bunx playwright test", + "typecheck": "react-router typegen && tsc" + }, + "dependencies": { + "@react-router/fs-routes": "^7.0.2", + "@react-router/node": "^7.0.2", + "@react-router/serve": "^7.0.2", + "@trpc/client": "^11.0.0-rc.660", + "isbot": "^5.1.17", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router": "^7.0.2" + }, + "devDependencies": { + "@babel/preset-typescript": "7.26.0", + "@biomejs/biome": "^1.9.4", + "@playwright/test": "^1.49.0", + "@react-router/dev": "^7.0.2", + "@tailwindcss/vite": "^4.0.0-beta.6", + "@types/node": "^22.10.1", + "@types/react": "^19.0.1", + "@types/react-dom": "^19.0.1", + "babel-plugin-react-compiler": "19.0.0-beta-df7b47d-20241124", + "postcss": "^8.4.49", + "react-router-devtools": "1.0.5", + "tailwindcss": "^4.0.0-beta.6", + "typescript": "^5.7.2", + "vite": "^6.0.3", + "vite-plugin-babel": "1.3.0", + "vite-tsconfig-paths": "^5.1.4" + } +} diff --git a/apps/web/playwright.config.ts b/apps/web/playwright.config.ts new file mode 100644 index 0000000..a0c9512 --- /dev/null +++ b/apps/web/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: 'html', + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://127.0.0.1:5173', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + // webServer: { + // command: 'bun dev', + // url: 'http://127.0.0.1:5173', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/apps/web/public/favicon.ico b/apps/web/public/favicon.ico new file mode 100644 index 0000000..5dbdfcd Binary files /dev/null and b/apps/web/public/favicon.ico differ diff --git a/apps/web/react-router.config.ts b/apps/web/react-router.config.ts new file mode 100644 index 0000000..51e8967 --- /dev/null +++ b/apps/web/react-router.config.ts @@ -0,0 +1,5 @@ +import type { Config } from '@react-router/dev/config'; + +export default { + ssr: true, +} satisfies Config; diff --git a/apps/web/tests/index.spec.ts b/apps/web/tests/index.spec.ts new file mode 100644 index 0000000..6ae8caa --- /dev/null +++ b/apps/web/tests/index.spec.ts @@ -0,0 +1,7 @@ +import { expect, test } from '@playwright/test'; + +test('has title', async ({ page }) => { + await page.goto('http://localhost:5173'); + + await expect(page.getByRole('heading', { name: '4' })).toBeVisible(); +}); diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..dc391a4 --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,27 @@ +{ + "include": [ + "**/*", + "**/.server/**/*", + "**/.client/**/*", + ".react-router/types/**/*" + ], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "types": ["node", "vite/client"], + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "rootDirs": [".", "./.react-router/types"], + "baseUrl": ".", + "paths": { + "~/*": ["./app/*"] + }, + "esModuleInterop": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true + } +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..f8afbc5 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,23 @@ +import { reactRouter } from '@react-router/dev/vite'; +import tailwindcss from '@tailwindcss/vite'; +import { reactRouterDevTools } from 'react-router-devtools'; +import { defineConfig } from 'vite'; +import babel from 'vite-plugin-babel'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [ + tailwindcss(), + babel({ + include: ['./app/**/*'], + filter: (name) => name.endsWith('tsx'), + babelConfig: { + presets: ['@babel/preset-typescript'], + plugins: ['babel-plugin-react-compiler'], + }, + }), + reactRouterDevTools(), + reactRouter(), + tsconfigPaths(), + ], +}); diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..477a628 Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index 98110e3..4c0e588 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,18 @@ { - "name": "server", + "name": "swipe-move", + "private": true, "scripts": { - "dev": "bun run --hot src/index.ts", - "lint": "bunx biome check --write .", - "test": "bun test", - "types": "tsc --noEmit" - }, - "dependencies": { - "@trpc/server": "^11.0.0-rc.660", - "hono": "^4.6.13", - "uuid": "^11.0.3", - "zod": "^3.23.8" + "build": "turbo build", + "dev": "turbo dev", + "lint": "turbo lint", + "test": "turbo test" }, "devDependencies": { - "@biomejs/biome": "^1.9.4", - "@hono/trpc-server": "0.3.4", - "@types/bun": "latest" - } + "turbo": "^2.3.3", + "typescript": "5.5.4" + }, + "packageManager": "bun@1.1.38", + "workspaces": [ + "apps/*" + ] } diff --git a/turbo.json b/turbo.json new file mode 100644 index 0000000..603669a --- /dev/null +++ b/turbo.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://turbo.build/schema.json", + "ui": "tui", + "tasks": { + "lint": { + "dependsOn": ["^lint"] + }, + "test": { + "dependsOn": ["^test"] + }, + "dev": { + "cache": false, + "persistent": true + } + } +}