@carecard/jwt-read 3.18.0 → 3.19.0

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/lib/jwtLib.js CHANGED
@@ -26,7 +26,12 @@ function doesJwtUserHasRole(req, userRole) {
26
26
 
27
27
  const jwtObj = req?.jwt || this;
28
28
 
29
- if (!userRole || typeof userRole !== 'string' || !jwtObj?.payload?.roles || !Array.isArray(jwtObj.payload.roles)) {
29
+ if (
30
+ !userRole ||
31
+ typeof userRole !== 'string' ||
32
+ !jwtObj?.payload?.roles ||
33
+ !Array.isArray(jwtObj.payload.roles)
34
+ ) {
30
35
  throwNotAuthorizedError();
31
36
  }
32
37
 
@@ -40,7 +45,9 @@ function throwError(customErrorFunction) {
40
45
 
41
46
  const customError = customErrorFunction();
42
47
 
43
- if (customError instanceof Error) throw customError;
48
+ if (customError instanceof Error) {
49
+ throw customError;
50
+ }
44
51
 
45
52
  throw new Error('Custom authentication error function returned without throwing');
46
53
  }
@@ -118,13 +125,26 @@ function validateAndExtractWebToken(req, publicKey, headerName, customErrorFunct
118
125
 
119
126
  // Pattern: Decorator - extends no-throw JWT extraction without changing req.jwt failure semantics.
120
127
  function validateAndExtractJwtObjectNoThrow(req, publicKey, options) {
121
- _validateAndExtractGenericNoThrow(req, publicKey, _validateJwtNoThrow, _extractJwtObjectNoThrow, 'jwt');
128
+ _validateAndExtractGenericNoThrow(
129
+ req,
130
+ publicKey,
131
+ _validateJwtNoThrow,
132
+ _extractJwtObjectNoThrow,
133
+ 'jwt',
134
+ );
122
135
  validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
123
136
  return req;
124
137
  }
125
138
 
126
139
  // Pattern: Decorator - keeps service-JWT checks distinct from optional user authorization context.
127
- function validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction, options) {
140
+ function validateAndExtractServiceJwtObject(
141
+ req,
142
+ publicKey,
143
+ expectedIssuer,
144
+ expectedAudience,
145
+ customErrorFunction,
146
+ options,
147
+ ) {
128
148
  const jwtString = _validateJwt(req, customErrorFunction);
129
149
  const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
130
150
 
@@ -144,10 +164,21 @@ function validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expe
144
164
  }
145
165
 
146
166
  // Pattern: Decorator - normalizes primary auth first, then applies optional scoped authorization.
147
- async function validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options) {
167
+ async function validateAndExtractJwtOrServerAuthObject(
168
+ req,
169
+ publicKey,
170
+ serverAuthIntrospector,
171
+ customErrorFunction,
172
+ options,
173
+ ) {
148
174
  if (!tryValidateAndExtractJwtObject(req, publicKey)) {
149
175
  const serverAuthToken = _validateServerAuthToken(req, customErrorFunction);
150
- const claims = await introspectServerAuthToken(serverAuthIntrospector, serverAuthToken, req, customErrorFunction);
176
+ const claims = await introspectServerAuthToken(
177
+ serverAuthIntrospector,
178
+ serverAuthToken,
179
+ req,
180
+ customErrorFunction,
181
+ );
151
182
  attachServerAuthClaims(req, claims, customErrorFunction);
152
183
  }
153
184
 
@@ -157,14 +188,26 @@ async function validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAut
157
188
 
158
189
  // Pattern: Decorator - extends custom-header no-throw extraction with optional user authorization context.
159
190
  function validateAndExtractWebTokenObjectNoThrow(req, publicKey, headerName, options) {
160
- _validateAndExtractGenericNoThrow(req, publicKey, r => _validateWebTokenNoThrow(r, headerName), _extractJwtObjectNoThrow, 'jwt');
191
+ _validateAndExtractGenericNoThrow(
192
+ req,
193
+ publicKey,
194
+ r => _validateWebTokenNoThrow(r, headerName),
195
+ _extractJwtObjectNoThrow,
196
+ 'jwt',
197
+ );
161
198
  validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
162
199
  return req;
163
200
  }
164
201
 
165
202
  // Pattern: Decorator - keeps visitor token extraction independent from optional user authorization context.
166
203
  function validateAndExtractVisitorObjectNoThrow(req, publicKey, options) {
167
- _validateAndExtractGenericNoThrow(req, publicKey, _validateVisitorNoThrow, _extractVisitorObjectNoThrow, 'visitor');
204
+ _validateAndExtractGenericNoThrow(
205
+ req,
206
+ publicKey,
207
+ _validateVisitorNoThrow,
208
+ _extractVisitorObjectNoThrow,
209
+ 'visitor',
210
+ );
168
211
  validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options);
169
212
  return req;
170
213
  }
@@ -196,10 +239,23 @@ function verifyJwt(publicKey, customErrorFunction, options) {
196
239
  }
197
240
 
198
241
  // Pattern: Middleware - verifies service identity and optionally carries user authorization context.
199
- function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErrorFunction, options) {
242
+ function verifyServiceJwt(
243
+ publicKey,
244
+ expectedIssuer,
245
+ expectedAudience,
246
+ customErrorFunction,
247
+ options,
248
+ ) {
200
249
  return function (req, res, next) {
201
250
  try {
202
- validateAndExtractServiceJwtObject(req, publicKey, expectedIssuer, expectedAudience, customErrorFunction, options);
251
+ validateAndExtractServiceJwtObject(
252
+ req,
253
+ publicKey,
254
+ expectedIssuer,
255
+ expectedAudience,
256
+ customErrorFunction,
257
+ options,
258
+ );
203
259
  next();
204
260
  } catch (err) {
205
261
  next(err);
@@ -211,7 +267,13 @@ function verifyServiceJwt(publicKey, expectedIssuer, expectedAudience, customErr
211
267
  function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFunction, options) {
212
268
  return async function (req, res, next) {
213
269
  try {
214
- await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options);
270
+ await validateAndExtractJwtOrServerAuthObject(
271
+ req,
272
+ publicKey,
273
+ serverAuthIntrospector,
274
+ customErrorFunction,
275
+ options,
276
+ );
215
277
  next();
216
278
  } catch (err) {
217
279
  next(err);
@@ -220,10 +282,22 @@ function verifyJwtOrServerAuth(publicKey, serverAuthIntrospector, customErrorFun
220
282
  }
221
283
 
222
284
  // Pattern: Middleware - composes flexible auth, optional user authorization, and role checks.
223
- function verifyJwtOrServerAuthAndHasRole(role, publicKey, serverAuthIntrospector, customErrorFunction, options) {
285
+ function verifyJwtOrServerAuthAndHasRole(
286
+ role,
287
+ publicKey,
288
+ serverAuthIntrospector,
289
+ customErrorFunction,
290
+ options,
291
+ ) {
224
292
  return async function (req, res, next) {
225
293
  try {
226
- await validateAndExtractJwtOrServerAuthObject(req, publicKey, serverAuthIntrospector, customErrorFunction, options);
294
+ await validateAndExtractJwtOrServerAuthObject(
295
+ req,
296
+ publicKey,
297
+ serverAuthIntrospector,
298
+ customErrorFunction,
299
+ options,
300
+ );
227
301
  const isRoleExist = doesJwtUserHasRole(req, role);
228
302
  _isLoginRequired(isRoleExist, customErrorFunction);
229
303
  next();
@@ -313,7 +387,9 @@ function validateAndExtractUserAuthorizationObject(req, publicKey, customErrorFu
313
387
  if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
314
388
  _extractUserAuthorizationObjectNoThrow(req, header.token);
315
389
  } else {
316
- if (req) req.userAuthorization = null;
390
+ if (req) {
391
+ req.userAuthorization = null;
392
+ }
317
393
  throwError(customErrorFunction);
318
394
  }
319
395
 
@@ -337,7 +413,9 @@ function validateAndExtractUserAuthorizationObjectNoThrow(req, publicKey, option
337
413
  // Pattern: Guard Clause - skips optional context handling unless a caller explicitly configured it.
338
414
  function validateAndExtractOptionalUserAuthorizationObject(req, options, customErrorFunction) {
339
415
  const config = createOptionalUserAuthorizationConfig(options);
340
- if (!config) return req;
416
+ if (!config) {
417
+ return req;
418
+ }
341
419
 
342
420
  const header = readUserAuthorizationHeader(req, config);
343
421
  if (!header.present) {
@@ -358,7 +436,9 @@ function validateAndExtractOptionalUserAuthorizationObject(req, options, customE
358
436
  // Pattern: Guard Clause - optional no-throw context extraction never changes primary auth results.
359
437
  function validateAndExtractOptionalUserAuthorizationObjectNoThrow(req, options) {
360
438
  const config = createOptionalUserAuthorizationConfig(options);
361
- if (!config) return req;
439
+ if (!config) {
440
+ return req;
441
+ }
362
442
 
363
443
  const header = readUserAuthorizationHeader(req, config);
364
444
  if (header.present && isUserAuthorizationTokenAllowed(header.token, config)) {
@@ -385,7 +465,9 @@ function _isLoginRequired(hasRequiredRole, customErrorFunction) {
385
465
 
386
466
  function tryValidateAndExtractJwtObject(req, publicKey) {
387
467
  const jwtString = _validateJwtNoThrow(req);
388
- if (!jwtString || !isJwtString(jwtString)) return false;
468
+ if (!jwtString || !isJwtString(jwtString)) {
469
+ return false;
470
+ }
389
471
 
390
472
  const isJwtSignatureValid = jwtVerifySignedToken(jwtString, publicKey);
391
473
  if (!isJwtSignatureValid) {
@@ -400,7 +482,9 @@ function tryValidateAndExtractJwtObject(req, publicKey) {
400
482
  function _validateServerAuthToken(req, customErrorFunction) {
401
483
  const authorizationHeader = req?.get?.('Authorization') || req?.get?.('authorization');
402
484
  const token = _extractJwtNoThrow(authorizationHeader);
403
- if (token) return token;
485
+ if (token) {
486
+ return token;
487
+ }
404
488
 
405
489
  throwError(customErrorFunction);
406
490
  return null;
@@ -422,7 +506,9 @@ async function introspectServerAuthToken(serverAuthIntrospector, token, req, cus
422
506
  function attachServerAuthClaims(req, claims, customErrorFunction) {
423
507
  const payload = createServerAuthPayload(claims);
424
508
  if (!payload.sub) {
425
- if (req) req.jwt = null;
509
+ if (req) {
510
+ req.jwt = null;
511
+ }
426
512
  throwError(customErrorFunction);
427
513
  }
428
514
 
@@ -439,10 +525,9 @@ function attachServerAuthClaims(req, claims, customErrorFunction) {
439
525
  // Pattern: Projection - copies only authoritative verification names and preserves their exact values.
440
526
  function pickEmailVerificationClaims(claims) {
441
527
  return Object.fromEntries(
442
- EMAIL_VERIFICATION_CLAIM_NAMES.filter(claimName => Object.prototype.hasOwnProperty.call(claims, claimName)).map(claimName => [
443
- claimName,
444
- claims[claimName],
445
- ]),
528
+ EMAIL_VERIFICATION_CLAIM_NAMES.filter(claimName =>
529
+ Object.prototype.hasOwnProperty.call(claims, claimName),
530
+ ).map(claimName => [claimName, claims[claimName]]),
446
531
  );
447
532
  }
448
533
 
@@ -462,51 +547,85 @@ function createServerAuthPayload(claims) {
462
547
  }
463
548
 
464
549
  function readEpochSeconds(value) {
465
- if (value === undefined || value === null || value === '') return undefined;
466
- if (typeof value === 'number') return normalizeSeconds(value);
550
+ if (value === undefined || value === null || value === '') {
551
+ return undefined;
552
+ }
553
+ if (typeof value === 'number') {
554
+ return normalizeSeconds(value);
555
+ }
467
556
  const millis = Date.parse(value);
468
557
  return Number.isFinite(millis) ? Math.floor(millis / 1000) : undefined;
469
558
  }
470
559
 
471
560
  function normalizeSeconds(value) {
472
- if (!Number.isFinite(value)) return null;
561
+ if (!Number.isFinite(value)) {
562
+ return null;
563
+ }
473
564
  return value > 1000000000000 ? Math.floor(value / 1000) : Math.floor(value);
474
565
  }
475
566
 
476
567
  function _isServiceJwtFor(payload, expectedIssuer, expectedAudience) {
477
- if (!payload) return false;
478
- if (payload.iss !== expectedIssuer) return false;
479
- if (payload.sub !== expectedIssuer) return false;
480
- if (!payloadAudienceMatches(payload.aud, expectedAudience)) return false;
481
- if (isJwtPayloadIssuedInFuture(payload)) return false;
482
- if (isJwtPayloadExpired(payload)) return false;
483
- if (isJwtPayloadNotYetValid(payload)) return false;
568
+ if (!payload) {
569
+ return false;
570
+ }
571
+ if (payload.iss !== expectedIssuer) {
572
+ return false;
573
+ }
574
+ if (payload.sub !== expectedIssuer) {
575
+ return false;
576
+ }
577
+ if (!payloadAudienceMatches(payload.aud, expectedAudience)) {
578
+ return false;
579
+ }
580
+ if (isJwtPayloadIssuedInFuture(payload)) {
581
+ return false;
582
+ }
583
+ if (isJwtPayloadExpired(payload)) {
584
+ return false;
585
+ }
586
+ if (isJwtPayloadNotYetValid(payload)) {
587
+ return false;
588
+ }
484
589
  return true;
485
590
  }
486
591
 
487
592
  function payloadAudienceMatches(actualAudience, expectedAudience) {
488
- if (Array.isArray(actualAudience)) return actualAudience.includes(expectedAudience);
593
+ if (Array.isArray(actualAudience)) {
594
+ return actualAudience.includes(expectedAudience);
595
+ }
489
596
  return actualAudience === expectedAudience;
490
597
  }
491
598
 
492
599
  function isJwtPayloadExpired(payload) {
493
- if (!payload.exp) return true;
600
+ if (!payload.exp) {
601
+ return true;
602
+ }
494
603
  const exp = normalizeSeconds(payload.exp);
495
- if (!Number.isInteger(exp)) return true;
604
+ if (!Number.isInteger(exp)) {
605
+ return true;
606
+ }
496
607
  return Math.floor(Date.now() / 1000) >= exp;
497
608
  }
498
609
 
499
610
  function isJwtPayloadIssuedInFuture(payload) {
500
- if (!payload.iat) return true;
611
+ if (!payload.iat) {
612
+ return true;
613
+ }
501
614
  const iat = normalizeSeconds(payload.iat);
502
- if (!Number.isInteger(iat)) return true;
615
+ if (!Number.isInteger(iat)) {
616
+ return true;
617
+ }
503
618
  return Math.floor(Date.now() / 1000) < iat;
504
619
  }
505
620
 
506
621
  function isJwtPayloadNotYetValid(payload) {
507
- if (!payload.nbf) return false;
622
+ if (!payload.nbf) {
623
+ return false;
624
+ }
508
625
  const nbf = normalizeSeconds(payload.nbf);
509
- if (!Number.isInteger(nbf)) return true;
626
+ if (!Number.isInteger(nbf)) {
627
+ return true;
628
+ }
510
629
  return Math.floor(Date.now() / 1000) < nbf;
511
630
  }
512
631
 
@@ -520,7 +639,9 @@ function createUserAuthorizationConfig(publicKey, options) {
520
639
 
521
640
  // Pattern: Factory - detects optional user authorization configuration without affecting legacy callers.
522
641
  function createOptionalUserAuthorizationConfig(options) {
523
- if (!options || !Object.prototype.hasOwnProperty.call(options, 'userAuthorization')) return null;
642
+ if (!options || !Object.prototype.hasOwnProperty.call(options, 'userAuthorization')) {
643
+ return null;
644
+ }
524
645
  return normalizeUserAuthorizationConfig(options.userAuthorization || {});
525
646
  }
526
647
 
@@ -529,7 +650,10 @@ function normalizeUserAuthorizationConfig(options) {
529
650
  return {
530
651
  publicKey: options?.publicKey,
531
652
  headerName: options?.headerName || DEFAULT_USER_AUTHORIZATION_HEADER_NAME,
532
- maxTokenLength: normalizePositiveInteger(options?.maxTokenLength, DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH),
653
+ maxTokenLength: normalizePositiveInteger(
654
+ options?.maxTokenLength,
655
+ DEFAULT_USER_AUTHORIZATION_MAX_TOKEN_LENGTH,
656
+ ),
533
657
  expectedType: options?.expectedType,
534
658
  expectedIssuer: options?.expectedIssuer,
535
659
  expectedAudience: options?.expectedAudience,
@@ -546,35 +670,59 @@ function normalizePositiveInteger(value, defaultValue) {
546
670
  function readUserAuthorizationHeader(req, config) {
547
671
  const rawToken = getRequestHeader(req, config.headerName);
548
672
  const present = rawToken !== undefined && rawToken !== null;
549
- if (!present || typeof rawToken !== 'string') return { present, token: null };
673
+ if (!present || typeof rawToken !== 'string') {
674
+ return { present, token: null };
675
+ }
550
676
 
551
677
  const token = rawToken.trim();
552
- if (!token || token.length > config.maxTokenLength || !isJwtString(token)) return { present: true, token: null };
678
+ if (!token || token.length > config.maxTokenLength || !isJwtString(token)) {
679
+ return { present: true, token: null };
680
+ }
553
681
  return { present: true, token };
554
682
  }
555
683
 
556
684
  // Pattern: Adapter - supports Express case-insensitive headers and simple test doubles.
557
685
  function getRequestHeader(req, headerName) {
558
686
  const value = req?.get?.(headerName);
559
- if (value !== undefined && value !== null) return value;
687
+ if (value !== undefined && value !== null) {
688
+ return value;
689
+ }
560
690
 
561
691
  const lowerCaseHeaderName = headerName.toLowerCase();
562
- if (lowerCaseHeaderName === headerName) return value;
692
+ if (lowerCaseHeaderName === headerName) {
693
+ return value;
694
+ }
563
695
  return req?.get?.(lowerCaseHeaderName);
564
696
  }
565
697
 
566
698
  // Pattern: Single Responsibility - verifies signature and registered JWT time claims for user authorization.
567
699
  function isUserAuthorizationTokenAllowed(token, config) {
568
- if (!token || !config.publicKey || !jwtVerifySignedToken(token, config.publicKey)) return false;
700
+ if (!token || !config.publicKey || !jwtVerifySignedToken(token, config.publicKey)) {
701
+ return false;
702
+ }
569
703
 
570
704
  const payload = readVerifiedJwtPayload(token);
571
- if (!payload) return false;
572
- if (isJwtPayloadIssuedInFuture(payload)) return false;
573
- if (isJwtPayloadExpired(payload)) return false;
574
- if (isJwtPayloadNotYetValid(payload)) return false;
575
- if (config.expectedType && payload.typ !== config.expectedType) return false;
576
- if (config.expectedIssuer && payload.iss !== config.expectedIssuer) return false;
577
- if (!expectedAudienceMatches(payload.aud, config.expectedAudience)) return false;
705
+ if (!payload) {
706
+ return false;
707
+ }
708
+ if (isJwtPayloadIssuedInFuture(payload)) {
709
+ return false;
710
+ }
711
+ if (isJwtPayloadExpired(payload)) {
712
+ return false;
713
+ }
714
+ if (isJwtPayloadNotYetValid(payload)) {
715
+ return false;
716
+ }
717
+ if (config.expectedType && payload.typ !== config.expectedType) {
718
+ return false;
719
+ }
720
+ if (config.expectedIssuer && payload.iss !== config.expectedIssuer) {
721
+ return false;
722
+ }
723
+ if (!expectedAudienceMatches(payload.aud, config.expectedAudience)) {
724
+ return false;
725
+ }
578
726
 
579
727
  return true;
580
728
  }
@@ -586,14 +734,20 @@ function readVerifiedJwtPayload(token) {
586
734
 
587
735
  // Pattern: Pure Function - supports either one expected audience or a small allowed set.
588
736
  function expectedAudienceMatches(actualAudience, expectedAudience) {
589
- if (expectedAudience === undefined || expectedAudience === null || expectedAudience === '') return true;
590
- if (Array.isArray(expectedAudience)) return expectedAudience.some(audience => payloadAudienceMatches(actualAudience, audience));
737
+ if (expectedAudience === undefined || expectedAudience === null || expectedAudience === '') {
738
+ return true;
739
+ }
740
+ if (Array.isArray(expectedAudience)) {
741
+ return expectedAudience.some(audience => payloadAudienceMatches(actualAudience, audience));
742
+ }
591
743
  return payloadAudienceMatches(actualAudience, expectedAudience);
592
744
  }
593
745
 
594
746
  function _validateGenericNoThrow(req, headerName, extractor) {
595
747
  let jwtRaw = req.get(headerName);
596
- if (!jwtRaw) return null;
748
+ if (!jwtRaw) {
749
+ return null;
750
+ }
597
751
  let jwt = extractor(jwtRaw);
598
752
  return isJwtString(jwt) ? jwt : null;
599
753
  }
@@ -601,7 +755,9 @@ function _validateGenericNoThrow(req, headerName, extractor) {
601
755
  function _validateGeneric(req, headerName, extractor, customErrorFunction) {
602
756
  let jwtRaw = req.get(headerName);
603
757
  let jwt = extractor(jwtRaw, customErrorFunction);
604
- if (isJwtString(jwt)) return jwt;
758
+ if (isJwtString(jwt)) {
759
+ return jwt;
760
+ }
605
761
  throwError(customErrorFunction);
606
762
  return null;
607
763
  }
@@ -617,7 +773,9 @@ function _validateAndExtractGenericNoThrow(req, publicKey, validator, extractor,
617
773
  req[propertyName] = null;
618
774
  }
619
775
  } catch (err) {
620
- if (req) req[propertyName] = null;
776
+ if (req) {
777
+ req[propertyName] = null;
778
+ }
621
779
  throw err;
622
780
  }
623
781
  return req;
@@ -629,7 +787,9 @@ function _extractGenericObjectNoThrow(req, jwt, attacher, propertyName) {
629
787
  attacher(obj);
630
788
  req[propertyName] = obj;
631
789
  } else {
632
- if (req) req[propertyName] = null;
790
+ if (req) {
791
+ req[propertyName] = null;
792
+ }
633
793
  }
634
794
  }
635
795
 
package/lib/jwtRoles.js CHANGED
@@ -44,8 +44,12 @@ function getContext(req) {
44
44
 
45
45
  function getUserAuthorization(req) {
46
46
  const userAuthorization = req?.userAuthorization;
47
- if (!userAuthorization || typeof userAuthorization !== 'object') return null;
48
- if (!userAuthorization.payload || typeof userAuthorization.payload !== 'object') return null;
47
+ if (!userAuthorization || typeof userAuthorization !== 'object') {
48
+ return null;
49
+ }
50
+ if (!userAuthorization.payload || typeof userAuthorization.payload !== 'object') {
51
+ return null;
52
+ }
49
53
 
50
54
  return userAuthorization;
51
55
  }
@@ -0,0 +1,36 @@
1
+ import { ESLint } from 'eslint';
2
+
3
+ const eslint = new ESLint();
4
+
5
+ // Pattern: Pure Function - builds one deterministic command without shell interpolation.
6
+ const createCommand = (command, filePaths) =>
7
+ `${command} ${filePaths.map(filePath => JSON.stringify(filePath)).join(' ')}`;
8
+
9
+ // Pattern: Adapter - derives lint-staged input from ESLint's authoritative ignore rules.
10
+ const removeEslintIgnoredFiles = async filePaths => {
11
+ const ignoredFileStates = await Promise.all(
12
+ filePaths.map(filePath => eslint.isPathIgnored(filePath)),
13
+ );
14
+
15
+ return filePaths.flatMap((filePath, index) => (ignoredFileStates[index] ? [] : [filePath]));
16
+ };
17
+
18
+ // Pattern: Pipeline - preserves ESLint-before-Prettier ordering for staged code.
19
+ const createJavaScriptTasks = async filePaths => {
20
+ const lintableFilePaths = await removeEslintIgnoredFiles(filePaths);
21
+ const tasks = [];
22
+
23
+ if (lintableFilePaths.length > 0) {
24
+ tasks.push(createCommand('eslint --fix --max-warnings 0', lintableFilePaths));
25
+ }
26
+
27
+ tasks.push(createCommand('prettier --write', filePaths));
28
+ return tasks;
29
+ };
30
+
31
+ const lintStagedConfig = {
32
+ '*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}': createJavaScriptTasks,
33
+ '*.{json,jsonc,md,mdx,css,scss,yaml,yml}': ['prettier --write'],
34
+ };
35
+
36
+ export default lintStagedConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carecard/jwt-read",
3
- "version": "3.18.0",
3
+ "version": "3.19.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/CareCard-ca/pkg-jwt-read.git"
@@ -10,15 +10,17 @@
10
10
  "types": "index.d.ts",
11
11
  "scripts": {
12
12
  "test": "node scripts/runPackageTask.mjs test",
13
- "test:order": "node --test scripts/testOrder/randomizeTestOrder.test.mjs scripts/testOrder/testOrderPolicy.test.mjs scripts/testParallel/runIndexedMochaTests.test.mjs scripts/testParallel/parallelTestPolicy.test.mjs scripts/packageTaskRunner.test.mjs",
13
+ "test:order": "node --test scripts/testOrder/randomizeTestOrder.test.mjs scripts/testParallel/runIndexedMochaTests.test.mjs scripts/packageTaskRunner.test.mjs scripts/canonicalTestCommand.test.mjs",
14
14
  "test:types": "node scripts/runPackageTask.mjs test:types",
15
15
  "test:coverage": "node scripts/runPackageTask.mjs test:coverage",
16
16
  "test:All": "node scripts/runPackageTask.mjs test:All",
17
- "format": "prettier --write .",
18
- "format:check": "prettier --check .",
17
+ "validate:audits": "node scripts/runPackageTask.mjs validate:audits",
18
+ "format": "prettier --write \"**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}\"",
19
+ "format:check": "prettier --check \"**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts}\"",
20
+ "lint-staged": "lint-staged",
19
21
  "prepare": "husky",
20
- "lint:fix": "eslint --fix",
21
- "lint": "eslint"
22
+ "lint:fix": "eslint . --fix --max-warnings 0",
23
+ "lint": "eslint . --max-warnings 0"
22
24
  },
23
25
  "keywords": [
24
26
  "auth",
@@ -29,15 +31,18 @@
29
31
  "author": "CareCard team",
30
32
  "license": "ISC",
31
33
  "devDependencies": {
34
+ "@eslint/js": "9.39.5",
32
35
  "@types/express": "5.0.6",
33
36
  "@types/mocha": "10.0.10",
34
37
  "@types/node": "25.9.3",
35
- "eslint": "9.39.4",
38
+ "@typescript-eslint/parser": "8.67.0",
39
+ "eslint": "9.39.5",
40
+ "globals": "17.7.0",
36
41
  "husky": "9.1.7",
37
- "lint-staged": "17.0.7",
42
+ "lint-staged": "17.2.0",
38
43
  "mocha": "11.7.6",
39
44
  "nyc": "18.0.0",
40
- "prettier": "3.8.4",
45
+ "prettier": "3.9.6",
41
46
  "ts-node": "10.9.2",
42
47
  "typescript": "6.0.3"
43
48
  },
@@ -48,8 +53,9 @@
48
53
  },
49
54
  "overrides": {
50
55
  "diff": "8.0.4",
51
- "minimatch": "10.2.5",
56
+ "brace-expansion": "5.0.9",
57
+ "minimatch": "10.2.6",
52
58
  "serialize-javascript": "7.0.5",
53
- "js-yaml": "4.3.0"
59
+ "js-yaml": "4.3.1"
54
60
  }
55
61
  }
package/readme.md CHANGED
@@ -14,7 +14,9 @@ introspected by `ms-auth`.
14
14
 
15
15
  ## Development Rule
16
16
 
17
- Non-negotiable TDD rule: Always write the failing test first, run it to confirm it fails for the intended reason, then implement the code and rerun the test until it passes. Test Driven Development is required for all coding work and must not be skipped. For documentation- or skill-only edits, add or update the relevant validation check before changing the prose.
17
+ Non-negotiable TDD rule: Always write the failing test first, run it to confirm it fails for the intended reason, then implement the code and rerun the test until it passes. Test Driven Development is required for all coding work and must not be skipped. For documentation- or skill-only edits, run the relevant focused non-test
18
+ validation before changing the prose; do not add automated tests that inspect
19
+ prose, files, or repository structure.
18
20
 
19
21
  Non-negotiable repository isolation rule: Every repository must run its Husky hooks and tests using only files, code, fixtures, dependencies, and services contained within that repository. Tests and Husky scripts must not import, require, read, execute, or otherwise depend on sibling repositories or paths outside the repository root. app-e2e-tests is the only exception because cross-repository end-to-end testing is its explicit responsibility.
20
22
 
@@ -249,3 +251,22 @@ immediately when no helper remains, allow only a bounded 250 ms settlement
249
251
  window for already-stopping helpers, fail persistent descendants, preserve
250
252
  failures and output, use exit code `124` only for a real outer deadline, and
251
253
  remain a final guard rather than a substitute for explicit cleanup.
254
+
255
+ ## TDD And Validation
256
+
257
+ Test Driven Development is a non-negotiable requirement.
258
+
259
+ The sole purpose of automated tests is to verify observable functionality and externally visible behavior.
260
+ Tests must validate what the system does through its public interfaces and expected outcomes.
261
+
262
+ Tests must not assert, inspect, or depend on implementation details, including but not limited to:
263
+
264
+ - The existence of specific lines of code, statements, functions, classes, files, or modules.
265
+ - Specific algorithms, control flow, variable names, method calls, code snippets, or internal implementation choices.
266
+ - Any internal structure that can change without changing externally observable behavior.
267
+
268
+ A correct implementation may be completely rewritten or refactored without requiring changes to functional tests, provided its externally observable behavior remains unchanged.
269
+
270
+ Any test that fails solely because the implementation changed while the externally observable behavior remained correct is incorrectly designed and must be rewritten or removed.
271
+
272
+ This requirement is mandatory for all new tests and must be applied whenever existing tests are modified.