From 49f3f15a6a7b2dbc259ad8ee241a335d3380a6d7 Mon Sep 17 00:00:00 2001 From: coderrob Date: Thu, 2 Apr 2026 18:03:22 -0500 Subject: [PATCH 1/4] fix: update .npmignore comments and refine build script for npm publishing --- .npmignore | 9 ++++----- README.md | 2 ++ package.json | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.npmignore b/.npmignore index 62c173b..c4709f8 100644 --- a/.npmignore +++ b/.npmignore @@ -1,7 +1,6 @@ -# This project uses `"files": ["dist"]` in package.json, so only files under -# dist/ (plus standard top-level files like README/LICENSE) are published. -# These patterns therefore only affect the contents of dist/, not the repo root. - -# Build artifacts that should not be published +# This project publishes via `"files": ["dist"]` in package.json, so this file +# only acts as a secondary safeguard for files inside dist/. *.tsbuildinfo *.map +!README.md +!LICENSE diff --git a/README.md b/README.md index 02294d1..114182c 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,8 @@ const { isString } = require('@coderrob/typescript-type-guards'); Type declarations are emitted during build and included in the published package. +The published tarball also includes the root `README.md`, `LICENSE`, and `package.json`. Those top-level files are included automatically by npm and are not copied into `dist/`. + ## Benchmarks Run the local micro-benchmarks with: diff --git a/package.json b/package.json index 708b7af..6994a56 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "url": "git+https://github.com/Coderrob/typescript-type-guards.git" }, "scripts": { - "build": "tsup src/index.ts --format cjs,esm --dts --sourcemap --clean --target es2020", + "build": "tsup src/index.ts --format cjs,esm --dts --clean --target es2020", "changeset": "changeset", "changeset:publish": "changeset publish", "changeset:version": "changeset version", @@ -83,7 +83,7 @@ "package:quality": "npm run check:publint && npm run check:types:package", "prepack": "npm run clean && npm run build && npm run test:package && npm run typecheck", "prepublishOnly": "npm run verify && npm run test:coverage", - "publish:package": "npm publish", + "publish:package": "npm run verify && npm run test:coverage && npm pack --dry-run && npm publish --access public --provenance", "release:publish": "npm run verify && npm run test:coverage && npm run changeset:publish", "test": "vitest run --root . src/__tests__/runtime.test.ts --pool=threads --fileParallelism=false", "test:consumers": "node scripts/run-consumer-smoke-tests.mjs", From ef06e0fe7ca308a88fef09ec51e9d287dd12d6ab Mon Sep 17 00:00:00 2001 From: coderrob Date: Thu, 2 Apr 2026 18:10:19 -0500 Subject: [PATCH 2/4] feat: add CONTRIBUTING.md and CHANGELOG.md for project guidelines and release history --- CHANGELOG.md | 43 ++++++++++++++++++++++++++ CONTRIBUTING.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 5 +++ 3 files changed, 129 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..37fc269 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,43 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +This repository uses [Changesets](https://github.com/changesets/changesets) to +manage versioning and changelog generation. Release entries are created from +merged changesets during the release process. + +## Unreleased + +- No unreleased entries yet. + +## 1.0.0 - 2026-04-02 + +### Added + +- Initial public release of `@coderrob/typescript-type-guards`. +- A reusable TypeScript type guard library with primitive, numeric, + collection, object-like, and utility guards. +- Generic factory helpers for constructor-based and enum-based narrowing via + `createTypeGuard` and `createEnumGuard`. +- Behavioral runtime tests with strong coverage expectations for positive and + negative guard usage. +- Repository quality tooling for formatting, linting, typechecking, duplicate + detection, and automated package verification. +- npm packaging support files, including publish-oriented ignore rules and + package metadata hardening. +- Project documentation and branding, including the package README and logo. + +### Changed + +- Expanded the guard surface and test coverage as the library matured toward + the `1.0.0` release. +- Refined npm publishing behavior and package build output for release + readiness. + +### Security + +- Updated transitive dependencies through Dependabot-driven maintenance, + including `brace-expansion`, `flatted`, `picomatch`, and `handlebars`. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b5b9495 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,81 @@ +# Contributing + +## Requirements + +- Node.js `^20.19.0`, `^22.13.0`, or `>=24.0.0` +- npm + +## Setup + +```bash +npm ci +``` + +## Development Workflow + +1. Create a branch from `main`. +2. Make the smallest coherent change that solves the problem. +3. Add or update runtime tests, type tests, and documentation as needed. +4. Run the verification commands before opening a pull request. +5. Add a changeset if the published package behavior changes. + +## Verification + +Run the full local gate: + +```bash +npm run verify +``` + +Run coverage separately when needed: + +```bash +npm run test:coverage +``` + +Useful focused commands: + +```bash +npm run lint +npm run typecheck +npm test +npm run build +npm run test:package +npm run bench +``` + +## Testing Expectations + +- Add positive and negative behavioral coverage for runtime guards. +- Keep type-level narrowing coverage current in `src/__tests__/narrowing.types.ts`. +- Preserve the per-file coverage threshold enforced by the repository scripts. +- Keep consumer smoke tests passing for both CommonJS and ESM package usage. + +## Changesets + +Create a changeset whenever a pull request changes the published package in a +way that should produce a version bump or release note entry. + +```bash +npm run changeset +``` + +Changesets are applied during release with: + +```bash +npm run changeset:version +``` + +## Packaging Notes + +- The package publishes built files from `dist/`. +- The root `README.md`, `LICENSE`, and `package.json` are included in the npm + tarball automatically. +- Source maps are intentionally not published. + +## Pull Requests + +- Keep pull requests focused. +- Update documentation when behavior, packaging, or workflow changes. +- Do not relax quality gates to make a change pass. Fix the code, tests, or + tooling instead. diff --git a/README.md b/README.md index 114182c..1752054 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,11 @@ These are indicative micro-benchmark results from a single local machine. They a ## Development +See also: + +- `CONTRIBUTING.md` for local setup, verification, and contribution expectations +- `CHANGELOG.md` for release history + ```bash npm run verify npm run test:coverage From 5758ba1319092e0fdd52b76b24f9aa0fa976eba5 Mon Sep 17 00:00:00 2001 From: coderrob Date: Thu, 2 Apr 2026 18:17:00 -0500 Subject: [PATCH 3/4] refactor: update release scripts and documentation for consistency --- .changeset/README.md | 4 ++-- .github/workflows/release.yml | 4 ++-- .npmignore | 6 ++++-- CONTRIBUTING.md | 9 ++++++++- README.md | 10 ++++++---- package.json | 7 +++---- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/.changeset/README.md b/.changeset/README.md index 4d5e912..e597f25 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -6,8 +6,8 @@ This repository uses Changesets for versioning and changelog automation. ```bash npm run changeset -npm run changeset:version -npm run changeset:publish +npm run release:version +npm run release:changesets ``` Create a changeset whenever a pull request changes the published package in a way diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3fcbdba..5232eb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,8 @@ jobs: - name: Create release PR or publish package uses: changesets/action@v1 with: - version: npm run changeset:version - publish: npm run release:publish + version: npm run release:version + publish: npm run release:changesets commit: 'chore: version package' title: 'chore: version package' env: diff --git a/.npmignore b/.npmignore index c4709f8..1e4a7e2 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,7 @@ -# This project publishes via `"files": ["dist"]` in package.json, so this file -# only acts as a secondary safeguard for files inside dist/. +# This project publishes via `"files": ["dist"]` in package.json, which is the +# primary allowlist for package contents. `.npmignore` still applies across the +# packed artifact and can affect top-level files as well, so the negations +# below explicitly preserve the root README and LICENSE. *.tsbuildinfo *.map !README.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5b9495..caa312b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,7 @@ npm test npm run build npm run test:package npm run bench +npm run publish:package ``` ## Testing Expectations @@ -63,7 +64,13 @@ npm run changeset Changesets are applied during release with: ```bash -npm run changeset:version +npm run release:version +``` + +Changesets can be published with: + +```bash +npm run release:changesets ``` ## Packaging Notes diff --git a/README.md b/README.md index 1752054..ad9e40d 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,9 @@ npm run test:coverage npm run build npm run bench npm run changeset -npm run changeset:version -npm run changeset:publish +npm run release:version +npm run release:changesets +npm run publish:package ``` ## Verification @@ -145,6 +146,7 @@ npm run test:coverage ## Releases - `npm run changeset` creates a release note entry for a package change. -- `npm run changeset:version` applies pending changesets and updates the changelog. -- `npm run release:publish` runs the full verification stack, coverage, and publishes through Changesets. +- `npm run release:version` applies pending changesets and updates the changelog. +- `npm run release:changesets` runs the full verification stack, coverage, and then publishes through Changesets. +- `npm run publish:package` performs a direct npm publish with a dry-run pack check first. - `.github/workflows/release.yml` is a manual `workflow_dispatch` workflow for optional release publishing. diff --git a/package.json b/package.json index 6994a56..9a22b52 100644 --- a/package.json +++ b/package.json @@ -67,8 +67,6 @@ "scripts": { "build": "tsup src/index.ts --format cjs,esm --dts --clean --target es2020", "changeset": "changeset", - "changeset:publish": "changeset publish", - "changeset:version": "changeset version", "check:publint": "publint run --strict .", "check:types:package": "attw --pack .", "clean": "rimraf coverage dist", @@ -83,8 +81,9 @@ "package:quality": "npm run check:publint && npm run check:types:package", "prepack": "npm run clean && npm run build && npm run test:package && npm run typecheck", "prepublishOnly": "npm run verify && npm run test:coverage", - "publish:package": "npm run verify && npm run test:coverage && npm pack --dry-run && npm publish --access public --provenance", - "release:publish": "npm run verify && npm run test:coverage && npm run changeset:publish", + "publish:package": "npm pack --dry-run && npm publish --access public --provenance", + "release:changesets": "npm run verify && npm run test:coverage && changeset publish", + "release:version": "changeset version", "test": "vitest run --root . src/__tests__/runtime.test.ts --pool=threads --fileParallelism=false", "test:consumers": "node scripts/run-consumer-smoke-tests.mjs", "test:package": "node scripts/check-package-interop.mjs && npm run test:consumers", From dcfeabba132250f9ffbed2d67e6ed768c435f79d Mon Sep 17 00:00:00 2001 From: coderrob Date: Thu, 2 Apr 2026 18:24:11 -0500 Subject: [PATCH 4/4] feat: enhance README and add package metadata usage examples; update ESLint config and consumer scripts --- README.md | 10 ++++ eslint.config.mjs | 56 +++++++++---------- fixtures/cjs-consumer/package.json | 2 +- .../cjs-consumer/{smoke.mjs => smoke.cjs} | 14 +++-- fixtures/esm-consumer/smoke.mjs | 2 + package.json | 3 +- scripts/run-consumer-smoke-tests.mjs | 2 +- 7 files changed, 51 insertions(+), 38 deletions(-) rename fixtures/cjs-consumer/{smoke.mjs => smoke.cjs} (50%) diff --git a/README.md b/README.md index ad9e40d..d712863 100644 --- a/README.md +++ b/README.md @@ -89,6 +89,16 @@ import { isString } from '@coderrob/typescript-type-guards'; const { isString } = require('@coderrob/typescript-type-guards'); ``` +Package metadata can also be resolved explicitly when needed: + +```js +const packageMetadata = require('@coderrob/typescript-type-guards/package.json'); +``` + +```ts +import packageMetadata from '@coderrob/typescript-type-guards/package.json' with { type: 'json' }; +``` + Type declarations are emitted during build and included in the published package. The published tarball also includes the root `README.md`, `LICENSE`, and `package.json`. Those top-level files are included automatically by npm and are not copied into `dist/`. diff --git a/eslint.config.mjs b/eslint.config.mjs index 0bbf9a4..94e8387 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -18,29 +18,37 @@ import { defineConfig } from 'eslint/config'; import zeroTolerance from '@coderrob/eslint-plugin-zero-tolerance'; import tseslint from 'typescript-eslint'; +const GENERAL_IGNORES = [ + 'coverage/**', + 'dist/**', + 'eslint.config.mjs', + 'node_modules/**', + 'scripts/**', +]; +const MAX_LINES_RULE = [ + 'error', + { max: 25, skipComments: true, skipBlankLines: true }, +]; +const MAX_LINES_EXEMPT_FILES = [ + '**/*.spec.ts', + '**/*.test.ts', + '**/__tests__/**/*.ts', + '**/guards/createEnumGuard.ts', + '**/index.ts', + 'benchmarks/**/*.mjs', +]; + export default defineConfig( { - ignores: [ - 'dist/**', - 'node_modules/**', - 'coverage/**', - 'scripts/**', - 'eslint.config.mjs', - ], + ignores: GENERAL_IGNORES, }, ...tseslint.configs.recommended, zeroTolerance.configs.strict, { rules: { complexity: ['error', { max: 3 }], - 'max-lines': [ - 'error', - { max: 25, skipComments: true, skipBlankLines: true }, - ], - 'max-lines-per-function': [ - 'error', - { max: 25, skipComments: true, skipBlankLines: true }, - ], + 'max-lines': MAX_LINES_RULE, + 'max-lines-per-function': MAX_LINES_RULE, }, }, { @@ -50,26 +58,16 @@ export default defineConfig( }, }, { - files: ['**/index.ts'], + files: MAX_LINES_EXEMPT_FILES, rules: { 'max-lines': 'off', }, }, { - files: ['**/guards/createEnumGuard.ts'], + files: ['fixtures/**/*.cjs'], rules: { - 'max-lines': 'off', - }, - }, - { - files: [ - '**/__tests__/**/*.ts', - '**/*.test.ts', - '**/*.spec.ts', - 'benchmarks/**/*.mjs', - ], - rules: { - 'max-lines': 'off', + '@typescript-eslint/no-require-imports': 'off', + 'zero-tolerance/no-dynamic-import': 'off', }, }, ); diff --git a/fixtures/cjs-consumer/package.json b/fixtures/cjs-consumer/package.json index bbbb82c..7ad7c94 100644 --- a/fixtures/cjs-consumer/package.json +++ b/fixtures/cjs-consumer/package.json @@ -2,6 +2,6 @@ "name": "cjs-consumer-fixture", "private": true, "scripts": { - "smoke": "node smoke.mjs" + "smoke": "node smoke.cjs" } } diff --git a/fixtures/cjs-consumer/smoke.mjs b/fixtures/cjs-consumer/smoke.cjs similarity index 50% rename from fixtures/cjs-consumer/smoke.mjs rename to fixtures/cjs-consumer/smoke.cjs index 993b50a..02dd270 100644 --- a/fixtures/cjs-consumer/smoke.mjs +++ b/fixtures/cjs-consumer/smoke.cjs @@ -1,11 +1,12 @@ -import assert from 'node:assert/strict'; -import { createRequire } from 'node:module'; +const assert = require('node:assert/strict'); const ANSWER = Number('42'); -const requirePackage = createRequire(import.meta.url); -const { createEnumGuard, isPlainObject, isString } = requirePackage( - '@coderrob/typescript-type-guards', -); +const { + createEnumGuard, + isPlainObject, + isString, +} = require('@coderrob/typescript-type-guards'); +const packageMetadata = require('@coderrob/typescript-type-guards/package.json'); const isStatus = createEnumGuard( { @@ -15,6 +16,7 @@ const isStatus = createEnumGuard( 'Status', ); +assert.equal(packageMetadata.name, '@coderrob/typescript-type-guards'); assert.equal(isString('hello'), true); assert.equal(isPlainObject({ answer: ANSWER }), true); assert.equal(isStatus('ACTIVE'), true); diff --git a/fixtures/esm-consumer/smoke.mjs b/fixtures/esm-consumer/smoke.mjs index 4978120..b678399 100644 --- a/fixtures/esm-consumer/smoke.mjs +++ b/fixtures/esm-consumer/smoke.mjs @@ -5,6 +5,7 @@ import { isPlainObject, isString, } from '@coderrob/typescript-type-guards'; +import packageMetadata from '@coderrob/typescript-type-guards/package.json' with { type: 'json' }; const isStatus = createEnumGuard( { @@ -14,6 +15,7 @@ const isStatus = createEnumGuard( 'Status', ); +assert.equal(packageMetadata.name, '@coderrob/typescript-type-guards'); assert.equal(isString('hello'), true); const ANSWER = Number('42'); diff --git a/package.json b/package.json index 9a22b52..51c50d6 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "import": "./dist/index.mjs", "require": "./dist/index.js", "default": "./dist/index.js" - } + }, + "./package.json": "./package.json" }, "files": [ "dist" diff --git a/scripts/run-consumer-smoke-tests.mjs b/scripts/run-consumer-smoke-tests.mjs index 64cf0ba..1f4fdd5 100644 --- a/scripts/run-consumer-smoke-tests.mjs +++ b/scripts/run-consumer-smoke-tests.mjs @@ -19,7 +19,7 @@ const require = createRequire(import.meta.url); prepareTempDirectory(); installPackagedFiles(); -await runFixture('cjs-consumer', 'smoke.mjs'); +await runFixture('cjs-consumer', 'smoke.cjs'); await runFixture('esm-consumer', 'smoke.mjs'); rmSync(TEMP_ROOT, { force: true, recursive: true });