@venizia/dev-configs 0.1.0 → 0.2.0-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/README.md +87 -1695
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -5,1360 +5,38 @@
|
|
|
5
5
|
**Shared ESLint, Prettier, and TypeScript configs for the IGNIS ecosystem**
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/@venizia/dev-configs)
|
|
8
|
-
[](
|
|
9
|
-
[](./LICENSE.md)
|
|
9
|
+
[](https://www.typescriptlang.org/)
|
|
10
|
+
[](https://eslint.org/)
|
|
11
11
|
[](https://prettier.io/)
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
[Documentation](https://ignis.venizia.ai) •
|
|
14
|
+
[Best practices](https://ignis.venizia.ai/best-practices/) •
|
|
15
|
+
[Repository](https://github.com/VENIZIA-AI/ignis)
|
|
14
16
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
## Highlights
|
|
20
|
-
|
|
21
|
-
| | Feature | |
|
|
22
|
-
| :---: | :--- | :--- |
|
|
23
|
-
| **1** | **3-Line Setup** | Import and re-export -- ESLint, Prettier, and TypeScript ready |
|
|
24
|
-
| **2** | **Flat Config (ESLint v9+)** | Modern flat config format with composable layers |
|
|
25
|
-
| **3** | **Decorator Support** | `experimentalDecorators` + `emitDecoratorMetadata` pre-configured |
|
|
26
|
-
| **4** | **Consistent Formatting** | 100-char width, single quotes, trailing commas everywhere |
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
|
|
30
|
-
## Table of Contents
|
|
31
|
-
|
|
32
|
-
- [Installation](#installation)
|
|
33
|
-
- [Quick Start](#quick-start)
|
|
34
|
-
- [ESLint Configuration](#eslint-configuration)
|
|
35
|
-
- [Architecture: Three-Layer Rule Stack](#architecture-three-layer-rule-stack)
|
|
36
|
-
- [Layer 1: eslint-common (Foundation)](#layer-1-eslint-common-foundation)
|
|
37
|
-
- [Layer 2: eslint-node (Node.js Specialization)](#layer-2-eslint-node-nodejs-specialization)
|
|
38
|
-
- [Layer 3: dev-configs (IGNIS Overrides)](#layer-3-dev-configs-ignis-overrides)
|
|
39
|
-
- [Complete Rule Reference](#complete-rule-reference)
|
|
40
|
-
- [Rule Examples: Pass vs Fail](#rule-examples-pass-vs-fail)
|
|
41
|
-
- [Flat Config Format (ESLint v9+)](#flat-config-format-eslint-v9)
|
|
42
|
-
- [Adding Project-Specific Overrides](#adding-project-specific-overrides)
|
|
43
|
-
- [Adding File-Specific Overrides](#adding-file-specific-overrides)
|
|
44
|
-
- [Prettier Configuration](#prettier-configuration)
|
|
45
|
-
- [Settings with Before/After Examples](#settings-with-beforeafter-examples)
|
|
46
|
-
- [Overriding Prettier Settings](#overriding-prettier-settings)
|
|
47
|
-
- [.prettierignore Patterns](#prettierignore-patterns)
|
|
48
|
-
- [TypeScript Configuration](#typescript-configuration)
|
|
49
|
-
- [tsconfig.base.json](#tsconfigbasejson)
|
|
50
|
-
- [tsconfig.common.json](#tsconfigcommonjson)
|
|
51
|
-
- [Critical Flags for Decorator-Based DI](#critical-flags-for-decorator-based-di)
|
|
52
|
-
- [Consumption Pattern](#consumption-pattern)
|
|
53
|
-
- [Real-World Consumer Examples](#real-world-consumer-examples)
|
|
54
|
-
- [Setting Up a New Package](#setting-up-a-new-package)
|
|
55
|
-
- [Extending and Overriding](#extending-and-overriding)
|
|
56
|
-
- [IDE Integration](#ide-integration)
|
|
57
|
-
- [CI/CD Integration](#cicd-integration)
|
|
58
|
-
- [Troubleshooting](#troubleshooting)
|
|
59
|
-
- [Version Compatibility](#version-compatibility)
|
|
60
|
-
- [Comparison with Alternatives](#comparison-with-alternatives)
|
|
61
|
-
- [Package Exports](#package-exports)
|
|
62
|
-
- [Important Notes](#important-notes)
|
|
63
|
-
- [License](#license)
|
|
64
|
-
|
|
65
|
-
---
|
|
66
|
-
|
|
67
|
-
## Installation
|
|
68
|
-
|
|
69
|
-
```bash
|
|
70
|
-
bun add -d @venizia/dev-configs
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
Peer dependencies -- install only the tools you intend to use:
|
|
74
|
-
|
|
75
|
-
```bash
|
|
76
|
-
bun add -d eslint@^9.0.0 prettier@^3.0.0 typescript@^5.0.0
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
All peer dependencies are marked optional, so you only need to install the ones relevant to your workflow.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## Quick Start
|
|
84
|
-
|
|
85
|
-
Three lines per config file -- that is all any consuming package needs:
|
|
86
|
-
|
|
87
|
-
**ESLint** (`eslint.config.mjs`):
|
|
88
|
-
|
|
89
|
-
```javascript
|
|
90
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
91
|
-
|
|
92
|
-
export default eslintConfigs;
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
**Prettier** (`.prettierrc.mjs`):
|
|
96
|
-
|
|
97
|
-
```javascript
|
|
98
|
-
import { prettierConfigs } from '@venizia/dev-configs';
|
|
99
|
-
|
|
100
|
-
export default prettierConfigs;
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**TypeScript** (`tsconfig.json`):
|
|
104
|
-
|
|
105
|
-
```json
|
|
106
|
-
{
|
|
107
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
108
|
-
"compilerOptions": {
|
|
109
|
-
"outDir": "dist",
|
|
110
|
-
"rootDir": "src"
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## ESLint Configuration
|
|
118
|
-
|
|
119
|
-
The ESLint configuration uses the **flat config format** (ESLint v9+) and is exported as a `Linter.Config[]` array.
|
|
120
|
-
|
|
121
|
-
### Architecture: Three-Layer Rule Stack
|
|
122
|
-
|
|
123
|
-
The final ESLint configuration is built by composing three layers, each applied in order. Later layers override earlier ones:
|
|
124
|
-
|
|
125
|
-
```
|
|
126
|
-
Layer 1: @minimaltech/eslint-common (Foundation: JS recommended + Prettier + TS recommended + naming conventions)
|
|
127
|
-
|
|
|
128
|
-
Layer 2: @minimaltech/eslint-node (Node.js: LB4-derived rules + eslint-plugin-n + TS strictness adjustments)
|
|
129
|
-
|
|
|
130
|
-
Layer 3: @venizia/dev-configs (IGNIS: relax no-explicit-any + add unicorn + enforce curly braces)
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Each layer is a flat config array. The final export is the concatenation of all three, meaning rules defined later take precedence.
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
### Layer 1: eslint-common (Foundation)
|
|
138
|
-
|
|
139
|
-
Provided by `@minimaltech/eslint-common`. This layer establishes:
|
|
140
|
-
|
|
141
|
-
- **`@eslint/js` recommended** -- core JavaScript best-practice rules
|
|
142
|
-
- **`eslint-plugin-prettier/recommended`** -- runs Prettier as an ESLint rule and disables formatting rules that conflict with Prettier
|
|
143
|
-
- **`typescript-eslint` recommended** -- TypeScript-specific rules from the `@typescript-eslint` project
|
|
144
|
-
|
|
145
|
-
Key rules from this layer:
|
|
146
|
-
|
|
147
|
-
| Rule | Value | Purpose |
|
|
148
|
-
| :--- | :---- | :------ |
|
|
149
|
-
| `curly` | `["error", "all"]` | Braces required on all control flow |
|
|
150
|
-
| `prefer-const` | `"error"` | Must use `const` when variable is never reassigned |
|
|
151
|
-
| `no-restricted-imports` | Blocks `lodash` barrel import | Forces `import get from 'lodash/get'` for tree-shaking |
|
|
152
|
-
| `@typescript-eslint/no-explicit-any` | `"warn"` | Warns on `any` types (overridden later) |
|
|
153
|
-
| `@typescript-eslint/no-unused-vars` | `"warn"` | Warns on unused variables; `_` prefix suppresses |
|
|
154
|
-
| `@typescript-eslint/naming-convention` | Complex | Interfaces must have `I` prefix; type aliases must have `T` prefix or `Type`/`Like` suffix; booleans must have `is`/`has`/`should`/etc. prefix |
|
|
155
|
-
|
|
156
|
-
**File targeting:** Applies to `**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}`.
|
|
157
|
-
|
|
158
|
-
**Default ignores:** `node_modules/`, `*.d.ts`, `build/`, `dist/`, `release/`, `babel.config.*`, `.eslintrc.*`, `.prettierrc.*`, `eslint.config.*`.
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
### Layer 2: eslint-node (Node.js Specialization)
|
|
163
|
-
|
|
164
|
-
Provided by `@minimaltech/eslint-node`. Built on top of the common layer, this adds:
|
|
165
|
-
|
|
166
|
-
**LoopBack 4-derived rules (`lbRules`)** -- a comprehensive rule set originally from the LoopBack Next project, adapted for IGNIS:
|
|
167
|
-
|
|
168
|
-
| Rule | Value | Purpose |
|
|
169
|
-
| :--- | :---- | :------ |
|
|
170
|
-
| `prefer-const` | `"error"` | Enforce immutable bindings |
|
|
171
|
-
| `no-unused-labels` | `"error"` | Disallow unused labels |
|
|
172
|
-
| `no-new-wrappers` | `"error"` | Disallow `new String()`, `new Number()`, etc. |
|
|
173
|
-
| `no-throw-literal` | `"error"` | Must throw `Error` objects, not strings |
|
|
174
|
-
| `no-unused-expressions` | `"error"` | Disallow expression statements with no side effects |
|
|
175
|
-
| `no-var` | `"error"` | Disallow `var`; use `let`/`const` |
|
|
176
|
-
| `eqeqeq` | `["error", "smart"]` | Require `===`/`!==` except for `null` checks |
|
|
177
|
-
| `no-void` | `"error"` | Disallow `void` operator |
|
|
178
|
-
| `no-caller` | `"error"` | Disallow `arguments.caller`/`arguments.callee` |
|
|
179
|
-
| `no-shadow` | `"error"` | Disallow variable shadowing |
|
|
180
|
-
| `@typescript-eslint/no-explicit-any` | `"error"` | Error on `any` (overridden by dev-configs) |
|
|
181
|
-
| `@typescript-eslint/no-unused-vars` | `"error"` (all vars, no args) | Error on unused variables |
|
|
182
|
-
| `@typescript-eslint/await-thenable` | `"error"` | Disallow `await` on non-Promise values |
|
|
183
|
-
| `@typescript-eslint/no-floating-promises` | `"error"` | Must handle or return Promises |
|
|
184
|
-
| `@typescript-eslint/no-misused-promises` | `"error"` | Prevent passing Promises where not expected |
|
|
185
|
-
| `@typescript-eslint/prefer-optional-chain` | `"error"` | Prefer `a?.b` over `a && a.b` |
|
|
186
|
-
| `@typescript-eslint/prefer-nullish-coalescing` | `"error"` | Prefer `a ?? b` over `a \|\| b` |
|
|
187
|
-
| `@typescript-eslint/return-await` | `"error"` | Require `return await` in try/catch |
|
|
188
|
-
| `@typescript-eslint/prefer-for-of` | `"error"` | Prefer `for...of` over indexed `for` loops |
|
|
189
|
-
| `@typescript-eslint/unified-signatures` | `"error"` | Merge overloaded signatures when possible |
|
|
190
|
-
| `@typescript-eslint/no-use-before-define` | `"error"` | Disallow use before declaration |
|
|
191
|
-
| `@typescript-eslint/naming-convention` | Complex | Overrides Layer 1 naming; allows `camelCase`, `PascalCase`, `UPPER_CASE` for variables/properties; `PascalCase` for types; allows leading underscore for private/protected members; allows `Mixin` suffix on PascalCase functions |
|
|
192
|
-
|
|
193
|
-
**Node.js-specific rules** via `eslint-plugin-n`:
|
|
194
|
-
|
|
195
|
-
| Rule | Value | Purpose |
|
|
196
|
-
| :--- | :---- | :------ |
|
|
197
|
-
| `n/prefer-node-protocol` | `"error"` | Require `node:` prefix: `import fs from 'node:fs'` |
|
|
198
|
-
|
|
199
|
-
**Relaxations applied at this layer:**
|
|
200
|
-
|
|
201
|
-
| Rule | Value | Purpose |
|
|
202
|
-
| :--- | :---- | :------ |
|
|
203
|
-
| `@typescript-eslint/no-inferrable-types` | `"off"` | Allow explicit types on initialized variables |
|
|
204
|
-
| `@typescript-eslint/no-misused-promises` | `"off"` | Re-disabled (was enabled in lbRules, turned off here) |
|
|
205
|
-
| `@typescript-eslint/ban-ts-comment` | `"off"` | Allow `@ts-ignore`, `@ts-expect-error` |
|
|
206
|
-
| Various legacy TS rules | `"off"` | Old rules no longer relevant in modern TS-ESLint |
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
### Layer 3: dev-configs (IGNIS Overrides)
|
|
211
|
-
|
|
212
|
-
This is the final layer defined in `@venizia/dev-configs` itself:
|
|
213
|
-
|
|
214
|
-
```typescript
|
|
215
|
-
import mtLinterConfs from '@minimaltech/eslint-node';
|
|
216
|
-
import type { Linter } from 'eslint';
|
|
217
|
-
import unicorn from 'eslint-plugin-unicorn';
|
|
218
|
-
|
|
219
|
-
export const eslintConfigs: Linter.Config[] = [
|
|
220
|
-
...mtLinterConfs,
|
|
221
|
-
{
|
|
222
|
-
rules: {
|
|
223
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
224
|
-
},
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
plugins: { unicorn },
|
|
228
|
-
rules: {
|
|
229
|
-
curly: ['error', 'all'],
|
|
230
|
-
'unicorn/switch-case-braces': ['error', 'always'],
|
|
231
|
-
},
|
|
232
|
-
},
|
|
233
|
-
];
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
**Config object 1** -- overrides `@typescript-eslint/no-explicit-any` from `"error"` (Layer 2) to `"off"`:
|
|
237
|
-
|
|
238
|
-
| Rule | Value | Purpose |
|
|
239
|
-
| :--- | :---- | :------ |
|
|
240
|
-
| `@typescript-eslint/no-explicit-any` | `"off"` | Pragmatic decision. Framework-level code dealing with metadata, decorators, and dynamic DI resolution frequently requires `any`. |
|
|
241
|
-
|
|
242
|
-
**Config object 2** -- adds `eslint-plugin-unicorn` and enforces brace rules:
|
|
243
|
-
|
|
244
|
-
| Rule | Value | Purpose |
|
|
245
|
-
| :--- | :---- | :------ |
|
|
246
|
-
| `curly` | `["error", "all"]` | Re-enforces braces on all control flow (already set in Layer 1, repeated for clarity) |
|
|
247
|
-
| `unicorn/switch-case-braces` | `["error", "always"]` | Every `case`/`default` in a `switch` must be wrapped in braces |
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
### Complete Rule Reference
|
|
252
|
-
|
|
253
|
-
The following table shows every explicitly configured rule across all three layers, with the **final effective value** after all overrides:
|
|
254
|
-
|
|
255
|
-
| Rule | Final Value | Set By |
|
|
256
|
-
| :--- | :---------- | :----- |
|
|
257
|
-
| **Core JavaScript** | | |
|
|
258
|
-
| `curly` | `["error", "all"]` | Layer 1 + 3 |
|
|
259
|
-
| `prefer-const` | `"error"` | Layer 1 + 2 |
|
|
260
|
-
| `eqeqeq` | `["error", "smart"]` | Layer 2 |
|
|
261
|
-
| `no-var` | `"error"` | Layer 2 |
|
|
262
|
-
| `no-void` | `"error"` | Layer 2 |
|
|
263
|
-
| `no-caller` | `"error"` | Layer 2 |
|
|
264
|
-
| `no-shadow` | `"error"` | Layer 2 |
|
|
265
|
-
| `no-throw-literal` | `"error"` | Layer 2 |
|
|
266
|
-
| `no-new-wrappers` | `"error"` | Layer 2 |
|
|
267
|
-
| `no-unused-labels` | `"error"` | Layer 2 |
|
|
268
|
-
| `no-unused-expressions` | `"error"` | Layer 2 |
|
|
269
|
-
| `no-restricted-imports` | Blocks `lodash` barrel | Layer 1 |
|
|
270
|
-
| `no-console` | `"off"` | Layer 2 |
|
|
271
|
-
| `no-undef` | `"off"` | Layer 1 |
|
|
272
|
-
| `no-constant-condition` | `"off"` | Layer 1 |
|
|
273
|
-
| `no-mixed-operators` | `"off"` | Layer 2 |
|
|
274
|
-
| `no-inner-declarations` | `"off"` | Layer 2 |
|
|
275
|
-
| `no-dupe-class-members` | `"off"` | Layer 2 |
|
|
276
|
-
| `no-redeclare` | `"off"` | Layer 2 |
|
|
277
|
-
| `no-invalid-this` | `"off"` | Layer 2 |
|
|
278
|
-
| `no-return-await` | `"off"` | Layer 2 |
|
|
279
|
-
| `camelcase` | `"off"` | Layer 2 |
|
|
280
|
-
| `prefer-promise-reject-errors` | `"off"` | Layer 1 |
|
|
281
|
-
| **TypeScript** | | |
|
|
282
|
-
| `@typescript-eslint/no-explicit-any` | `"off"` | Layer 3 (override) |
|
|
283
|
-
| `@typescript-eslint/no-unused-vars` | `["error", ...]` | Layer 2 (override) |
|
|
284
|
-
| `@typescript-eslint/naming-convention` | Complex (see above) | Layer 2 (override) |
|
|
285
|
-
| `@typescript-eslint/await-thenable` | `"error"` | Layer 2 |
|
|
286
|
-
| `@typescript-eslint/no-floating-promises` | `"error"` | Layer 2 |
|
|
287
|
-
| `@typescript-eslint/prefer-optional-chain` | `"error"` | Layer 2 |
|
|
288
|
-
| `@typescript-eslint/prefer-nullish-coalescing` | `"error"` | Layer 2 |
|
|
289
|
-
| `@typescript-eslint/return-await` | `"error"` | Layer 2 |
|
|
290
|
-
| `@typescript-eslint/prefer-for-of` | `"error"` | Layer 2 |
|
|
291
|
-
| `@typescript-eslint/unified-signatures` | `"error"` | Layer 2 |
|
|
292
|
-
| `@typescript-eslint/no-use-before-define` | `"error"` | Layer 2 |
|
|
293
|
-
| `@typescript-eslint/no-non-null-asserted-optional-chain` | `"error"` | Layer 2 |
|
|
294
|
-
| `@typescript-eslint/adjacent-overload-signatures` | `"error"` | Layer 2 |
|
|
295
|
-
| `@typescript-eslint/no-misused-new` | `"error"` | Layer 2 |
|
|
296
|
-
| `@typescript-eslint/no-invalid-this` | `"error"` | Layer 2 |
|
|
297
|
-
| `@typescript-eslint/no-shadow` | `"error"` | Layer 2 |
|
|
298
|
-
| `@typescript-eslint/no-misused-promises` | `"off"` | Layer 2 (node override) |
|
|
299
|
-
| `@typescript-eslint/no-inferrable-types` | `"off"` | Layer 2 (node) |
|
|
300
|
-
| `@typescript-eslint/ban-ts-comment` | `"off"` | Layer 2 (node) |
|
|
301
|
-
| `@typescript-eslint/no-non-null-assertion` | `"off"` | Layer 2 |
|
|
302
|
-
| `@typescript-eslint/explicit-function-return-type` | `"off"` | Layer 1 + 2 |
|
|
303
|
-
| `@typescript-eslint/no-namespace` | `"off"` | Layer 2 |
|
|
304
|
-
| `@typescript-eslint/no-require-imports` | `"off"` | Layer 1 + 2 |
|
|
305
|
-
| `@typescript-eslint/no-empty-function` | `"off"` | Layer 2 |
|
|
306
|
-
| `@typescript-eslint/consistent-type-assertions` | `"off"` | Layer 2 |
|
|
307
|
-
| `@typescript-eslint/explicit-member-accessibility` | `"off"` | Layer 2 |
|
|
308
|
-
| `@typescript-eslint/no-empty-object-type` | `"off"` | Layer 1 |
|
|
309
|
-
| `@typescript-eslint/no-unsafe-function-type` | `"off"` | Layer 1 |
|
|
310
|
-
| `@typescript-eslint/no-extraneous-class` | `"off"` | Layer 1 |
|
|
311
|
-
| `@typescript-eslint/strict-boolean-expressions` | `"off"` | Layer 1 |
|
|
312
|
-
| **Node.js** | | |
|
|
313
|
-
| `n/prefer-node-protocol` | `"error"` | Layer 2 |
|
|
314
|
-
| **Unicorn** | | |
|
|
315
|
-
| `unicorn/switch-case-braces` | `["error", "always"]` | Layer 3 |
|
|
316
|
-
|
|
317
|
-
---
|
|
318
|
-
|
|
319
|
-
### Rule Examples: Pass vs Fail
|
|
320
|
-
|
|
321
|
-
#### `curly: ["error", "all"]` -- Braces Required on All Control Flow
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// PASSES -- braces on every branch
|
|
325
|
-
if (user.isAdmin) {
|
|
326
|
-
grantAccess();
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (count > 0) {
|
|
330
|
-
process();
|
|
331
|
-
} else {
|
|
332
|
-
skip();
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
for (const item of items) {
|
|
336
|
-
transform(item);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
while (hasNext()) {
|
|
340
|
-
advance();
|
|
341
|
-
}
|
|
342
|
-
```
|
|
343
|
-
|
|
344
|
-
```typescript
|
|
345
|
-
// FAILS -- missing braces
|
|
346
|
-
if (user.isAdmin) grantAccess(); // Error: Expected { after 'if' condition
|
|
347
|
-
|
|
348
|
-
if (count > 0) process(); // Error: Expected { after 'if' condition
|
|
349
|
-
else skip(); // Error: Expected { after 'else'
|
|
350
|
-
|
|
351
|
-
for (const item of items) transform(item); // Error: Expected { after 'for'
|
|
352
|
-
|
|
353
|
-
while (hasNext()) advance(); // Error: Expected { after 'while' condition
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
---
|
|
357
|
-
|
|
358
|
-
#### `@typescript-eslint/no-explicit-any: off` -- Why This Is Pragmatic
|
|
359
|
-
|
|
360
|
-
In framework-level code, `any` is sometimes unavoidable when dealing with decorator metadata, dynamic DI resolution, and generic container patterns:
|
|
361
|
-
|
|
362
|
-
```typescript
|
|
363
|
-
// These patterns are common in IGNIS and would be painful to type perfectly:
|
|
364
|
-
|
|
365
|
-
// Decorator factory that accepts any class
|
|
366
|
-
function controller(opts: { path: string }) {
|
|
367
|
-
return function (target: any) { // 'any' is acceptable here
|
|
368
|
-
MetadataRegistry.define({ target, key: 'controller', value: opts });
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// IoC container resolving unknown types at runtime
|
|
373
|
-
class Container {
|
|
374
|
-
get<T>({ key }: { key: string }): T {
|
|
375
|
-
const binding = this.bindings.get(key);
|
|
376
|
-
return binding?.getValue(this) as any; // Runtime type, compile-time unknown
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// Dynamic metadata inspection
|
|
381
|
-
const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', target) ?? [];
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
While `any` is turned off globally, individual packages can re-enable it:
|
|
385
|
-
|
|
386
|
-
```javascript
|
|
387
|
-
// eslint.config.mjs -- stricter for application code
|
|
388
|
-
export default [
|
|
389
|
-
...eslintConfigs,
|
|
390
|
-
{ rules: { '@typescript-eslint/no-explicit-any': 'error' } },
|
|
391
|
-
];
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
#### `unicorn/switch-case-braces: ["error", "always"]` -- Case Clause Braces
|
|
397
|
-
|
|
398
|
-
```typescript
|
|
399
|
-
// PASSES -- every case wrapped in braces
|
|
400
|
-
switch (action.type) {
|
|
401
|
-
case 'CREATE': {
|
|
402
|
-
const entity = buildEntity(action.payload);
|
|
403
|
-
await repository.create({ data: entity });
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
case 'UPDATE': {
|
|
407
|
-
const changes = diffEntity(action.payload);
|
|
408
|
-
await repository.updateById({ id: action.id, data: changes });
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
default: {
|
|
412
|
-
throw getError({ message: `Unknown action: ${action.type}`, statusCode: 400 });
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
```typescript
|
|
418
|
-
// FAILS -- case clauses without braces
|
|
419
|
-
switch (action.type) {
|
|
420
|
-
case 'CREATE': // Error: Missing braces around case clause
|
|
421
|
-
const entity = buildEntity(action.payload);
|
|
422
|
-
await repository.create({ data: entity });
|
|
423
|
-
break;
|
|
424
|
-
case 'UPDATE': // Error: Missing braces around case clause
|
|
425
|
-
const changes = diffEntity(action.payload); // 'changes' leaks into 'CREATE' scope!
|
|
426
|
-
await repository.updateById({ id: action.id, data: changes });
|
|
427
|
-
break;
|
|
428
|
-
default: // Error: Missing braces around case clause
|
|
429
|
-
throw getError({ message: `Unknown action: ${action.type}`, statusCode: 400 });
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
Without braces, `const entity` and `const changes` share the same lexical scope. This can cause `SyntaxError: Identifier already declared` or subtle bugs where variables from one case are visible in another.
|
|
434
|
-
|
|
435
|
-
---
|
|
436
|
-
|
|
437
|
-
#### `n/prefer-node-protocol: "error"` -- Require `node:` Prefix
|
|
438
|
-
|
|
439
|
-
```typescript
|
|
440
|
-
// PASSES
|
|
441
|
-
import fs from 'node:fs';
|
|
442
|
-
import path from 'node:path';
|
|
443
|
-
import { createServer } from 'node:http';
|
|
444
|
-
import { pipeline } from 'node:stream/promises';
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
```typescript
|
|
448
|
-
// FAILS
|
|
449
|
-
import fs from 'fs'; // Error: Prefer 'node:fs' over 'fs'
|
|
450
|
-
import path from 'path'; // Error: Prefer 'node:path' over 'path'
|
|
451
|
-
import { createServer } from 'http'; // Error: Prefer 'node:http' over 'http'
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
---
|
|
455
|
-
|
|
456
|
-
#### `eqeqeq: ["error", "smart"]` -- Strict Equality with Exceptions
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
// PASSES
|
|
460
|
-
if (user.role === 'admin') { /* ... */ }
|
|
461
|
-
if (count !== 0) { /* ... */ }
|
|
462
|
-
if (value == null) { /* ... */ } // 'smart' mode: allows == null (checks null AND undefined)
|
|
463
|
-
if (typeof x === 'string') { /* ... */ }
|
|
464
|
-
|
|
465
|
-
// FAILS
|
|
466
|
-
if (user.role == 'admin') { /* ... */ } // Error: Expected '===' but found '=='
|
|
467
|
-
if (count != 0) { /* ... */ } // Error: Expected '!==' but found '!='
|
|
468
|
-
```
|
|
469
|
-
|
|
470
|
-
---
|
|
471
|
-
|
|
472
|
-
### Flat Config Format (ESLint v9+)
|
|
473
|
-
|
|
474
|
-
ESLint v9 introduced the **flat config** format, replacing the legacy `.eslintrc` approach. Understanding the difference is important when working with `@venizia/dev-configs`:
|
|
475
|
-
|
|
476
|
-
**Flat config (ESLint v9+ -- what we use):**
|
|
477
|
-
|
|
478
|
-
```javascript
|
|
479
|
-
// eslint.config.mjs -- a simple array of config objects
|
|
480
|
-
export default [
|
|
481
|
-
{
|
|
482
|
-
// Config object 1: applies to all files
|
|
483
|
-
rules: { 'no-console': 'warn' },
|
|
484
|
-
},
|
|
485
|
-
{
|
|
486
|
-
// Config object 2: applies only to test files
|
|
487
|
-
files: ['**/*.test.ts'],
|
|
488
|
-
rules: { 'no-console': 'off' },
|
|
489
|
-
},
|
|
490
|
-
{
|
|
491
|
-
// Config object 3: ignore patterns
|
|
492
|
-
ignores: ['dist/**'],
|
|
493
|
-
},
|
|
494
|
-
];
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
Key differences from the legacy format:
|
|
498
|
-
|
|
499
|
-
| Aspect | Legacy `.eslintrc` | Flat Config |
|
|
500
|
-
| :----- | :----------------- | :---------- |
|
|
501
|
-
| File name | `.eslintrc.json`, `.eslintrc.js` | `eslint.config.mjs` |
|
|
502
|
-
| Structure | Single object with `extends`, `overrides` | Array of config objects |
|
|
503
|
-
| Plugin loading | `plugins: ['unicorn']` (string name) | `plugins: { unicorn: importedPlugin }` (object reference) |
|
|
504
|
-
| Inheritance | `extends: ['config-name']` | `...spread` array |
|
|
505
|
-
| File targeting | `overrides: [{ files, rules }]` | Each array element can have `files` |
|
|
506
|
-
| Ignore patterns | `.eslintignore` file | `ignores` key in a config object |
|
|
507
|
-
|
|
508
|
-
**Why flat config matters for `@venizia/dev-configs`:** The exported `eslintConfigs` is already a `Linter.Config[]` array. To extend it, you spread it into your own array and append additional config objects. No `extends` keyword, no magic resolution -- just arrays and objects.
|
|
509
|
-
|
|
510
|
-
---
|
|
511
|
-
|
|
512
|
-
### Adding Project-Specific Overrides
|
|
513
|
-
|
|
514
|
-
Since the config is a standard flat config array, you can append additional config objects after spreading:
|
|
515
|
-
|
|
516
|
-
```javascript
|
|
517
|
-
// eslint.config.mjs
|
|
518
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
519
|
-
|
|
520
|
-
export default [
|
|
521
|
-
...eslintConfigs,
|
|
522
|
-
{
|
|
523
|
-
rules: {
|
|
524
|
-
// Stricter rules for this specific package
|
|
525
|
-
'no-console': 'error',
|
|
526
|
-
'@typescript-eslint/no-explicit-any': 'error',
|
|
527
|
-
},
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
// Package-specific ignore patterns
|
|
531
|
-
ignores: ['generated/**', 'migrations/**'],
|
|
532
|
-
},
|
|
533
|
-
];
|
|
534
|
-
```
|
|
535
|
-
|
|
536
|
-
---
|
|
537
|
-
|
|
538
|
-
### Adding File-Specific Overrides
|
|
539
|
-
|
|
540
|
-
You can target specific file patterns with different rules:
|
|
541
|
-
|
|
542
|
-
```javascript
|
|
543
|
-
// eslint.config.mjs
|
|
544
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
545
|
-
|
|
546
|
-
export default [
|
|
547
|
-
...eslintConfigs,
|
|
548
|
-
|
|
549
|
-
// Relax rules for test files
|
|
550
|
-
{
|
|
551
|
-
files: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**/*.ts'],
|
|
552
|
-
rules: {
|
|
553
|
-
'@typescript-eslint/no-unused-vars': 'off',
|
|
554
|
-
'@typescript-eslint/no-floating-promises': 'off',
|
|
555
|
-
'no-unused-expressions': 'off',
|
|
556
|
-
},
|
|
557
|
-
},
|
|
558
|
-
|
|
559
|
-
// Stricter rules for controller files
|
|
560
|
-
{
|
|
561
|
-
files: ['**/controllers/**/*.ts'],
|
|
562
|
-
rules: {
|
|
563
|
-
'@typescript-eslint/no-explicit-any': 'warn',
|
|
564
|
-
},
|
|
565
|
-
},
|
|
566
|
-
|
|
567
|
-
// Allow require() in config files
|
|
568
|
-
{
|
|
569
|
-
files: ['*.config.mjs', '*.config.js'],
|
|
570
|
-
rules: {
|
|
571
|
-
'@typescript-eslint/no-require-imports': 'off',
|
|
572
|
-
},
|
|
573
|
-
},
|
|
574
|
-
];
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
---
|
|
578
|
-
|
|
579
|
-
## Prettier Configuration
|
|
580
|
-
|
|
581
|
-
All formatting settings are exported as a `Config` object from Prettier's type definitions.
|
|
582
|
-
|
|
583
|
-
### Source
|
|
584
|
-
|
|
585
|
-
```typescript
|
|
586
|
-
import type { Config } from 'prettier';
|
|
587
|
-
|
|
588
|
-
export const prettierConfigs: Config = {
|
|
589
|
-
bracketSpacing: true,
|
|
590
|
-
singleQuote: true,
|
|
591
|
-
printWidth: 100,
|
|
592
|
-
tabWidth: 2,
|
|
593
|
-
trailingComma: 'all',
|
|
594
|
-
arrowParens: 'avoid',
|
|
595
|
-
semi: true,
|
|
596
|
-
};
|
|
597
|
-
```
|
|
598
|
-
|
|
599
|
-
---
|
|
600
|
-
|
|
601
|
-
### Settings with Before/After Examples
|
|
602
|
-
|
|
603
|
-
#### `printWidth: 100` -- Maximum Line Length
|
|
604
|
-
|
|
605
|
-
Wider than the 80-character default. This reduces unnecessary line breaks in TypeScript generics, decorator signatures, and long import paths.
|
|
606
|
-
|
|
607
|
-
```typescript
|
|
608
|
-
// With printWidth: 80 (default) -- forces wrapping
|
|
609
|
-
const result = await this.userRepository.find({
|
|
610
|
-
filter: {
|
|
611
|
-
where: { isActive: true },
|
|
612
|
-
},
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
// With printWidth: 100 (our setting) -- fits on fewer lines
|
|
616
|
-
const result = await this.userRepository.find({ filter: { where: { isActive: true } } });
|
|
617
|
-
```
|
|
618
|
-
|
|
619
|
-
```typescript
|
|
620
|
-
// With printWidth: 80 -- long generic signatures break awkwardly
|
|
621
|
-
export class DefaultCRUDRepository<
|
|
622
|
-
T extends Record<string, unknown>,
|
|
623
|
-
> extends PersistableRepository<T> {
|
|
624
|
-
|
|
625
|
-
// With printWidth: 100 -- fits naturally
|
|
626
|
-
export class DefaultCRUDRepository<T extends Record<string, unknown>> extends PersistableRepository<T> {
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
---
|
|
630
|
-
|
|
631
|
-
#### `tabWidth: 2` -- Indentation
|
|
632
|
-
|
|
633
|
-
Two spaces per indentation level. Consistent with most Node.js/TypeScript projects.
|
|
634
|
-
|
|
635
|
-
```typescript
|
|
636
|
-
// tabWidth: 2
|
|
637
|
-
class UserController extends BaseController {
|
|
638
|
-
constructor(
|
|
639
|
-
@inject({ key: 'services.UserService' }) private userService: UserService,
|
|
640
|
-
) {
|
|
641
|
-
super();
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
```typescript
|
|
647
|
-
// tabWidth: 4 (NOT our setting)
|
|
648
|
-
class UserController extends BaseController {
|
|
649
|
-
constructor(
|
|
650
|
-
@inject({ key: 'services.UserService' }) private userService: UserService,
|
|
651
|
-
) {
|
|
652
|
-
super();
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
```
|
|
656
|
-
|
|
657
|
-
---
|
|
658
|
-
|
|
659
|
-
#### `singleQuote: true` -- Quote Style
|
|
660
|
-
|
|
661
|
-
Single quotes for string literals. Double quotes are still used in JSX attributes and when a string contains a single quote.
|
|
662
|
-
|
|
663
|
-
```typescript
|
|
664
|
-
// singleQuote: true (our setting)
|
|
665
|
-
import { controller } from '@venizia/ignis';
|
|
666
|
-
const name = 'hello';
|
|
667
|
-
const message = "it's a test"; // Double quotes when string contains single quote
|
|
668
|
-
|
|
669
|
-
// singleQuote: false (NOT our setting)
|
|
670
|
-
import { controller } from "@venizia/ignis";
|
|
671
|
-
const name = "hello";
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
---
|
|
675
|
-
|
|
676
|
-
#### `semi: true` -- Semicolons
|
|
677
|
-
|
|
678
|
-
Always include semicolons at the end of statements. Prevents ASI (Automatic Semicolon Insertion) edge cases.
|
|
679
|
-
|
|
680
|
-
```typescript
|
|
681
|
-
// semi: true (our setting)
|
|
682
|
-
const port = 3000;
|
|
683
|
-
const host = 'localhost';
|
|
684
|
-
app.start({ port, host });
|
|
685
|
-
|
|
686
|
-
// semi: false (NOT our setting)
|
|
687
|
-
const port = 3000
|
|
688
|
-
const host = 'localhost'
|
|
689
|
-
app.start({ port, host })
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
---
|
|
693
|
-
|
|
694
|
-
#### `trailingComma: "all"` -- Trailing Commas
|
|
695
|
-
|
|
696
|
-
Trailing commas in objects, arrays, function parameters, and type parameters wherever valid in ES5+. This produces cleaner git diffs because adding a new item only changes one line instead of two.
|
|
697
|
-
|
|
698
|
-
```typescript
|
|
699
|
-
// trailingComma: "all" (our setting)
|
|
700
|
-
const config = {
|
|
701
|
-
host: 'localhost',
|
|
702
|
-
port: 3000,
|
|
703
|
-
debug: true, // <-- trailing comma
|
|
704
|
-
};
|
|
705
|
-
|
|
706
|
-
function createUser(
|
|
707
|
-
name: string,
|
|
708
|
-
email: string,
|
|
709
|
-
role: string, // <-- trailing comma
|
|
710
|
-
) {
|
|
711
|
-
// ...
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
const items = [
|
|
715
|
-
'controllers',
|
|
716
|
-
'services',
|
|
717
|
-
'repositories', // <-- trailing comma
|
|
718
|
-
];
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
```typescript
|
|
722
|
-
// trailingComma: "none" (NOT our setting)
|
|
723
|
-
const config = {
|
|
724
|
-
host: 'localhost',
|
|
725
|
-
port: 3000,
|
|
726
|
-
debug: true // no trailing comma -- adding a new property changes this line too
|
|
727
|
-
};
|
|
728
|
-
```
|
|
729
|
-
|
|
730
|
-
**Git diff advantage:**
|
|
731
|
-
|
|
732
|
-
```diff
|
|
733
|
-
# With trailing commas -- adding a property is a 1-line diff
|
|
734
|
-
const config = {
|
|
735
|
-
host: 'localhost',
|
|
736
|
-
port: 3000,
|
|
737
|
-
debug: true,
|
|
738
|
-
+ verbose: false,
|
|
739
|
-
};
|
|
740
|
-
|
|
741
|
-
# Without trailing commas -- adding a property is a 2-line diff
|
|
742
|
-
const config = {
|
|
743
|
-
host: 'localhost',
|
|
744
|
-
port: 3000,
|
|
745
|
-
- debug: true
|
|
746
|
-
+ debug: true,
|
|
747
|
-
+ verbose: false
|
|
748
|
-
};
|
|
749
|
-
```
|
|
750
|
-
|
|
751
|
-
---
|
|
752
|
-
|
|
753
|
-
#### `arrowParens: "avoid"` -- Arrow Function Parentheses
|
|
754
|
-
|
|
755
|
-
Omit parentheses around a sole arrow function parameter when possible.
|
|
756
|
-
|
|
757
|
-
```typescript
|
|
758
|
-
// arrowParens: "avoid" (our setting)
|
|
759
|
-
const double = x => x * 2;
|
|
760
|
-
const greet = name => `Hello, ${name}`;
|
|
761
|
-
items.map(item => item.id);
|
|
762
|
-
items.filter(item => item.isActive);
|
|
763
|
-
|
|
764
|
-
// Multiple parameters always need parens (regardless of setting)
|
|
765
|
-
const add = (a, b) => a + b;
|
|
766
|
-
|
|
767
|
-
// Type annotations always need parens (regardless of setting)
|
|
768
|
-
const parse = (input: string) => JSON.parse(input);
|
|
769
|
-
```
|
|
770
|
-
|
|
771
|
-
```typescript
|
|
772
|
-
// arrowParens: "always" (NOT our setting)
|
|
773
|
-
const double = (x) => x * 2;
|
|
774
|
-
const greet = (name) => `Hello, ${name}`;
|
|
775
|
-
items.map((item) => item.id);
|
|
776
|
-
items.filter((item) => item.isActive);
|
|
777
|
-
```
|
|
778
|
-
|
|
779
|
-
---
|
|
780
|
-
|
|
781
|
-
#### `bracketSpacing: true` -- Object Literal Spacing
|
|
782
|
-
|
|
783
|
-
Spaces inside object literal braces.
|
|
784
|
-
|
|
785
|
-
```typescript
|
|
786
|
-
// bracketSpacing: true (our setting)
|
|
787
|
-
const point = { x: 10, y: 20 };
|
|
788
|
-
const { name, age } = user;
|
|
789
|
-
import { controller, inject } from '@venizia/ignis';
|
|
790
|
-
|
|
791
|
-
// bracketSpacing: false (NOT our setting)
|
|
792
|
-
const point = {x: 10, y: 20};
|
|
793
|
-
const {name, age} = user;
|
|
794
|
-
import {controller, inject} from '@venizia/ignis';
|
|
795
|
-
```
|
|
796
|
-
|
|
797
|
-
---
|
|
798
|
-
|
|
799
|
-
### Overriding Prettier Settings
|
|
800
|
-
|
|
801
|
-
Import and spread to override specific settings:
|
|
802
|
-
|
|
803
|
-
```javascript
|
|
804
|
-
// .prettierrc.mjs
|
|
805
|
-
import { prettierConfigs } from '@venizia/dev-configs';
|
|
806
|
-
|
|
807
|
-
export default {
|
|
808
|
-
...prettierConfigs,
|
|
809
|
-
printWidth: 120, // Wider lines for this project
|
|
810
|
-
arrowParens: 'always', // Always wrap arrow params
|
|
811
|
-
};
|
|
812
|
-
```
|
|
813
|
-
|
|
814
|
-
---
|
|
815
|
-
|
|
816
|
-
### .prettierignore Patterns
|
|
817
|
-
|
|
818
|
-
Prettier does not have a config for ignore patterns in the shared config object. Create a `.prettierignore` file in your package root:
|
|
819
|
-
|
|
820
|
-
```
|
|
821
|
-
# Build output
|
|
822
|
-
dist/
|
|
823
|
-
build/
|
|
824
|
-
coverage/
|
|
825
|
-
|
|
826
|
-
# Dependencies
|
|
827
|
-
node_modules/
|
|
828
|
-
|
|
829
|
-
# Generated files
|
|
830
|
-
*.tsbuildinfo
|
|
831
|
-
*.generated.ts
|
|
832
|
-
|
|
833
|
-
# Lock files
|
|
834
|
-
bun.lockb
|
|
835
|
-
|
|
836
|
-
# Assets
|
|
837
|
-
*.min.js
|
|
838
|
-
*.min.css
|
|
839
|
-
```
|
|
840
|
-
|
|
841
|
-
---
|
|
842
|
-
|
|
843
|
-
## TypeScript Configuration
|
|
844
|
-
|
|
845
|
-
Two TypeScript config files are provided via package exports. They are consumed through the `"extends"` field in `tsconfig.json`.
|
|
846
|
-
|
|
847
|
-
### tsconfig.base.json
|
|
848
|
-
|
|
849
|
-
The comprehensive base configuration containing all compiler options. Located at `tsconfig/tsconfig.base.json` and exported as `@venizia/dev-configs/tsconfig.base.json`.
|
|
850
|
-
|
|
851
|
-
```json
|
|
852
|
-
{
|
|
853
|
-
"compilerOptions": {
|
|
854
|
-
"target": "ES2022",
|
|
855
|
-
"lib": ["ES2022"],
|
|
856
|
-
"experimentalDecorators": true,
|
|
857
|
-
"emitDecoratorMetadata": true,
|
|
858
|
-
"useDefineForClassFields": false,
|
|
859
|
-
|
|
860
|
-
"module": "Node16",
|
|
861
|
-
"moduleResolution": "node16",
|
|
862
|
-
"resolveJsonModule": true,
|
|
863
|
-
"allowSyntheticDefaultImports": true,
|
|
864
|
-
"esModuleInterop": true,
|
|
865
|
-
"isolatedModules": false,
|
|
866
|
-
"forceConsistentCasingInFileNames": true,
|
|
867
|
-
|
|
868
|
-
"declaration": true,
|
|
869
|
-
"declarationMap": true,
|
|
870
|
-
"sourceMap": true,
|
|
871
|
-
"removeComments": false,
|
|
872
|
-
"importHelpers": false,
|
|
873
|
-
"downlevelIteration": true,
|
|
874
|
-
"preserveConstEnums": true,
|
|
875
|
-
|
|
876
|
-
"strict": true,
|
|
877
|
-
"noImplicitAny": false,
|
|
878
|
-
"strictNullChecks": true,
|
|
879
|
-
"strictFunctionTypes": true,
|
|
880
|
-
"strictBindCallApply": true,
|
|
881
|
-
"strictPropertyInitialization": false,
|
|
882
|
-
"noImplicitThis": true,
|
|
883
|
-
"useUnknownInCatchVariables": false,
|
|
884
|
-
"alwaysStrict": true,
|
|
885
|
-
|
|
886
|
-
"noUnusedLocals": true,
|
|
887
|
-
"noUnusedParameters": true,
|
|
888
|
-
"noImplicitReturns": false,
|
|
889
|
-
"noFallthroughCasesInSwitch": true,
|
|
890
|
-
"noUncheckedIndexedAccess": false,
|
|
891
|
-
"noImplicitOverride": true,
|
|
892
|
-
"allowUnusedLabels": false,
|
|
893
|
-
"allowUnreachableCode": false,
|
|
894
|
-
|
|
895
|
-
"incremental": true,
|
|
896
|
-
"skipLibCheck": true,
|
|
897
|
-
"noEmitOnError": true
|
|
898
|
-
},
|
|
899
|
-
"exclude": [
|
|
900
|
-
"node_modules",
|
|
901
|
-
"dist",
|
|
902
|
-
"build",
|
|
903
|
-
"coverage",
|
|
904
|
-
"**/*.spec.ts",
|
|
905
|
-
"**/*.test.ts",
|
|
906
|
-
"**/__tests__/**"
|
|
907
|
-
]
|
|
908
|
-
}
|
|
909
|
-
```
|
|
910
|
-
|
|
911
|
-
---
|
|
912
|
-
|
|
913
|
-
#### Compiler Options Reference
|
|
914
|
-
|
|
915
|
-
**Language and Environment**
|
|
916
|
-
|
|
917
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
918
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
919
|
-
| `target` | `ES2022` | Sets the JavaScript version for emit output. | ES2022 provides native `class`, `async/await`, `top-level await`, private class fields, and `Array.at()` without downleveling. Bun and modern Node.js fully support ES2022. | Lowering to ES5/ES6 introduces unnecessary transpilation overhead. Raising to ESNext may use features not yet stable in all runtimes. |
|
|
920
|
-
| `lib` | `["ES2022"]` | Includes type definitions for ES2022 standard library APIs (`structuredClone`, `Array.at()`, `Object.hasOwn()`, `Error.cause`, etc.). | Matches the `target` to ensure type-checked APIs match the emitted code. | Removing this causes TypeScript to not recognize modern APIs like `structuredClone`. Adding `"DOM"` would introduce browser-specific types (e.g., `window`, `document`) that do not exist in Node.js/Bun. |
|
|
921
|
-
| `experimentalDecorators` | `true` | **CRITICAL.** Enables the legacy/experimental decorator syntax (`@decorator`) used by IGNIS for DI, controllers, models, and repositories. | See [Critical Flags](#critical-flags-for-decorator-based-di). | All IGNIS decorators fail to compile. |
|
|
922
|
-
| `emitDecoratorMetadata` | `true` | **CRITICAL.** Emits design-time type metadata via `Reflect.metadata()`. | See [Critical Flags](#critical-flags-for-decorator-based-di). | DI container cannot resolve constructor parameter types. |
|
|
923
|
-
| `useDefineForClassFields` | `false` | **CRITICAL.** Uses assignment semantics for class fields instead of `Object.defineProperty()`. | See [Critical Flags](#critical-flags-for-decorator-based-di). | Decorator-based property injection is overwritten by defineProperty. |
|
|
924
|
-
|
|
925
|
-
**Modules**
|
|
926
|
-
|
|
927
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
928
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
929
|
-
| `module` | `Node16` | Sets the module system for emitted code. | Node16 provides full ESM and CJS interop with `package.json` `"type"` field support. Matches the target Node.js/Bun environment. | Changing to `CommonJS` loses ESM support. Changing to `ESNext` loses CJS interop. |
|
|
930
|
-
| `moduleResolution` | `node16` | Controls how TypeScript resolves `import` paths to files. | Supports `package.json` `"exports"` field, conditional exports, and the `#imports` private imports pattern. | Changing to `node` (legacy) ignores `"exports"` field in `package.json`, causing resolution failures for modern packages. |
|
|
931
|
-
| `resolveJsonModule` | `true` | Allows importing `.json` files with full type inference. | Used for importing `package.json` and configuration files. | `import pkg from './package.json'` stops working. |
|
|
932
|
-
| `allowSyntheticDefaultImports` | `true` | Allows `import foo from 'module'` even when the module has no explicit `default` export. | Many CJS packages (e.g., `express`, `lodash`) do not have default exports but are commonly imported this way. | `import _ from 'lodash/get'` and similar patterns produce type errors. |
|
|
933
|
-
| `esModuleInterop` | `true` | Emits `__importDefault` and `__importStar` helpers for correct CJS/ESM interop at runtime. | Without this, `import` of CJS modules may resolve to `{ default: module }` instead of `module` directly. | Default imports from CJS modules break at runtime (the module object itself becomes `{ default: ... }`). |
|
|
934
|
-
| `isolatedModules` | `false` | When `true`, restricts features that require cross-file analysis (const enums, namespace merging). | IGNIS uses `const enum` for binding scopes and namespace merging for type augmentation. These features require cross-file compilation. | Enabling this would disallow `const enum` usage in `@venizia/ignis-inversion` and related packages. |
|
|
935
|
-
| `forceConsistentCasingInFileNames` | `true` | Errors on imports that do not match the file system's actual casing. | Prevents cross-platform bugs where `import './UserService'` works on macOS (case-insensitive) but fails on Linux (case-sensitive). | Disabling this allows case mismatches that cause runtime `MODULE_NOT_FOUND` errors on Linux CI servers. |
|
|
936
|
-
|
|
937
|
-
**Emit**
|
|
938
|
-
|
|
939
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
940
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
941
|
-
| `declaration` | `true` | Generates `.d.ts` type declaration files alongside JavaScript output. | Required for packages consumed as dependencies so that consumers get type information. | Downstream packages lose all type information and IntelliSense. |
|
|
942
|
-
| `declarationMap` | `true` | Generates `.d.ts.map` files mapping declarations back to original `.ts` source. | Enables "Go to Definition" in IDEs to navigate to original TypeScript source instead of the `.d.ts` file. | IDE "Go to Definition" lands on `.d.ts` stubs instead of real source code. |
|
|
943
|
-
| `sourceMap` | `true` | Generates `.js.map` files for debugging. | Allows debuggers and stack traces to show original TypeScript source locations instead of compiled JavaScript. | Stack traces and breakpoints reference compiled JS line numbers, making debugging harder. |
|
|
944
|
-
| `removeComments` | `false` | Preserves comments in output files. | Keeps JSDoc comments in `.d.ts` files, which IDEs display as hover documentation. | Hover documentation in IDEs for framework APIs disappears. |
|
|
945
|
-
| `importHelpers` | `false` | When `true`, imports shared helper functions from `tslib` instead of inlining them per file. | Keeps each file self-contained without requiring `tslib` as a dependency. With `target: ES2022`, very few helpers are needed anyway. | Enabling this requires adding `tslib` to dependencies. |
|
|
946
|
-
| `downlevelIteration` | `true` | Full support for iteration protocols on `for...of`, spread (`...`), and destructuring when targeting older runtimes. | Ensures `for (const x of generator())` works correctly with custom iterables. With ES2022 target, this has minimal runtime cost. | Spread and destructuring on non-Array iterables (Map, Set, generators) may produce incorrect code. |
|
|
947
|
-
| `preserveConstEnums` | `true` | Keeps `const enum` declarations in output instead of inlining values. | Required for `.d.ts` file compatibility -- inlined enums cannot be re-exported in declaration files. | Consumers of the package cannot reference `const enum` values from their own code. |
|
|
948
|
-
| `noEmitOnError` | `true` | Prevents emitting output files if any type errors exist. | Prevents shipping builds with type errors. A broken build should fail loudly. | Build output may contain code with type errors, leading to runtime failures that should have been caught at compile time. |
|
|
949
|
-
|
|
950
|
-
**Type Checking -- Strict Mode (Balanced for Production)**
|
|
951
|
-
|
|
952
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
953
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
954
|
-
| `strict` | `true` | Enables all strict type-checking options as a baseline. Individual overrides below relax specific checks. | Maximum type safety as the default, with targeted relaxations for framework patterns. | Disabling this removes all strict checks at once, dramatically reducing type safety. |
|
|
955
|
-
| `noImplicitAny` | `false` | Allows variables and parameters to implicitly have the `any` type when TypeScript cannot infer a more specific type. | Pragmatic choice. Framework code dealing with decorator metadata (`Reflect.getMetadata`) and dynamic DI patterns frequently encounters values with no inferrable type. | Enabling this would require explicit type annotations on every decorator metadata access and dynamic container resolution, adding significant boilerplate to framework internals. |
|
|
956
|
-
| `strictNullChecks` | `true` | Makes `null` and `undefined` their own distinct types rather than assignable to everything. | Prevents the most common class of runtime errors (`Cannot read property of null/undefined`). | Disabling this allows `null` to be assigned anywhere, removing compile-time null safety. |
|
|
957
|
-
| `strictFunctionTypes` | `true` | Enforces contravariant function parameter type checking. | Catches type-unsafe function assignments like passing `(animal: Dog) => void` where `(animal: Animal) => void` is expected. | Disabling this allows unsound function type assignments that can cause runtime type errors. |
|
|
958
|
-
| `strictBindCallApply` | `true` | Enables strict type checking on `bind`, `call`, and `apply` methods. | Catches incorrect argument types when using `.call()` and `.apply()`. | Disabling this allows `fn.call(null, wrongArgType)` to pass type checking. |
|
|
959
|
-
| `strictPropertyInitialization` | `false` | When `true`, requires all class properties to be initialized in the constructor or with a default value. | **Disabled for DI.** Properties injected via `@inject` decorators are set by the IoC container, not in the constructor body. Enabling this would require `!` (definite assignment assertion) on every injected property. | Enabling this would require adding `!` to every `@inject`-decorated property: `@inject({ key }) private service!: Service`. |
|
|
960
|
-
| `noImplicitThis` | `true` | Errors when `this` has an implicit `any` type. | Catches bugs where `this` is used in a context where its type is ambiguous (e.g., standalone functions passed as callbacks). | Disabling this allows `this` to silently become `any` in ambiguous contexts. |
|
|
961
|
-
| `useUnknownInCatchVariables` | `false` | When `true`, types `catch` clause variables as `unknown` instead of `any`. | Simplifies error handling. Framework code frequently catches errors and accesses `.message`, `.statusCode`, etc. without narrowing. | Enabling this would require `if (error instanceof Error)` guards in every `catch` block throughout the codebase. |
|
|
962
|
-
| `alwaysStrict` | `true` | Emits `"use strict"` directive in every output file. | Ensures all generated JavaScript runs in strict mode, which prevents common JavaScript pitfalls. | Disabling this allows sloppy mode, which permits silent errors and behaviors like implicit global variable creation. |
|
|
963
|
-
|
|
964
|
-
**Additional Checks**
|
|
965
|
-
|
|
966
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
967
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
968
|
-
| `noUnusedLocals` | `true` | Errors on declared but unused local variables. | Keeps code clean and prevents dead code accumulation. | Disabling this allows unused variables to accumulate silently. |
|
|
969
|
-
| `noUnusedParameters` | `true` | Errors on declared but unused function parameters. Prefix with `_` to suppress. | Catches dead parameters that may indicate incomplete refactoring. The `_` prefix convention explicitly marks intentionally unused params. | Disabling this allows function signatures to diverge from implementation. |
|
|
970
|
-
| `noImplicitReturns` | `false` | When `true`, errors when not all code paths return a value. | Disabled because many methods intentionally return `void` from some branches (e.g., early return guards). | Enabling this would require explicit `return undefined` in many guard-clause patterns. |
|
|
971
|
-
| `noFallthroughCasesInSwitch` | `true` | Errors on `switch` cases that fall through without `break` or `return`. | Prevents accidental fallthrough bugs. Intentional fallthrough must use `// falls through` comment or empty case body. | Disabling this allows silent fallthrough, a common source of bugs. |
|
|
972
|
-
| `noUncheckedIndexedAccess` | `false` | When `true`, adds `undefined` to the type of index signature access (`obj[key]` becomes `T | undefined`). | Disabled because framework-level dynamic access patterns (binding maps, metadata registries) would require excessive null checking. | Enabling this would require null guards on every `container.bindings.get(key)`, `metadata[prop]`, etc. |
|
|
973
|
-
| `noImplicitOverride` | `true` | Requires the `override` keyword when overriding base class methods. | Makes the inheritance chain explicit. Prevents accidentally overriding a base class method and catches renames in base classes. | Disabling this allows silent overrides that may break when the base class changes. |
|
|
974
|
-
| `allowUnusedLabels` | `false` | Errors on declared but unused labels. | Unused labels are almost always mistakes. | Disabling this allows dead labels to accumulate. |
|
|
975
|
-
| `allowUnreachableCode` | `false` | Errors on unreachable code (code after `return`, `throw`, etc.). | Dead code indicates logic errors or incomplete refactoring. | Disabling this allows dead code to accumulate silently. |
|
|
976
|
-
|
|
977
|
-
**Performance**
|
|
978
|
-
|
|
979
|
-
| Option | Value | What It Does | Why This Value | What Breaks If Changed |
|
|
980
|
-
| :----- | :---- | :----------- | :------------- | :--------------------- |
|
|
981
|
-
| `incremental` | `true` | Stores build state in `.tsbuildinfo` files and only recompiles changed files. | Significantly speeds up rebuilds in the monorepo. First build is the same speed; subsequent builds only process changed files. | Disabling this forces full recompilation on every build, which is noticeably slower in a 50k+ LOC monorepo. |
|
|
982
|
-
| `skipLibCheck` | `true` | Skips type checking of `.d.ts` files from `node_modules`. | Dramatically reduces build times. Type errors in third-party `.d.ts` files are not actionable anyway. | Disabling this adds seconds to every build for no practical benefit, and may surface false positives from poorly-typed third-party packages. |
|
|
983
|
-
|
|
984
|
-
**Default Excludes**
|
|
985
|
-
|
|
986
|
-
The base config excludes the following from compilation:
|
|
987
|
-
|
|
988
|
-
- `node_modules` -- third-party dependencies
|
|
989
|
-
- `dist` -- build output directory
|
|
990
|
-
- `build` -- alternative build output directory
|
|
991
|
-
- `coverage` -- test coverage reports
|
|
992
|
-
- `**/*.spec.ts`, `**/*.test.ts`, `**/__tests__/**` -- test files (compiled separately by test runners)
|
|
993
|
-
|
|
994
|
-
---
|
|
995
|
-
|
|
996
|
-
### tsconfig.common.json
|
|
997
|
-
|
|
998
|
-
The consumer-facing configuration that most packages should extend. Located at `tsconfig/tsconfig.common.json` and exported as `@venizia/dev-configs/tsconfig.common.json`.
|
|
999
|
-
|
|
1000
|
-
```json
|
|
1001
|
-
{
|
|
1002
|
-
"extends": "./tsconfig.base.json",
|
|
1003
|
-
"compilerOptions": {
|
|
1004
|
-
"target": "ES2022",
|
|
1005
|
-
"module": "nodenext",
|
|
1006
|
-
"moduleResolution": "nodenext"
|
|
1007
|
-
},
|
|
1008
|
-
"ts-node": {
|
|
1009
|
-
"transpileOnly": true
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
```
|
|
1013
|
-
|
|
1014
|
-
This config extends `tsconfig.base.json` and makes two adjustments:
|
|
1015
|
-
|
|
1016
|
-
| Override | Base Value | Common Value | Rationale |
|
|
1017
|
-
| :------- | :--------- | :----------- | :-------- |
|
|
1018
|
-
| `module` | `Node16` | `nodenext` | Uses the latest Node.js module resolution. `nodenext` tracks the most current Node.js behavior and will adopt future changes automatically. While `Node16` is frozen to Node 16 semantics, `nodenext` evolves with the Node.js release cycle. |
|
|
1019
|
-
| `moduleResolution` | `node16` | `nodenext` | Matching module resolution strategy for `nodenext` module mode. Must always match the `module` setting. |
|
|
1020
|
-
|
|
1021
|
-
**The `ts-node` section:**
|
|
1022
|
-
|
|
1023
|
-
```json
|
|
1024
|
-
{
|
|
1025
|
-
"ts-node": {
|
|
1026
|
-
"transpileOnly": true
|
|
1027
|
-
}
|
|
1028
|
-
}
|
|
1029
|
-
```
|
|
1030
|
-
|
|
1031
|
-
This configures `ts-node` (when used during development) to run in `transpileOnly` mode:
|
|
1032
|
-
|
|
1033
|
-
- **What it does:** Skips type checking at runtime, only transpiling TypeScript to JavaScript.
|
|
1034
|
-
- **Why:** Type checking at runtime via `ts-node` is slow and redundant -- the IDE and `tsc` build step already catch type errors. `transpileOnly` makes `ts-node` execution near-instant.
|
|
1035
|
-
- **When it matters:** When running scripts directly with `ts-node`, or when tools like Drizzle Kit load TypeScript config files (e.g., `drizzle-kit push --config=src/migration.ts`).
|
|
1036
|
-
|
|
1037
|
-
**Use `tsconfig.common.json` unless you have a specific reason to extend `tsconfig.base.json` directly.**
|
|
1038
|
-
|
|
1039
|
-
---
|
|
1040
|
-
|
|
1041
|
-
### Critical Flags for Decorator-Based DI
|
|
1042
|
-
|
|
1043
|
-
Three TypeScript compiler options are **absolutely essential** for the IGNIS decorator-based dependency injection system to function. Changing any of these will break the entire framework.
|
|
1044
|
-
|
|
1045
|
-
#### `experimentalDecorators: true`
|
|
1046
|
-
|
|
1047
|
-
Enables the TypeScript experimental decorator syntax used throughout IGNIS:
|
|
1048
|
-
|
|
1049
|
-
```typescript
|
|
1050
|
-
@controller({ path: '/users' })
|
|
1051
|
-
export class UserController extends BaseController {
|
|
1052
|
-
constructor(
|
|
1053
|
-
@inject({ key: 'services.UserService' }) private userService: UserService,
|
|
1054
|
-
) {
|
|
1055
|
-
super();
|
|
1056
|
-
}
|
|
1057
|
-
}
|
|
1058
|
-
```
|
|
1059
|
-
|
|
1060
|
-
Without this flag, TypeScript will emit a compilation error on every decorator:
|
|
1061
|
-
|
|
1062
|
-
```
|
|
1063
|
-
error TS1219: Experimental support for decorators is a feature that is subject
|
|
1064
|
-
to change in a future release. Set the 'experimentalDecorators' option in your
|
|
1065
|
-
'tsconfig' or 'jsconfig' to remove this error.
|
|
1066
|
-
```
|
|
1067
|
-
|
|
1068
|
-
The following decorators all require this flag: `@controller`, `@inject`, `@injectable`, `@repository`, `@datasource`, `@service`, `@model`, `@get`, `@post`, `@put`, `@patch`, `@del`, `@api`.
|
|
1069
|
-
|
|
1070
|
-
**Note on TC39 decorators:** TypeScript 5.0+ supports the new TC39 stage 3 decorator proposal (without `experimentalDecorators`). However, the TC39 decorators do NOT support `emitDecoratorMetadata`, which IGNIS relies on for automatic DI resolution. IGNIS intentionally uses the legacy/experimental decorator syntax for this reason.
|
|
1071
|
-
|
|
1072
|
-
---
|
|
1073
|
-
|
|
1074
|
-
#### `emitDecoratorMetadata: true`
|
|
1075
|
-
|
|
1076
|
-
Instructs TypeScript to emit design-time type information as metadata using `Reflect.metadata()`. This is what allows the IoC container (`@venizia/ignis-inversion`) to inspect constructor parameter types at runtime and automatically resolve dependencies.
|
|
1077
|
-
|
|
1078
|
-
When this flag is enabled, TypeScript emits additional metadata for decorated classes:
|
|
1079
|
-
|
|
1080
|
-
```typescript
|
|
1081
|
-
// Source code:
|
|
1082
|
-
@injectable()
|
|
1083
|
-
class UserService {
|
|
1084
|
-
constructor(
|
|
1085
|
-
@inject({ key: 'repositories.UserRepository' }) private repo: UserRepository,
|
|
1086
|
-
) {}
|
|
1087
|
-
}
|
|
1088
|
-
|
|
1089
|
-
// TypeScript emits (simplified):
|
|
1090
|
-
UserService = __decorate([
|
|
1091
|
-
injectable(),
|
|
1092
|
-
__metadata("design:paramtypes", [UserRepository]) // <-- This is emitDecoratorMetadata
|
|
1093
|
-
], UserService);
|
|
1094
|
-
```
|
|
1095
|
-
|
|
1096
|
-
The `design:paramtypes` metadata tells the DI container: "The first constructor parameter should be of type `UserRepository`." The container uses this to:
|
|
1097
|
-
|
|
1098
|
-
1. Look up the binding for `UserRepository` in its registry
|
|
1099
|
-
2. Instantiate or retrieve the cached `UserRepository` instance
|
|
1100
|
-
3. Pass it as the first constructor argument
|
|
1101
|
-
|
|
1102
|
-
Without this flag:
|
|
1103
|
-
|
|
1104
|
-
- `Reflect.getMetadata('design:paramtypes', UserService)` returns `undefined`
|
|
1105
|
-
- The DI container cannot determine what types the constructor expects
|
|
1106
|
-
- All automatic constructor injection silently fails
|
|
1107
|
-
- `@inject` decorators lose their type-resolution capability
|
|
1108
|
-
|
|
1109
|
-
---
|
|
1110
|
-
|
|
1111
|
-
#### `useDefineForClassFields: false`
|
|
1112
|
-
|
|
1113
|
-
Controls how TypeScript initializes class fields. This flag has a subtle but critical interaction with decorators.
|
|
1114
|
-
|
|
1115
|
-
**The problem with `useDefineForClassFields: true` (default for ES2022+ targets):**
|
|
1116
|
-
|
|
1117
|
-
When set to `true`, TypeScript uses `Object.defineProperty()` semantics for class fields. The `defineProperty` call runs **after** the constructor, which means it runs **after** decorators have already set property values. The result: `defineProperty` overwrites the decorator-injected value with `undefined`.
|
|
1118
|
-
|
|
1119
|
-
```typescript
|
|
1120
|
-
class UserController extends BaseController {
|
|
1121
|
-
@inject({ key: 'services.UserService' })
|
|
1122
|
-
private userService: UserService; // Declared but not assigned
|
|
1123
|
-
|
|
1124
|
-
handleRequest() {
|
|
1125
|
-
this.userService.findAll(); // TypeError: Cannot read property 'findAll' of undefined
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
```
|
|
1129
|
-
|
|
1130
|
-
**What happens at runtime with `useDefineForClassFields: true`:**
|
|
1131
|
-
|
|
1132
|
-
```javascript
|
|
1133
|
-
// Step 1: Decorator runs, sets this.userService = <resolved UserService instance>
|
|
1134
|
-
// Step 2: Object.defineProperty(this, 'userService', { value: undefined, ... })
|
|
1135
|
-
// Result: userService is undefined, decorator's work is lost
|
|
1136
|
-
```
|
|
1137
|
-
|
|
1138
|
-
**What happens at runtime with `useDefineForClassFields: false`:**
|
|
1139
|
-
|
|
1140
|
-
```javascript
|
|
1141
|
-
// Step 1: Decorator runs, sets this.userService = <resolved UserService instance>
|
|
1142
|
-
// Step 2: this.userService = value (assignment semantics, in constructor body)
|
|
1143
|
-
// Result: userService retains the decorator-injected value
|
|
1144
|
-
```
|
|
1145
|
-
|
|
1146
|
-
**Related flag:** `strictPropertyInitialization` is also set to `false` because DI-injected properties are initialized by the container, not in the constructor body, and would otherwise trigger errors like:
|
|
1147
|
-
|
|
1148
|
-
```
|
|
1149
|
-
error TS2564: Property 'userService' has no initializer and is not definitely
|
|
1150
|
-
assigned in the constructor.
|
|
1151
|
-
```
|
|
1152
|
-
|
|
1153
|
-
---
|
|
1154
|
-
|
|
1155
|
-
## Consumption Pattern
|
|
1156
|
-
|
|
1157
|
-
Every package in the IGNIS monorepo follows an identical pattern to consume these configs. Here is the complete setup for a new package:
|
|
1158
|
-
|
|
1159
|
-
### 1. ESLint (`eslint.config.mjs`)
|
|
1160
|
-
|
|
1161
|
-
```javascript
|
|
1162
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
1163
|
-
|
|
1164
|
-
export default eslintConfigs;
|
|
1165
|
-
```
|
|
1166
|
-
|
|
1167
|
-
### 2. Prettier (`.prettierrc.mjs`)
|
|
1168
|
-
|
|
1169
|
-
```javascript
|
|
1170
|
-
import { prettierConfigs } from '@venizia/dev-configs';
|
|
1171
|
-
|
|
1172
|
-
export default prettierConfigs;
|
|
1173
|
-
```
|
|
1174
|
-
|
|
1175
|
-
### 3. TypeScript (`tsconfig.json`)
|
|
1176
|
-
|
|
1177
|
-
```json
|
|
1178
|
-
{
|
|
1179
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1180
|
-
"compilerOptions": {
|
|
1181
|
-
"outDir": "dist",
|
|
1182
|
-
"rootDir": "src"
|
|
1183
|
-
},
|
|
1184
|
-
"include": ["src"],
|
|
1185
|
-
"exclude": ["node_modules", "dist"]
|
|
1186
|
-
}
|
|
1187
|
-
```
|
|
1188
|
-
|
|
1189
|
-
Packages that need path aliases or additional settings simply add them under `compilerOptions`:
|
|
1190
|
-
|
|
1191
|
-
```json
|
|
1192
|
-
{
|
|
1193
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1194
|
-
"compilerOptions": {
|
|
1195
|
-
"outDir": "dist",
|
|
1196
|
-
"rootDir": "src",
|
|
1197
|
-
"baseUrl": "src",
|
|
1198
|
-
"paths": {
|
|
1199
|
-
"@/*": ["./*"]
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
```
|
|
1204
|
-
|
|
1205
|
-
---
|
|
1206
|
-
|
|
1207
|
-
## Real-World Consumer Examples
|
|
1208
|
-
|
|
1209
|
-
The following are actual configurations from packages in the IGNIS monorepo.
|
|
1210
|
-
|
|
1211
|
-
### packages/inversion (Standard Package)
|
|
1212
|
-
|
|
1213
|
-
The simplest and most common pattern -- direct consumption with path aliases:
|
|
1214
|
-
|
|
1215
|
-
**`eslint.config.mjs`:**
|
|
1216
|
-
|
|
1217
|
-
```javascript
|
|
1218
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
1219
|
-
|
|
1220
|
-
export default eslintConfigs;
|
|
1221
|
-
```
|
|
1222
|
-
|
|
1223
|
-
**`.prettierrc.mjs`:**
|
|
1224
|
-
|
|
1225
|
-
```javascript
|
|
1226
|
-
import { prettierConfigs } from '@venizia/dev-configs';
|
|
1227
|
-
|
|
1228
|
-
export default prettierConfigs;
|
|
1229
|
-
```
|
|
1230
|
-
|
|
1231
|
-
**`tsconfig.json`:**
|
|
1232
|
-
|
|
1233
|
-
```json
|
|
1234
|
-
{
|
|
1235
|
-
"$schema": "http://json.schemastore.org/tsconfig",
|
|
1236
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1237
|
-
"compilerOptions": {
|
|
1238
|
-
"outDir": "dist/cjs",
|
|
1239
|
-
"rootDir": "src",
|
|
1240
|
-
"baseUrl": "src",
|
|
1241
|
-
"paths": {
|
|
1242
|
-
"@/*": ["./*"]
|
|
1243
|
-
}
|
|
1244
|
-
},
|
|
1245
|
-
"include": ["src", "./*.config.*", ".prettierrc.*"],
|
|
1246
|
-
"exclude": ["node_modules", "dist", "app_data"]
|
|
1247
|
-
}
|
|
1248
|
-
```
|
|
1249
|
-
|
|
1250
|
-
Note that `include` adds `./*.config.*` and `.prettierrc.*` so ESLint and Prettier config files are also type-checked.
|
|
1251
|
-
|
|
1252
|
-
---
|
|
1253
|
-
|
|
1254
|
-
### packages/core (Extended TypeScript Config)
|
|
1255
|
-
|
|
1256
|
-
The core package needs JSX support for Hono's JSX rendering, so it uses a two-level tsconfig chain:
|
|
1257
|
-
|
|
1258
|
-
**`tsconfig.core.json`** (base for core):
|
|
1259
|
-
|
|
1260
|
-
```json
|
|
1261
|
-
{
|
|
1262
|
-
"$schema": "http://json.schemastore.org/tsconfig",
|
|
1263
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1264
|
-
"compilerOptions": {
|
|
1265
|
-
"outDir": "dist",
|
|
1266
|
-
"rootDir": "src",
|
|
1267
|
-
"baseUrl": "src",
|
|
1268
|
-
"paths": {
|
|
1269
|
-
"@/*": ["./*"]
|
|
1270
|
-
}
|
|
1271
|
-
},
|
|
1272
|
-
"include": ["src", "./*.config.*", ".prettierrc.*"],
|
|
1273
|
-
"exclude": ["node_modules", "dist", "app_data"]
|
|
1274
|
-
}
|
|
1275
|
-
```
|
|
1276
|
-
|
|
1277
|
-
**`tsconfig.json`** (adds JSX on top):
|
|
1278
|
-
|
|
1279
|
-
```json
|
|
1280
|
-
{
|
|
1281
|
-
"$schema": "http://json.schemastore.org/tsconfig",
|
|
1282
|
-
"extends": "./tsconfig.core.json",
|
|
1283
|
-
"compilerOptions": {
|
|
1284
|
-
"jsx": "react-jsx",
|
|
1285
|
-
"jsxImportSource": "hono/jsx"
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
```
|
|
1289
|
-
|
|
1290
|
-
This pattern demonstrates that you can chain `extends` references: `tsconfig.json` extends `tsconfig.core.json` which extends `@venizia/dev-configs/tsconfig.common.json`.
|
|
1291
|
-
|
|
1292
|
-
---
|
|
1293
|
-
|
|
1294
|
-
### examples/vert (Application-Level Config)
|
|
1295
|
-
|
|
1296
|
-
Applications use the same pattern as library packages:
|
|
1297
|
-
|
|
1298
|
-
**`tsconfig.json`:**
|
|
1299
|
-
|
|
1300
|
-
```json
|
|
1301
|
-
{
|
|
1302
|
-
"$schema": "http://json.schemastore.org/tsconfig",
|
|
1303
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1304
|
-
"compilerOptions": {
|
|
1305
|
-
"outDir": "dist",
|
|
1306
|
-
"rootDir": "src",
|
|
1307
|
-
"baseUrl": "src",
|
|
1308
|
-
"paths": {
|
|
1309
|
-
"@/*": ["./*"]
|
|
1310
|
-
}
|
|
1311
|
-
},
|
|
1312
|
-
"include": ["src", "./*.config.*", ".prettierrc.*"],
|
|
1313
|
-
"exclude": ["node_modules", "dist", "app_data"]
|
|
1314
|
-
}
|
|
1315
|
-
```
|
|
17
|
+
</div>
|
|
1316
18
|
|
|
1317
19
|
---
|
|
1318
20
|
|
|
1319
|
-
|
|
21
|
+
One source of truth for lint, format, and compile settings across every IGNIS package, example, and
|
|
22
|
+
downstream app. It ships three things: an ESLint flat config array, a Prettier config object, and two
|
|
23
|
+
`tsconfig` files - one base, one for NodeNext modules.
|
|
1320
24
|
|
|
1321
|
-
|
|
25
|
+
Reach for it when you are building on IGNIS and want the same rules the framework itself is written
|
|
26
|
+
under. Consumption is three lines per tool.
|
|
1322
27
|
|
|
1323
|
-
|
|
28
|
+
## Install
|
|
1324
29
|
|
|
1325
30
|
```bash
|
|
1326
|
-
|
|
31
|
+
bun add -d @venizia/dev-configs eslint prettier typescript
|
|
1327
32
|
```
|
|
1328
33
|
|
|
1329
|
-
|
|
34
|
+
`eslint`, `prettier`, and `typescript` are optional peers - install only the ones you use.
|
|
35
|
+
TypeScript is pinned at **6.x** across the IGNIS monorepo.
|
|
1330
36
|
|
|
1331
|
-
|
|
1332
|
-
{
|
|
1333
|
-
"name": "@venizia/ignis-my-package",
|
|
1334
|
-
"version": "0.0.1",
|
|
1335
|
-
"description": "My new IGNIS package",
|
|
1336
|
-
"exports": {
|
|
1337
|
-
".": {
|
|
1338
|
-
"types": "./dist/index.d.ts",
|
|
1339
|
-
"default": "./dist/index.js"
|
|
1340
|
-
}
|
|
1341
|
-
},
|
|
1342
|
-
"scripts": {
|
|
1343
|
-
"build": "tsc",
|
|
1344
|
-
"rebuild": "bun run clean && bun run build",
|
|
1345
|
-
"clean": "rm -rf dist *.tsbuildinfo",
|
|
1346
|
-
"eslint": "eslint --report-unused-disable-directives .",
|
|
1347
|
-
"lint": "bun run eslint && bun run prettier:cli",
|
|
1348
|
-
"lint:fix": "bun run eslint --fix && bun run prettier:fix",
|
|
1349
|
-
"prettier:cli": "prettier \"**/*.{js,ts}\" -l",
|
|
1350
|
-
"prettier:fix": "bun run prettier:cli --write"
|
|
1351
|
-
},
|
|
1352
|
-
"devDependencies": {
|
|
1353
|
-
"@venizia/dev-configs": "workspace:*",
|
|
1354
|
-
"eslint": "^9.36.0",
|
|
1355
|
-
"prettier": "^3.6.2",
|
|
1356
|
-
"typescript": "^5.9.3"
|
|
1357
|
-
}
|
|
1358
|
-
}
|
|
1359
|
-
```
|
|
37
|
+
## Use it
|
|
1360
38
|
|
|
1361
|
-
|
|
39
|
+
**`eslint.config.mjs`**
|
|
1362
40
|
|
|
1363
41
|
```javascript
|
|
1364
42
|
import { eslintConfigs } from '@venizia/dev-configs';
|
|
@@ -1366,7 +44,7 @@ import { eslintConfigs } from '@venizia/dev-configs';
|
|
|
1366
44
|
export default eslintConfigs;
|
|
1367
45
|
```
|
|
1368
46
|
|
|
1369
|
-
|
|
47
|
+
**`.prettierrc.mjs`**
|
|
1370
48
|
|
|
1371
49
|
```javascript
|
|
1372
50
|
import { prettierConfigs } from '@venizia/dev-configs';
|
|
@@ -1374,399 +52,113 @@ import { prettierConfigs } from '@venizia/dev-configs';
|
|
|
1374
52
|
export default prettierConfigs;
|
|
1375
53
|
```
|
|
1376
54
|
|
|
1377
|
-
|
|
55
|
+
**`tsconfig.json`**
|
|
1378
56
|
|
|
1379
57
|
```json
|
|
1380
58
|
{
|
|
1381
|
-
"$schema": "http://json.schemastore.org/tsconfig",
|
|
1382
59
|
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1383
|
-
"compilerOptions": {
|
|
1384
|
-
|
|
1385
|
-
"rootDir": "src",
|
|
1386
|
-
"baseUrl": "src",
|
|
1387
|
-
"paths": {
|
|
1388
|
-
"@/*": ["./*"]
|
|
1389
|
-
}
|
|
1390
|
-
},
|
|
1391
|
-
"include": ["src", "./*.config.*", ".prettierrc.*"],
|
|
1392
|
-
"exclude": ["node_modules", "dist", "app_data"]
|
|
60
|
+
"compilerOptions": { "outDir": "dist", "rootDir": "src" },
|
|
61
|
+
"include": ["src"]
|
|
1393
62
|
}
|
|
1394
63
|
```
|
|
1395
64
|
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
```typescript
|
|
1399
|
-
// src/index.ts
|
|
1400
|
-
export const hello = 'world';
|
|
1401
|
-
```
|
|
1402
|
-
|
|
1403
|
-
### Step 7: Install Dependencies and Verify
|
|
1404
|
-
|
|
1405
|
-
```bash
|
|
1406
|
-
# From the monorepo root
|
|
1407
|
-
bun install
|
|
1408
|
-
|
|
1409
|
-
# Verify TypeScript compiles
|
|
1410
|
-
cd packages/my-package
|
|
1411
|
-
tsc --noEmit
|
|
1412
|
-
|
|
1413
|
-
# Verify ESLint runs
|
|
1414
|
-
bun run eslint
|
|
1415
|
-
|
|
1416
|
-
# Verify Prettier checks
|
|
1417
|
-
bun run prettier:cli
|
|
1418
|
-
```
|
|
1419
|
-
|
|
1420
|
-
---
|
|
1421
|
-
|
|
1422
|
-
## Extending and Overriding
|
|
1423
|
-
|
|
1424
|
-
### ESLint
|
|
1425
|
-
|
|
1426
|
-
Spread the base configs and append your overrides:
|
|
1427
|
-
|
|
1428
|
-
```javascript
|
|
1429
|
-
import { eslintConfigs } from '@venizia/dev-configs';
|
|
1430
|
-
|
|
1431
|
-
export default [
|
|
1432
|
-
...eslintConfigs,
|
|
1433
|
-
{
|
|
1434
|
-
rules: {
|
|
1435
|
-
'no-console': 'error',
|
|
1436
|
-
'@typescript-eslint/no-explicit-any': 'error',
|
|
1437
|
-
},
|
|
1438
|
-
},
|
|
1439
|
-
];
|
|
1440
|
-
```
|
|
1441
|
-
|
|
1442
|
-
### Prettier
|
|
1443
|
-
|
|
1444
|
-
Spread the base config object:
|
|
65
|
+
To prepend your own layers, spread the array:
|
|
1445
66
|
|
|
1446
67
|
```javascript
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
export default {
|
|
1450
|
-
...prettierConfigs,
|
|
1451
|
-
printWidth: 120,
|
|
1452
|
-
};
|
|
68
|
+
export default [{ ignores: ['scripts/'] }, ...eslintConfigs];
|
|
1453
69
|
```
|
|
1454
70
|
|
|
1455
|
-
|
|
71
|
+
## Decorators: read this or DI breaks
|
|
1456
72
|
|
|
1457
|
-
|
|
73
|
+
> [!IMPORTANT]
|
|
74
|
+
> `experimentalDecorators`, `emitDecoratorMetadata`, and `useDefineForClassFields: false` are what
|
|
75
|
+
> make the IGNIS DI system work. Without them `@inject`, `@controller`, and `@repository` metadata is
|
|
76
|
+
> **silently dropped** - no error, just an application that cannot resolve its own bindings.
|
|
77
|
+
>
|
|
78
|
+
> **Bun does not resolve these flags through `extends`.** An application `tsconfig.json` must declare
|
|
79
|
+
> them inline, even when it already extends `tsconfig.common.json`.
|
|
1458
80
|
|
|
1459
81
|
```json
|
|
1460
82
|
{
|
|
1461
83
|
"extends": "@venizia/dev-configs/tsconfig.common.json",
|
|
1462
84
|
"compilerOptions": {
|
|
1463
|
-
"
|
|
1464
|
-
"
|
|
85
|
+
"experimentalDecorators": true,
|
|
86
|
+
"emitDecoratorMetadata": true,
|
|
87
|
+
"useDefineForClassFields": false
|
|
1465
88
|
}
|
|
1466
89
|
}
|
|
1467
90
|
```
|
|
1468
91
|
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
---
|
|
1472
|
-
|
|
1473
|
-
## IDE Integration
|
|
1474
|
-
|
|
1475
|
-
### Visual Studio Code
|
|
1476
|
-
|
|
1477
|
-
Install these extensions for full integration:
|
|
1478
|
-
|
|
1479
|
-
1. **ESLint** (`dbaeumer.vscode-eslint`) -- Lint on save, show inline errors.
|
|
1480
|
-
2. **Prettier** (`esbenp.prettier-vscode`) -- Format on save.
|
|
1481
|
-
|
|
1482
|
-
Recommended `.vscode/settings.json` for the monorepo:
|
|
1483
|
-
|
|
1484
|
-
```json
|
|
1485
|
-
{
|
|
1486
|
-
"editor.formatOnSave": true,
|
|
1487
|
-
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
1488
|
-
"editor.codeActionsOnSave": {
|
|
1489
|
-
"source.fixAll.eslint": "explicit"
|
|
1490
|
-
},
|
|
1491
|
-
"eslint.useFlatConfig": true,
|
|
1492
|
-
"typescript.tsdk": "node_modules/typescript/lib",
|
|
1493
|
-
"typescript.enablePromptUseWorkspaceTsdk": true
|
|
1494
|
-
}
|
|
1495
|
-
```
|
|
1496
|
-
|
|
1497
|
-
Key points:
|
|
1498
|
-
|
|
1499
|
-
- **`eslint.useFlatConfig: true`** -- Tells the ESLint extension to use the flat config format. Without this, the extension may look for `.eslintrc` files instead of `eslint.config.mjs`.
|
|
1500
|
-
- **`typescript.tsdk`** -- Points to the workspace's TypeScript version instead of the VS Code bundled one. Ensures the same TypeScript version is used for type checking in the IDE and during builds.
|
|
1501
|
-
- Prettier handles formatting; ESLint handles code quality rules. The `eslint-plugin-prettier` integration in the config stack ensures they do not conflict.
|
|
1502
|
-
|
|
1503
|
-
---
|
|
1504
|
-
|
|
1505
|
-
### WebStorm / IntelliJ IDEA
|
|
1506
|
-
|
|
1507
|
-
WebStorm automatically detects `eslint.config.mjs` and `.prettierrc.mjs` files. Ensure these settings are enabled:
|
|
1508
|
-
|
|
1509
|
-
1. **Settings > Languages & Frameworks > JavaScript > Code Quality Tools > ESLint**: Select "Automatic ESLint configuration."
|
|
1510
|
-
2. **Settings > Languages & Frameworks > JavaScript > Prettier**: Check "On save" and "On 'Reformat Code' action."
|
|
1511
|
-
3. **Settings > Languages & Frameworks > TypeScript**: Point the TypeScript service to `node_modules/typescript/lib`.
|
|
1512
|
-
|
|
1513
|
-
---
|
|
1514
|
-
|
|
1515
|
-
## CI/CD Integration
|
|
1516
|
-
|
|
1517
|
-
### Lint Check in CI
|
|
1518
|
-
|
|
1519
|
-
Add a lint step to your CI pipeline. Example using GitHub Actions:
|
|
1520
|
-
|
|
1521
|
-
```yaml
|
|
1522
|
-
name: Lint
|
|
1523
|
-
on: [push, pull_request]
|
|
1524
|
-
|
|
1525
|
-
jobs:
|
|
1526
|
-
lint:
|
|
1527
|
-
runs-on: ubuntu-latest
|
|
1528
|
-
steps:
|
|
1529
|
-
- uses: actions/checkout@v4
|
|
1530
|
-
- uses: oven-sh/setup-bun@v2
|
|
1531
|
-
with:
|
|
1532
|
-
bun-version: latest
|
|
1533
|
-
- run: bun install --frozen-lockfile
|
|
1534
|
-
- run: bun run lint # Runs ESLint + Prettier check across all packages
|
|
1535
|
-
```
|
|
1536
|
-
|
|
1537
|
-
### Per-Package Lint
|
|
1538
|
-
|
|
1539
|
-
If you need to lint a specific package:
|
|
1540
|
-
|
|
1541
|
-
```bash
|
|
1542
|
-
# From the monorepo root
|
|
1543
|
-
cd packages/core && bun run lint
|
|
1544
|
-
|
|
1545
|
-
# Or using make targets
|
|
1546
|
-
make lint # All packages
|
|
1547
|
-
make lint-all # All packages + examples
|
|
1548
|
-
```
|
|
1549
|
-
|
|
1550
|
-
### Type Check in CI
|
|
1551
|
-
|
|
1552
|
-
```yaml
|
|
1553
|
-
- run: make build # tsc compilation validates types as a side effect
|
|
1554
|
-
```
|
|
1555
|
-
|
|
1556
|
-
Since `noEmitOnError: true` is set, a failed type check will cause the build to fail.
|
|
1557
|
-
|
|
1558
|
-
---
|
|
1559
|
-
|
|
1560
|
-
## Troubleshooting
|
|
1561
|
-
|
|
1562
|
-
### ESLint flat config not recognized
|
|
1563
|
-
|
|
1564
|
-
**Symptom:** ESLint ignores `eslint.config.mjs` and reports "no configuration found."
|
|
1565
|
-
|
|
1566
|
-
**Cause:** ESLint version is below v9.0.0, which does not support flat config as the default format.
|
|
1567
|
-
|
|
1568
|
-
**Fix:**
|
|
1569
|
-
|
|
1570
|
-
```bash
|
|
1571
|
-
bun add -d eslint@^9.0.0
|
|
1572
|
-
```
|
|
1573
|
-
|
|
1574
|
-
Verify your version:
|
|
1575
|
-
|
|
1576
|
-
```bash
|
|
1577
|
-
bun eslint --version # Should be 9.x.x or later
|
|
1578
|
-
```
|
|
1579
|
-
|
|
1580
|
-
---
|
|
1581
|
-
|
|
1582
|
-
### Prettier conflicts with ESLint
|
|
1583
|
-
|
|
1584
|
-
**Symptom:** ESLint and Prettier disagree on formatting, causing one to undo the other's changes.
|
|
1585
|
-
|
|
1586
|
-
**Cause:** Usually happens when ESLint formatting rules are not disabled by `eslint-plugin-prettier`.
|
|
1587
|
-
|
|
1588
|
-
**Fix:** The `@venizia/dev-configs` ESLint config already includes `eslint-plugin-prettier/recommended` (via the `@minimaltech/eslint-common` base layer), which automatically disables ESLint rules that conflict with Prettier. If you see conflicts, check that you are not adding formatting rules in your project-level overrides that conflict with Prettier settings.
|
|
1589
|
-
|
|
1590
|
-
---
|
|
1591
|
-
|
|
1592
|
-
### TypeScript decorator errors
|
|
1593
|
-
|
|
1594
|
-
**Symptom:**
|
|
1595
|
-
|
|
1596
|
-
```
|
|
1597
|
-
error TS1219: Experimental support for decorators is a feature that is subject
|
|
1598
|
-
to change in a future release.
|
|
1599
|
-
```
|
|
1600
|
-
|
|
1601
|
-
**Cause:** `experimentalDecorators` is not enabled, usually because `tsconfig.json` does not extend the shared config or the `extends` path is wrong.
|
|
1602
|
-
|
|
1603
|
-
**Fix:** Verify your `tsconfig.json`:
|
|
1604
|
-
|
|
1605
|
-
```json
|
|
1606
|
-
{
|
|
1607
|
-
"extends": "@venizia/dev-configs/tsconfig.common.json"
|
|
1608
|
-
}
|
|
1609
|
-
```
|
|
1610
|
-
|
|
1611
|
-
If the path cannot be resolved, ensure `@venizia/dev-configs` is installed:
|
|
92
|
+
`useDefineForClassFields: false` keeps class field declarations as assignments; the `define`
|
|
93
|
+
semantics of ES2022 would overwrite values a decorator wrote during construction.
|
|
1612
94
|
|
|
1613
|
-
|
|
1614
|
-
bun add -d @venizia/dev-configs
|
|
1615
|
-
```
|
|
1616
|
-
|
|
1617
|
-
---
|
|
1618
|
-
|
|
1619
|
-
### DI injection returns undefined at runtime
|
|
1620
|
-
|
|
1621
|
-
**Symptom:** `@inject`-decorated properties are `undefined` at runtime even though the binding exists in the container.
|
|
1622
|
-
|
|
1623
|
-
**Cause:** Usually one of:
|
|
1624
|
-
|
|
1625
|
-
1. `emitDecoratorMetadata: false` -- the container cannot read constructor parameter types.
|
|
1626
|
-
2. `useDefineForClassFields: true` -- `Object.defineProperty` overwrites decorator-injected values.
|
|
95
|
+
## What each config sets
|
|
1627
96
|
|
|
1628
|
-
|
|
97
|
+
### ESLint - `eslintConfigs`
|
|
1629
98
|
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
### Path alias resolution fails at runtime (tsc-alias)
|
|
99
|
+
A flat config array (ESLint 9+; the legacy `.eslintrc` format is not supported). Three layers, later
|
|
100
|
+
ones winning:
|
|
1633
101
|
|
|
1634
|
-
|
|
102
|
+
| Layer | Source | Brings |
|
|
103
|
+
| :--- | :--- | :--- |
|
|
104
|
+
| 1 | `@minimaltech/eslint-common` | `eslint:recommended`, `typescript-eslint` recommended, `eslint-plugin-prettier` (format violations are lint errors), naming-convention rules |
|
|
105
|
+
| 2 | `@minimaltech/eslint-node` | `eslint-plugin-n`, LoopBack-style strict rules |
|
|
106
|
+
| 3 | this package | the IGNIS overrides below |
|
|
1635
107
|
|
|
1636
|
-
|
|
108
|
+
| Rule | Setting | Why |
|
|
109
|
+
| :--- | :--- | :--- |
|
|
110
|
+
| `@typescript-eslint/no-explicit-any` | `off` | framework generics need escape hatches; prefer a simple cast over a baroque one |
|
|
111
|
+
| `curly` | `['error', 'all']` | always braces, no single-line ifs |
|
|
112
|
+
| `unicorn/switch-case-braces` | `['error', 'always']` | braced `case` blocks so a `const` cannot leak between cases |
|
|
1637
113
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
```bash
|
|
1641
|
-
bun add -d tsc-alias
|
|
1642
|
-
```
|
|
114
|
+
Layer 1 sets `parserOptions.project: './tsconfig.json'`, so a `tsconfig.json` must exist at the lint
|
|
115
|
+
root and **must `include` the config files themselves** or ESLint fails on them:
|
|
1643
116
|
|
|
1644
117
|
```json
|
|
1645
|
-
{
|
|
1646
|
-
"scripts": {
|
|
1647
|
-
"build": "tsc && tsc-alias"
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
```
|
|
1651
|
-
|
|
1652
|
-
---
|
|
1653
|
-
|
|
1654
|
-
### Module resolution errors (node16 vs nodenext)
|
|
1655
|
-
|
|
1656
|
-
**Symptom:**
|
|
1657
|
-
|
|
1658
|
-
```
|
|
1659
|
-
error TS2835: Relative import paths need explicit file extensions in ECMAScript
|
|
1660
|
-
imports when '--moduleResolution' is 'node16' or 'nodenext'.
|
|
1661
|
-
```
|
|
1662
|
-
|
|
1663
|
-
**Cause:** With `moduleResolution: nodenext`, TypeScript enforces Node.js ESM resolution rules, which require explicit file extensions on relative imports.
|
|
1664
|
-
|
|
1665
|
-
**Fix:** Add `.js` extensions to relative imports (even though the source files are `.ts`):
|
|
1666
|
-
|
|
1667
|
-
```typescript
|
|
1668
|
-
// CORRECT
|
|
1669
|
-
import { UserService } from './services/user.service.js';
|
|
1670
|
-
|
|
1671
|
-
// INCORRECT (will error with nodenext resolution)
|
|
1672
|
-
import { UserService } from './services/user.service';
|
|
1673
|
-
```
|
|
1674
|
-
|
|
1675
|
-
This is a TypeScript convention: you write `.js` in the import path because the compiled output will be `.js` files.
|
|
1676
|
-
|
|
1677
|
-
---
|
|
1678
|
-
|
|
1679
|
-
### Incremental build stale cache
|
|
1680
|
-
|
|
1681
|
-
**Symptom:** TypeScript does not pick up changes, or emits stale output after modifying files.
|
|
1682
|
-
|
|
1683
|
-
**Cause:** The `.tsbuildinfo` incremental cache file is corrupted or stale.
|
|
1684
|
-
|
|
1685
|
-
**Fix:** Delete the `.tsbuildinfo` file and rebuild:
|
|
1686
|
-
|
|
1687
|
-
```bash
|
|
1688
|
-
rm -f *.tsbuildinfo
|
|
1689
|
-
tsc
|
|
1690
|
-
```
|
|
1691
|
-
|
|
1692
|
-
Or use the clean script:
|
|
1693
|
-
|
|
1694
|
-
```bash
|
|
1695
|
-
bun run clean && bun run build
|
|
118
|
+
{ "include": ["src", "./*.config.*", ".prettierrc.*"] }
|
|
1696
119
|
```
|
|
1697
120
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
## Version Compatibility
|
|
1701
|
-
|
|
1702
|
-
| Tool | Required Version | Notes |
|
|
1703
|
-
| :--- | :--------------- | :---- |
|
|
1704
|
-
| **ESLint** | `^9.0.0` | Flat config format. Versions below 9.0 do not support `eslint.config.mjs` as the default config file. |
|
|
1705
|
-
| **Prettier** | `^3.0.0` | Major version 3 for `trailingComma: "all"` support in type parameters. |
|
|
1706
|
-
| **TypeScript** | `^5.0.0` | Required for `@typescript-eslint` v8 compatibility and modern decorator metadata support. |
|
|
1707
|
-
| **Bun** | `>=1.3` | Primary runtime. Specified in `package.json` `engines` field. |
|
|
1708
|
-
| **Node.js** | `>=18.0` | Secondary runtime. ES2022 target requires Node.js 18+ for full API support. |
|
|
1709
|
-
| **@typescript-eslint** | `^8.44.0` | Provided transitively via `@minimaltech/eslint-node`. |
|
|
1710
|
-
| **eslint-plugin-unicorn** | `^62.0.0` | Direct dependency of `@venizia/dev-configs`. |
|
|
1711
|
-
| **eslint-plugin-n** | `^17.21.3` | Provided transitively via `@minimaltech/eslint-node`. |
|
|
1712
|
-
|
|
1713
|
-
---
|
|
1714
|
-
|
|
1715
|
-
## Comparison with Alternatives
|
|
121
|
+
### Prettier - `prettierConfigs`
|
|
1716
122
|
|
|
1717
|
-
|
|
123
|
+
| Setting | Value |
|
|
124
|
+
| :--- | :--- |
|
|
125
|
+
| `printWidth` | `100` |
|
|
126
|
+
| `tabWidth` | `2` |
|
|
127
|
+
| `singleQuote` | `true` |
|
|
128
|
+
| `semi` | `true` |
|
|
129
|
+
| `trailingComma` | `'all'` |
|
|
130
|
+
| `bracketSpacing` | `true` |
|
|
131
|
+
| `arrowParens` | `'avoid'` |
|
|
1718
132
|
|
|
1719
|
-
|
|
1720
|
-
| :------- | :--- | :--- |
|
|
1721
|
-
| **Shared package (what we do)** | Single source of truth; update once, propagate everywhere; guaranteed consistency; 3-line consumption | Requires publishing/building the package before consumers can use it; tighter coupling |
|
|
1722
|
-
| **Copy-paste configs** | Independent per package; no build dependency | Config drift across packages; painful to update 10+ packages; no guarantee of consistency |
|
|
1723
|
-
| **Root-level configs only** | Zero duplication | Does not work for published packages that need their own `tsconfig.json` for declaration emit; monorepo tools may not respect root configs |
|
|
1724
|
-
| **Tool-specific workspace configs** (e.g., ESLint `root: true`) | Built into the tool | Only works for ESLint (legacy format); Prettier and TypeScript have no equivalent workspace inheritance |
|
|
1725
|
-
|
|
1726
|
-
---
|
|
1727
|
-
|
|
1728
|
-
### Why flat config (ESLint v9+) instead of `.eslintrc`?
|
|
1729
|
-
|
|
1730
|
-
- **Explicit composition:** Config is a plain array of objects. No magic resolution, no `extends` chains, no cascading from parent directories.
|
|
1731
|
-
- **Type safety:** The `Linter.Config[]` type provides autocomplete and type checking.
|
|
1732
|
-
- **Plugin loading:** Plugins are imported as ES modules, not resolved by string name. No more version mismatch confusion.
|
|
1733
|
-
- **Future-proof:** ESLint has deprecated `.eslintrc` as of v9. It will be removed in a future major version.
|
|
1734
|
-
|
|
1735
|
-
---
|
|
1736
|
-
|
|
1737
|
-
### Why Prettier config in `.prettierrc.mjs` instead of `.prettierrc.json`?
|
|
1738
|
-
|
|
1739
|
-
- **Import support:** `.mjs` files can `import` from npm packages. `.json` files cannot reference external configs.
|
|
1740
|
-
- **Spread operator:** JavaScript allows `{ ...baseConfig, override: value }`. JSON does not support any form of inheritance.
|
|
1741
|
-
- **Consistency:** All three configs (ESLint, Prettier, TypeScript) follow the same "import from shared package" pattern.
|
|
1742
|
-
|
|
1743
|
-
---
|
|
1744
|
-
|
|
1745
|
-
## Package Exports
|
|
1746
|
-
|
|
1747
|
-
| Export Path | Resolves To | Description |
|
|
1748
|
-
| :---------- | :---------- | :---------- |
|
|
1749
|
-
| `@venizia/dev-configs` | `dist/index.js` | ESLint and Prettier configs (`eslintConfigs`, `prettierConfigs`) |
|
|
1750
|
-
| `@venizia/dev-configs/tsconfig.base.json` | `tsconfig/tsconfig.base.json` | Comprehensive base TypeScript config |
|
|
1751
|
-
| `@venizia/dev-configs/tsconfig.common.json` | `tsconfig/tsconfig.common.json` | Consumer-facing TypeScript config (recommended) |
|
|
1752
|
-
| `@venizia/dev-configs/package.json` | `package.json` | Package manifest |
|
|
1753
|
-
|
|
1754
|
-
---
|
|
133
|
+
### TypeScript
|
|
1755
134
|
|
|
1756
|
-
|
|
135
|
+
Two files, both reachable under the `@venizia/dev-configs/` export path.
|
|
1757
136
|
|
|
1758
|
-
|
|
137
|
+
| File | Use for |
|
|
138
|
+
| :--- | :--- |
|
|
139
|
+
| `tsconfig.base.json` | the foundation: `target`/`lib` ES2024, `module` Node16, the decorator flags, strict mode, `declaration` + source maps, `noEmitOnError` |
|
|
140
|
+
| `tsconfig.common.json` | applications: extends the base, switches `module` and `moduleResolution` to `nodenext` |
|
|
1759
141
|
|
|
1760
|
-
|
|
142
|
+
Strictness is deliberately balanced rather than maximal - `strict: true`, but `noImplicitAny`,
|
|
143
|
+
`strictPropertyInitialization`, and `useUnknownInCatchVariables` are `false` because DI-injected
|
|
144
|
+
properties and framework generics cannot satisfy them. `noUnusedLocals`, `noUnusedParameters`,
|
|
145
|
+
`noImplicitOverride`, and `noFallthroughCasesInSwitch` are on.
|
|
1761
146
|
|
|
1762
|
-
|
|
147
|
+
The base config `exclude`s tests (`**/*.test.ts`, `**/*.spec.ts`, `**/__tests__/**`). If your build
|
|
148
|
+
config re-includes them, remember `noEmitOnError: true` means one broken test aborts the whole emit.
|
|
1763
149
|
|
|
1764
|
-
|
|
150
|
+
## Notes
|
|
1765
151
|
|
|
1766
|
-
-
|
|
152
|
+
- Changing anything here propagates to every IGNIS package and example. Treat edits as a
|
|
153
|
+
monorepo-wide change, not a local one.
|
|
154
|
+
- It is the root of the build chain: `dev-configs -> inversion -> helpers -> boot -> core`.
|
|
155
|
+
- Use `tsc` directly. Never `npx`, `bunx`, or `bun x` for TypeScript compilation.
|
|
1767
156
|
|
|
1768
|
-
|
|
157
|
+
## Links
|
|
1769
158
|
|
|
1770
|
-
|
|
159
|
+
[Documentation](https://ignis.venizia.ai) •
|
|
160
|
+
[Best practices](https://ignis.venizia.ai/best-practices/) •
|
|
161
|
+
[Changelog](https://ignis.venizia.ai/changelogs/) •
|
|
162
|
+
[Issues](https://github.com/VENIZIA-AI/ignis/issues)
|
|
1771
163
|
|
|
1772
|
-
[
|
|
164
|
+
MIT licensed - see [LICENSE.md](./LICENSE.md).
|