tablsx is a TypeScript library for reading and writing Excel .xlsx files. It uses ES modules, targets Node >=18, and has minimal dependencies (fflate for zip, fast-xml-parser for XML). The published package ships compiled JS + type declarations from dist/; development and tests run directly on the TypeScript sources (no build step needed).
npm test— run all tests (vitest)npm run test:watch— run tests in watch modenpm run typecheck— typecheck src/examples and tests (two tsconfig projects)npm run build— compile src todist/(only needed for publishing)npm run lint— run ESLint (typed typescript-eslint rules)npm run lint:fix— run ESLint with auto-fixnpm run format— format all JS/TS files with Prettiernpm run format:check— check formatting without writingnpm run check:headers— verify copyright headers on all source files
A husky pre-commit hook runs automatically on every commit:
lint-staged— ESLint + Prettier on staged.js/.tsfilesnpm test— full test suite
src/model/— data model (Workbook, Worksheet, Cell, CellType) and shared type aliases intypes.tssrc/reader/— .xlsx parsing (worksheet-parser, shared-strings, styles);xml-types.tstypes the fast-xml-parser boundarysrc/writer/— .xlsx generation (worksheet-writer, shared-strings-writer, styles-writer, workbook-writer, zip)src/utils/— helpers (cell-ref encoding, XML escaping, date conversion, vector serialization)test/— mirrors src structure, uses vitest;test/tsconfig.jsonrelaxesnoUncheckedIndexedAccessfor test code onlydist/— build output, gitignored, published to npm
- ES modules (
import/export), no CommonJS; import specifiers use.jsextensions (NodeNext resolution) - Strict TypeScript: always declare parameter types and return types explicitly (enforced by
@typescript-eslint/explicit-function-return-type) - Reuse the shared aliases in
src/model/types.ts(Cell,CellValue,Row,Worksheet,Workbook,XlsxInput) instead of redeclaring structures - Use
import type { ... }for type-only imports (enforced byverbatimModuleSyntax) - Use
CellTypeconstants (e.g.,CellType.STRING), never raw string literals for cell types - JSDoc on all exported functions: prose descriptions,
@throws,@param name description— no type braces (types live in signatures) - In tests, mark deliberate invalid-input calls with
// @ts-expect-error — testing invalid input - Run
npm testafter changes to verify nothing breaks - Every
.js/.tsfile must start with a copyright header as the first line(s), before any imports:When creating new files or modifying existing files that lack this header, add it.// Copyright © 2026 – present NapSoft LLC. All rights reserved.
- Never add
Co-Authored-Bylines to commit messages — suppress the default trailer entirely - Keep commit messages concise (1-2 sentences) focused on "why" not "what"
- Stage specific files, avoid
git add -A
- All feature and fix branches create PRs targeting
main(mainrequires PRs — its ruleset blocks direct pushes, force pushes, and deletion) - Pushing a branch has no side effects (safe for syncing between machines). When the work is ready, open the PR deliberately:
gh pr create --base main - The legacy
devbranch is deprecated — do not target or merge it - Before merging, record changes under
## [Unreleased]in CHANGELOG.md (the Changelog Check workflow fails release-labeled PRs that skip this), then add exactly one release label to publish on merge:release:patch,release:minor, orrelease:major - On merge of a labeled PR, CI bumps the version, promotes the changelog, tags, and publishes to npm (
release-on-merge.yml). Unlabeled merges publish nothing — use that for CI/docs-only changes - Never cut a release by tagging manually;
publish-rc.yml(manually pushedv*-rc.*tags) is the only manual-tag path and exists for release candidates