client-certificate-auth 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -4
- package/lib/clientCertificateAuth.d.ts +1 -1
- package/lib/clientCertificateAuth.js +7 -4
- package/lib/extractor.js +6 -2
- package/lib/helpers.js +3 -0
- package/lib/parsers.js +107 -17
- package/package.json +21 -14
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
# client-certificate-auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Comprehensive toolkit for client SSL certificate authentication (mTLS) in Node.js. Includes Express/Connect middleware, framework-agnostic certificate extraction for reverse proxies (AWS ALB, Envoy, Cloudflare, Traefik, and more), and pre-built authorization helpers.
|
|
4
4
|
|
|
5
5
|
[](https://github.com/tgies/client-certificate-auth/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/client-certificate-auth)
|
|
7
7
|
[](https://codecov.io/gh/tgies/client-certificate-auth)
|
|
8
8
|
[](https://dashboard.stryker-mutator.io/reports/github.com/tgies/client-certificate-auth/master)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
[**Full Documentation**](https://tgies.github.io/client-certificate-auth/) - guides, API reference, and runnable examples
|
|
11
|
+
[**Commercial Support**](#commercial-support) - consulting, custom features, and priority support for production deployments
|
|
12
|
+
|
|
13
|
+
**Recommended by AWS** - Featured in the [AWS API Gateway documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/getting-started-client-side-ssl-authentication.html#certificate-validation).
|
|
14
|
+
|
|
15
|
+
**Fanatically Tested** - 100% line/branch/function/statement coverage, plus mutation testing and E2E tests against real nginx/Envoy/Traefik containers. ~4,776 lines of test code for ~711 lines of source (measured by [cloc](https://github.com/AlDanial/cloc)).
|
|
11
16
|
|
|
12
17
|
## Installation
|
|
13
18
|
|
|
@@ -19,9 +24,11 @@ npm install client-certificate-auth
|
|
|
19
24
|
|
|
20
25
|
## Synopsis
|
|
21
26
|
|
|
22
|
-
This
|
|
27
|
+
This library provides everything you need to implement mutual TLS (mTLS) authentication in Node.js. It extracts client certificates from direct TLS connections (`req.socket`) or from HTTP headers forwarded by reverse proxies (AWS ALB, Envoy, Cloudflare, Traefik, nginx, HAProxy).
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
The certificate is parsed into a standard `tls.PeerCertificate` object and passed to your callback for authorization logic.
|
|
30
|
+
|
|
31
|
+
Compatible with Express, Connect, or any Node.js HTTP server framework by using the framework-agnostic `extractClientCertificate` function.
|
|
25
32
|
|
|
26
33
|
## Usage
|
|
27
34
|
|
|
@@ -292,6 +299,8 @@ This package provides everything you need to build mTLS authentication for any N
|
|
|
292
299
|
|
|
293
300
|
If you build an adapter for another framework (Koa, Fastify, Hapi, NestJS, etc.), please open an issue or PR to get it listed here!
|
|
294
301
|
|
|
302
|
+
> For complete API documentation with all types, parameters, and examples, see the [API Reference](https://tgies.github.io/client-certificate-auth/api/).
|
|
303
|
+
|
|
295
304
|
### Accessing the Certificate
|
|
296
305
|
|
|
297
306
|
After authentication, the certificate is attached to `req.clientCertificate` for downstream handlers:
|
|
@@ -903,6 +912,30 @@ The sync CJS wrapper does not support reverse proxy options (`certificateSource`
|
|
|
903
912
|
|
|
904
913
|
The `/helpers`, `/parsers`, and `/extractor` subpath exports each provide a `load()` function in CJS. See [Subpath Exports in CJS](#subpath-exports-in-cjs) for details.
|
|
905
914
|
|
|
915
|
+
## Commercial Support
|
|
916
|
+
|
|
917
|
+
`client-certificate-auth` is built and maintained by [Tony Gies](https://github.com/tgies). For organizations running it in production, commercial support is available through his consultancy, Crash United, LLC.
|
|
918
|
+
|
|
919
|
+
### Support Offerings
|
|
920
|
+
|
|
921
|
+
| Service | Description |
|
|
922
|
+
|---------|-------------|
|
|
923
|
+
| **Priority bug fixes** | Reported issues triaged and patched ahead of the public queue |
|
|
924
|
+
| **Custom features & integrations** | Adapters for new reverse proxies, encoding formats, or framework wrappers |
|
|
925
|
+
| **mTLS architecture consulting** | Review of your certificate issuance, rotation, and trust-chain design |
|
|
926
|
+
| **Deployment security review** | Threat modeling for your specific proxy + middleware + auth flow |
|
|
927
|
+
| **Private security advisories** | Coordinated disclosure for vulnerabilities affecting your deployment |
|
|
928
|
+
|
|
929
|
+
For pricing, scoping, or anything not listed above, email **[support@crashunited.com](mailto:support@crashunited.com)** to discuss your needs.
|
|
930
|
+
|
|
931
|
+
### Sponsorship
|
|
932
|
+
|
|
933
|
+
To support ongoing development without a formal contract, [GitHub Sponsors](https://github.com/sponsors/tgies) is the simplest path.
|
|
934
|
+
|
|
935
|
+
### Enterprise Procurement
|
|
936
|
+
|
|
937
|
+
This package is enrolled in [Tidelift](https://tidelift.com/) (now part of SonarQube Advanced Security). If your organization already subscribes, `client-certificate-auth` is included in your coverage for security disclosures, license compliance, and version metadata.
|
|
938
|
+
|
|
906
939
|
## License
|
|
907
940
|
|
|
908
941
|
MIT © Tony Gies
|
|
@@ -135,7 +135,7 @@ export type Middleware = (
|
|
|
135
135
|
* Express/Connect middleware for client SSL certificate authentication.
|
|
136
136
|
*
|
|
137
137
|
* @param callback - Validation function that receives the client certificate
|
|
138
|
-
* and returns true/false (sync) or Promise<boolean
|
|
138
|
+
* and returns true/false (sync) or `Promise<boolean>` (async).
|
|
139
139
|
* @param options - Configuration options
|
|
140
140
|
* @returns Express middleware function
|
|
141
141
|
*
|
|
@@ -15,11 +15,14 @@ import { extractClientCertificate } from './extractor.js';
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @typedef {Object} ClientCertificateAuthOptions
|
|
18
|
-
* @property {'aws-alb' | 'envoy' | 'cloudflare' | 'traefik'} [certificateSource] - Use a preset
|
|
18
|
+
* @property {'aws-alb' | 'envoy' | 'cloudflare' | 'traefik'} [certificateSource] - Use a preset
|
|
19
19
|
* configuration for a known reverse proxy. Header-based certs are only checked if this or
|
|
20
|
-
* certificateHeader is set.
|
|
20
|
+
* certificateHeader is set. Trust boundary: the proxy must strip the preset's header from
|
|
21
|
+
* external requests; any source that can set it is trusted to assert client identity.
|
|
21
22
|
* @property {string} [certificateHeader] - Custom header name to read certificate from.
|
|
22
|
-
* Overrides preset header name if also using certificateSource.
|
|
23
|
+
* Overrides preset header name if also using certificateSource. Trust boundary: the proxy
|
|
24
|
+
* must strip this header from external requests; any source that can set it is trusted to
|
|
25
|
+
* assert client identity.
|
|
23
26
|
* @property {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} [headerEncoding] -
|
|
24
27
|
* How to decode the header value. Required when using certificateHeader without certificateSource.
|
|
25
28
|
* @property {boolean} [fallbackToSocket=false] - If header-based extraction is configured but
|
|
@@ -46,7 +49,7 @@ import { extractClientCertificate } from './extractor.js';
|
|
|
46
49
|
*
|
|
47
50
|
* @param {(cert: import('tls').PeerCertificate, req: ClientCertRequest) => boolean | Promise<boolean>} callback
|
|
48
51
|
* Validation function that receives the client certificate and the request
|
|
49
|
-
* object. Returns true/false (sync) or a Promise<boolean
|
|
52
|
+
* object. Returns true/false (sync) or a `Promise<boolean>` (async) to
|
|
50
53
|
* allow/deny access.
|
|
51
54
|
* @param {ClientCertificateAuthOptions} [options={}]
|
|
52
55
|
* @returns {Middleware}
|
package/lib/extractor.js
CHANGED
|
@@ -21,8 +21,12 @@ import { getCertificateFromHeaders } from './parsers.js';
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* @typedef {Object} ExtractorOptions
|
|
24
|
-
* @property {'aws-alb' | 'envoy' | 'cloudflare' | 'traefik'} [certificateSource] - Preset
|
|
25
|
-
*
|
|
24
|
+
* @property {'aws-alb' | 'envoy' | 'cloudflare' | 'traefik'} [certificateSource] - Preset
|
|
25
|
+
* configuration. Trust boundary: the proxy must strip the preset's header from external
|
|
26
|
+
* requests; any source that can set it is trusted to assert client identity.
|
|
27
|
+
* @property {string} [certificateHeader] - Custom header name. Trust boundary: the proxy must
|
|
28
|
+
* strip this header from external requests; any source that can set it is trusted to assert
|
|
29
|
+
* client identity.
|
|
26
30
|
* @property {'url-pem' | 'url-pem-aws' | 'xfcc' | 'base64-der' | 'rfc9440'} [headerEncoding] - Header encoding
|
|
27
31
|
* @property {boolean} [fallbackToSocket=false] - Try socket if header extraction fails
|
|
28
32
|
* @property {boolean} [includeChain=false] - Include issuerCertificate chain
|
package/lib/helpers.js
CHANGED
|
@@ -245,6 +245,9 @@ export function allowEmail(emails) {
|
|
|
245
245
|
* )));
|
|
246
246
|
*/
|
|
247
247
|
export function allOf(...callbacks) {
|
|
248
|
+
if (callbacks.length === 0) {
|
|
249
|
+
return () => false;
|
|
250
|
+
}
|
|
248
251
|
return async (cert, req) => {
|
|
249
252
|
const results = await Promise.all(callbacks.map((cb) => cb(cert, req)));
|
|
250
253
|
return results.every((r) => r === true);
|
package/lib/parsers.js
CHANGED
|
@@ -60,7 +60,7 @@ export const PRESETS = {
|
|
|
60
60
|
* @returns {PeerCertificate | null} Parsed certificate or null on failure
|
|
61
61
|
*/
|
|
62
62
|
export function parseUrlPem(headerValue) {
|
|
63
|
-
// Stryker disable next-line BlockStatement: falsy input falls through to try-catch → same null return
|
|
63
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
64
64
|
if (!headerValue) {
|
|
65
65
|
return null;
|
|
66
66
|
}
|
|
@@ -73,17 +73,88 @@ export function parseUrlPem(headerValue) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Split a string of concatenated PEM CERTIFICATE blocks into an array of
|
|
78
|
+
* individual PEM strings. Uses indexOf scanning (O(N) in input length) rather
|
|
79
|
+
* than regex to avoid polynomial-time backtracking on adversarial input
|
|
80
|
+
* (e.g., many unterminated BEGIN markers).
|
|
81
|
+
*
|
|
82
|
+
* @param {string} pem - Concatenated PEM blocks
|
|
83
|
+
* @returns {string[]} Individual PEM blocks (empty array if none found)
|
|
84
|
+
*/
|
|
85
|
+
function splitPemBlocks(pem) {
|
|
86
|
+
// Stryker disable next-line ArrayDeclaration: sentinel-array mutant pushes a non-PEM string that pemToCertificate rejects and .filter(Boolean) drops downstream — same chain output
|
|
87
|
+
const blocks = [];
|
|
88
|
+
// Stryker disable next-line StringLiteral: empty beginMarker → indexOf returns scanPos every iteration; END markers still bracket the same blocks correctly
|
|
89
|
+
const beginMarker = '-----BEGIN CERTIFICATE-----';
|
|
90
|
+
const endMarker = '-----END CERTIFICATE-----';
|
|
91
|
+
let scanPos = 0;
|
|
92
|
+
while (true) {
|
|
93
|
+
const begin = pem.indexOf(beginMarker, scanPos);
|
|
94
|
+
if (begin === -1) {break;}
|
|
95
|
+
const end = pem.indexOf(endMarker, begin + beginMarker.length);
|
|
96
|
+
if (end === -1) {break;}
|
|
97
|
+
blocks.push(pem.substring(begin, end + endMarker.length));
|
|
98
|
+
// Stryker disable next-line ArithmeticOperator: end-endMarker.length backs up before the END marker, but the next BEGIN is past it so indexOf still advances correctly
|
|
99
|
+
scanPos = end + endMarker.length;
|
|
100
|
+
}
|
|
101
|
+
return blocks;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse a multi-block PEM blob into a chained PeerCertificate. Splits the
|
|
106
|
+
* input into individual blocks, parses each, drops blocks that fail to
|
|
107
|
+
* parse, and links the remainder via issuerCertificate.
|
|
108
|
+
*
|
|
109
|
+
* Used by parsers whose proxies forward the full chain in a single field
|
|
110
|
+
* (parseUrlPemAws, parseXfcc Chain). Without explicit chain linking, calling
|
|
111
|
+
* X509Certificate() on a multi-block PEM returns just the leaf with no
|
|
112
|
+
* issuerCertificate (toLegacyObject drops the property), silently losing
|
|
113
|
+
* chain information for `includeChain: true` consumers.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} pem - Concatenated PEM blocks
|
|
116
|
+
* @returns {PeerCertificate | null} Leaf cert with chain via issuerCertificate, or null if no blocks parse
|
|
117
|
+
*/
|
|
118
|
+
function chainFromMultiBlockPem(pem) {
|
|
119
|
+
const pemBlocks = splitPemBlocks(pem);
|
|
120
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through hits the next certs.length===0 check with the same null result
|
|
121
|
+
if (pemBlocks.length === 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const certs = pemBlocks.map(block => {
|
|
126
|
+
try {
|
|
127
|
+
return pemToCertificate(block);
|
|
128
|
+
} catch {
|
|
129
|
+
// Stryker disable next-line BlockStatement: empty catch returns undefined which .filter(Boolean) drops alongside null — same chain output
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}).filter(Boolean);
|
|
133
|
+
|
|
134
|
+
if (certs.length === 0) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Stryker disable next-line EqualityOperator: setting issuerCertificate = undefined on last cert is same as not setting it
|
|
139
|
+
for (let i = 0; i < certs.length - 1; i++) {
|
|
140
|
+
certs[i].issuerCertificate = certs[i + 1];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return certs[0];
|
|
144
|
+
}
|
|
145
|
+
|
|
76
146
|
/**
|
|
77
147
|
* Parse URL-encoded PEM certificate with AWS ALB safe character handling.
|
|
78
148
|
* AWS ALB uses +, =, / as safe characters (not encoded), but decodeURIComponent
|
|
79
|
-
* interprets + as space, so we must escape it first.
|
|
80
|
-
*
|
|
149
|
+
* interprets + as space, so we must escape it first. AWS sends the full chain
|
|
150
|
+
* as concatenated PEM blocks, which we split and link via issuerCertificate.
|
|
151
|
+
*
|
|
81
152
|
* @see https://docs.aws.amazon.com/elasticloadbalancing/latest/application/mutual-authentication.html
|
|
82
153
|
* @param {string} headerValue - AWS ALB URL-encoded PEM certificate
|
|
83
154
|
* @returns {PeerCertificate | null} Parsed certificate or null on failure
|
|
84
155
|
*/
|
|
85
156
|
export function parseUrlPemAws(headerValue) {
|
|
86
|
-
// Stryker disable next-line BlockStatement: falsy input falls through to try-catch → same null return
|
|
157
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
87
158
|
if (!headerValue) {
|
|
88
159
|
return null;
|
|
89
160
|
}
|
|
@@ -93,7 +164,7 @@ export function parseUrlPemAws(headerValue) {
|
|
|
93
164
|
// Must escape before decodeURIComponent or + becomes space
|
|
94
165
|
const escaped = headerValue.replace(/\+/g, '%2B');
|
|
95
166
|
const pem = decodeURIComponent(escaped);
|
|
96
|
-
return
|
|
167
|
+
return chainFromMultiBlockPem(pem);
|
|
97
168
|
} catch {
|
|
98
169
|
return null;
|
|
99
170
|
}
|
|
@@ -101,14 +172,17 @@ export function parseUrlPemAws(headerValue) {
|
|
|
101
172
|
|
|
102
173
|
/**
|
|
103
174
|
* Parse Envoy XFCC (X-Forwarded-Client-Cert) structured header format.
|
|
104
|
-
* Format: Key=Value;Key=Value;... where Cert
|
|
105
|
-
*
|
|
175
|
+
* Format: Key=Value;Key=Value;... where Cert (single URL-encoded PEM) or
|
|
176
|
+
* Chain (URL-encoded multi-block PEM with the full chain incl. leaf) carry
|
|
177
|
+
* the certificate. When both are present we prefer Chain because it carries
|
|
178
|
+
* more information, and link the chain via issuerCertificate.
|
|
179
|
+
*
|
|
106
180
|
* @see https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_conn_man/headers#x-forwarded-client-cert
|
|
107
181
|
* @param {string} headerValue - XFCC formatted header value
|
|
108
182
|
* @returns {PeerCertificate | null} Parsed certificate or null on failure
|
|
109
183
|
*/
|
|
110
184
|
export function parseXfcc(headerValue) {
|
|
111
|
-
// Stryker disable next-line BlockStatement: falsy input falls through to try-catch → same null return
|
|
185
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
112
186
|
if (!headerValue) {
|
|
113
187
|
return null;
|
|
114
188
|
}
|
|
@@ -132,8 +206,11 @@ export function parseXfcc(headerValue) {
|
|
|
132
206
|
}
|
|
133
207
|
const firstElement = headerValue.substring(0, endOfFirst);
|
|
134
208
|
|
|
135
|
-
// Parse key=value pairs separated by semicolons
|
|
209
|
+
// Parse key=value pairs separated by semicolons. Collect Cert and
|
|
210
|
+
// Chain values; prefer Chain when both are present.
|
|
136
211
|
const pairs = firstElement.split(';');
|
|
212
|
+
let certValue = null;
|
|
213
|
+
let chainValue = null;
|
|
137
214
|
|
|
138
215
|
for (const pair of pairs) {
|
|
139
216
|
const eqIndex = pair.indexOf('=');
|
|
@@ -147,19 +224,32 @@ export function parseXfcc(headerValue) {
|
|
|
147
224
|
// Stryker disable next-line MethodExpression: defensive normalization, no real proxy sends whitespace
|
|
148
225
|
let value = pair.substring(eqIndex + 1).trim();
|
|
149
226
|
|
|
150
|
-
// Cert or Chain contain the certificate
|
|
151
227
|
if (key === 'Cert' || key === 'Chain') {
|
|
152
|
-
//
|
|
228
|
+
// Stryker disable next-line LogicalOperator,MethodExpression,StringLiteral,BlockStatement: quote-stripping mutations are equivalent given the secondary unbalanced-quote reject below and lenient PEM marker scan downstream — broken quotes either trigger reject or produce a stripped value that fails to parse, both yielding null
|
|
153
229
|
if (value.startsWith('"') && value.endsWith('"')) {
|
|
230
|
+
// Stryker disable next-line MethodExpression: skipping the slice leaves quotes around the value; PEM marker scan still finds BEGIN/END inside them and parses the cert content correctly — equivalent for tests
|
|
154
231
|
value = value.slice(1, -1);
|
|
232
|
+
} else if (value.startsWith('"') || value.endsWith('"')) {
|
|
233
|
+
// Unbalanced quote indicates malformed XFCC. Reject rather
|
|
234
|
+
// than parse the cert content out of the broken wrapping.
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (key === 'Chain') {
|
|
238
|
+
chainValue = value;
|
|
239
|
+
} else {
|
|
240
|
+
certValue = value;
|
|
155
241
|
}
|
|
156
|
-
|
|
157
|
-
const pem = decodeURIComponent(value);
|
|
158
|
-
return pemToCertificate(pem);
|
|
159
242
|
}
|
|
160
243
|
}
|
|
161
244
|
|
|
162
|
-
|
|
245
|
+
const value = chainValue ?? certValue;
|
|
246
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: short-circuit; falling through with null hands "null" string to chainFromMultiBlockPem which finds no PEM markers and returns null
|
|
247
|
+
if (!value) {
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const pem = decodeURIComponent(value);
|
|
252
|
+
return chainFromMultiBlockPem(pem);
|
|
163
253
|
} catch {
|
|
164
254
|
return null;
|
|
165
255
|
}
|
|
@@ -175,7 +265,7 @@ export function parseXfcc(headerValue) {
|
|
|
175
265
|
* @returns {PeerCertificate | null} Parsed certificate or null on failure
|
|
176
266
|
*/
|
|
177
267
|
export function parseBase64Der(headerValue) {
|
|
178
|
-
// Stryker disable next-line BlockStatement: falsy input falls through to try-catch → same null return
|
|
268
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
179
269
|
if (!headerValue) {
|
|
180
270
|
return null;
|
|
181
271
|
}
|
|
@@ -222,7 +312,7 @@ export function parseBase64Der(headerValue) {
|
|
|
222
312
|
* @returns {PeerCertificate | null} Parsed certificate or null on failure
|
|
223
313
|
*/
|
|
224
314
|
export function parseRfc9440(headerValue) {
|
|
225
|
-
// Stryker disable next-line BlockStatement: falsy input falls through to try-catch → same null return
|
|
315
|
+
// Stryker disable next-line BlockStatement,ConditionalExpression: falsy input falls through to try-catch → same null return
|
|
226
316
|
if (!headerValue) {
|
|
227
317
|
return null;
|
|
228
318
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "client-certificate-auth",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"description": "Express/Connect middleware for mTLS client certificate authentication with reverse proxy support (AWS ALB, Envoy, Cloudflare, Traefik)",
|
|
5
5
|
"homepage": "https://github.com/tgies/client-certificate-auth",
|
|
6
6
|
"bugs": {
|
|
@@ -72,21 +72,24 @@
|
|
|
72
72
|
"node": ">= 20"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@commitlint/cli": "^20.4.
|
|
76
|
-
"@commitlint/config-conventional": "^20.
|
|
77
|
-
"@stryker-mutator/core": "^9.
|
|
78
|
-
"@stryker-mutator/jest-runner": "^9.
|
|
75
|
+
"@commitlint/cli": "^20.4.3",
|
|
76
|
+
"@commitlint/config-conventional": "^20.5.0",
|
|
77
|
+
"@stryker-mutator/core": "^9.6.0",
|
|
78
|
+
"@stryker-mutator/jest-runner": "^9.6.1",
|
|
79
79
|
"@types/express": "^5.0.6",
|
|
80
|
-
"@types/node": "^25.
|
|
81
|
-
"c8": "^
|
|
82
|
-
"eslint": "^9.
|
|
83
|
-
"globals": "^17.
|
|
80
|
+
"@types/node": "^25.6.0",
|
|
81
|
+
"c8": "^11.0.0",
|
|
82
|
+
"eslint": "^9.39.4",
|
|
83
|
+
"globals": "^17.5.0",
|
|
84
84
|
"husky": "^9.1.7",
|
|
85
|
-
"jest": "^30.
|
|
86
|
-
"lint-staged": "^16.2
|
|
85
|
+
"jest": "^30.3.0",
|
|
86
|
+
"lint-staged": "^16.3.2",
|
|
87
87
|
"selfsigned": "^5.5.0",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
88
|
+
"typedoc": "^0.28.19",
|
|
89
|
+
"typedoc-plugin-markdown": "^4.11.0",
|
|
90
|
+
"typescript": "^6.0.3",
|
|
91
|
+
"vitepress": "^2.0.0-alpha.17",
|
|
92
|
+
"ws": "^8.20.0"
|
|
90
93
|
},
|
|
91
94
|
"directories": {
|
|
92
95
|
"lib": "./lib",
|
|
@@ -103,7 +106,11 @@
|
|
|
103
106
|
"build:types": "tsc --declaration --emitDeclarationOnly --outDir lib",
|
|
104
107
|
"check": "npm run lint && npm run typecheck && npm run test:coverage",
|
|
105
108
|
"stats": "echo '=== Source ===' && cloc lib/ --quiet | tail -n +2 && echo '=== Tests ===' && cloc test/ --exclude-dir=docker --quiet | tail -n +2 && echo '=== Package ===' && npm pack --dry-run 2>&1 | grep -E 'package size|unpacked size|total files'",
|
|
106
|
-
"prepare": "husky"
|
|
109
|
+
"prepare": "husky",
|
|
110
|
+
"docs:api": "typedoc",
|
|
111
|
+
"docs:dev": "vitepress dev docs",
|
|
112
|
+
"docs:build": "npm run docs:api && vitepress build docs",
|
|
113
|
+
"docs:preview": "vitepress preview docs"
|
|
107
114
|
},
|
|
108
115
|
"repository": {
|
|
109
116
|
"type": "git",
|