@noctcore/eslint-plugin-contracts 0.6.1 → 0.7.1

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.
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Monetary fields typed as the JS `number` primitive lose precision to float rounding — use a Decimal money type.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  Money stored as a JS `number` accumulates IEEE-754 rounding errors (`0.1 + 0.2 !== 0.3`), which is
@@ -16,10 +20,6 @@ positions that carry real precision risk:
16
20
  - class properties — `class Invoice { total: number }`
17
21
  - annotated variable declarators — `const amount: number = …`
18
22
 
19
- Conservative on purpose. Untyped declarations and numeric-literal initializers (`let total = 0`) are
20
- **not** flagged — those are usually counters/accumulators. Interface and type-literal members
21
- (`{ amount: number }`) are **out of scope** so non-money type members do not regress.
22
-
23
23
  ```ts bad reports=2
24
24
  class Invoice { total: number; }
25
25
  const amount: number = 5;
@@ -31,6 +31,15 @@ const count: number = 3; // not a money name
31
31
  interface Payment { amount: number; } // type member, out of scope
32
32
  ```
33
33
 
34
+ ## What it does not flag
35
+
36
+ Conservative on purpose. Untyped declarations and numeric-literal initializers (`let total = 0`) are
37
+ **not** flagged — those are usually counters/accumulators. Interface and type-literal members
38
+ (`{ amount: number }`) are **out of scope** so non-money type members do not regress.
39
+
40
+ - Object-literal properties (`const x = { total: 5 }`).
41
+ - Fields matching `minorUnitPatterns`, and every field in a file listed in `allowedFiles`.
42
+
34
43
  ## Options
35
44
 
36
45
  | Option | Type | Default | Meaning |
@@ -47,7 +56,7 @@ interface Payment { amount: number; } // type member, out of scope
47
56
  }]
48
57
  ```
49
58
 
50
- ## Talking to a payment API
59
+ ### Talking to a payment API
51
60
 
52
61
  Stripe and most payment APIs deal in integer minor units: `amount` is a number of cents, and that
53
62
  is correct at that boundary. Without `minorUnitPatterns` this rule flags every one of those
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Read environment variables through a typed, validated config accessor — never `process.env` directly.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  `process.env.X` is `string | undefined`, unvalidated, and reachable from anywhere. A typo or a missing
@@ -27,8 +31,12 @@ import { config } from '@/config';
27
31
  const isProd = config.isProduction;
28
32
  ```
29
33
 
30
- Files matched by the `allowedFiles` glob allowlist are skipped entirely, so bootstrap entrypoints,
31
- config files, and tests may still read `process.env` directly.
34
+ ## What it does not flag
35
+
36
+ - Files matched by the `allowedFiles` glob allowlist are skipped entirely, so bootstrap entrypoints,
37
+ config files, and tests may still read `process.env` directly.
38
+ - `import.meta.env` reads and other `process` properties (`process.environment`, `process.argv`):
39
+ only `process.env` itself is policed.
32
40
 
33
41
  ```ts good filename=vite.config.ts relocation
34
42
  const isProd = process.env.NODE_ENV === 'production';
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Stringifying an error with `${error}`, `error.toString()`, or `error + ""` drops its cause chain.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  `` `${error}` ``, `error.toString()`, and `error + ""` all coerce an `Error` to its `message` alone,
@@ -31,6 +35,12 @@ const msg = error instanceof Error ? error.message : String(error);
31
35
  const m = `${error.message}`;
32
36
  ```
33
37
 
38
+ ## What it does not flag
39
+
40
+ - Bare `String(error)`, which the guarded extractor idiom relies on.
41
+ - Member reads such as `` `${error.message}` `` or `error.stack`.
42
+ - `x + ""` where `x` is not one of `errorIdentifierNames` (`count + ""`).
43
+
34
44
  ## Options
35
45
 
36
46
  | Option | Type | Default | Meaning |
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Re-throwing inside a `catch` without `{ cause }` severs the chain to the original error. 🔧
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 🔧 Fixable with `--fix` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  When you catch an error and throw a new one, the new error is what reaches your logger. If you do not
@@ -20,16 +24,6 @@ try {
20
24
  ## What it flags
21
25
 
22
26
  A `throw new SomeError(...)` inside a `catch` block when **no** argument carries a `cause` property.
23
- Deliberately conservative:
24
-
25
- - Only constructors whose simple name ends in `Error` or `Exception` are treated as errors
26
- (`Error`, `TypeError`, `ValidationError`, `HttpException`). Throwing `new Response(...)` for
27
- control flow is not policed.
28
- - Fires only when the enclosing catch binds a plain identifier (`catch (err)`). A parameterless
29
- `catch {}` or a destructured binding (`catch ({ message })`) has no single name to attach, so the
30
- throw is left alone.
31
- - Any `cause` property (whatever its value), and any spread the rule cannot see through, counts as
32
- "has a cause" and suppresses the report — a hand-written cause is never second-guessed.
33
27
 
34
28
  A `throw` nested in a closure declared inside the catch is still flagged: the binding is genuinely in
35
29
  scope there. Nested `try/catch` uses the nearest binding.
@@ -47,7 +41,7 @@ try { work(); } catch (err) { throw new Error('failed', { cause: err }); }
47
41
  try { work(); } catch (err) { throw err; }
48
42
  ```
49
43
 
50
- ## Fix
44
+ ### Fix
51
45
 
52
46
  Autofix attaches `{ cause: <binding> }`:
53
47
 
@@ -58,9 +52,19 @@ Autofix attaches `{ cause: <binding> }`:
58
52
  A zero-argument `new SomeError()` is **reported but not autofixed** — inserting an options object as
59
53
  the first argument could clobber a positional message the rule cannot see.
60
54
 
61
- ## Options
55
+ ## What it does not flag
62
56
 
63
- None.
57
+ Deliberately conservative:
58
+
59
+ - Only constructors whose simple name ends in `Error` or `Exception` are treated as errors
60
+ (`Error`, `TypeError`, `ValidationError`, `HttpException`). Throwing `new Response(...)` for
61
+ control flow is not policed.
62
+ - Fires only when the enclosing catch binds a plain identifier (`catch (err)`). A parameterless
63
+ `catch {}` or a destructured binding (`catch ({ message })`) has no single name to attach, so the
64
+ throw is left alone.
65
+ - Any `cause` property (whatever its value), and any spread the rule cannot see through, counts as
66
+ "has a cause" and suppresses the report — a hand-written cause is never second-guessed.
67
+ - A bare re-throw of the caught error (`throw err`).
64
68
 
65
69
  ## When not to use it
66
70
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  > The key/name argument of a configured sink API must be an imported constant, not a raw string.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ⚙️ Opt-in: `off` in `recommended`; needs options (see Options) · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  String keys threaded into sink APIs — storage slots, event channels, feature flags, query-cache keys
@@ -26,9 +30,18 @@ localStorage.getItem(USER_PROFILE_KEY);
26
30
  emitter.on(TASK_DONE, handler);
27
31
  ```
28
32
 
33
+ ## What it does not flag
34
+
29
35
  Only string literals are flagged. An already-imported identifier, a template literal, or any computed
30
36
  expression is left alone — those are not the raw-string smell.
31
37
 
38
+ `callee` is matched against the call's dotted identifier path (`localStorage.getItem`, `emitter.on`).
39
+ A callee that is computed or not a plain identifier chain (`this.emitter.on`, `obj[k].on`, `a().b`)
40
+ cannot be matched and is skipped.
41
+
42
+ Calls to callees not listed in `sinks` (`sessionStorage.getItem` when only `localStorage.getItem` is
43
+ configured) are not policed.
44
+
32
45
  ## Options
33
46
 
34
47
  | Option | Type | Default | Meaning |
@@ -51,10 +64,6 @@ key sinks to hard-code, so you declare which callees matter for your project.
51
64
  }]
52
65
  ```
53
66
 
54
- `callee` is matched against the call's dotted identifier path (`localStorage.getItem`, `emitter.on`).
55
- A callee that is computed or not a plain identifier chain (`this.emitter.on`, `obj[k].on`, `a().b`)
56
- cannot be matched and is skipped.
57
-
58
67
  ## When not to use it
59
68
 
60
69
  If your keys are already constants, or you have no registry module to import from, leave `sinks` empty
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Parse external boundary data at runtime — don't assert its shape with `as T`.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ Opt-in: `off` in `recommended` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  External data — a fetch body, a `JSON.parse` result, a message-event payload — has whatever shape the
@@ -17,7 +21,8 @@ const user = UserSchema.parse(await res.json()); // validated
17
21
  ## What it flags
18
22
 
19
23
  This is a **conservative syntactic slice** of a concept that is fully general only with type
20
- information. It flags a cast applied **directly** to a call site that is unmistakably a boundary read:
24
+ information. It flags a cast whose target is a **shape claim** (a named type, an array, or a union
25
+ containing one: `as User`, `as User[]`, `as User | null`) applied to a boundary read:
21
26
 
22
27
  ```ts bad reports=3
23
28
  const user = JSON.parse(raw) as User;
@@ -27,18 +32,107 @@ const fetched = (await res.json()) as User;
27
32
 
28
33
  ```ts good
29
34
  const user = UserSchema.parse(JSON.parse(raw));
35
+ const users = UserSchema.array().parse(JSON.parse(raw));
30
36
  const fetched = UserSchema.parse(await res.json());
31
- const data = JSON.parse(raw) as unknown; // safe widening, not a shape claim
32
37
  ```
33
38
 
34
- Only these forms are flagged: `JSON.parse(...) as T` and `(await <expr>.json()) as T`, and only when
35
- the cast target is a shape claim (a named type or array — `as User`, `as User[]`). Casts to `unknown`,
36
- `any`, or `const` are the safe/neutral forms and are never flagged. A boundary value stored in a
37
- variable and cast later is out of syntactic reach — enforce that with a type-aware setup.
39
+ The boundary reads it knows:
40
+
41
+ | Source | Example |
42
+ | --- | --- |
43
+ | `JSON.parse(...)` | covers web storage and OpenAI `call.function.arguments` fed to it |
44
+ | `await <expr>.json()` | a fetch `Response` body |
45
+ | `localStorage.getItem` / `sessionStorage.getItem` | also through `window.` / `globalThis.` |
46
+ | `URLSearchParams` `.get` / `.getAll` | `new URLSearchParams(...)`, `<x>.searchParams`, a `searchParams` binding, or a `const` bound to one of those |
47
+ | `event.data` in a `message` listener | `x.addEventListener('message', fn)`, `x.onmessage = fn`, `onmessage = fn`, including a `{ data }` parameter |
48
+ | Anthropic `tool_use` `.input` | `block.input` inside `if (block.type === 'tool_use')`, a `?:` / `&&` guard, `case 'tool_use':`, a `.find((b) => b.type === 'tool_use')` result, or a `.filter(...)` of that shape followed by `.map` / `.flatMap` / `.forEach` |
49
+ | `boundaries` option | any callee you list |
50
+
51
+ ```ts bad reports=5
52
+ const prefs = JSON.parse(localStorage.getItem('prefs') ?? '{}') as Prefs;
53
+ const sort = new URLSearchParams(location.search).get('sort') as Sort;
54
+ window.addEventListener('message', (event) => handle(event.data as Message));
55
+ const args = JSON.parse(call.function.arguments) as WeatherArgs;
56
+ if (block.type === 'tool_use') run(block.input as WeatherArgs);
57
+ ```
58
+
59
+ ```ts good
60
+ const prefs = PrefsSchema.parse(JSON.parse(localStorage.getItem('prefs') ?? '{}'));
61
+ const sort = SortSchema.parse(new URLSearchParams(location.search).get('sort'));
62
+ window.addEventListener('message', (event) => handle(MessageSchema.parse(event.data)));
63
+ const args = WeatherArgsSchema.parse(JSON.parse(call.function.arguments));
64
+ if (block.type === 'tool_use') run(WeatherArgsSchema.parse(block.input));
65
+ ```
66
+
67
+ ### Through a `const`
68
+
69
+ A boundary value stored in a `const` and cast later in the same function is flagged too:
70
+
71
+ ```ts bad
72
+ async function loadUser(res: Response) {
73
+ const raw = await res.json();
74
+ return raw as User;
75
+ }
76
+ ```
77
+
78
+ ```ts good
79
+ async function loadUser(res: Response) {
80
+ const raw = await res.json();
81
+ return UserSchema.parse(raw);
82
+ }
83
+ ```
84
+
85
+ The binding is followed only when nothing could have checked the value first, so the rule gives the
86
+ benefit of the doubt whenever it cannot prove otherwise:
87
+
88
+ - Only a single `const name = <boundary>` is followed, never `let`, `var`, a destructuring pattern
89
+ or a parameter. Chains of such consts (`const body = await res.json(); const raw = body;`) are
90
+ followed.
91
+ - The cast must be in the same function as the declaration.
92
+ - Any read of the binding before the cast other than another `as` cast (a guard like
93
+ `isUser(raw)`, an `assertUser(raw)` call, an `'id' in raw` check, passing it to a function,
94
+ mutating a property) keeps the rule silent, as does any read from a nested function. Reads after
95
+ the cast do not excuse it.
96
+
97
+ ```ts prose reason="shows what the rule deliberately leaves alone, not a fix for an example above"
98
+ async function loadUser(res: Response) {
99
+ const raw = await res.json();
100
+ assertUser(raw); // might have validated it: not flagged
101
+ return raw as User;
102
+ }
103
+ ```
104
+
105
+ ## What it does not flag
106
+
107
+ - Casts to `unknown`, `any`, `const` or a primitive keyword (`as string | null`): the safe or
108
+ neutral forms.
109
+ - `satisfies`, which checks the value against the type instead of asserting it.
110
+ - A cast of the parse result (`UserSchema.parse(raw) as User`): the schema already checked it.
111
+ - Look-alikes that are not boundary reads: `cache.getItem(...)`, `map.get(...)`,
112
+ `headers.get(...)`, `.data` outside a `message` listener, `.input` without a `tool_use` guard.
113
+ - A boundary value that is neither cast directly nor bound by a followed `const` (a property of
114
+ an object, a function return value). That needs a type-aware setup.
38
115
 
39
116
  ## Options
40
117
 
41
- None.
118
+ | Option | Type | Default | Meaning |
119
+ | --- | --- | --- | --- |
120
+ | `boundaries` | `string[]` | `[]` | Extra callees whose result is boundary data: a bare name or a dotted path. |
121
+
122
+ ```js
123
+ 'noctcore-contracts/require-schema-parse-at-boundary': ['error', {
124
+ // `readBody(event) as T` and `(await readBody(event)) as T` are then flagged.
125
+ boundaries: ['readBody', 'ipcRenderer.invoke'],
126
+ }]
127
+ ```
128
+
129
+ ```ts bad options={"boundaries":["readBody"]}
130
+ const body = (await readBody(event)) as Body;
131
+ ```
132
+
133
+ ```ts good
134
+ const body = BodySchema.parse(await readBody(event));
135
+ ```
42
136
 
43
137
  ## When not to use it
44
138
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  > `throw` only members of your error taxonomy — never an ad hoc built-in nor a bare value.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  A codebase that throws a curated set of error types can handle them exhaustively at the boundary:
@@ -15,10 +19,6 @@ breaks that: a non-Error carries no stack and no cause, and an unclassified erro
15
19
  - `throw new SomethingError(...)` whose constructor is **not** in the `allow` list.
16
20
  - `throw <non-Error value>` — a string, number, boolean, template literal, object, or array literal.
17
21
 
18
- Conservative on the ambiguous forms. A bare identifier (`throw err` — the re-throw), a member
19
- (`throw ctx.error`), and a call (`throw makeError()`) are all left alone: a syntactic rule cannot know
20
- whether they resolve to an Error, and re-throwing a caught error is the most common `throw` there is.
21
-
22
22
  ```ts bad reports=3
23
23
  // built-in not in the taxonomy
24
24
  throw new TypeError('bad');
@@ -34,6 +34,12 @@ throw new Error('boom');
34
34
  try { work(); } catch (err) { throw err; }
35
35
  ```
36
36
 
37
+ ## What it does not flag
38
+
39
+ Conservative on the ambiguous forms. A bare identifier (`throw err` — the re-throw), a member
40
+ (`throw ctx.error`), and a call (`throw makeError()`) are all left alone: a syntactic rule cannot know
41
+ whether they resolve to an Error, and re-throwing a caught error is the most common `throw` there is.
42
+
37
43
  ## Options
38
44
 
39
45
  | Option | Type | Default | Meaning |
@@ -3,6 +3,10 @@
3
3
  > A field that is an enum in one zod object schema must not be `z.string()` in another schema of the
4
4
  > same module.
5
5
 
6
+ <!-- begin generated rule header -->
7
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
8
+ <!-- end generated rule header -->
9
+
6
10
  ## Why
7
11
 
8
12
  When schemas double as wire types (tRPC procedures, a shared contract package), the output schema is
@@ -50,6 +54,16 @@ export const ticketOutput = z.object({
50
54
  });
51
55
  ```
52
56
 
57
+ ## What it does not flag
58
+
59
+ - `z.union([z.string(), z.null()])` and any `z.string()` chain with `.pipe()` or `.transform()`:
60
+ neither is a plain string.
61
+ - A single `z.literal('X')`: a constant, not an enum.
62
+ - An imported identifier, unless its name matches `enumIdentifierPattern` (see Options).
63
+ - An enum in another file: the scope is one module.
64
+ - Keys in `ignoreFields`, computed keys, and schemas built from a namespace not listed in
65
+ `zodIdentifiers`.
66
+
53
67
  ## Options
54
68
 
55
69
  | Option | Type | Default | Meaning |
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Every static translation key must exist in the catalog of the namespace in scope.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ⚙️ Opt-in: `off` in `recommended`; needs options (see Options) · 💭 Type information: used when available
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  i18next does not fail on a missing key. It renders the key itself (`admin.portalAccounts.revokeTitle`)
@@ -56,7 +60,7 @@ Plural forms (`key_one`, `key_few`, `key_ordinal_other`) answer for `key` only w
56
60
  `count`, and context variants (`key_male`) only when it passes `context`, because without them
57
61
  i18next looks up the bare key and misses. A subtree or array answers only with `returnObjects`.
58
62
 
59
- ### What it never reports
63
+ ## What it does not flag
60
64
 
61
65
  Anything it cannot resolve statically: a variable key (`t(someKey)`), a template key
62
66
  (`` t(`status.${s}`) ``), a key computed by a helper, a namespace held in an identifier it cannot
@@ -64,6 +68,15 @@ resolve (an import not listed in `namespaceIdentifiers`), an options bag it cann
64
68
  (`t('k', opts)`, `{ ...opts }`, `{ ns: someNs }`), and a `t` parameter with no `TFunction` type. The
65
69
  rule stays silent on those rather than guessing.
66
70
 
71
+ ### Dead keys are out of scope
72
+
73
+ The reverse check, "a catalog key nothing uses", is not something a per-file ESLint rule can do
74
+ soundly: it needs every source file at once, and ESLint may lint one file (editor), a subset
75
+ (`--cache`, lint-staged), or shard files across workers. Worse, keys routinely flow as data
76
+ (navigation tables, key-builder helpers, `` `errors:${code}` ``), which no call-site analysis
77
+ sees. Run it as a whole-tree check instead, one that unions static keys, template-key prefixes
78
+ and string literals that equal a key.
79
+
67
80
  ## Options
68
81
 
69
82
  | Option | Type | Default | Meaning |
@@ -140,15 +153,6 @@ found one real bug (`t('common.cancel')` in the default namespace, which renders
140
153
  namespace registered at runtime only inside a test (`registerFeatureNamespace('late-arrival', ...)`)
141
154
  is reported as unknown; exclude test files or add a catalog entry for it.
142
155
 
143
- ## Dead keys are out of scope
144
-
145
- The reverse check, "a catalog key nothing uses", is not something a per-file ESLint rule can do
146
- soundly: it needs every source file at once, and ESLint may lint one file (editor), a subset
147
- (`--cache`, lint-staged), or shard files across workers. Worse, keys routinely flow as data
148
- (navigation tables, key-builder helpers, `` `errors:${code}` ``), which no call-site analysis
149
- sees. Run it as a whole-tree check instead, one that unions static keys, template-key prefixes
150
- and string literals that equal a key.
151
-
152
156
  ## When not to use it
153
157
 
154
158
  If your keys are mostly computed, or your catalogs are not JSON files on disk (fetched from a TMS at
@@ -2,6 +2,10 @@
2
2
 
3
3
  > A message-schema's `type` discriminant must be the kebab-case of its const name minus its role suffix. 🔧
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 🔧 Fixable with `--fix` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  When wire messages are modelled as zod objects with a `type: z.literal('…')` discriminant, the const
@@ -29,7 +33,11 @@ export const TaskCompletedEvent = z.object({ type: z.literal('task-completed') }
29
33
  export const RunTaskCommand = z.object({ type: z.literal('run-task') });
30
34
  ```
31
35
 
32
- Consts without a role suffix, and role-suffixed consts without a `type` literal, are ignored.
36
+ ## What it does not flag
37
+
38
+ - Consts without a role suffix (`TaskSchema`), whatever their `type` literal says.
39
+ - Role-suffixed consts without a `type: z.literal(...)` property.
40
+ - Consts that are not exported: only `export const` declarations are checked.
33
41
 
34
42
  ## Options
35
43
 
@@ -2,6 +2,10 @@
2
2
 
3
3
  > Every exported zod schema is a PascalCase const suffixed `Schema`, paired with a same-named inferred type.
4
4
 
5
+ <!-- begin generated rule header -->
6
+ ✅ In `recommended` at `error` · 💭 Type information: not needed
7
+ <!-- end generated rule header -->
8
+
5
9
  ## Why
6
10
 
7
11
  A contracts package is a shared spine. A uniform `FooSchema` + `Foo` pairing keeps the schema and
@@ -30,6 +34,12 @@ export const TaskSchema = z.object({ id: z.string() });
30
34
  export type Task = z.infer<typeof TaskSchema>;
31
35
  ```
32
36
 
37
+ ## What it does not flag
38
+
39
+ - Exported consts whose initializer is not rooted at `z` (`export const MAX = 10`).
40
+ - Zod schemas that are not exported.
41
+ - Consts ending in one of the configured `roleSuffixes` (see Options).
42
+
33
43
  ## Options
34
44
 
35
45
  | Option | Type | Default | Meaning |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noctcore/eslint-plugin-contracts",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "ESLint rules for shared contract, config, error-handling, and money-precision conventions (zod schema naming, wire discriminants, no-direct-process-env, decimal money).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,7 +38,7 @@
38
38
  "url": "git+https://github.com/noctcore/eslint-plugins.git",
39
39
  "directory": "packages/eslint-plugin-contracts"
40
40
  },
41
- "homepage": "https://github.com/noctcore/eslint-plugins/tree/main/packages/eslint-plugin-contracts",
41
+ "homepage": "https://noctcore.github.io/eslint-plugins/packages/contracts/",
42
42
  "bugs": "https://github.com/noctcore/eslint-plugins/issues",
43
43
  "scripts": {
44
44
  "build": "tsup src/index.ts --format esm,cjs --dts --clean",
@@ -46,7 +46,7 @@
46
46
  "test": "vitest run"
47
47
  },
48
48
  "dependencies": {
49
- "@noctcore/eslint-utils": "^0.1.1",
49
+ "@noctcore/eslint-utils": "^0.1.2",
50
50
  "@typescript-eslint/utils": "^8.61.1"
51
51
  },
52
52
  "peerDependencies": {
@@ -55,11 +55,11 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@noctcore/eslint-test-utils": "workspace:*",
58
- "@types/node": "^22.0.0",
59
- "@typescript-eslint/parser": "^8.61.1",
60
- "@typescript-eslint/rule-tester": "^8.61.1",
58
+ "@types/node": "^22.20.3",
59
+ "@typescript-eslint/parser": "^8.70.0",
60
+ "@typescript-eslint/rule-tester": "^8.70.0",
61
61
  "tsup": "^8.5.1",
62
62
  "typescript": "^5.6.0",
63
- "vitest": "^4"
63
+ "vitest": "^5"
64
64
  }
65
65
  }