Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
{
"name": "Tailspin Toys (Astro + Drizzle)",
"name": "Tailspin Toys (Astro + Drizzle + Node SQLite)",
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
"features": {
"ghcr.io/schlich/devcontainer-features/playwright:0": {}
Expand Down
2 changes: 1 addition & 1 deletion .github/agents/pr-readiness.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ If any of these are unclear, ask the user before proceeding.

1. Before writing, report the gaps to the user and confirm they want you to fill them.
2. Write the minimum tests needed to cover the gaps, following project conventions:
- Unit tests: `db/*.test.ts` and `src/**/*.test.ts` — Vitest, in-memory libSQL, type hints (see `.github/instructions/unit-tests.instructions.md`)
- Unit tests: `db/*.test.ts` and `src/**/*.test.ts` — Vitest, in-memory Node SQLite, type hints (see `.github/instructions/unit-tests.instructions.md`)
- Frontend: `e2e-tests/*.spec.ts` — use role-based Playwright locators, `test.step`, no `waitForTimeout` (see `.github/instructions/playwright.instructions.md`)
3. Add `data-testid` attributes to any interactive elements that are missing them.
4. Do not rewrite existing tests — only add what is missing.
Expand Down
10 changes: 5 additions & 5 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Tailspin Toys Crowd Funding Development Guidelines

This is a crowdfunding platform for games with a developer theme. The application is a single **Astro 7** site (fully prerendered/static output) styled with **Tailwind CSS v4**. Data is stored in a local SQLite database accessed at build time through **Drizzle ORM + libSQL**; pages query the database directly in frontmatter — there is no separate backend API or client-side UI framework. Please follow these guidelines when contributing:
This is a crowdfunding platform for games with a developer theme. The application is a single **Astro 7** site (fully prerendered/static output) styled with **Tailwind CSS v4**. Data is stored in a local SQLite database accessed at build time through **Drizzle ORM + Node.js's built-in SQLite driver**; pages query the database directly in frontmatter — there is no separate backend API or client-side UI framework. Please follow these guidelines when contributing:

## Agent notes

Expand Down Expand Up @@ -37,7 +37,7 @@ This is a crowdfunding platform for games with a developer theme. The applicatio
- Use TypeScript with explicit types for function parameters and return values, especially in the data layer (`db/`, `src/lib/`)
- Frontend code (TypeScript, Astro) must pass ESLint checks (`npm run lint`)

### Data Layer Patterns (Drizzle + libSQL)
### Data Layer Patterns (Drizzle + Node SQLite)

- Define tables in `db/schema.ts`; manage schema changes with drizzle-kit migrations - see `drizzle.instructions.md`
- Keep data-access helpers in `src/lib/` with an **injectable `db`** argument so they're testable
Expand Down Expand Up @@ -70,8 +70,8 @@ This is a crowdfunding platform for games with a developer theme. The applicatio
- The project uses **npm scripts** for all development tasks — there is no `scripts/` directory.
- **Skills take precedence.** Before running a command directly, check whether a skill covers the task (e.g. the `quality-checks` skill wraps tests and lint). If one applies, follow it.
- Key npm scripts:
- `npm run dev` — start the Astro dev server (`predev` migrates + seeds the database)
- `npm run build` — build the static site (`prebuild` migrates + seeds the database)
- `npm run dev` — start the Astro dev server (`predev` migrates + seeds the local SQLite database)
- `npm run build` — build the static site (`prebuild` migrates + seeds the local SQLite database)
- `npm run preview` — serve the built `dist/` output
- `npm run lint` — ESLint
- `npm run test:unit` — Vitest unit tests
Expand All @@ -89,7 +89,7 @@ This is a crowdfunding platform for games with a developer theme. The applicatio
The application lives at the repository root:

- `db/`: Drizzle schema, migrations, transforms, seed, and `games.csv`
- `src/lib/`: database client (`db.ts`) and data-access helpers (`games.ts`)
- `src/lib/`: Node SQLite client (`db.ts`) and data-access helpers (`games.ts`)
- `src/components/`: reusable `.astro` components
- `src/layouts/`: Astro layout templates
- `src/pages/`: Astro page routes (`index.astro` listing, `game/[id].astro`, `404.astro`, `about.astro`)
Expand Down
4 changes: 2 additions & 2 deletions .github/instructions/astro.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ applyTo: '**/*.astro'

## Astro Component Patterns

Astro handles everything in the UI: pages, layouts, components, routing, and content. The site is **fully prerendered** (`output: 'static'`) — there is no client-side UI framework and no separate API server. Pages read data **directly in frontmatter** at build time via the Drizzle/libSQL data-access helpers in `src/lib/`.
Astro handles everything in the UI: pages, layouts, components, routing, and content. The site is **fully prerendered** (`output: 'static'`) — there is no client-side UI framework and no separate API server. Pages read data **directly in frontmatter** at build time via the Drizzle/Node SQLite data-access helpers in `src/lib/`.

### Component Structure

Expand Down Expand Up @@ -98,7 +98,7 @@ const game = await getGameById(getDatabase(), Number(id));

## Data Access

- Build-time data comes from a local SQLite database via **Drizzle ORM + libSQL** (see [`drizzle.instructions.md`](drizzle.instructions.md)).
- Build-time data comes from a local SQLite database via **Drizzle ORM + Node SQLite** (see [`drizzle.instructions.md`](drizzle.instructions.md)).
- Import `getDatabase()` from `src/lib/db.ts` and the typed helpers from `src/lib/games.ts`.
- The database must be migrated and seeded before `astro build`; the `prebuild` npm script (`db:setup`) handles this.

Expand Down
15 changes: 9 additions & 6 deletions .github/instructions/drizzle.instructions.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
description: 'Drizzle ORM + libSQL data layer patterns for the Astro app'
description: 'Drizzle ORM + Node SQLite data layer patterns for the Astro app'
applyTo: 'db/**/*.ts,src/lib/*.ts'
---

# Drizzle ORM + libSQL Instructions
# Drizzle ORM + Node SQLite Instructions

The app's data lives in a local SQLite database accessed through **Drizzle ORM** over a **libSQL** client (`@libsql/client`). It is consumed at **build time** from Astro page frontmatter — there is no runtime API server. Schema changes are managed with **drizzle-kit** migrations.
The app's data lives in a local SQLite database accessed through **Drizzle ORM** over Node.js's built-in `node:sqlite` driver. It is consumed at **build time** from Astro page frontmatter — there is no runtime API server. Schema changes are managed with **drizzle-kit** migrations.

## Layout

Expand All @@ -14,8 +14,8 @@ The app's data lives in a local SQLite database accessed through **Drizzle ORM**
- `db/seed.ts` — idempotent seeding from `db/games.csv` using the transforms.
- `db/migrate.ts` — applies generated migrations.
- `db/migrations/` — generated SQL migrations (do not hand-edit).
- `db/test-helpers.ts` — `createTestDatabase()` returns a migrated in-memory libSQL db for tests.
- `src/lib/db.ts` — `createDatabase(url)` / `getDatabase()` build the Drizzle client from `DATABASE_URL` (defaults to a local file under `.data/`).
- `db/test-helpers.ts` — `createTestDatabase()` returns a migrated in-memory Node SQLite db for tests.
- `src/lib/db.ts` — `createDatabase(url)` / `getDatabase()` build the Drizzle client from `DATABASE_URL` (defaults to the local `tailspin.db` file).
- `src/lib/games.ts` — typed, **injectable-db** data-access helpers used by pages and tests.

## Schema Conventions
Expand Down Expand Up @@ -63,7 +63,10 @@ Seed-derived values must be reproducible across builds. Derive star ratings from

Unit-test transforms directly and helpers against `createTestDatabase()`. See [`unit-tests.instructions.md`](unit-tests.instructions.md).

## Node.js requirement

Node.js 22.13 or later is required because the data layer uses the built-in `node:sqlite` module without an experimental flag. Do not introduce third-party SQLite drivers that ship platform-specific binaries.

## Type checking

The data layer (`db/**/*.ts`, `src/lib/*.ts`) is type-checked by `npm run typecheck`, which runs the native **TypeScript 7** compiler (`tsgo`, from `@typescript/native-preview`) against `tsconfig.tsgo.json`. Keep helpers exported with explicit parameter and return types so `tsgo` can verify them. Linting is unaffected — ESLint + `typescript-eslint` still run on the classic `typescript` package.

10 changes: 5 additions & 5 deletions .github/instructions/unit-tests.instructions.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
description: 'Vitest unit test guidelines for the Astro + Drizzle/libSQL data layer'
description: 'Vitest unit test guidelines for the Astro + Drizzle/Node SQLite data layer'
applyTo: '**/*.test.ts'
---

# Unit Testing Guidelines (Vitest + Drizzle/libSQL)
# Unit Testing Guidelines (Vitest + Drizzle/Node SQLite)

Unit tests run with **Vitest** (`npm run test:unit`). They cover the two highest-value, framework-free layers:

1. **Pure transforms** (`db/transforms.ts`) — CSV parsing, description building, de-duplication, deterministic ratings.
2. **Data-access helpers** (`src/lib/games.ts`) — ordering, lookups — exercised against a real in-memory **libSQL** database.
2. **Data-access helpers** (`src/lib/games.ts`) — ordering, lookups — exercised against a real in-memory **Node SQLite** database.

> [!IMPORTANT]
> Keep tests independent of the Astro runtime. Helpers accept an **injectable `db`** argument; tests pass an in-memory database, pages pass the real client. Never start an Astro server to unit test data logic.
Expand Down Expand Up @@ -43,7 +43,7 @@ describe('ratingFromTitle', () => {

## Testing Data-Access Helpers

- Build a fresh in-memory database per test with the shared helper `createTestDatabase()` (`db/test-helpers.ts`), which runs migrations on a `:memory:` libSQL client.
- Build a fresh in-memory database per test with the shared helper `createTestDatabase()` (`db/test-helpers.ts`), which runs migrations on a `:memory:` Node SQLite client.
- Seed only the fixtures the test needs, then call the helper with that `db`.
- Always assert the cheap thing first (counts, totals, ordering) before deep object shape.

Expand Down Expand Up @@ -81,6 +81,6 @@ describe('getAllGames', () => {

- Follow Arrange-Act-Assert.
- One behaviour per `it`; avoid asserting unrelated things in a single case.
- Don't mock the database — an in-memory libSQL instance is fast and exercises real SQL/joins.
- Don't mock the database — an in-memory Node SQLite instance is fast and exercises real SQL/joins.
- Keep fixtures minimal but representative of relationships (game → publisher, game → category).
- If a schema change breaks tests, regenerate migrations with `npm run db:generate` and update fixtures.
14 changes: 7 additions & 7 deletions .github/skills/quality-checks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ allowed-tools:

# Quality Checks

This is a single Astro application (Astro 7 + Drizzle ORM/libSQL). All commands run from the repository root via npm scripts.
This is a single Astro application (Astro 7 + Drizzle ORM/Node SQLite). All commands run from the repository root via npm scripts.

## Quick Reference

Expand All @@ -31,7 +31,7 @@ npm run test:unit
```

- Runs Vitest (`vitest run`) over `db/**/*.test.ts` and `src/**/*.test.ts`.
- Covers the pure seed/transform functions and the Drizzle data-access helpers against an in-memory libSQL database.
- Covers the pure seed/transform functions and the Drizzle data-access helpers against an in-memory Node SQLite database.

### Frontend E2E Tests

Expand Down Expand Up @@ -74,7 +74,7 @@ npm ci
npx playwright install --with-deps chromium # only needed for E2E
```

- Ensure Node 20+ is available: `node --version`.
- Ensure Node 22.13+ is available: `node --version`.
- Run `npx astro sync` if editor/type errors reference missing generated Astro types.

---
Expand All @@ -89,8 +89,8 @@ The SQLite database must be migrated and seeded **before** `astro build`. The `p
npm run db:setup # db:migrate + db:seed
```

- The database lives at `.data/tailspin.db` (gitignored) and is regenerated from `db/games.csv`.
- To force a clean rebuild: `rm -rf .data dist && npm run build`.
- The database lives at `tailspin.db` (gitignored) and is regenerated from `db/games.csv`.
- To force a clean rebuild: `rm -f tailspin.db && rm -rf dist && npm run build`.

---

Expand Down Expand Up @@ -129,7 +129,7 @@ npx playwright test e2e-tests/games.spec.ts
**Symptom**: Assertion failures in `npm run test:unit`.

1. **Read the failing assertion** — Vitest prints expected vs received inline.
2. **In-memory database**: Helper tests build a fresh `:memory:` libSQL database, run migrations, and seed fixtures per test. If a schema change isn't reflected, regenerate migrations with `npm run db:generate`.
2. **In-memory database**: Helper tests build a fresh `:memory:` Node SQLite database, run migrations, and seed fixtures per test. If a schema change isn't reflected, regenerate migrations with `npm run db:generate`.
3. **Determinism**: Star ratings are derived from a stable hash of the title (`ratingFromTitle`) — never `Math.random`. A flaky rating assertion usually means non-deterministic data crept in.

Run a single file:
Expand All @@ -156,7 +156,7 @@ npx vitest run src/lib/games.test.ts
**Symptom**: Tests pass locally but fail in CI (or vice versa).

- **Node version mismatch**: CI uses the current Node LTS release.
- **Database state**: CI always builds from a clean seed. Locally, delete `.data/` and rebuild if you suspect stale data.
- **Database state**: CI always builds from a clean seed. Locally, delete `tailspin.db` and rebuild if you suspect stale data.
- **Built vs dev**: CI tests the built `dist/` via `astro preview`. Reproduce locally with `npm run test:e2e` (which builds first) rather than against `astro dev`.

---
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/copilot-setup-steps.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v5

# Frontend / app setup - Node.js (the whole app is now Astro + Drizzle/libSQL)
# Frontend / app setup - Node.js (the whole app is Astro + Drizzle/Node SQLite)
- name: Set up Node.js
uses: actions/setup-node@v6
with:
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ dist/
node_modules/

# local SQLite database (regenerated from db/games.csv)
.data/
*.db

# environment variables
Expand Down
1 change: 0 additions & 1 deletion .npmrc

This file was deleted.

4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Please note that this project is released with a [Contributor Code of Conduct][c

Before you can run and test the application locally, you'll need to install:

- **Node.js 20+** - [Download](https://nodejs.org/) | [Homebrew](https://formulae.brew.sh/formula/node)
- **Node.js 22.13+** - [Download](https://nodejs.org/) | [Homebrew](https://formulae.brew.sh/formula/node)
- **Git** - [Download](https://git-scm.com/downloads) | [Homebrew](https://formulae.brew.sh/formula/git)

### Setting Up Your Development Environment
Expand Down Expand Up @@ -49,7 +49,7 @@ Before you can run and test the application locally, you'll need to install:

## Making Changes

### Data Layer (Drizzle + libSQL)
### Data Layer (Drizzle + Node SQLite)

- Define tables in `db/schema.ts`; generate a migration with `npm run db:generate` after schema changes
- Use type hints for all function parameters and return values
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
# Tailspin Toys

Tailspin Toys is a crowdfunding platform for games with a developer theme. The project is a website for a fictional game crowd-funding company, built as a single [Astro](https://astro.build/) site (fully prerendered/static output) styled with [Tailwind CSS](https://tailwindcss.com/). Its data lives in a local SQLite database accessed through [Drizzle ORM](https://orm.drizzle.team/) over [libSQL](https://github.com/tursodatabase/libsql); pages query the database directly in frontmatter at build time, so there is no separate backend service.
Tailspin Toys is a crowdfunding platform for games with a developer theme. The project is a website for a fictional game crowd-funding company, built as a single [Astro](https://astro.build/) site (fully prerendered/static output) styled with [Tailwind CSS](https://tailwindcss.com/). Its data lives in a local SQLite database accessed through [Drizzle ORM](https://orm.drizzle.team/) and Node.js's built-in SQLite driver; pages query the database directly in frontmatter at build time, so there is no separate backend service.

## Architecture

- **Astro 7** — pages, layouts, components, and routing. `output: 'static'`, so the whole site is prerendered to HTML at build time.
- **Drizzle ORM + libSQL** — the data layer. The schema lives in `db/schema.ts`; data is seeded from `db/games.csv`. Migrations are managed with `drizzle-kit`.
- **Drizzle ORM + Node SQLite** — the data layer. The schema lives in `db/schema.ts`; data is seeded from `db/games.csv`. Migrations are managed with `drizzle-kit`.
- **Tailwind CSS v4** — styling via utility classes (dark theme).
- **Vitest** — unit tests for the data layer and pure transforms.
- **Playwright** — end-to-end tests run against the built static site.

The database is migrated and seeded automatically before `dev`/`build` (via the `predev`/`prebuild` npm scripts) and is written to `.data/` (gitignored).
The database is migrated and seeded automatically before `dev`/`build` (via the `predev`/`prebuild` npm scripts) and is written to the gitignored `tailspin.db` file.

## Using this template

Expand All @@ -20,7 +20,7 @@ The workflow only runs on repositories created from the template (the `if: ${{ !

## Getting started

Install dependencies once:
Install dependencies once with Node.js 22.13 or later:

```bash
npm ci
Expand Down Expand Up @@ -54,7 +54,7 @@ npm run db:setup # migrate + seed (run automatically by predev/prebuild)
```

> [!NOTE]
> Seeding is idempotent — it skips games that already exist (matched by title) rather than reconciling changed rows. CI always starts from a clean database, so it reflects `games.csv` exactly. Locally, if you edit or remove rows in `games.csv`, delete `.data/` and re-run `npm run db:setup` to fully regenerate.
> Seeding is idempotent — it skips games that already exist (matched by title) rather than reconciling changed rows. CI always starts from a clean database, so it reflects `games.csv` exactly. Locally, if you edit or remove rows in `games.csv`, delete `tailspin.db` and re-run `npm run db:setup` to fully regenerate.

## Running tests

Expand Down
12 changes: 8 additions & 4 deletions db/migrate.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import { migrate } from 'drizzle-orm/libsql/migrator';
import { migrate } from 'drizzle-orm/sqlite-proxy/migrator';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { createDatabase } from '../src/lib/db';
import { createDatabaseConnection, executeMigrationQueries } from '../src/lib/db';

const here = dirname(fileURLToPath(import.meta.url));

async function run(): Promise<void> {
const db = createDatabase();
await migrate(db, { migrationsFolder: join(here, 'migrations') });
const { db, sqlite } = createDatabaseConnection();
await migrate(
db,
async (queries: string[]): Promise<void> => executeMigrationQueries(sqlite, queries),
{ migrationsFolder: join(here, 'migrations') },
);
console.log('Migrations applied.');
}

Expand Down
Loading
Loading