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/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
# generated at publish time from the root README.md (see scripts/publish-lib.js)
|
# generated at publish time from apps/mnswpr/README.md (see scripts/publish-lib.js)
|
||||||
lib/README.md
|
packages/mnswpr/README.md
|
||||||
|
|
||||||
.claude
|
.claude
|
||||||
|
|
||||||
# Production / local Firebase config values are not committed — set them as
|
# 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
|
# 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.production
|
||||||
.env.local
|
.env.local
|
||||||
.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
|
```bash
|
||||||
pnpm i # install (pnpm is required — this is a pnpm workspace)
|
pnpm i # install (pnpm is required — this is a pnpm workspace)
|
||||||
pnpm dev # run the website dev server (vite app) — most common
|
pnpm dev # run the website dev server (vite apps/mnswpr) — most common
|
||||||
pnpm build # build the website -> app/dist
|
pnpm build # build the website -> apps/mnswpr/dist
|
||||||
pnpm build:lib # build the publishable library -> lib/dist
|
pnpm build:lib # build the publishable library -> packages/mnswpr/dist
|
||||||
pnpm lint # eslint . (JS + CSS); runs automatically on pre-commit
|
pnpm lint # eslint . (JS + CSS); runs automatically on pre-commit
|
||||||
pnpm lint:fix # eslint --fix
|
pnpm lint:fix # eslint --fix
|
||||||
pnpm build:preview # build the app and serve the production preview
|
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
|
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/*`.
|
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/`.
|
- **`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>/`.
|
||||||
- **`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.
|
- **`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`.
|
||||||
- **`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`.
|
- **`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
|
## 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.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.
|
- `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.
|
**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`.
|
`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
|
## 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`).
|
- **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)
|
## 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.
|
- **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]
|
# Cozy Games
|
||||||
> **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)**.
|
|
||||||
|
|
||||||
# Play Minesweeper Online for Free
|
A monorepo for **Cozy Games** — a growing collection of small browser
|
||||||
[](https://app.netlify.com/sites/mnswpr/deploys)
|
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
|
||||||
|
|
||||||

|
```
|
||||||
|
cozy-games/
|
||||||
## How to Play
|
├── apps/ Playable games (each deploys independently)
|
||||||
|
│ └── mnswpr/ Minesweeper — mnswpr.com (@ayo-run/mnswpr engine + Firebase leaderboard)
|
||||||
The goal is to reveal every safe cell without detonating a mine. Your **first click is always safe**.
|
├── packages/ Shared, publishable libraries
|
||||||
|
│ ├── mnswpr/ @ayo-run/mnswpr — the vanilla Minesweeper game engine
|
||||||
- **Left click** — reveal a cell
|
│ ├── leaderboard/ @cozy-games/leaderboard — backend-agnostic, time-windowed leaderboard
|
||||||
- **Right click** — flag / unflag a suspected mine
|
│ └── utils/ @cozy-games/utils — shared browser utilities (storage, timer, …)
|
||||||
- **Left + right click together** (chording) — reveal the neighbors of a satisfied number
|
└── sites/ Docs (Astro Starlight) and UI demos — placeholders for now
|
||||||
- **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()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tooling
|
Each app owns its own backend config (e.g. mnswpr's Firestore rules live in
|
||||||
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!
|
`apps/mnswpr/`); the shared packages stay backend-agnostic.
|
||||||
|
|
||||||
As of now the tooling I use are:
|
## Getting started
|
||||||
- [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
|
|
||||||
|
|
||||||
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.
|
This is a [pnpm](https://pnpm.io) workspace (pnpm is required).
|
||||||
|
|
||||||
## 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:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm test # run the Vitest suite
|
pnpm i # install
|
||||||
pnpm lint # ESLint (JS + CSS)
|
pnpm dev # run the mnswpr app (Vite dev server)
|
||||||
pnpm lint:fix # ESLint with autofix
|
pnpm test # run all package tests (vitest)
|
||||||
pnpm build # build the website
|
pnpm lint # eslint
|
||||||
pnpm build:lib # build the publishable library
|
pnpm build # build the mnswpr app -> apps/mnswpr/dist
|
||||||
|
pnpm build:lib # build the engine package -> packages/mnswpr/dist
|
||||||
```
|
```
|
||||||
|
|
||||||
### Leaderboard (local Firestore emulator)
|
See [apps/mnswpr/README.md](apps/mnswpr/README.md) for the game itself, and each package's
|
||||||
|
README for library usage.
|
||||||
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) ✨
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
[BSD-2-Clause](./LICENSE)
|
BSD-2-Clause © Ayo Ayco
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
_Just keep building._<br>
|
|
||||||
_A project by [Ayo](https://ayo.ayco.io)_
|
|
||||||
|
|
|
||||||
|
|
@ -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).
|
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:
|
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()
|
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
|
## 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!
|
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.
|
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
|
## 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:
|
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`
|
1. Install dependencies: `pnpm i`
|
||||||
2. Start the dev server: `pnpm run dev`
|
2. Start the dev server: `pnpm run dev`
|
||||||
|
|
@ -63,6 +64,22 @@ pnpm build # build the website
|
||||||
pnpm build:lib # build the publishable library
|
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
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! See [`AGENTS.md`](./AGENTS.md) for the architecture, conventions, and release workflow before opening a pull request.
|
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="shortcut icon" type="image/png" href="/favicon.ico" />
|
||||||
<link rel="stylesheet" href="./main.css" />
|
<link rel="stylesheet" href="./main.css" />
|
||||||
<link rel="stylesheet" href="../utils/loading/loading.css" />
|
|
||||||
<style>
|
<style>
|
||||||
:host, :root{
|
:host, :root{
|
||||||
--mnswpr-transition: 10s ease-in-out;
|
--mnswpr-transition: 10s ease-in-out;
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import mnswpr from '@ayo-run/mnswpr/mnswpr.js'
|
import mnswpr from '@ayo-run/mnswpr/mnswpr.js'
|
||||||
import '@ayo-run/mnswpr/mnswpr.css'
|
import '@ayo-run/mnswpr/mnswpr.css'
|
||||||
|
import '@cozy-games/utils/loading/loading.css'
|
||||||
import * as pkg from '@ayo-run/mnswpr/package.json'
|
import * as pkg from '@ayo-run/mnswpr/package.json'
|
||||||
import { configureLeaderboard } from '@cozy-games/leaderboard/leaderboard-element.js'
|
import { configureLeaderboard } from '@cozy-games/leaderboard/leaderboard-element.js'
|
||||||
import { FirebaseAdapter } from '@cozy-games/leaderboard/adapters/firebase.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 NICK_KEY = 'nickname'
|
||||||
const MAX_LENGTH = 24
|
const MAX_LENGTH = 24
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ayo-run/mnswpr": "workspace:*",
|
"@ayo-run/mnswpr": "workspace:*",
|
||||||
"@cozy-games/leaderboard": "workspace:*",
|
"@cozy-games/leaderboard": "workspace:*",
|
||||||
|
"@cozy-games/utils": "workspace:*",
|
||||||
"firebase": "^12.11.0",
|
"firebase": "^12.11.0",
|
||||||
"web-component-base": "^4.1.2"
|
"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
|
* 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.
|
* 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
|
* 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,
|
* 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:
|
* 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 { writeFileSync } from 'node:fs'
|
||||||
import { resolve, dirname } from 'node:path'
|
import { resolve, dirname } from 'node:path'
|
||||||
|
|
@ -17,9 +17,9 @@ import {
|
||||||
getFirestore, getDocs, collection, query, orderBy, limit
|
getFirestore, getDocs, collection, query, orderBy, limit
|
||||||
} from 'firebase/firestore/lite'
|
} 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.
|
// generator has no cross-module import chain to resolve under raw Node.
|
||||||
const clean = (str, separator) => (str === '00' ? '' : `${str}${separator}`)
|
const clean = (str, separator) => (str === '00' ? '' : `${str}${separator}`)
|
||||||
const pretty = duration => {
|
const pretty = duration => {
|
||||||
|
|
@ -131,6 +131,6 @@ ${sections.join('\n')}
|
||||||
`
|
`
|
||||||
|
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
const out = resolve(__dirname, '../app/legends.html')
|
const out = resolve(__dirname, '../legends.html')
|
||||||
writeFileSync(out, html)
|
writeFileSync(out, html)
|
||||||
console.log(`\nWrote ${out}`)
|
console.log(`\nWrote ${out}`)
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
* firestore.rules to be deployed first (they permit writes to
|
* firestore.rules to be deployed first (they permit writes to
|
||||||
* `mw-test-scores`), else every write returns permission-denied:
|
* `mw-test-scores`), else every write returns permission-denied:
|
||||||
* npx firebase deploy --only firestore:rules --project dev
|
* 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
|
* `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.
|
* Node can resolve it. Optional namespace override: LB_NAMESPACE=mw-test.
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
import { initializeApp } from 'firebase/app'
|
import { initializeApp } from 'firebase/app'
|
||||||
import { getFirestore, connectFirestoreEmulator, doc, collection, setDoc } from 'firebase/firestore/lite'
|
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 = {
|
const firebaseConfig = {
|
||||||
apiKey: 'AIzaSyCTi_5Sm5dHFNf0d_Gn0MNWmlGheFBf6MQ',
|
apiKey: 'AIzaSyCTi_5Sm5dHFNf0d_Gn0MNWmlGheFBf6MQ',
|
||||||
|
|
@ -50,7 +50,7 @@ export default defineConfig([
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ['scripts/**/*.js'],
|
files: ['**/scripts/**/*.js'],
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: globals.node
|
globals: globals.node
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
package.json
16
package.json
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "monorepo",
|
"name": "cozy-games",
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Classic Minesweeper browser game",
|
"description": "Cozy Games monorepo — apps, shared packages, and sites",
|
||||||
"author": "Ayo Ayco",
|
"author": "Ayo Ayco",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
@ -13,12 +13,12 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:watch": "vitest",
|
"test:watch": "vitest",
|
||||||
"dev": "vite app",
|
"dev": "vite apps/mnswpr",
|
||||||
"start": "vite app",
|
"start": "vite apps/mnswpr",
|
||||||
"emulators": "npx -y firebase-tools emulators:start --only firestore",
|
"emulators": "cd apps/mnswpr && 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",
|
"seed:emulator": "cd apps/mnswpr && FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 node scripts/seed-dev-scores.js",
|
||||||
"build": "vite build app",
|
"build": "vite build apps/mnswpr",
|
||||||
"build:lib": "vite build lib",
|
"build:lib": "vite build packages/mnswpr",
|
||||||
"publish:lib": "node scripts/publish-lib.js",
|
"publish:lib": "node scripts/publish-lib.js",
|
||||||
"release": "pnpm build:lib && pnpm -F @ayo-run/mnswpr run release && pnpm publish:lib",
|
"release": "pnpm build:lib && pnpm -F @ayo-run/mnswpr run release && pnpm publish:lib",
|
||||||
"build:preview": "pnpm -F app run build:preview",
|
"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
|
const DAY_MS = 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
|
|
@ -27,6 +27,9 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"web-component-base": "^4.1.2"
|
"web-component-base": "^4.1.2"
|
||||||
},
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@cozy-games/utils": "workspace:*"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"firebase": "^12.11.0"
|
"firebase": "^12.11.0"
|
||||||
},
|
},
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
LoggerService,
|
LoggerService,
|
||||||
StorageService,
|
StorageService,
|
||||||
TimerService
|
TimerService
|
||||||
} from '../utils/index.js'
|
} from '@cozy-games/utils'
|
||||||
import { levels } from './levels.js'
|
import { levels } from './levels.js'
|
||||||
|
|
||||||
const TEST_MODE = false // set to true if you want to test the game with visual hints
|
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",
|
"homepage": "https://mnswpr.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"release": "bumpp && node ../scripts/release.js"
|
"release": "bumpp && node ../../scripts/release.js"
|
||||||
},
|
},
|
||||||
"main": "mnswpr.js",
|
"main": "mnswpr.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"@cozy-games/utils": "workspace:*"
|
||||||
|
},
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"default": "./dist/mnswpr.js"
|
"default": "./dist/mnswpr.js"
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect } from 'vitest'
|
import { describe, it, expect } from 'vitest'
|
||||||
import { levels } from '../lib/levels.js'
|
import { levels } from '../levels.js'
|
||||||
|
|
||||||
describe('levels', () => {
|
describe('levels', () => {
|
||||||
it('defines the four expected difficulty presets', () => {
|
it('defines the four expected difficulty presets', () => {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
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>.
|
// Build a fresh board mounted on #app and return its grid <table>.
|
||||||
function mountGame() {
|
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 { 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', () => {
|
describe('date-bucket', () => {
|
||||||
it('builds a zero-padded UTC day key', () => {
|
it('builds a zero-padded UTC day key', () => {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect, beforeEach } from 'vitest'
|
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', () => {
|
describe('StorageService', () => {
|
||||||
let storage
|
let storage
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
|
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', () => {
|
describe('TimerService.pretty', () => {
|
||||||
const timer = new TimerService()
|
const timer = new TimerService()
|
||||||
|
|
@ -42,14 +42,17 @@ importers:
|
||||||
specifier: ^4.1.9
|
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))
|
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:
|
devDependencies:
|
||||||
'@ayo-run/mnswpr':
|
'@ayo-run/mnswpr':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../lib
|
version: link:../../packages/mnswpr
|
||||||
'@cozy-games/leaderboard':
|
'@cozy-games/leaderboard':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../leaderboard
|
version: link:../../packages/leaderboard
|
||||||
|
'@cozy-games/utils':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../packages/utils
|
||||||
firebase:
|
firebase:
|
||||||
specifier: ^12.11.0
|
specifier: ^12.11.0
|
||||||
version: 12.11.0
|
version: 12.11.0
|
||||||
|
|
@ -57,7 +60,7 @@ importers:
|
||||||
specifier: ^4.1.2
|
specifier: ^4.1.2
|
||||||
version: 4.1.2
|
version: 4.1.2
|
||||||
|
|
||||||
leaderboard:
|
packages/leaderboard:
|
||||||
dependencies:
|
dependencies:
|
||||||
firebase:
|
firebase:
|
||||||
specifier: ^12.11.0
|
specifier: ^12.11.0
|
||||||
|
|
@ -65,8 +68,18 @@ importers:
|
||||||
web-component-base:
|
web-component-base:
|
||||||
specifier: ^4.1.2
|
specifier: ^4.1.2
|
||||||
version: 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:
|
packages:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
packages:
|
packages:
|
||||||
- "lib"
|
- "apps/*"
|
||||||
- "app"
|
- "packages/*"
|
||||||
- "leaderboard"
|
- "sites/*"
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@firebase/util': false
|
'@firebase/util': false
|
||||||
protobufjs: 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).
|
// package's README (what npmjs.com displays).
|
||||||
//
|
//
|
||||||
// lib/README.md is a generated copy (gitignored) — the source of truth is the
|
// packages/mnswpr/README.md is a generated copy (gitignored) — the source of
|
||||||
// root README.md. The library's own guide lives in lib/TUTORIAL.md.
|
// truth is apps/mnswpr/README.md. The library's own guide lives in
|
||||||
|
// packages/mnswpr/TUTORIAL.md.
|
||||||
import { execSync } from 'node:child_process'
|
import { execSync } from 'node:child_process'
|
||||||
import { copyFileSync } from 'node:fs'
|
import { copyFileSync } from 'node:fs'
|
||||||
import { resolve } from 'node:path'
|
import { resolve } from 'node:path'
|
||||||
|
|
||||||
const root = resolve(import.meta.dirname, '..')
|
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')
|
console.log('Copying apps/mnswpr/README.md into packages/mnswpr/ for the published package')
|
||||||
copyFileSync(resolve(root, 'README.md'), resolve(libDir, 'README.md'))
|
copyFileSync(resolve(root, 'apps/mnswpr/README.md'), resolve(libDir, 'README.md'))
|
||||||
|
|
||||||
execSync('npm login')
|
execSync('npm login')
|
||||||
execSync('npm publish', { cwd: libDir, stdio: 'inherit' })
|
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({
|
export default defineConfig({
|
||||||
test: {
|
test: {
|
||||||
environment: 'jsdom',
|
environment: 'jsdom',
|
||||||
include: ['test/**/*.test.js']
|
include: ['packages/**/test/**/*.test.js', 'apps/**/test/**/*.test.js']
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue