Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .changeset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 6 additions & 5 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# 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, 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
!LICENSE
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`.
88 changes: 88 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 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
npm run publish:package
```

## 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 release:version
```

Changesets can be published with:

```bash
npm run release:changesets
```

## 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.
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,20 @@ 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/`.

## Benchmarks

Run the local micro-benchmarks with:
Expand All @@ -112,14 +124,20 @@ 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
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
Expand All @@ -138,6 +156,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.
56 changes: 27 additions & 29 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},
{
Expand All @@ -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',
},
},
);
2 changes: 1 addition & 1 deletion fixtures/cjs-consumer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@
"name": "cjs-consumer-fixture",
"private": true,
"scripts": {
"smoke": "node smoke.mjs"
"smoke": "node smoke.cjs"
}
}
Original file line number Diff line number Diff line change
@@ -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(
{
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions fixtures/esm-consumer/smoke.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
{
Expand All @@ -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');

Expand Down
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"import": "./dist/index.mjs",
"require": "./dist/index.js",
"default": "./dist/index.js"
}
},
"./package.json": "./package.json"
},
"files": [
"dist"
Expand All @@ -65,10 +66,8 @@
"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",
"check:publint": "publint run --strict .",
"check:types:package": "attw --pack .",
"clean": "rimraf coverage dist",
Expand All @@ -83,8 +82,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 publish",
"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",
Expand Down
Loading
Loading