This repo is "Node.js in a Month": 20 hands-on, test-driven blocks under
blocks/, written in TypeScript and tested with the built-in
node:test runner. The day-by-day curriculum is in PLAN.md.
Blocks 01 and 02 are the reference implementation of the template below.
When generating or editing any block (03–20), match them exactly.
Every block folder blocks/NN-name/ contains exactly these files:
blocks/NN-name/
README.md # theory + the list of exercises
src/examples.ts # runnable worked examples (npm run play <file>)
src/exercises.ts # student stubs: throw "Not implemented"
tests/exercises.test.ts # student-facing tests (npm test)
solutions/exercises.solution.ts # reference answers
solutions/exercises.solution.test.ts # solution check (npm run test:solutions)
Module-heavy blocks (e.g. a small multi-file app) may use several src/*.ts files plus
an index.ts; mirror them under solutions/. See how the TypeScript sibling course did
blocks 15/20 if needed.
-
src/exercises.ts— each task is anexport functionwith fully annotated parameters and return type already filled in; the body is a stub:export function add(a: number, b: number): number { void a; void b; // remove these lines once you use the parameters // TODO: return a + b throw new Error("Not implemented"); }
The
void x;lines are required becausenoUnusedParametersis on. Keep a short// TODO:hint describing the intended implementation. -
solutions/exercises.solution.ts— the working implementation, same signatures, plus a short// Notes:comment block explaining the why (the Node lesson). -
Tests use
node:test+node:assert/strict(NOT Vitest):import { test } from "node:test"; import assert from "node:assert/strict"; import { add } from "../src/exercises.ts"; test("add sums two numbers", () => { assert.equal(add(2, 3), 5); });
Group related cases with
describe/itfromnode:testwhen helpful. Async tests: make the callbackasyncandawait; for rejections useawait assert.rejects(promise, /message/). -
Imports in tests/solutions carry the
.tsextension (../src/exercises.ts). Node's test runner + tsx resolve the real file; tsc (Bundler resolution) accepts it. -
solutions/exercises.solution.test.tsimports from./exercises.solution.tsand duplicates the same cases — the author/CI guarantee that the reference answers pass. It is excluded fromnpm test(which globs onlyblocks/**/tests/*.test.ts). -
src/examples.ts— start withexport {};so it's a module (prevents top-level names clashing across blocks). Make it runnable:npm run play blocks/NN/src/examples.ts. -
README.md— explain the topic with small code samples and// ❌error examples, then a numbered## Exerciseslist matching the functions insrc/exercises.ts. End by pointing atsolutions/. AREADME.uk.mdUkrainian translation is added later (English is the source of truth); link the two with a**English** · [Українська]switcher.
npm run typecheck # tsc --noEmit, zero errors (covers blocks/**/*.ts)
npm run test:solutions # the reference solutions are GREENnpm test (student run) shows the new block's exercises failing with
Not implemented — that is correct and expected; the student makes them green.
- Node 22+ is required (
node:testglob support +node --import tsx). package.jsonscripts:test→node --import tsx --test "blocks/**/tests/*.test.ts"(student run).test:solutions→ same butblocks/**/solutions/*.solution.test.ts.typecheck→tsc --noEmit.play→tsx(run any file).
tsconfig.jsonis strict, incl.noUncheckedIndexedAccessandnoUnusedParameters. Indexing an array yieldsT | undefined; use!(with a comment) where presence is guaranteed.libisES2022only (no DOM) andtypes: ["node"].- Tests that touch
process.envmust save/restore it (usebeforeEach/afterEachor restore in afinally) so they don't leak into other tests. - Workflow: commit & push to
dev; the user mergesdev→main. Commits are authored as the user alone (no Co-Authored-By / AI mention).