chore: restructure to new cozy-games monorepo

Reviewed-on: https://git.ayo.run/ayo/cozy-games/pulls/1
Co-authored-by: Ayo <ayo@ayco.io>
Co-committed-by: Ayo <ayo@ayco.io>
This commit is contained in:
ayo 2026-07-03 13:53:13 +00:00 committed by ayo
parent 5f6e389258
commit 969ebba067
60 changed files with 169 additions and 180 deletions

6
.gitignore vendored
View file

@ -1,14 +1,14 @@
node_modules/
dist/
# generated at publish time from the root README.md (see scripts/publish-lib.js)
lib/README.md
# generated at publish time from apps/mnswpr/README.md (see scripts/publish-lib.js)
packages/mnswpr/README.md
.claude
# Production / local Firebase config values are not committed — set them as
# Netlify env vars, or keep them in a local, gitignored env file. Dev values
# live in the committed app/.env.development (public, non-secret keys).
# live in the committed apps/mnswpr/.env.development (public, non-secret keys).
.env.production
.env.local
.env.*.local

View file

@ -10,9 +10,9 @@ Classic Minesweeper as a vanilla web game — no framework, no TypeScript (JSDoc
```bash
pnpm i # install (pnpm is required — this is a pnpm workspace)
pnpm dev # run the website dev server (vite app) — most common
pnpm build # build the website -> app/dist
pnpm build:lib # build the publishable library -> lib/dist
pnpm dev # run the website dev server (vite apps/mnswpr) — most common
pnpm build # build the website -> apps/mnswpr/dist
pnpm build:lib # build the publishable library -> packages/mnswpr/dist
pnpm lint # eslint . (JS + CSS); runs automatically on pre-commit
pnpm lint:fix # eslint --fix
pnpm build:preview # build the app and serve the production preview
@ -20,21 +20,23 @@ pnpm test # run the Vitest suite once (jsdom)
pnpm test:watch # run Vitest in watch mode
```
Tests live in `test/` and run under **Vitest** with a jsdom environment (config in `vitest.config.js`). They cover the shared utils and drive the engine through real DOM events (mount `#app`, dispatch mouse events, assert on cell/grid attributes). For anything visual or input-timing related, also verify by running `pnpm dev` and playing.
Tests are co-located with the package they exercise (`packages/utils/test/`, `packages/mnswpr/test/`) and run under **Vitest** with a jsdom environment (root config in `vitest.config.js`). They cover the shared utils and drive the engine through real DOM events (mount `#app`, dispatch mouse events, assert on cell/grid attributes). For anything visual or input-timing related, also verify by running `pnpm dev` and playing.
Node version: `.nvmrc` pins `lts/*`.
## Repository layout (pnpm workspace)
## Repository layout (Cozy Games monorepo, pnpm workspace)
Workspaces are declared in `pnpm-workspace.yaml` as `lib` and `app`. Note that `utils/` is **not** a workspace package — it's a plain shared folder imported by relative path from both `lib` and `app`.
This is the **Cozy Games** monorepo. Workspaces are declared in `pnpm-workspace.yaml` as `apps/*`, `packages/*`, and `sites/*`. `utils/` is now a real workspace package (`@cozy-games/utils`), imported by name — no more `../utils` relative paths.
- **`lib/`** — `@ayo-run/mnswpr`, the standalone, framework-free game engine. This is what gets published to npm. `lib/mnswpr.js` is the whole engine; `lib/levels.js` defines the four difficulty presets. Depends only on `utils/`.
- **`app/`** — the mnswpr.com website. Consumes the library via `workspace:*` (`import mnswpr from '@ayo-run/mnswpr/mnswpr.js'`) and adds the Firebase leaderboard. `app/main.js` wires the engine to the leaderboard through the engine's hooks.
- **`utils/`** — shared services with no dependencies, re-exported from `utils/index.js`: `StorageService` (localStorage/sessionStorage JSON wrapper), `TimerService` (game clock + `pretty()` time formatting used by both engine and leaderboard), `LoggerService`, `LoadingService`.
- **`apps/mnswpr/`** — `@ayo-run/mnswpr`'s host, the mnswpr.com website. Consumes the engine and leaderboard via `workspace:*` (`import mnswpr from '@ayo-run/mnswpr/mnswpr.js'`) and wires them together in `apps/mnswpr/main.js`. Owns its Firebase config (`firebase.json`, `firestore.rules`, `.firebaserc`) and app-specific scripts (`apps/mnswpr/scripts/`). A future app (e.g. sudoku) gets its own `apps/<name>/`.
- **`packages/mnswpr/`** — `@ayo-run/mnswpr`, the standalone, framework-free game engine published to npm. `packages/mnswpr/mnswpr.js` is the whole engine; `levels.js` defines the four difficulty presets. Depends only on `@cozy-games/utils`.
- **`packages/leaderboard/`** — `@cozy-games/leaderboard`, a backend-agnostic, time-windowed leaderboard (adapter-injected storage).
- **`packages/utils/`** — `@cozy-games/utils`, shared services with no dependencies, re-exported from `index.js`: `StorageService`, `TimerService` (`pretty()` time formatting used by both engine and leaderboard), `LoggerService`, `LoadingService`, and date-bucket helpers.
- **`sites/`** — docs (Astro Starlight) and UI demos. Placeholders for now.
## Architecture
**The engine is decoupled from the app via two hooks.** `Minesweeper(appId, version, hooks)` (`lib/mnswpr.js`) is a classic constructor function that imperatively builds a `<table>` grid in the DOM. It knows nothing about Firebase or leaderboards. The app injects behavior through:
**The engine is decoupled from the app via two hooks.** `Minesweeper(appId, version, hooks)` (`packages/mnswpr/mnswpr.js`) is a classic constructor function that imperatively builds a `<table>` grid in the DOM. It knows nothing about Firebase or leaderboards. The app injects behavior through:
- `hooks.levelChanged(setting)` — fired when the difficulty level changes; the app uses this to re-fetch and render the leaderboard for that level.
- `hooks.gameDone(game)` — fired when a game ends (win or loss) with a `game` object (`time`, `status`, `level`, `time_stamp`, `isMobile`); the app uses this to submit the score.
@ -47,9 +49,9 @@ When adding engine features that the website needs to react to, prefer adding a
**Input handling is intricate.** Mouse (left/right/middle, plus simultaneous left+right "chording") and touch (long-press to flag) are handled through a state machine of flags (`isLeft`, `isRight`, `pressed`, `bothPressed`, `skip`, `isBusy`) in `initializeEventHandlers`/`initializeTouchEventHandlers`. `isBusy` debounces input (`MOBILE_BUSY_DELAY`/`PC_BUSY_DELAY`). Tread carefully here — small changes easily break chording or mobile flagging.
**Test mode:** set `TEST_MODE = true` at the top of `lib/mnswpr.js` to render mine positions as visual hints and enable debug logging.
**Test mode:** set `TEST_MODE = true` at the top of `packages/mnswpr/mnswpr.js` to render mine positions as visual hints and enable debug logging.
## Leaderboard / Firebase (`app/modules/`)
## Leaderboard / Firebase (`apps/mnswpr/modules/`)
`LeaderBoardService` (`leader-board.js`) reads/writes Firestore (`firebase/firestore/lite`). Structure: top-10 per level in `mw-leaders/{level}/games`, all sessions in `mw-all/{browserId}/games`, and remote runtime `configuration` in `mw-config`. A score is only offered to the leaderboard when it beats the current 10th place *and* matches the server-side `passingStatus`.
@ -60,9 +62,9 @@ The **Firebase config in `leader-board.js` is intentionally public and committed
## Conventions
- **Code style is enforced by ESLint Stylistic**, not Prettier: 2-space indent, single quotes, **no semicolons**, no trailing commas, spaces inside `{ braces }` but not `[brackets]`. Run `pnpm lint:fix` before committing. Both `**/*.js` and `**/*.css` are linted (CSS via `@eslint/css`).
- The engine uses **plain functions and `var`/`let` closures**, not classes; `utils/` and `app/modules/` use ES classes. Match the surrounding style of the file you edit.
- The engine uses **plain functions and `var`/`let` closures**, not classes; `packages/utils/` and `apps/mnswpr/modules/` use ES classes. Match the surrounding style of the file you edit.
## Release & git hooks (maintainer workflow)
- **Husky hooks:** `pre-commit` runs `pnpm lint`; `post-commit` auto-pushes to two extra remotes (`git push gh`, `git push sh`). If those remotes aren't configured locally, expect post-commit failures — that's environmental, not a code problem.
- **Releasing** (`pnpm release`) builds the lib, runs `bumpp` (version bump + tag) in `lib/`, then `scripts/release.js` force-pushes a `release` branch and tags to remotes `gh`/`sh`. Pushing a `v*` tag triggers `.github/workflows/release.yml` (`changelogithub`) to publish GitHub release notes. Only run this when explicitly releasing.
- **Releasing** (`pnpm release`) builds the lib, runs `bumpp` (version bump + tag) in `packages/mnswpr/`, then `scripts/release.js` force-pushes a `release` branch and tags to remotes `gh`/`sh`. Pushing a `v*` tag triggers `.github/workflows/release.yml` (`changelogithub`) to publish GitHub release notes. Only run this when explicitly releasing.

135
README.md
View file

@ -1,123 +1,40 @@
> [!IMPORTANT]
> **mnswpr has a new home.** Development now happens in the **Cozy Games** monorepo:
> **[git.ayo.run/ayo/cozy-games](https://git.ayo.run/ayo/cozy-games)**.
# Cozy Games
# Play Minesweeper Online for Free
[![Netlify Status](https://api.netlify.com/api/v1/badges/172478bd-afc5-4e47-95ba-d9ab814248fb/deploy-status)](https://app.netlify.com/sites/mnswpr/deploys)
A monorepo for **Cozy Games** — a growing collection of small browser
games and the shared, reusable packages that power them.
Play it here: [mnswpr.com](https://mnswpr.com). This is the classic game **Minesweeper** built with vanilla web technologies (i.e., no framework dependency).
## Layout
![mnswpr gameplay](https://git.ayo.run/ayo/mnswpr/raw/branch/main/screenshot.png)
## How to Play
The goal is to reveal every safe cell without detonating a mine. Your **first click is always safe**.
- **Left click** — reveal a cell
- **Right click** — flag / unflag a suspected mine
- **Left + right click together** (chording) — reveal the neighbors of a satisfied number
- **Touch** — tap to reveal, long-press to flag
## Ways to Use
The web is a wonderful, free, and open platform to create and distribute value. You can use **mnswpr** in different ways:
- as a deployed [web app](https://mnswpr.com)
- as a [library](https://npmx.dev/package/@ayo-run/mnswpr) with `npm i @ayo-run/mnswpr`
- as a `web component` (coming soon).
Using it as a library takes only a few lines — mount it onto any element by `id`:
```js
import '@ayo-run/mnswpr/mnswpr.css'
import mnswpr from '@ayo-run/mnswpr'
const game = new mnswpr('app')
game.initialize()
```
cozy-games/
├── apps/ Playable games (each deploys independently)
│ └── mnswpr/ Minesweeper — mnswpr.com (@ayo-run/mnswpr engine + Firebase leaderboard)
├── packages/ Shared, publishable libraries
│ ├── mnswpr/ @ayo-run/mnswpr — the vanilla Minesweeper game engine
│ ├── leaderboard/ @cozy-games/leaderboard — backend-agnostic, time-windowed leaderboard
│ └── utils/ @cozy-games/utils — shared browser utilities (storage, timer, …)
└── sites/ Docs (Astro Starlight) and UI demos — placeholders for now
```
## Tooling
The project has gone through years of existence. It started from 2019 when tooling was massively different. I have [modernized it](https://elk.zone/social.ayco.io/@ayo/116333804543330938) since and have witnessed how much easier and faster it is to build now - even without web frameworks or LLMs!
Each app owns its own backend config (e.g. mnswpr's Firestore rules live in
`apps/mnswpr/`); the shared packages stay backend-agnostic.
As of now the tooling I use are:
- [Vite](https://vite.dev/) for bundling and development server
- [Eslint](https://eslint.org) for JS linting & [CSS linting](https://eslint.org/blog/2025/02/eslint-css-support/)
- [ESLint Stylistic](https://eslint.style) for JS formatting
- [Husky](https://typicode.github.io/husky/) for git hooks
- [PNPM](https://pnpm.io/installation) for dependency & workspace management
- and a bunch of automation using scripts and Continuous Integration actions
## Getting started
Because a big part of this project's purpose is to track how the software development industry evolves — and because it has come a long way in modernizing along the way — I now also use it as a **playground for coding agents**. It's a small, framework-free, well-scoped codebase, which makes it a great sandbox to see how AI agents read, reason about, and change real code. To help them get their bearings quickly, the repo ships an [`AGENTS.md`](./AGENTS.md) describing the architecture and conventions.
## Development
Technology Stack: HTML, JS, and CSS; [Google Firebase](https://firebase.google.com) for leader board store; [Netlify](https://netlify.com) for hosting
To start development, you need [`node`](https://nodejs.org/en/download). I highly recommend [`pnpm`](https://pnpm.io/installation) to be used as well. Once you know you have this, you can do the following:
1. Install dependencies: `pnpm i`
2. Start the dev server: `pnpm run dev`
The rest of the everyday commands:
This is a [pnpm](https://pnpm.io) workspace (pnpm is required).
```bash
pnpm test # run the Vitest suite
pnpm lint # ESLint (JS + CSS)
pnpm lint:fix # ESLint with autofix
pnpm build # build the website
pnpm build:lib # build the publishable library
pnpm i # install
pnpm dev # run the mnswpr app (Vite dev server)
pnpm test # run all package tests (vitest)
pnpm lint # eslint
pnpm build # build the mnswpr app -> apps/mnswpr/dist
pnpm build:lib # build the engine package -> packages/mnswpr/dist
```
### Leaderboard (local Firestore emulator)
The leader board is backed by [Google Firestore](https://firebase.google.com). For local development the app talks to the **Firestore emulator** by default — fully local, no cloud, no deploy. The flag `VITE_FIRESTORE_EMULATOR=1` is already set in `app/.env.development`.
You need a **JDK 21+** installed (the emulator runs on Java; `firebase-tools` itself is fetched on demand via `npx`). Then, in two terminals:
```bash
pnpm emulators # terminal 1 — start the Firestore emulator on :8080 (+ UI)
pnpm seed:emulator # terminal 2, once — fill it with sample scores
pnpm dev # terminal 2 — run the app against the emulator
```
If the emulator isn't running, the board simply shows *"unavailable"* (a refused connection). To skip the emulator — for quick UI-only work, or if you don't have a JDK — set `VITE_FIRESTORE_EMULATOR=` (empty) in a local, gitignored `app/.env.local`; the app then uses the cloud `mw-test` namespace instead.
See [`docs/firebase-leaderboards.md`](./docs/firebase-leaderboards.md) for the full data model, security rules, environments, and deployment.
## Contributing
Contributions are welcome! See [`AGENTS.md`](./AGENTS.md) for the architecture, conventions, and release workflow before opening a pull request.
## You just want to play?
*👉 The live site is here: [mnswpr.com](https://mnswpr.com)*
## Background
One day, while working in my home office, I heard loud and fast mouse clicks coming from our bedroom. It's my wife, playing her favorite game (Minesweeper) on a crappy website full of advertisements.
I can't allow this, it's a security issue. 🤣
But it is also an opportunity.
I wanted to give her the same game, with a similar leader board she can dominate. And this is also a chance for me to dig deeper into vanilla JS.
Can I make a page with complex interactions (more on this later) without any library dependency?
## What I have learned:
1. JS is awesome ✨
1. We don't always *need* JS frameworks (or TS) ✨
1. Even subtle UI changes *can improve* user gameplay experience ✨
1. There's more ways to break your app than you are initially aware of ✨
1. Competition motivates users to use your app more ✨
1. Hash in bundled filenames helps avoid issues with browser caching (when shipping versions fast) ✨
See [apps/mnswpr/README.md](apps/mnswpr/README.md) for the game itself, and each package's
README for library usage.
## License
[BSD-2-Clause](./LICENSE)
---
_Just keep building._<br>
_A project by [Ayo](https://ayo.ayco.io)_
BSD-2-Clause © Ayo Ayco

View file

@ -3,12 +3,19 @@
Play it here: [mnswpr.com](https://mnswpr.com). This is the classic game **Minesweeper** built with vanilla web technologies (i.e., no framework dependency).
<!-- TODO: replace with the actual screenshot/GIF once supplied (confirm final path/filename) -->
![mnswpr gameplay](app/public/screenshot.png)
![mnswpr gameplay](https://git.ayo.run/ayo/mnswpr/raw/branch/main/screenshot.png)
Technology Stack: HTML, JS, and CSS; [Google Firebase](https://firebase.google.com) for leader board store; [Netlify](https://netlify.com) for hosting
## How to Play
## Usage
The goal is to reveal every safe cell without detonating a mine. Your **first click is always safe**.
- **Left click** — reveal a cell
- **Right click** — flag / unflag a suspected mine
- **Left + right click together** (chording) — reveal the neighbors of a satisfied number
- **Touch** — tap to reveal, long-press to flag
## Ways to Use
The web is a wonderful, free, and open platform to create and distribute value. You can use **mnswpr** in different ways:
@ -26,15 +33,6 @@ const game = new mnswpr('app')
game.initialize()
```
## How to Play
The goal is to reveal every safe cell without detonating a mine. Your **first click is always safe**.
- **Left click** — reveal a cell
- **Right click** — flag / unflag a suspected mine
- **Left + right click together** (chording) — reveal the neighbors of a satisfied number
- **Touch** — tap to reveal, long-press to flag
## Tooling
The project has gone through years of existence. It started from 2019 when tooling was massively different. I have [modernized it](https://elk.zone/social.ayco.io/@ayo/116333804543330938) since and have witnessed how much easier and faster it is to build now - even without web frameworks or LLMs!
@ -49,6 +47,9 @@ As of now the tooling I use are:
Because a big part of this project's purpose is to track how the software development industry evolves — and because it has come a long way in modernizing along the way — I now also use it as a **playground for coding agents**. It's a small, framework-free, well-scoped codebase, which makes it a great sandbox to see how AI agents read, reason about, and change real code. To help them get their bearings quickly, the repo ships an [`AGENTS.md`](./AGENTS.md) describing the architecture and conventions.
## Development
Technology Stack: HTML, JS, and CSS; [Google Firebase](https://firebase.google.com) for leader board store; [Netlify](https://netlify.com) for hosting
To start development, you need [`node`](https://nodejs.org/en/download). I highly recommend [`pnpm`](https://pnpm.io/installation) to be used as well. Once you know you have this, you can do the following:
1. Install dependencies: `pnpm i`
2. Start the dev server: `pnpm run dev`
@ -63,6 +64,22 @@ pnpm build # build the website
pnpm build:lib # build the publishable library
```
### Leaderboard (local Firestore emulator)
The leader board is backed by [Google Firestore](https://firebase.google.com). For local development the app talks to the **Firestore emulator** by default — fully local, no cloud, no deploy. The flag `VITE_FIRESTORE_EMULATOR=1` is already set in `app/.env.development`.
You need a **JDK 21+** installed (the emulator runs on Java; `firebase-tools` itself is fetched on demand via `npx`). Then, in two terminals:
```bash
pnpm emulators # terminal 1 — start the Firestore emulator on :8080 (+ UI)
pnpm seed:emulator # terminal 2, once — fill it with sample scores
pnpm dev # terminal 2 — run the app against the emulator
```
If the emulator isn't running, the board simply shows *"unavailable"* (a refused connection). To skip the emulator — for quick UI-only work, or if you don't have a JDK — set `VITE_FIRESTORE_EMULATOR=` (empty) in a local, gitignored `app/.env.local`; the app then uses the cloud `mw-test` namespace instead.
See [`docs/firebase-leaderboards.md`](./docs/firebase-leaderboards.md) for the full data model, security rules, environments, and deployment.
## Contributing
Contributions are welcome! See [`AGENTS.md`](./AGENTS.md) for the architecture, conventions, and release workflow before opening a pull request.

View file

@ -10,7 +10,6 @@
<link rel="shortcut icon" type="image/png" href="/favicon.ico" />
<link rel="stylesheet" href="./main.css" />
<link rel="stylesheet" href="../utils/loading/loading.css" />
<style>
:host, :root{
--mnswpr-transition: 10s ease-in-out;

View file

@ -1,5 +1,6 @@
import mnswpr from '@ayo-run/mnswpr/mnswpr.js'
import '@ayo-run/mnswpr/mnswpr.css'
import '@cozy-games/utils/loading/loading.css'
import * as pkg from '@ayo-run/mnswpr/package.json'
import { configureLeaderboard } from '@cozy-games/leaderboard/leaderboard-element.js'
import { FirebaseAdapter } from '@cozy-games/leaderboard/adapters/firebase.js'

View file

@ -1,4 +1,4 @@
import { StorageService } from '../../../utils/index.js'
import { StorageService } from '@cozy-games/utils'
const NICK_KEY = 'nickname'
const MAX_LENGTH = 24

View file

@ -12,6 +12,7 @@
"devDependencies": {
"@ayo-run/mnswpr": "workspace:*",
"@cozy-games/leaderboard": "workspace:*",
"@cozy-games/utils": "workspace:*",
"firebase": "^12.11.0",
"web-component-base": "^4.1.2"
},

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,12 +1,12 @@
/**
* One-time generator: snapshot the all-time leaders from the legacy
* `mw-leaders/{level}/games` collection and write them into app/legends.html as
* `mw-leaders/{level}/games` collection and write them into legends.html as
* a FULLY-RENDERED, static page no runtime JS, no Firebase at page load.
*
* The data never changes, so the page is a frozen artifact. Re-run only if you
* ever need to regenerate it. Because `firebase` is an app-workspace dependency,
* run it so Node can resolve it, e.g. from the app directory:
* (cd app && node ../scripts/export-legends.js)
* (cd apps/mnswpr && node scripts/export-legends.js)
*/
import { writeFileSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
@ -17,9 +17,9 @@ import {
getFirestore, getDocs, collection, query, orderBy, limit
} from 'firebase/firestore/lite'
import { levels } from '../lib/levels.js'
import { levels } from '@ayo-run/mnswpr/levels.js'
// Mirror of TimerService.pretty() (utils/timer/timer.js) — inlined so this
// Mirror of TimerService.pretty() (@cozy-games/utils timer) — inlined so this
// generator has no cross-module import chain to resolve under raw Node.
const clean = (str, separator) => (str === '00' ? '' : `${str}${separator}`)
const pretty = duration => {
@ -131,6 +131,6 @@ ${sections.join('\n')}
`
const __dirname = dirname(fileURLToPath(import.meta.url))
const out = resolve(__dirname, '../app/legends.html')
const out = resolve(__dirname, '../legends.html')
writeFileSync(out, html)
console.log(`\nWrote ${out}`)

View file

@ -13,7 +13,7 @@
* firestore.rules to be deployed first (they permit writes to
* `mw-test-scores`), else every write returns permission-denied:
* npx firebase deploy --only firestore:rules --project dev
* (cd app && node ../scripts/seed-dev-scores.js)
* (cd apps/mnswpr && node scripts/seed-dev-scores.js)
*
* `firebase` is an app-workspace dependency, so run it from the app directory so
* Node can resolve it. Optional namespace override: LB_NAMESPACE=mw-test.
@ -21,7 +21,7 @@
import { initializeApp } from 'firebase/app'
import { getFirestore, connectFirestoreEmulator, doc, collection, setDoc } from 'firebase/firestore/lite'
import { buckets } from '../utils/date-bucket/date-bucket.js'
import { buckets } from '@cozy-games/utils/date-bucket/date-bucket.js'
const firebaseConfig = {
apiKey: 'AIzaSyCTi_5Sm5dHFNf0d_Gn0MNWmlGheFBf6MQ',

View file

@ -50,7 +50,7 @@ export default defineConfig([
}
},
{
files: ['scripts/**/*.js'],
files: ['**/scripts/**/*.js'],
languageOptions: {
globals: globals.node
}

View file

@ -1,8 +1,8 @@
{
"name": "monorepo",
"name": "cozy-games",
"version": "0.0.1",
"private": true,
"description": "Classic Minesweeper browser game",
"description": "Cozy Games monorepo — apps, shared packages, and sites",
"author": "Ayo Ayco",
"type": "module",
"repository": {
@ -13,12 +13,12 @@
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"dev": "vite app",
"start": "vite app",
"emulators": "npx -y firebase-tools emulators:start --only firestore",
"seed:emulator": "cd app && FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 node ../scripts/seed-dev-scores.js",
"build": "vite build app",
"build:lib": "vite build lib",
"dev": "vite apps/mnswpr",
"start": "vite apps/mnswpr",
"emulators": "cd apps/mnswpr && npx -y firebase-tools emulators:start --only firestore",
"seed:emulator": "cd apps/mnswpr && FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 node scripts/seed-dev-scores.js",
"build": "vite build apps/mnswpr",
"build:lib": "vite build packages/mnswpr",
"publish:lib": "node scripts/publish-lib.js",
"release": "pnpm build:lib && pnpm -F @ayo-run/mnswpr run release && pnpm publish:lib",
"build:preview": "pnpm -F app run build:preview",

View file

@ -1,4 +1,4 @@
import { buckets } from '../utils/date-bucket/date-bucket.js'
import { buckets } from '@cozy-games/utils/date-bucket/date-bucket.js'
const DAY_MS = 24 * 60 * 60 * 1000

View file

@ -27,6 +27,9 @@
"dependencies": {
"web-component-base": "^4.1.2"
},
"devDependencies": {
"@cozy-games/utils": "workspace:*"
},
"peerDependencies": {
"firebase": "^12.11.0"
},

View file

@ -9,7 +9,7 @@ import {
LoggerService,
StorageService,
TimerService
} from '../utils/index.js'
} from '@cozy-games/utils'
import { levels } from './levels.js'
const TEST_MODE = false // set to true if you want to test the game with visual hints

View file

@ -10,9 +10,12 @@
},
"homepage": "https://mnswpr.com",
"scripts": {
"release": "bumpp && node ../scripts/release.js"
"release": "bumpp && node ../../scripts/release.js"
},
"main": "mnswpr.js",
"devDependencies": {
"@cozy-games/utils": "workspace:*"
},
"exports": {
".": {
"default": "./dist/mnswpr.js"

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { levels } from '../lib/levels.js'
import { levels } from '../levels.js'
describe('levels', () => {
it('defines the four expected difficulty presets', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import Minesweeper from '../lib/mnswpr.js'
import Minesweeper from '../mnswpr.js'
// Build a fresh board mounted on #app and return its grid <table>.
function mountGame() {

View file

@ -0,0 +1,22 @@
{
"name": "@cozy-games/utils",
"version": "0.0.1",
"description": "Shared, dependency-free browser utilities for Cozy Games (storage, timer, logger, loading, date buckets)",
"author": "Ayo Ayco",
"private": true,
"type": "module",
"repository": {
"type": "git",
"url": "https://github.com/ayo-run/mnswpr"
},
"main": "index.js",
"exports": {
".": {
"default": "./index.js"
},
"./*": {
"default": "./*"
}
},
"license": "BSD-2-Clause"
}

View file

@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { dayKey, weekKey, monthKey, buckets } from '../utils/date-bucket/date-bucket.js'
import { dayKey, weekKey, monthKey, buckets } from '@cozy-games/utils/date-bucket/date-bucket.js'
describe('date-bucket', () => {
it('builds a zero-padded UTC day key', () => {

View file

@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach } from 'vitest'
import { StorageService } from '../utils/storage/storage.js'
import { StorageService } from '@cozy-games/utils/storage/storage.js'
describe('StorageService', () => {
let storage

View file

@ -1,5 +1,5 @@
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { TimerService } from '../utils/timer/timer.js'
import { TimerService } from '@cozy-games/utils/timer/timer.js'
describe('TimerService.pretty', () => {
const timer = new TimerService()

View file

@ -42,14 +42,17 @@ importers:
specifier: ^4.1.9
version: 4.1.9(@types/node@25.5.2)(jsdom@29.1.1)(vite@8.0.3(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)(@types/node@25.5.2)(jiti@2.6.1)(yaml@2.8.3))
app:
apps/mnswpr:
devDependencies:
'@ayo-run/mnswpr':
specifier: workspace:*
version: link:../lib
version: link:../../packages/mnswpr
'@cozy-games/leaderboard':
specifier: workspace:*
version: link:../leaderboard
version: link:../../packages/leaderboard
'@cozy-games/utils':
specifier: workspace:*
version: link:../../packages/utils
firebase:
specifier: ^12.11.0
version: 12.11.0
@ -57,7 +60,7 @@ importers:
specifier: ^4.1.2
version: 4.1.2
leaderboard:
packages/leaderboard:
dependencies:
firebase:
specifier: ^12.11.0
@ -65,8 +68,18 @@ importers:
web-component-base:
specifier: ^4.1.2
version: 4.1.2
devDependencies:
'@cozy-games/utils':
specifier: workspace:*
version: link:../utils
lib: {}
packages/mnswpr:
devDependencies:
'@cozy-games/utils':
specifier: workspace:*
version: link:../utils
packages/utils: {}
packages:

View file

@ -1,7 +1,7 @@
packages:
- "lib"
- "app"
- "leaderboard"
- "apps/*"
- "packages/*"
- "sites/*"
allowBuilds:
'@firebase/util': false
protobufjs: false

View file

@ -1,17 +1,18 @@
// Publishes @ayo-run/mnswpr to npm using the root project README.md as the
// Publishes @ayo-run/mnswpr to npm using the mnswpr app README.md as the
// package's README (what npmjs.com displays).
//
// lib/README.md is a generated copy (gitignored) — the source of truth is the
// root README.md. The library's own guide lives in lib/TUTORIAL.md.
// packages/mnswpr/README.md is a generated copy (gitignored) — the source of
// truth is apps/mnswpr/README.md. The library's own guide lives in
// packages/mnswpr/TUTORIAL.md.
import { execSync } from 'node:child_process'
import { copyFileSync } from 'node:fs'
import { resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
const libDir = resolve(root, 'lib')
const libDir = resolve(root, 'packages/mnswpr')
console.log('Copying root README.md into lib/ for the published package')
copyFileSync(resolve(root, 'README.md'), resolve(libDir, 'README.md'))
console.log('Copying apps/mnswpr/README.md into packages/mnswpr/ for the published package')
copyFileSync(resolve(root, 'apps/mnswpr/README.md'), resolve(libDir, 'README.md'))
execSync('npm login')
execSync('npm publish', { cwd: libDir, stdio: 'inherit' })

5
sites/demos/README.md Normal file
View file

@ -0,0 +1,5 @@
# Cozy Games — UI Demos (placeholder)
Interactive UI/component demos will live here. Not yet scaffolded.
Tracked in the monorepo transition plan as a later pass.

5
sites/docs/README.md Normal file
View file

@ -0,0 +1,5 @@
# Cozy Games — Docs (placeholder)
The documentation site (Astro Starlight) will live here. Not yet scaffolded.
Tracked in the monorepo transition plan as a later pass.

View file

@ -3,6 +3,6 @@ import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom',
include: ['test/**/*.test.js']
include: ['packages/**/test/**/*.test.js', 'apps/**/test/**/*.test.js']
}
})