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:
parent
5f6e389258
commit
969ebba067
60 changed files with 169 additions and 180 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
30
AGENTS.md
30
AGENTS.md
|
|
@ -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
135
README.md
|
|
@ -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
|
||||
[](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
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
|
|
|||
|
|
@ -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) -->
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
|
@ -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;
|
||||
|
|
@ -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'
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { StorageService } from '../../../utils/index.js'
|
||||
import { StorageService } from '@cozy-games/utils'
|
||||
|
||||
const NICK_KEY = 'nickname'
|
||||
const MAX_LENGTH = 24
|
||||
|
|
@ -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"
|
||||
},
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
|
@ -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}`)
|
||||
|
|
@ -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',
|
||||
|
|
@ -50,7 +50,7 @@ export default defineConfig([
|
|||
}
|
||||
},
|
||||
{
|
||||
files: ['scripts/**/*.js'],
|
||||
files: ['**/scripts/**/*.js'],
|
||||
languageOptions: {
|
||||
globals: globals.node
|
||||
}
|
||||
|
|
|
|||
16
package.json
16
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -27,6 +27,9 @@
|
|||
"dependencies": {
|
||||
"web-component-base": "^4.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cozy-games/utils": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"firebase": "^12.11.0"
|
||||
},
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
@ -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', () => {
|
||||
|
|
@ -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() {
|
||||
22
packages/utils/package.json
Normal file
22
packages/utils/package.json
Normal 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"
|
||||
}
|
||||
|
|
@ -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', () => {
|
||||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
packages:
|
||||
- "lib"
|
||||
- "app"
|
||||
- "leaderboard"
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
- "sites/*"
|
||||
allowBuilds:
|
||||
'@firebase/util': false
|
||||
protobufjs: false
|
||||
|
|
|
|||
|
|
@ -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
5
sites/demos/README.md
Normal 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
5
sites/docs/README.md
Normal 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.
|
||||
|
|
@ -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']
|
||||
}
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue