Imported from bayudwiyansatria/templates-project-nodejs (
AGENTS.md). Install upstream withnpx skills add bayudwiyansatria/templates-project-nodejs. Copyright stays with the author.
AGENTS.md — AI Agent Guide for templates-project-nodejs.git
Overview
A reusable Node.js library template. It provides the working structure, build, test, lint, and documentation scaffolding
for a publishable TypeScript library, and is intended to be forked and adapted. The public API is exported from a single
barrel, src/index.ts; everything else is internal.
Because it is a template, a defect here is not one bug — it is one bug in every library forked from it. Treat the tooling as production code.
Module Structure
src/
index.ts ← public API barrel; the ENTIRE published surface. Re-exports only.
core/ ← domain layer — what the library does. Rename after your domain when forking.
Health.ts getHealthStatus (placeholder)
Metadata.ts createLibraryMetadata (placeholder)
index.ts barrel, with the layer table in its @module block
types/ ← shapes exchanged with callers
HealthStatus.ts, LibraryMetadata.ts, index.ts
constants/ ← values fixed for every caller
Defaults.ts, index.ts
exceptions/ ← faults raised; base class carries a stable `code`
LibraryException.ts, index.ts
utils/ ← helpers that do not know the domain; NOT re-exported publicly
Text.ts, index.ts
test/ ← Jest specs mirroring src/, *.spec.ts
index.spec.ts imports through the barrel — fails if the public surface changes shape
core/Metadata.spec.ts
utils/Text.spec.ts
lib/ ← build output (generated; never commit)
dist/ ← docs, coverage, book output (generated)
docs/ ← developer book: getting-started/, guides/, reference/, release-notes/, changes-log/
docker/ ← documentation site image (docs.Dockerfile, compose, nginx/)
Architecture Rules
src/index.tsdecides what is public. A symbol is API when re-exported there and internal when not. There is no second mechanism —package.json#exportsnames onlylib/index.d.tsand the bundles built from that one entry.- Keep
src/index.tsa barrel. No implementation. Behaviour goes incore/. - The dependency arrow runs one way.
types/,constants/,exceptions/, andutils/may not import from@/coreor the root barrel. Enforced byno-restricted-importsineslint.config.ts, not by convention. - One primary export per file, named after it. Tightly-coupled satellites (an options interface beside the function that takes it) may sit alongside; two unrelated exports belong in two files.
index.tsis always a barrel carrying an@moduleblock that states what belongs in its layer.
Module Resolution
Non-relative imports go through @/*, mapped in tsconfig.json#paths:
import { LibraryException } from '@/exceptions'
import { isNonEmpty } from '@/utils/Text'
baseUrl is deliberately not used: it would make import … from 'types' a bare specifier, and types,
constants, and utils are all real npm packages. Three consumers must agree on the mapping — tsc (paths), Jest
(moduleNameMapper), and Rollup (@rollup/plugin-typescript, reading the same tsconfig). A bare @/… specifier inside
lib/ means one has drifted.
Module Format & Package Configuration
- Type: ESM-first (
"type": "module") - Outputs: Rollup emits four artifacts, all with source maps:
lib/index.min.cjs— CommonJS (main,exports.require)lib/index.min.mjs— ESM (module,exports.import)lib/index.min.umd.cjs— UMD, globalTemplatesProjectNodejslib/index.d.ts— declarations (types), generated byrollup-plugin-dts
- The
.cjsextensions are load-bearing. Under"type": "module"Node reads.jsas ESM, so a CommonJS bundle named.jsparses as ESM andrequire()returns nothing usable — silently, since neither the build nor the tests load the packaged form. Do not rename them back. - Registry: GitHub Packages, not public npm
Developer Workflows
npm install
npm run build # Rollup: four artifacts into lib/
npm run dev # Rollup watch mode
npm run test:run # Jest, coverage into dist/coverage/
npm test # lint && test:run — the gate CI applies
npm run lint:run # ESLint over src
npm run lint:fix # ESLint over src with --fix
npm run format # Prettier, writing in place
npm run build:docs # TypeDoc -> dist/docs/ (FAILS on undocumented exports)
npm run build:docs:book # HonKit -> dist/book/
npm run build:static # everything + landing page -> dist/
npm run docker:serve:docs # serve the site at http://localhost/
Code Style & Linting
ESLint 10 flat config (eslint.config.ts — .eslintrc.json is gone; ESLint 10 cannot read it):
@typescript-eslint/no-unused-varsis an error, with^_as the escape. An unused import in a library ships.curlyis an error;multiline-comment-stylewarns.- The leaf-layer
no-restricted-importsboundary described above. *.spec.tsfiles are excluded from linting.prettier/prettieris off — formatting isnpm run format's job, and reporting it twice helps nobody.
Prettier (.prettierrc.json): no semicolons, single quotes, 120 columns, 2-space indent.
TypeScript (tsconfig.json, tsconfig.test.json): target ES2020, module ES2020, paths for @/*, output via
Rollup rather than tsc. ErrorOptions and two-argument Error are ES2022 and therefore unavailable — see
LibraryException, which declares cause itself.
Testing
- Framework: Jest 30 with ts-jest, configured in
jest.config.json - Pattern:
*.spec.tsundertest/, mirroring the source path - Coverage: collected from
src/**intodist/coverage/;lcovonlyfor Codecov,htmlfor the docs site bailis on — the run stops at the first failing suite- Specs compile under
tsconfig.test.jsonand resolve@/*throughmoduleNameMapper
Documentation
Two generators, deliberately separate:
- TypeDoc reads JSDoc from
src/index.ts→dist/docs/.treatWarningsAsErrorsis on withnotDocumented,invalidLink,notExported, andrewrittenLinkvalidation, so an undocumented export fails the build. This is the single most likely thing to break an otherwise-correct change. - HonKit reads Markdown from
docs/→dist/book/. A new page must be added todocs/SUMMARY.mdor it will not appear in the navigation.
JSDoc conventions:
@versionand@sinceare always1.0.0insrc/. This is a template; source is copied into a library that starts its own history at 1.0.0. Real release history lives inCHANGELOG.md,docs/release-notes/, andpackage.json#version.- Kind tags carry no argument (
@class, not@class LibraryException) — TypeDoc drops the tag but keeps its argument as a stray paragraph. - Do not use
@type,@property,@public,@private; they restate the signature and drift from it.
getting-started/, guides/, and reference/ are living docs — update them in the same change that alters what they
describe. release-notes/ and changes-log/ are point-in-time records and are never edited afterwards.
CI/CD
Workflows in .github/workflows/ call reusable workflows from bayudwiyansatria/.github:
features.yml— build + test onfeature/**andhotfix/**main.yml— build + docs + test onmasterrelease.yml— dispatch only; build → (docs, test) → publish → release → Pages, with the version supplied as input
Secrets required: NPM_AUTH_TOKEN, CODECOV_TOKEN.
Conventions
- Keep this repository framework-agnostic — it must suit any Node.js library.
- Update
README.mdand the relevant guide whenever the public API or setup behavior changes. - Keep
CHANGELOG.md,docs/release-notes/, anddocs/changes-log/current for each release. - Do not commit
lib/ordist/. - When adding a layer or renaming one, update the
fileslist in the boundary rule at the bottom ofeslint.config.ts.