oxlint-plugin-arch 0.6.3 → 0.8.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.
@@ -0,0 +1,1603 @@
1
+ // src/utils/helpers/preset.ts
2
+ import { readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+
5
+ // src/constants/preset.constants.ts
6
+ var presetExtensions = "*.{ts,tsx,mts,cts}";
7
+ var presetPolicyOrder = [
8
+ "formatting",
9
+ "statementLayout",
10
+ "imports",
11
+ "typePlacement",
12
+ "typeSafety",
13
+ "fileLayout",
14
+ "serviceStructure",
15
+ "naming",
16
+ "comments",
17
+ "trivialFunctions",
18
+ "mutableState",
19
+ "backend",
20
+ "tests",
21
+ "schemas",
22
+ "react",
23
+ "effects",
24
+ "routes",
25
+ "forms",
26
+ "memoization",
27
+ "boundaries",
28
+ "rpcClientOwnership",
29
+ "tailwind",
30
+ "shadcn"
31
+ ];
32
+ var presetCommonRules = {
33
+ "vitest/expect-expect": "off",
34
+ "vitest/no-standalone-expect": "off",
35
+ "jsx-a11y/no-noninteractive-tabindex": "error",
36
+ "no-case-declarations": "error",
37
+ "no-empty": "error",
38
+ "no-fallthrough": "error",
39
+ "no-prototype-builtins": "error",
40
+ "no-regex-spaces": "error",
41
+ "array-callback-return": "error",
42
+ "default-case-last": "error",
43
+ eqeqeq: "error",
44
+ "no-alert": "error",
45
+ "no-constructor-return": "error",
46
+ "no-else-return": "error",
47
+ "no-extend-native": "error",
48
+ "no-extra-bind": "error",
49
+ "no-extra-label": "error",
50
+ "no-labels": "error",
51
+ "no-lone-blocks": "error",
52
+ "no-new": "error",
53
+ "no-new-func": "error",
54
+ "no-new-wrappers": "error",
55
+ "no-param-reassign": "error",
56
+ "no-proto": "error",
57
+ "no-return-assign": "error",
58
+ "no-script-url": "error",
59
+ "no-self-compare": "error",
60
+ "no-sequences": "error",
61
+ "no-useless-call": "error",
62
+ "no-useless-concat": "error",
63
+ "no-useless-return": "error",
64
+ "no-useless-computed-key": "error",
65
+ "no-var": "error",
66
+ "object-shorthand": "error",
67
+ "prefer-const": "error",
68
+ "prefer-numeric-literals": "error",
69
+ "prefer-rest-params": "error",
70
+ "prefer-spread": "error",
71
+ "prefer-template": "error",
72
+ "symbol-description": "error",
73
+ "no-promise-executor-return": "error",
74
+ "no-template-curly-in-string": "error",
75
+ "new-cap": [
76
+ "error",
77
+ {
78
+ capIsNew: false
79
+ }
80
+ ],
81
+ "no-lonely-if": "error",
82
+ "no-multi-assign": "error",
83
+ "no-nested-ternary": "error",
84
+ "no-unneeded-ternary": "error",
85
+ "prefer-object-spread": "error",
86
+ "no-label-var": "error",
87
+ "no-await-in-loop": "error",
88
+ curly: "error",
89
+ "typescript/no-array-constructor": "error",
90
+ "typescript/no-dynamic-delete": "error",
91
+ "typescript/no-empty-object-type": "error",
92
+ "typescript/no-invalid-void-type": "error",
93
+ "typescript/no-mixed-enums": "error",
94
+ "typescript/no-namespace": "error",
95
+ "typescript/no-non-null-asserted-nullish-coalescing": "error",
96
+ "typescript/no-non-null-assertion": "error",
97
+ "typescript/no-require-imports": "error",
98
+ "typescript/no-unnecessary-boolean-literal-compare": "error",
99
+ "typescript/no-unnecessary-condition": "error",
100
+ "typescript/no-unnecessary-template-expression": "error",
101
+ "typescript/no-unnecessary-type-arguments": "error",
102
+ "typescript/no-unnecessary-type-assertion": "error",
103
+ "typescript/no-unnecessary-type-constraint": "error",
104
+ "typescript/no-unsafe-enum-comparison": "error",
105
+ "typescript/no-unsafe-function-type": "error",
106
+ "typescript/no-unused-expressions": "error",
107
+ "no-unused-vars": [
108
+ "error",
109
+ {
110
+ args: "after-used",
111
+ argsIgnorePattern: "^_",
112
+ ignoreRestSiblings: false,
113
+ vars: "all",
114
+ varsIgnorePattern: "^_"
115
+ }
116
+ ],
117
+ "typescript/no-useless-constructor": "error",
118
+ "typescript/prefer-literal-enum-member": "error",
119
+ "typescript/prefer-promise-reject-errors": "error",
120
+ "typescript/prefer-reduce-type-parameter": "error",
121
+ "typescript/prefer-return-this-type": "error",
122
+ "typescript/restrict-plus-operands": "error",
123
+ "typescript/return-await": ["error", "error-handling-correctness-only"],
124
+ "typescript/unbound-method": "error",
125
+ "typescript/use-unknown-in-catch-callback-variable": "error",
126
+ "typescript/unified-signatures": "error",
127
+ "typescript/adjacent-overload-signatures": "error",
128
+ "typescript/array-type": "error",
129
+ "typescript/ban-tslint-comment": "error",
130
+ "typescript/class-literal-property-style": "error",
131
+ "typescript/consistent-generic-constructors": "error",
132
+ "typescript/consistent-indexed-object-style": "error",
133
+ "typescript/consistent-type-assertions": "error",
134
+ "typescript/consistent-type-definitions": "error",
135
+ "typescript/consistent-type-imports": [
136
+ "error",
137
+ {
138
+ prefer: "type-imports",
139
+ disallowTypeAnnotations: true,
140
+ fixStyle: "separate-type-imports"
141
+ }
142
+ ],
143
+ "typescript/no-confusing-non-null-assertion": "error",
144
+ "typescript/no-empty-function": "error",
145
+ "typescript/no-inferrable-types": "error",
146
+ "typescript/non-nullable-type-assertion-style": "error",
147
+ "typescript/prefer-find": "error",
148
+ "typescript/prefer-for-of": "error",
149
+ "typescript/prefer-function-type": "error",
150
+ "typescript/prefer-includes": "error",
151
+ "typescript/prefer-nullish-coalescing": "error",
152
+ "typescript/prefer-optional-chain": "error",
153
+ "typescript/prefer-regexp-exec": "error",
154
+ "typescript/prefer-string-starts-ends-with": "error",
155
+ "typescript/consistent-type-exports": "error",
156
+ "typescript/switch-exhaustiveness-check": "error",
157
+ "typescript/default-param-last": "error",
158
+ "typescript/no-loop-func": "error",
159
+ "typescript/no-shadow": "error",
160
+ "unicorn/prefer-at": "error",
161
+ "unicorn/prefer-node-protocol": "error",
162
+ "react-hooks/rules-of-hooks": "error",
163
+ "react-hooks/exhaustive-deps": "error",
164
+ "oxc/no-accumulating-spread": "error",
165
+ "@regru/prefer-early-return/prefer-early-return": [
166
+ "error",
167
+ {
168
+ maximumStatements: 1
169
+ }
170
+ ],
171
+ "tsdoc/syntax": "error",
172
+ "@eslint-community/eslint-comments/no-aggregating-enable": "error",
173
+ "@eslint-community/eslint-comments/no-duplicate-disable": "error",
174
+ "@eslint-community/eslint-comments/no-unused-enable": "error"
175
+ };
176
+ var presetFormattingRules = {
177
+ "eslint-js/padding-line-between-statements": [
178
+ "error",
179
+ {
180
+ blankLine: "always",
181
+ prev: ["multiline-const", "multiline-let"],
182
+ next: "*"
183
+ },
184
+ {
185
+ blankLine: "always",
186
+ prev: "*",
187
+ next: ["multiline-const", "multiline-let"]
188
+ }
189
+ ],
190
+ "eslint-js/indent": [
191
+ "error",
192
+ 2,
193
+ {
194
+ MemberExpression: 1,
195
+ SwitchCase: 1,
196
+ flatTernaryExpressions: true
197
+ }
198
+ ],
199
+ "eslint-js/semi": ["error", "always"],
200
+ "eslint-js/quotes": [
201
+ "error",
202
+ "single",
203
+ {
204
+ avoidEscape: true
205
+ }
206
+ ],
207
+ "eslint-js/comma-dangle": ["error", "always-multiline"],
208
+ "eslint-js/no-trailing-spaces": "error",
209
+ "eslint-js/no-multiple-empty-lines": [
210
+ "error",
211
+ {
212
+ max: 1
213
+ }
214
+ ],
215
+ "eslint-js/eol-last": ["error", "always"],
216
+ "eslint-js/no-multi-spaces": "error",
217
+ "eslint-js/arrow-spacing": "error",
218
+ "eslint-js/block-spacing": "error",
219
+ "eslint-js/comma-spacing": [
220
+ "error",
221
+ {
222
+ before: false,
223
+ after: true
224
+ }
225
+ ],
226
+ "eslint-js/key-spacing": [
227
+ "error",
228
+ {
229
+ beforeColon: false,
230
+ afterColon: true
231
+ }
232
+ ],
233
+ "eslint-js/keyword-spacing": [
234
+ "error",
235
+ {
236
+ before: true,
237
+ after: true
238
+ }
239
+ ],
240
+ "eslint-js/object-curly-spacing": ["error", "always"],
241
+ "eslint-js/semi-spacing": [
242
+ "error",
243
+ {
244
+ before: false,
245
+ after: true
246
+ }
247
+ ],
248
+ "eslint-js/space-before-blocks": "error",
249
+ "eslint-js/space-before-function-paren": [
250
+ "error",
251
+ {
252
+ anonymous: "always",
253
+ named: "never",
254
+ asyncArrow: "always"
255
+ }
256
+ ],
257
+ "eslint-js/space-infix-ops": "error",
258
+ "eslint-js/space-in-parens": ["error", "never"],
259
+ "eslint-js/template-curly-spacing": ["error", "never"],
260
+ "eslint-js/rest-spread-spacing": ["error", "never"],
261
+ "eslint-js/computed-property-spacing": ["error", "never"],
262
+ "eslint-js/brace-style": [
263
+ "error",
264
+ "1tbs",
265
+ {
266
+ allowSingleLine: false
267
+ }
268
+ ],
269
+ "eslint-js/arrow-parens": ["error", "always"],
270
+ "eslint-js/no-extra-semi": "error",
271
+ "eslint-js/no-floating-decimal": "error",
272
+ "eslint-js/dot-location": ["error", "property"],
273
+ "eslint-js/no-whitespace-before-property": "error",
274
+ "eslint-js/object-curly-newline": [
275
+ "error",
276
+ {
277
+ consistent: true
278
+ }
279
+ ],
280
+ "eslint-js/object-property-newline": [
281
+ "error",
282
+ {
283
+ allowAllPropertiesOnSameLine: true
284
+ }
285
+ ],
286
+ "eslint-js/max-len": [
287
+ "error",
288
+ {
289
+ code: 160,
290
+ ignoreUrls: true,
291
+ ignoreStrings: true,
292
+ ignoreTemplateLiterals: true,
293
+ ignoreRegExpLiterals: true,
294
+ ignoreComments: true
295
+ }
296
+ ]
297
+ };
298
+ var presetReactRules = {
299
+ "react/display-name": "error",
300
+ "react/jsx-no-comment-textnodes": "error",
301
+ "react/jsx-no-target-blank": "error",
302
+ "react/no-unknown-property": "error",
303
+ "react/button-has-type": "error",
304
+ "react/jsx-boolean-value": "error",
305
+ "react/jsx-curly-brace-presence": "error",
306
+ "react/jsx-fragments": "error",
307
+ "react/jsx-no-useless-fragment": "error",
308
+ "react/jsx-pascal-case": "error",
309
+ "react/self-closing-comp": "error",
310
+ "react-perf/jsx-no-new-object-as-prop": "off",
311
+ "react-perf/jsx-no-new-array-as-prop": "off",
312
+ "react-perf/jsx-no-new-function-as-prop": "off",
313
+ "react-perf/jsx-no-jsx-as-prop": "off",
314
+ "react-extra/jsx-sort-props": [
315
+ "error",
316
+ {
317
+ ignoreCase: true,
318
+ callbacksLast: true,
319
+ shorthandFirst: true,
320
+ multiline: "ignore",
321
+ locale: "auto"
322
+ }
323
+ ],
324
+ "react-extra/jsx-first-prop-new-line": ["error", "multiline"],
325
+ "react-extra/jsx-max-props-per-line": [
326
+ "error",
327
+ {
328
+ when: "multiline",
329
+ maximum: 1
330
+ }
331
+ ],
332
+ "react-extra/jsx-indent-props": ["error", 2],
333
+ "react-extra/jsx-closing-bracket-location": ["error", "line-aligned"],
334
+ "react-extra/jsx-wrap-multilines": [
335
+ "error",
336
+ {
337
+ declaration: "parens-new-line",
338
+ assignment: "parens-new-line",
339
+ return: "parens-new-line",
340
+ arrow: "parens-new-line",
341
+ condition: "parens-new-line",
342
+ logical: "parens-new-line",
343
+ prop: "parens-new-line"
344
+ }
345
+ ],
346
+ "react-extra/jsx-curly-newline": ["error", "consistent"],
347
+ "react-extra/jsx-no-leaked-render": "error",
348
+ "react-extra/no-unstable-nested-components": "error",
349
+ "react-extra/function-component-definition": [
350
+ "error",
351
+ {
352
+ namedComponents: "function-declaration"
353
+ }
354
+ ],
355
+ "react-extra/hook-use-state": "error",
356
+ "react-extra/no-deprecated": "error",
357
+ "react-extra/jsx-uses-vars": "off",
358
+ "react-extra/require-render-return": "error",
359
+ "svg-jsx/camel-case-colon": "error",
360
+ "svg-jsx/no-style-string": "error"
361
+ };
362
+ var presetEffectsRules = {
363
+ "react-you-might-not-need-an-effect/no-adjust-state-on-prop-change": "error",
364
+ "react-you-might-not-need-an-effect/no-reset-all-state-on-prop-change": "error",
365
+ "react-you-might-not-need-an-effect/no-event-handler": "error",
366
+ "react-you-might-not-need-an-effect/no-pass-live-state-to-parent": "error",
367
+ "react-you-might-not-need-an-effect/no-pass-data-to-parent": "error",
368
+ "react-you-might-not-need-an-effect/no-initialize-state": "error",
369
+ "react-you-might-not-need-an-effect/no-chain-state-updates": "error",
370
+ "react-you-might-not-need-an-effect/no-derived-state": "error"
371
+ };
372
+ var presetTailwindRules = {
373
+ "tailwindcss/enforce-canonical": "error",
374
+ "tailwindcss/enforce-sort-order": "error",
375
+ "tailwindcss/enforce-shorthand": "error",
376
+ "tailwindcss/no-conflicting-classes": "error",
377
+ "tailwindcss/no-duplicate-classes": "error",
378
+ "tailwindcss/no-unknown-classes": "off",
379
+ "tailwindcss/no-unnecessary-whitespace": "error",
380
+ "tailwindcss/no-deprecated-classes": "error"
381
+ };
382
+ var presetImportRules = {
383
+ "import/first": "error",
384
+ "import/no-absolute-path": "error",
385
+ "import/no-mutable-exports": "error",
386
+ "import/no-self-import": "error",
387
+ "import/no-duplicates": "error",
388
+ "import/no-named-as-default": "error",
389
+ "import/no-named-as-default-member": "error",
390
+ "import/consistent-type-specifier-style": "error",
391
+ "unused-imports/no-unused-imports": "error",
392
+ "import-extra/no-useless-path-segments": "error",
393
+ "import-extra/no-relative-packages": "error",
394
+ "import-extra/newline-after-import": "error",
395
+ "perfectionist/sort-named-imports": [
396
+ "error",
397
+ {
398
+ type: "natural",
399
+ ignoreCase: true
400
+ }
401
+ ]
402
+ };
403
+ var presetNamingRules = {
404
+ "unicorn/filename-case": [
405
+ "error",
406
+ {
407
+ case: "kebabCase"
408
+ }
409
+ ]
410
+ };
411
+ var presetTestRules = {
412
+ "jest/expect-expect": "off",
413
+ "jest/no-conditional-expect": "off",
414
+ "jest/no-disabled-tests": "off",
415
+ "jest/no-export": "off",
416
+ "jest/no-standalone-expect": "off",
417
+ "jest/require-to-throw-message": "off",
418
+ "vitest/no-conditional-expect": "error",
419
+ "vitest/require-mock-type-parameters": "error"
420
+ };
421
+ var presetModuleMockingApis = ["*.mock", "*.doMock", "*.unstable_mockModule"];
422
+ var presetTestMockingApis = {
423
+ modules: presetModuleMockingApis,
424
+ strict: [...presetModuleMockingApis, "*.spyOn", "*.stubGlobal", "*.stubEnv"]
425
+ };
426
+ var presetTestMockingAllow = {
427
+ modules: [],
428
+ strict: [{ files: ["**/*.driver.test.ts"], apis: ["*"] }]
429
+ };
430
+ var presetStrictMockingMessage = "Inject a fake driver through the dependencies of the code under test instead of mocking or stubbing.";
431
+ var presetNextHandlerMethods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
432
+ var presetNextSegmentConfig = [
433
+ "generateStaticParams",
434
+ "dynamic",
435
+ "dynamicParams",
436
+ "revalidate",
437
+ "fetchCache",
438
+ "runtime",
439
+ "preferredRegion",
440
+ "maxDuration"
441
+ ];
442
+ var presetNextSegmentExports = [
443
+ "generateMetadata",
444
+ "metadata",
445
+ "generateViewport",
446
+ "viewport",
447
+ ...presetNextSegmentConfig
448
+ ];
449
+ var presetExportPrefix = {
450
+ stem: "before-first-dot",
451
+ trailingRoles: ["constants", "utils", "handler", "service", "types"],
452
+ roleSeparators: [".", "-"],
453
+ normalize: "remove-separators",
454
+ allowPattern: "^(make[A-Z]|\\*$)"
455
+ };
456
+ var presetGeneratedFiles = [
457
+ "**/*.generated.*",
458
+ "**/*.gen.*",
459
+ "**/*.d.ts",
460
+ "**/routes.tree.ts",
461
+ "**/routeTree.gen.ts",
462
+ "**/paraglide/**"
463
+ ];
464
+
465
+ // src/utils/helpers/preset.ts
466
+ function presetScopes(roots, suffix = `**/${presetExtensions}`) {
467
+ return roots.map((root) => root === "." ? suffix : `${root}/${suffix}`);
468
+ }
469
+ function presetOverride(files, rules, excludeFiles = []) {
470
+ return {
471
+ files,
472
+ excludeFiles: [...presetGeneratedFiles, ...excludeFiles],
473
+ rules
474
+ };
475
+ }
476
+ function presetRuleSeverities(rules, severities) {
477
+ const result = structuredClone(rules);
478
+ for (const [name, level] of Object.entries(severities)) {
479
+ const value = result[name];
480
+ const current = Array.isArray(value) ? value[0] : value;
481
+ if (value === undefined || current === "off" || current === "allow" || current === 0) {
482
+ continue;
483
+ }
484
+ result[name] = Array.isArray(value) && level !== "off" ? [level, ...value.slice(1)] : level;
485
+ }
486
+ return result;
487
+ }
488
+ function presetPackageName(root, directory) {
489
+ try {
490
+ const manifest = JSON.parse(readFileSync(resolve(root, directory, "package.json"), "utf8"));
491
+ return typeof manifest.name === "string" ? manifest.name : undefined;
492
+ } catch {
493
+ return;
494
+ }
495
+ }
496
+ function presetSeverity(rules, level) {
497
+ const result = structuredClone(rules);
498
+ for (const [name, value] of Object.entries(result)) {
499
+ const current = Array.isArray(value) ? value[0] : value;
500
+ if (current === "off" || current === "allow" || current === 0) {
501
+ continue;
502
+ }
503
+ result[name] = Array.isArray(value) ? [level, ...value.slice(1)] : level;
504
+ }
505
+ return result;
506
+ }
507
+
508
+ // src/configs/source.ts
509
+ function presetSourceConfig(context) {
510
+ const { options, web, scripts, app, appFiles, backendFiles, ui, safetyFiles, publicEntrypoints, baseImports } = context;
511
+ const policies = {};
512
+ const { semi = "always", quotes = "single" } = options.formatting ?? {};
513
+ policies.formatting = [
514
+ presetOverride([`**/${presetExtensions}`], {
515
+ ...presetFormattingRules,
516
+ "eslint-js/semi": ["error", semi],
517
+ "eslint-js/quotes": ["error", quotes, { avoidEscape: true }]
518
+ })
519
+ ];
520
+ policies.statementLayout = [
521
+ presetOverride([`**/${presetExtensions}`], {
522
+ "arch/padding-between-statements": ["error", { multilineVariables: true }],
523
+ "arch/object-multiline": ["error", { minProperties: 3, scope: "call-args" }],
524
+ "arch/call-array-multiline": ["error", { callees: ["Promise.all"], minElements: 2 }],
525
+ "arch/key-value-same-line": "error"
526
+ })
527
+ ];
528
+ policies.imports = [
529
+ presetOverride([`**/${presetExtensions}`], {
530
+ ...presetImportRules,
531
+ "arch/prefer-namespace-type-import": ["error", { max: 3 }],
532
+ "no-restricted-imports": ["error", { patterns: baseImports }],
533
+ "import-extra/no-relative-packages": "error",
534
+ "perfectionist/sort-imports": [
535
+ "error",
536
+ {
537
+ type: "natural",
538
+ ignoreCase: true,
539
+ newlinesBetween: 0,
540
+ environment: "bun",
541
+ internalPattern: options.imports?.internalSortPatterns ?? [
542
+ "^@/.*",
543
+ "^#.*",
544
+ "^~icons/.*",
545
+ ...publicEntrypoints.map((path) => `^${path.split("/").slice(0, 2).join("/")}/`)
546
+ ],
547
+ groups: [["builtin", "external"], "internal", ["parent", "sibling", "index"], "type"]
548
+ }
549
+ ]
550
+ })
551
+ ];
552
+ policies.comments = [
553
+ presetOverride(appFiles, {
554
+ "arch/no-comments": ["error", { allowWhy: false, allowPatterns: ["^\\s*SAFETY: "] }]
555
+ }, ui),
556
+ presetOverride(presetScopes(app, "**/*.{types,constants}.ts"), {
557
+ "arch/no-comments": ["error", { allowWhy: false }]
558
+ }),
559
+ presetOverride([`**/${presetExtensions}`], {
560
+ "modules/reasoned-directives": "error"
561
+ })
562
+ ];
563
+ policies.trivialFunctions = [
564
+ presetOverride(appFiles, {
565
+ "arch/no-trivial-functions": ["error", { mode: "precise", allowAsync: false, allowCallees: [] }]
566
+ }, ui)
567
+ ];
568
+ policies.trivialFunctions.push(presetOverride([...presetScopes(web, "services/**/*.{client,server,rsc}.ts"), ...presetScopes(web, "services/**/*.rsc.tsx")], {
569
+ "arch/no-trivial-functions": [
570
+ "error",
571
+ { mode: "precise", allowAsync: false, allowCallees: [], checkServiceMethods: true }
572
+ ]
573
+ }, ui));
574
+ policies.mutableState = [
575
+ presetOverride(safetyFiles, { "arch/no-module-mutable-state": ["error", { kinds: ["let", "var"] }] }, ui)
576
+ ];
577
+ policies.backend = [
578
+ presetOverride(backendFiles, { "modules/backend-switch": "error" }, options.cliFiles),
579
+ presetOverride([`**/${presetExtensions}`], { "no-console": "error" }, [
580
+ ...presetScopes(scripts, "**"),
581
+ ...options.cliFiles ?? []
582
+ ]),
583
+ presetOverride(safetyFiles, { "modules/double-negation": "error" }, ui)
584
+ ];
585
+ return policies;
586
+ }
587
+
588
+ // src/configs/types.ts
589
+ function presetTypesConfig(context) {
590
+ const {
591
+ options,
592
+ web,
593
+ backend,
594
+ app,
595
+ appFiles,
596
+ webFiles,
597
+ services,
598
+ utilities,
599
+ types,
600
+ ui,
601
+ testFiles,
602
+ safetyFiles,
603
+ components,
604
+ hooks,
605
+ publicEntrypoints,
606
+ tanstackWeb,
607
+ nextWeb
608
+ } = context;
609
+ const schemaOwners = backend.length ? backend : nextWeb;
610
+ const runtimeFreeTypes = backend.length ? web : tanstackWeb;
611
+ const colocatedTypes = options.modules?.colocatedTypeFiles ?? [];
612
+ const policies = {};
613
+ policies.typePlacement = [
614
+ presetOverride(appFiles, { "arch/no-type-declarations": "error" }, [
615
+ ...types,
616
+ ...colocatedTypes,
617
+ ...components,
618
+ ...hooks,
619
+ ...services,
620
+ ...ui,
621
+ ...testFiles
622
+ ]),
623
+ presetOverride(services, { "modules/service-types": "error" }, [...utilities, ...testFiles]),
624
+ presetOverride([...components, ...hooks], {
625
+ "modules/local-type-alias": "error",
626
+ "modules/pascal-interface": "error",
627
+ "arch/declaration-name": [
628
+ "error",
629
+ {
630
+ kinds: ["interface"],
631
+ stem: "before-first-dot",
632
+ normalize: "remove-separators"
633
+ }
634
+ ]
635
+ }, [...ui, ...testFiles]),
636
+ ...options.react?.componentProps === false ? [] : [presetOverride(components, { "arch/component-props": ["error", { filePrefix: true }] }, [...ui, ...testFiles])],
637
+ presetOverride(presetScopes(app, "**/*.types.ts"), {
638
+ "arch/no-restricted-files": ["error", { message: "Domain types belong in src/types." }]
639
+ }, [...types, ...colocatedTypes, ...ui]),
640
+ presetOverride(presetScopes(app, "**/*.{utils,constants}.ts"), {
641
+ "arch/no-type-declarations": "error"
642
+ }, [...ui, ...testFiles]),
643
+ presetOverride([...presetScopes(schemaOwners, "types/**/*.types.ts"), ...colocatedTypes, ...presetScopes(app, "**/*.constants.ts")], {
644
+ "arch/no-top-level-functions": ["error", { banReExports: false }]
645
+ }),
646
+ ...runtimeFreeTypes.length ? [
647
+ presetOverride(presetScopes(runtimeFreeTypes, "types/**/*.types.ts"), {
648
+ "arch/no-runtime-in-types": ["error", { runtimeImports: "ban", allowImportSources: [], banReExports: true }]
649
+ })
650
+ ] : []
651
+ ];
652
+ policies.typeSafety = [
653
+ presetOverride(safetyFiles, {
654
+ "typescript/no-explicit-any": "error",
655
+ "typescript/ban-ts-comment": [
656
+ "error",
657
+ {
658
+ "ts-ignore": true,
659
+ "ts-nocheck": true,
660
+ "ts-expect-error": true,
661
+ "ts-check": false
662
+ }
663
+ ],
664
+ "modules/no-unknown": "error",
665
+ "modules/shape-suffix": "error",
666
+ "arch/no-imported-type-alias": "error",
667
+ "arch/no-literal-in": "error",
668
+ "arch/no-promise-all-mutation": "error",
669
+ "arch/no-restricted-constructor": ["error", { constructors: ["Error"], checkCalls: true }],
670
+ "dillon-anti-slop/no-chained-type-assertions": "error",
671
+ "dillon-anti-slop/no-conditional-empty-object-spread": "error",
672
+ "dillon-anti-slop/no-known-value-widening": "error",
673
+ "dillon-anti-slop/no-reflect-apply": "error",
674
+ "dillon-anti-slop/no-reflect-get": "error",
675
+ "dillon-anti-slop/no-runtime-typeof": "error",
676
+ "dillon-anti-slop/no-unknown-parameters": "error",
677
+ "dillon-anti-slop/no-unknown-returns": "error",
678
+ "dillon-anti-slop/no-unknown-type-aliases": "error",
679
+ "dillon-anti-slop/no-unsafe-dictionary-type": "error",
680
+ "dillon-anti-slop/no-widen-then-assert": "error",
681
+ "dillon-anti-slop/require-safety-comment-for-type-assertion": "error"
682
+ })
683
+ ];
684
+ policies.schemas = [
685
+ presetOverride(appFiles, {
686
+ "arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
687
+ }, [...presetScopes(schemaOwners, "types/**"), ...colocatedTypes, ...ui]),
688
+ presetOverride(webFiles, {
689
+ "arch/no-rederive-schema": [
690
+ "error",
691
+ {
692
+ from: publicEntrypoints,
693
+ namespaces: ["z"],
694
+ operators: ["infer", "input"]
695
+ }
696
+ ]
697
+ }),
698
+ presetOverride(presetScopes(backend, "services/**/*.ts"), {
699
+ "arch/no-unescaped-like": [
700
+ "error",
701
+ {
702
+ methods: ["like", "ilike"],
703
+ operatorMethods: ["where"],
704
+ sanitizers: options.sql?.likeSanitizers ?? ["escapeLikeWildcards"],
705
+ allowSanitizedBindings: true
706
+ }
707
+ ]
708
+ }),
709
+ ...options.orpc?.publicProcedureFiles?.length ? [
710
+ presetOverride(options.orpc?.publicProcedureFiles, {
711
+ "arch/require-orpc-output": [
712
+ "error",
713
+ {
714
+ handlerMethod: "handler",
715
+ outputMethod: "output",
716
+ composers: options.orpc?.outputSchemaComposers ?? []
717
+ }
718
+ ]
719
+ })
720
+ ] : []
721
+ ];
722
+ return policies;
723
+ }
724
+
725
+ // src/configs/modules.ts
726
+ import { posix } from "node:path";
727
+ function presetModulesConfig(context) {
728
+ const { web, nextWeb, backend, app, services, utilities, ui, testFiles, components, hooks, options } = context;
729
+ const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
730
+ const migrations = options.modules?.migrationFiles ?? [];
731
+ const frontendEntries = [
732
+ ...presetScopes(web, "services/**/*.{client,server,rsc}.ts"),
733
+ ...presetScopes(web, "services/**/*.rsc.tsx")
734
+ ];
735
+ const policies = {};
736
+ policies.fileLayout = [
737
+ ...context.folders.flatMap(({ path, folder, mode }) => [
738
+ presetOverride([
739
+ mode === "flat" ? `${path}/*/**/${presetExtensions}` : `${path}/${presetExtensions}`,
740
+ ...mode === "domain" ? [`${path}/*/*/**/${presetExtensions}`] : []
741
+ ], {
742
+ "arch/no-restricted-files": [
743
+ "error",
744
+ {
745
+ message: mode === "flat" ? "This folder uses a flat layout; subfolders are forbidden." : "This folder uses exactly one owning domain directory; loose and deeper files are forbidden."
746
+ }
747
+ ]
748
+ }, migrations),
749
+ ...mode === "domain" ? [
750
+ presetOverride([`${path}/*/${presetExtensions}`], {
751
+ "arch/folder-prefix": [
752
+ "error",
753
+ {
754
+ singularize: folder === "components" ? "trailing-s" : "none",
755
+ separators: [".", "-"],
756
+ ...folder === "hooks" ? { stripPrefixes: ["use-"] } : {}
757
+ }
758
+ ]
759
+ })
760
+ ] : []
761
+ ]),
762
+ presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), `components/*/*/**/${presetExtensions}`), {
763
+ "arch/no-restricted-files": ["error", { message: "Components allow one group folder." }]
764
+ }, ui),
765
+ presetOverride([...presetScopes(web, "components/**/*.ts"), ...presetScopes(web, "hooks/**/*.tsx")], {
766
+ "arch/no-restricted-files": ["error", { message: "Components use TSX; hooks use .ts files." }]
767
+ }, ui),
768
+ presetOverride(presetScopes(web, "**/use-*.{ts,tsx}"), {
769
+ "arch/no-restricted-files": ["error", { message: "Hooks belong in src/hooks." }]
770
+ }, [...presetScopes(web, "hooks/**"), ...ui]),
771
+ presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
772
+ "arch/no-restricted-files": [
773
+ "error",
774
+ {
775
+ message: "Frontend services use .client.ts, .server.ts or .rsc.{ts,tsx}; shared domain helpers use .utils.ts and values use .constants.ts."
776
+ }
777
+ ]
778
+ }, [
779
+ "**/*.{client,server,rsc,utils,constants}.ts",
780
+ "**/*.rsc.tsx",
781
+ ...presetScopes(nextWeb, "services/**/*.action.ts"),
782
+ ...conceptFiles,
783
+ ...options.modules?.colocatedTypeFiles ?? []
784
+ ])
785
+ ];
786
+ policies.serviceStructure = [
787
+ presetOverride(services, {
788
+ "modules/service-functions": "error",
789
+ "arch/no-inline-types": [
790
+ "error",
791
+ {
792
+ parameters: true,
793
+ returns: true,
794
+ functionTypes: true,
795
+ minMembers: 1
796
+ }
797
+ ]
798
+ }, [...utilities, ...testFiles, ...conceptFiles, ...migrations]),
799
+ presetOverride(frontendEntries, {
800
+ "modules/service-functions": ["error", { frontend: true }]
801
+ }, testFiles),
802
+ ...conceptFiles.map((pattern) => presetOverride(presetScopes(app, `services/${pattern}`), {
803
+ "modules/service-functions": "off",
804
+ "arch/export-file-prefix": [
805
+ "error",
806
+ {
807
+ stem: "full-basename",
808
+ allFunctions: true
809
+ }
810
+ ]
811
+ }, testFiles)),
812
+ presetOverride([
813
+ ...presetScopes(app, "services/**/*-{action,query}.*.ts"),
814
+ ...presetScopes(web, "services/**/*-{action,query}.*.rsc.tsx")
815
+ ], {
816
+ "modules/service-functions": [
817
+ "error",
818
+ {
819
+ allowLocalHelpers: true,
820
+ singleExport: true,
821
+ message: "Action/query files export exactly one named operation. Keep helpers used only by this operation private inside its function; do not export them. Move a helper to the module's .utils.ts only when it is genuinely shared, not merely to satisfy lint."
822
+ }
823
+ ]
824
+ }, testFiles),
825
+ presetOverride(presetScopes(backend, "services/**/*.ts"), { "modules/domain-constants": "error" }, [
826
+ ...utilities,
827
+ ...testFiles
828
+ ]),
829
+ presetOverride(presetScopes(web, "services/**/*.{ts,tsx}"), {
830
+ "modules/domain-constants": ["error", { includeData: true }]
831
+ }, ["**/*.constants.ts", ...testFiles]),
832
+ presetOverride(frontendEntries, {
833
+ "modules/domain-constants": ["error", { includeData: true, allowServiceMethods: true }]
834
+ }, testFiles),
835
+ presetOverride([...presetScopes(backend, "services/**/*-{action,query}.*.ts"), ...frontendEntries], { "arch/require-object-params": ["error", { maxParams: 2 }] }, testFiles),
836
+ presetOverride(frontendEntries, {
837
+ "arch/require-object-params": ["error", { maxParams: 2, serviceMethods: true }]
838
+ }, testFiles),
839
+ presetOverride(presetScopes(backend, "services/**/*.service.ts"), {
840
+ "arch/no-extra-exports": [
841
+ "error",
842
+ {
843
+ names: ["make{Domain}Service", "{Domain}Service"],
844
+ domainStem: "before-first-dot",
845
+ allowTypeExports: false
846
+ }
847
+ ]
848
+ }, testFiles),
849
+ presetOverride(presetScopes(app, "services/**/{actions,queries}/**/*.ts"), {
850
+ "arch/no-restricted-files": [
851
+ "error",
852
+ { message: "Use domain-action.name.ts or domain-query.name.ts." }
853
+ ]
854
+ })
855
+ ];
856
+ policies.naming = [
857
+ presetOverride([`**/${presetExtensions}`], presetNamingRules),
858
+ presetOverride(presetScopes(app, "types/**/*.ts"), {
859
+ "arch/filename-match": [
860
+ "error",
861
+ {
862
+ pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*[.]types[.]ts$",
863
+ message: "Use domain.types.ts filenames."
864
+ }
865
+ ]
866
+ }, ["**/*.constants.ts"]),
867
+ presetOverride(presetScopes(app, "**/*.{constants,utils,handler,service,types,client,server,rsc}.ts"), { "arch/export-file-prefix": ["error", presetExportPrefix] }, [...ui, ...presetScopes(backend, "entry.*.ts")]),
868
+ presetOverride(frontendEntries, {
869
+ "arch/export-file-prefix": [
870
+ "error",
871
+ { ...presetExportPrefix, allFunctions: true, serviceMethods: true, allowPattern: "^\\*$" }
872
+ ]
873
+ }, testFiles),
874
+ presetOverride(presetScopes(web, "services/**/*.constants.ts"), {
875
+ "arch/export-file-prefix": [
876
+ "error",
877
+ { ...presetExportPrefix, allDeclarations: true, allowPattern: "^\\*$" }
878
+ ]
879
+ }, testFiles),
880
+ presetOverride(presetScopes(app, "**/*.utils.ts"), {
881
+ "arch/export-file-prefix": [
882
+ "error",
883
+ {
884
+ ...presetExportPrefix,
885
+ allFunctions: true,
886
+ allowPattern: "^\\*$"
887
+ }
888
+ ]
889
+ }, ui),
890
+ presetOverride(hooks, {
891
+ "arch/filename-match": [
892
+ "error",
893
+ {
894
+ pattern: "^use-[a-z0-9]+(?:-[a-z0-9]+)*[.]ts$",
895
+ message: "Hooks use use-domain.ts filenames."
896
+ }
897
+ ],
898
+ "arch/export-file-prefix": "error",
899
+ "arch/export-name-pattern": ["error", { pattern: "^use[A-Z]", ignoreTypeExports: true }]
900
+ }),
901
+ presetOverride(components, {
902
+ "arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true, trailingRoles: ["illustration"] }]
903
+ }, [...ui, ...testFiles]),
904
+ presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), "components/**/*.tsx"), {
905
+ "arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
906
+ }, [...ui, ...testFiles]),
907
+ ...["action", "query"].map((role) => presetOverride([
908
+ ...presetScopes(app, `services/**/*-${role}.*.ts`),
909
+ ...presetScopes(web, `services/**/*-${role}.*.rsc.tsx`)
910
+ ], {
911
+ "arch/filename-match": [
912
+ "error",
913
+ {
914
+ pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.ts$`,
915
+ message: "Use domain-action.name.ts or domain-query.name.ts."
916
+ }
917
+ ],
918
+ "arch/export-file-prefix": [
919
+ "error",
920
+ {
921
+ ...presetExportPrefix,
922
+ trailingRoles: [role],
923
+ allFunctions: true,
924
+ allowPattern: "^\\*$"
925
+ }
926
+ ],
927
+ "arch/filename-export-name": [
928
+ "error",
929
+ {
930
+ file: `{domain}-${role}.{name}.ts`,
931
+ export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
932
+ placeholderPattern: "[a-z0-9-]+",
933
+ camelCase: true
934
+ }
935
+ ]
936
+ })),
937
+ ...["action", "query"].flatMap((role) => ["client.ts", "server.ts", "rsc.ts", "rsc.tsx"].map((suffix) => presetOverride(presetScopes(web, `services/**/*-${role}.*.${suffix}`), {
938
+ "arch/filename-match": [
939
+ "error",
940
+ {
941
+ pattern: `^[a-z0-9]+(?:-[a-z0-9]+)*-${role}\\.[a-z0-9]+(?:-[a-z0-9]+)*\\.${suffix.replace(".", "\\.")}$`,
942
+ message: "Frontend operations retain a client, server or rsc runtime suffix."
943
+ }
944
+ ],
945
+ "arch/filename-export-name": [
946
+ "error",
947
+ {
948
+ file: `{domain}-${role}.{name}.${suffix}`,
949
+ export: `{domain}${role === "action" ? "Action" : "Query"}{Name}`,
950
+ placeholderPattern: "[a-z0-9-]+",
951
+ camelCase: true
952
+ }
953
+ ]
954
+ }))),
955
+ presetOverride(presetScopes(web, "services/**/*.rsc.{ts,tsx}"), {
956
+ "arch/export-name-pattern": ["error", { pattern: "^[a-z][a-z0-9]*Rsc[A-Z][a-zA-Z0-9]*$" }]
957
+ }, ["**/*-{action,query}.*.rsc.{ts,tsx}"])
958
+ ];
959
+ return policies;
960
+ }
961
+
962
+ // src/utils/helpers/mocking.ts
963
+ function mockingApiPatternsAssert(patterns) {
964
+ if (patterns.some((pattern) => !/^(?:\*|(?:vi|jest|\*)\.(?:[A-Za-z_$][\w$]*|\*))$/.test(pattern)))
965
+ throw new Error("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
966
+ }
967
+
968
+ // src/configs/tests.ts
969
+ function presetTestsConfig(context) {
970
+ const { backendRoots, testFiles, baseImports, options } = context;
971
+ const profile = options.tests?.profile ?? "fixtures";
972
+ if (profile !== "standard" && profile !== "fixtures")
973
+ throw new Error("Unknown test profile.");
974
+ const testFunctions = ["test", "it", ...options.tests?.additionalTestFunctions ?? []];
975
+ const assertionFunctions = options.tests?.additionalAssertionFunctions ?? [];
976
+ if (testFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*$/.test(name)))
977
+ throw new Error("Additional test functions must be literal identifiers or dotted names.");
978
+ if (assertionFunctions.some((name) => !/^[A-Za-z_$][\w$]*(?:\.[\w$*]+)*$/.test(name)))
979
+ throw new Error("Assertion functions must be rooted dotted names or patterns.");
980
+ const mocking = typeof options.tests?.mocking === "string" ? { preset: options.tests.mocking } : options.tests?.mocking ?? {};
981
+ const mockingPreset = mocking.preset ?? "modules";
982
+ if (mockingPreset !== "modules" && mockingPreset !== "strict")
983
+ throw new Error("Unknown test mocking preset.");
984
+ const mockingDeny = [...mocking.deny ?? [], ...presetTestMockingApis[mockingPreset]].map((entry) => {
985
+ if (typeof entry !== "string")
986
+ return entry;
987
+ return mockingPreset === "strict" ? { api: entry, message: presetStrictMockingMessage } : entry;
988
+ });
989
+ const mockingAllow = [...presetTestMockingAllow[mockingPreset], ...mocking.allow ?? []].sort((left, right) => Number(left.apis.includes("*")) - Number(right.apis.includes("*")));
990
+ mockingApiPatternsAssert([
991
+ ...mockingDeny.map((entry) => typeof entry === "string" ? entry : entry.api),
992
+ ...mockingAllow.flatMap((entry) => entry.apis)
993
+ ]);
994
+ const mockingCustomized = mockingPreset !== "modules" || (mocking.deny ?? []).length > 0;
995
+ const mockingRule = (allow) => [
996
+ "error",
997
+ { deny: mockingDeny, allow: ["*.fn", ...allow], checkDestructured: mockingCustomized }
998
+ ];
999
+ const policies = {};
1000
+ policies.tests = [
1001
+ presetOverride(testFiles, {
1002
+ ...presetTestRules,
1003
+ "dillon-anti-slop/no-module-mocking": mockingCustomized ? mockingRule([]) : "error",
1004
+ "modules/test-modifiers": [
1005
+ "error",
1006
+ { additionalTestFunctions: options.tests?.additionalTestFunctions ?? [] }
1007
+ ],
1008
+ "modules/test-assertions": [
1009
+ "error",
1010
+ {
1011
+ additionalTestFunctions: options.tests?.additionalTestFunctions ?? [],
1012
+ additionalAssertionFunctions: assertionFunctions
1013
+ }
1014
+ ],
1015
+ "arch/test-title-pattern": [
1016
+ "error",
1017
+ { callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
1018
+ ]
1019
+ }),
1020
+ presetOverride(presetScopes(backendRoots, "tests/**/*.ts"), {
1021
+ "modules/concurrent-db": "error",
1022
+ ...profile === "fixtures" ? {
1023
+ "no-restricted-imports": [
1024
+ "error",
1025
+ {
1026
+ patterns: [
1027
+ ...baseImports,
1028
+ {
1029
+ group: ["**/helpers/**"],
1030
+ message: "Use named fixtures or tests/support."
1031
+ }
1032
+ ]
1033
+ }
1034
+ ]
1035
+ } : {}
1036
+ }),
1037
+ ...mockingAllow.map((entry) => presetOverride(entry.files, {
1038
+ "dillon-anti-slop/no-module-mocking": entry.apis.includes("*") ? "off" : mockingRule(entry.apis)
1039
+ }))
1040
+ ];
1041
+ return policies;
1042
+ }
1043
+
1044
+ // src/configs/react.ts
1045
+ function presetReactConfig(context) {
1046
+ const { options, web, tanstackWeb, nextWeb, webFiles, ui, testFiles, components, css } = context;
1047
+ const policies = {};
1048
+ if (web.length) {
1049
+ policies.react = [
1050
+ presetOverride(webFiles, {
1051
+ ...presetReactRules,
1052
+ ...options.react?.compiler === false ? {
1053
+ "react-perf/jsx-no-new-object-as-prop": "error",
1054
+ "react-perf/jsx-no-new-array-as-prop": "error",
1055
+ "react-perf/jsx-no-new-function-as-prop": "error",
1056
+ "react-perf/jsx-no-jsx-as-prop": "error"
1057
+ } : {},
1058
+ "arch/jsx-attributes-multiline": ["error", { minAttributes: 3 }]
1059
+ }, ui),
1060
+ presetOverride(components, {
1061
+ "arch/no-file-level-helpers": ["error", { detectComponents: true, hookPattern: "^use[A-Z]" }]
1062
+ }, [...ui, ...testFiles])
1063
+ ];
1064
+ }
1065
+ if (web.length) {
1066
+ policies.effects = [presetOverride(webFiles, { ...presetEffectsRules, "modules/empty-effect": "error" }, ui)];
1067
+ }
1068
+ const nextPage = { bannedHooks: ["useState", "useEffect", "useMutation"], inspectExports: true };
1069
+ const routes = [
1070
+ ...tanstackWeb.length ? [
1071
+ presetOverride(presetScopes(tanstackWeb, "routes/**/*.{ts,tsx}"), {
1072
+ "arch/route-surface": [
1073
+ "error",
1074
+ {
1075
+ exportName: "Route",
1076
+ bannedHooks: ["useState", "useEffect", "useMutation"],
1077
+ banIntrinsicJsx: true
1078
+ }
1079
+ ]
1080
+ }, testFiles),
1081
+ presetOverride(presetScopes(tanstackWeb, "routes/handlers/**/*.ts"), {
1082
+ "arch/no-file-level-helpers": [
1083
+ "error",
1084
+ {
1085
+ detectComponents: false,
1086
+ message: "Nest this helper in its owning route handler callback, or move genuine domain logic to a service. Do not create a component, hook or public helper to bypass this rule."
1087
+ }
1088
+ ]
1089
+ })
1090
+ ] : [],
1091
+ ...nextWeb.length ? [
1092
+ presetOverride(presetScopes(nextWeb, "app/**/{page,template,default,not-found,forbidden,unauthorized,loading}.tsx"), {
1093
+ "arch/route-surface": [
1094
+ "error",
1095
+ { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: true, ...nextPage }
1096
+ ]
1097
+ }, [...testFiles, ...presetScopes(nextWeb, "app/not-found.tsx")]),
1098
+ presetOverride([...presetScopes(nextWeb, "app/**/{layout,global-not-found}.tsx"), ...presetScopes(nextWeb, "app/not-found.tsx")], {
1099
+ "arch/route-surface": [
1100
+ "error",
1101
+ { exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: false, ...nextPage }
1102
+ ]
1103
+ }, testFiles),
1104
+ presetOverride(presetScopes(nextWeb, "app/**/route.ts"), {
1105
+ "arch/route-surface": [
1106
+ "error",
1107
+ {
1108
+ exportName: "GET",
1109
+ allowedExports: [...presetNextHandlerMethods.slice(1), ...presetNextSegmentConfig],
1110
+ bannedHooks: [],
1111
+ banIntrinsicJsx: false
1112
+ }
1113
+ ],
1114
+ "arch/no-file-level-helpers": [
1115
+ "error",
1116
+ {
1117
+ detectComponents: false,
1118
+ allowPattern: `^(${[...presetNextHandlerMethods, ...presetNextSegmentConfig].join("|")})$`,
1119
+ message: "Nest this helper in its owning route handler, or move genuine domain logic to a service. Do not create a component, hook or public helper to bypass this rule."
1120
+ }
1121
+ ]
1122
+ })
1123
+ ] : []
1124
+ ];
1125
+ if (routes.length) {
1126
+ policies.routes = routes;
1127
+ }
1128
+ if (web.length) {
1129
+ policies.forms = [
1130
+ presetOverride(webFiles, {
1131
+ "arch/require-paired-call": [
1132
+ "error",
1133
+ {
1134
+ when: "useForm",
1135
+ require: options.forms?.schemaResolver ?? "standardSchemaResolver"
1136
+ }
1137
+ ]
1138
+ }, ui)
1139
+ ];
1140
+ }
1141
+ if (web.length && options.react?.compiler !== false) {
1142
+ policies.memoization = [presetOverride(webFiles, { "modules/memoization": "error" }, ui)];
1143
+ }
1144
+ if (web.length) {
1145
+ policies.rpcClientOwnership = [
1146
+ presetOverride(webFiles, {
1147
+ "arch/no-restricted-token": [
1148
+ "error",
1149
+ {
1150
+ restrictions: [
1151
+ {
1152
+ token: "RouterClient",
1153
+ allowIn: [...options.orpc?.clientOwnerFile ? [options.orpc?.clientOwnerFile] : context.rpcClients]
1154
+ },
1155
+ {
1156
+ token: "createContext",
1157
+ allowPathPatterns: [...presetScopes(web, "context/"), ...presetScopes(web, "components/ui/")]
1158
+ }
1159
+ ]
1160
+ }
1161
+ ]
1162
+ })
1163
+ ];
1164
+ }
1165
+ if (Object.keys(css).length) {
1166
+ policies.tailwind = Object.entries(css).map(([path, entryPoint]) => presetOverride(presetScopes([path]), Object.fromEntries(Object.entries(presetTailwindRules).map(([rule, severity]) => [
1167
+ rule,
1168
+ rule === "tailwindcss/no-duplicate-classes" || rule === "tailwindcss/no-unnecessary-whitespace" ? severity : [severity, { entryPoint }]
1169
+ ]))));
1170
+ }
1171
+ if (Object.keys(css).length && options.shadcn !== false) {
1172
+ const allow = ["layout", "opacity", "truncate"];
1173
+ const padding = ["p", "px", "py", "ps", "pe", "pt", "pr", "pb", "pl"];
1174
+ const gap = ["gap", "gap-x", "gap-y"];
1175
+ const safeArea = padding.flatMap((group) => ["top", "right", "bottom", "left"].flatMap((side) => [
1176
+ `${group}-[env(safe-area-inset-${side})]`,
1177
+ ...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
1178
+ ]));
1179
+ const illustrationPalette = [
1180
+ "text-color",
1181
+ "bg-color",
1182
+ "gradient-from",
1183
+ "gradient-via",
1184
+ "gradient-to",
1185
+ "border-color",
1186
+ "divide-color",
1187
+ "outline-color",
1188
+ "ring-color",
1189
+ "ring-offset-color",
1190
+ "shadow",
1191
+ "shadow-color",
1192
+ "inset-shadow",
1193
+ "inset-shadow-color",
1194
+ "drop-shadow",
1195
+ "drop-shadow-color",
1196
+ "fill",
1197
+ "stroke",
1198
+ "bg-image",
1199
+ "bg-size",
1200
+ "bg-position",
1201
+ "mask-image",
1202
+ "blur",
1203
+ "saturate"
1204
+ ];
1205
+ const restyle = (base) => ({
1206
+ allow: base,
1207
+ contracts: [
1208
+ { pattern: "^(CardContent|PopoverContent)$", allow: [...base, ...padding] },
1209
+ { pattern: "^TabsContent$", allow: [...base, ...gap, ...padding] },
1210
+ { pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...base, ...gap] }
1211
+ ]
1212
+ });
1213
+ policies.shadcn = [
1214
+ presetOverride(webFiles, {
1215
+ "shadcn/no-restyle": ["error", restyle(allow)],
1216
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
1217
+ "shadcn/require-static-classes": "error",
1218
+ "modules/dynamic-classes": "error"
1219
+ }, ui),
1220
+ presetOverride(presetScopes(web, "components/**/*.illustration.tsx"), {
1221
+ "shadcn/no-restyle": ["error", restyle([...allow, ...illustrationPalette])],
1222
+ "shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea, ...illustrationPalette] }]
1223
+ }, ui),
1224
+ presetOverride(webFiles, {
1225
+ "shadcn/no-unknown-classes": ["error", { allow: [] }]
1226
+ }),
1227
+ presetOverride(presetScopes(web, "components/ui/sonner.tsx"), {
1228
+ "shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
1229
+ })
1230
+ ];
1231
+ }
1232
+ return policies;
1233
+ }
1234
+
1235
+ // src/configs/boundaries.ts
1236
+ import { resolve as resolve2 } from "node:path";
1237
+ function presetBoundariesConfig(context) {
1238
+ const { options, root, architecture, packages, web, tanstackWeb, nextWeb, backend, appFiles, publicEntrypoints, baseImports } = context;
1239
+ const runtimeBoundary = {
1240
+ web: web.map((path) => resolve2(root, path)),
1241
+ backend: backend.map((path) => resolve2(root, path)),
1242
+ aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve2(root, path)])),
1243
+ backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1244
+ const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1245
+ return name ? [name] : [];
1246
+ })
1247
+ };
1248
+ const policies = {};
1249
+ policies.boundaries = [
1250
+ presetOverride([...appFiles, ...presetScopes(packages, "**/" + presetExtensions)], {
1251
+ "modules/import-boundaries": [
1252
+ "error",
1253
+ {
1254
+ web: web.map((path) => resolve2(root, path)),
1255
+ backend: backend.map((path) => resolve2(root, path)),
1256
+ packages: packages.map((path) => resolve2(root, path)),
1257
+ fileRoles: options.modules?.customFileRoles ?? [],
1258
+ portableLib: true,
1259
+ aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [
1260
+ alias.replace(/\/$/, ""),
1261
+ resolve2(root, path)
1262
+ ])),
1263
+ appPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1264
+ const name = role === "web" || role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1265
+ return name ? [name] : [];
1266
+ }),
1267
+ backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
1268
+ const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
1269
+ return name ? [name] : [];
1270
+ }),
1271
+ publicEntrypoints
1272
+ }
1273
+ ]
1274
+ }),
1275
+ presetOverride([...presetScopes(tanstackWeb, "services/**/*.{ts,tsx}"), ...presetScopes(tanstackWeb, "lib/**/*.{ts,tsx}")], {
1276
+ "modules/tanstack-runtime": [
1277
+ "error",
1278
+ {
1279
+ ...runtimeBoundary,
1280
+ rpcClients: ((options.orpc?.clientOwnerFile) ? [options.orpc?.clientOwnerFile] : context.rpcClients).map((path) => resolve2(root, path)),
1281
+ serverImports: options.tanstackStart?.additionalServerOnlyImports ?? [],
1282
+ clientImports: options.tanstackStart?.additionalClientOnlyImports ?? [],
1283
+ allowComputedImportsIn: options.tanstackStart?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1284
+ }
1285
+ ]
1286
+ }),
1287
+ presetOverride([...presetScopes(nextWeb, "services/**/*.{ts,tsx}"), ...presetScopes(nextWeb, "lib/**/*.{ts,tsx}")], {
1288
+ "modules/next-runtime": [
1289
+ "error",
1290
+ {
1291
+ ...runtimeBoundary,
1292
+ serverImports: options.next?.additionalServerOnlyImports ?? [],
1293
+ clientImports: options.next?.additionalClientOnlyImports ?? [],
1294
+ allowComputedImportsIn: options.next?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
1295
+ }
1296
+ ]
1297
+ }),
1298
+ presetOverride(presetScopes(backend, "rpc/**/*.ts"), {
1299
+ "modules/rpc-database": "error",
1300
+ "no-restricted-imports": ["error", { paths: ["kysely"], patterns: baseImports }]
1301
+ })
1302
+ ];
1303
+ return policies;
1304
+ }
1305
+
1306
+ // src/configs/plugins.ts
1307
+ import { createRequire } from "node:module";
1308
+ var require2 = createRequire(import.meta.url);
1309
+ function presetPluginsConfig(context) {
1310
+ const { options, web, css } = context;
1311
+ const jsPlugins = [
1312
+ { name: "eslint-js", specifier: require2.resolve("oxlint-plugin-eslint") },
1313
+ {
1314
+ name: "import-extra",
1315
+ specifier: require2.resolve("eslint-plugin-import")
1316
+ },
1317
+ require2.resolve("@regru/eslint-plugin-prefer-early-return"),
1318
+ require2.resolve("eslint-plugin-unused-imports"),
1319
+ require2.resolve("eslint-plugin-perfectionist"),
1320
+ require2.resolve("eslint-plugin-tsdoc"),
1321
+ require2.resolve("@eslint-community/eslint-plugin-eslint-comments"),
1322
+ {
1323
+ name: "arch",
1324
+ specifier: require2.resolve("oxlint-plugin-arch")
1325
+ },
1326
+ {
1327
+ name: "dillon-anti-slop",
1328
+ specifier: require2.resolve("oxlint-plugin-arch/rules/dillon-anti-slop")
1329
+ },
1330
+ {
1331
+ name: "modules",
1332
+ specifier: require2.resolve("oxlint-plugin-arch/rules/module-policy")
1333
+ }
1334
+ ];
1335
+ const settings = {
1336
+ react: { version: "19" },
1337
+ "import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx", ".mts", ".cts"] } }
1338
+ };
1339
+ if (web.length) {
1340
+ jsPlugins.push({
1341
+ name: "react-extra",
1342
+ specifier: require2.resolve("eslint-plugin-react")
1343
+ }, require2.resolve("eslint-plugin-react-you-might-not-need-an-effect"), require2.resolve("eslint-plugin-svg-jsx"));
1344
+ }
1345
+ if (Object.keys(css).length && options.tailwind !== false) {
1346
+ jsPlugins.push(require2.resolve("oxlint-tailwindcss"));
1347
+ settings.tailwindcss = { rootFontSize: options.tailwind?.rootFontSize ?? 16 };
1348
+ if (options.shadcn !== false) {
1349
+ jsPlugins.push(require2.resolve("@shadcn/lint"));
1350
+ settings.shadcn = {
1351
+ ui: options.shadcn?.uiImportPath ?? "@/components/ui",
1352
+ componentImports: options.shadcn?.componentImportSources ?? ["^@/components/"]
1353
+ };
1354
+ }
1355
+ }
1356
+ return { jsPlugins, settings };
1357
+ }
1358
+
1359
+ // src/services/preset-context.ts
1360
+ import { resolve as resolve3, posix as posix2 } from "node:path";
1361
+ function presetResolveContext(options) {
1362
+ const generated = presetGeneratedFiles;
1363
+ const root = resolve3(options.root ?? process.cwd());
1364
+ const configured = options.architecture ?? {
1365
+ "apps/web": "web",
1366
+ "apps/server": "server",
1367
+ scripts: "scripts",
1368
+ packages: "packages"
1369
+ };
1370
+ const entries = Object.entries(configured).map(([path, configuredRoot]) => {
1371
+ const entry = typeof configuredRoot === "string" ? { role: configuredRoot } : configuredRoot;
1372
+ if (!entry || typeof entry !== "object")
1373
+ throw new Error("Architecture entries must be roles or role/layout objects.");
1374
+ if (Object.keys(entry).some((key) => !["role", "layout", "framework", "sourceDir"].includes(key)))
1375
+ throw new Error("Unknown architecture entry option.");
1376
+ const role = entry.role;
1377
+ if ("framework" in entry && entry.framework !== undefined && role !== "web")
1378
+ throw new Error("Only web architecture roots accept a framework.");
1379
+ const framework = role === "web" ? entry.framework ?? options.framework ?? "tanstack-start" : undefined;
1380
+ if (framework !== undefined && framework !== "tanstack-start" && framework !== "next")
1381
+ throw new Error(`Unknown framework: ${framework}`);
1382
+ const sourced = role === "web" || role === "server" || role === "runner";
1383
+ if ("sourceDir" in entry && entry.sourceDir !== undefined && !sourced)
1384
+ throw new Error("Only web, server and runner architecture roots accept a sourceDir.");
1385
+ const sourceDir = entry.sourceDir ?? "src";
1386
+ if (typeof sourceDir !== "string" || sourceDir !== "." && sourceDir.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1387
+ throw new Error("Source folders must be `.` or literal root-relative paths without escapes, globs or empty segments.");
1388
+ const portable = path.replaceAll("\\", "/");
1389
+ if (!portable.trim() || posix2.isAbsolute(portable) || /^[a-z]:/i.test(portable))
1390
+ throw new Error("Architecture roots must be nonempty project-relative paths.");
1391
+ const normalized = posix2.normalize(portable).replace(/\/$/, "");
1392
+ if (normalized === ".." || normalized.startsWith("../"))
1393
+ throw new Error("Architecture roots must stay inside the project.");
1394
+ if (!["web", "server", "runner", "scripts", "packages"].includes(role))
1395
+ throw new Error(`Unknown architecture role: ${role}`);
1396
+ if (entry.layout !== undefined && (!entry.layout || typeof entry.layout !== "object" || Array.isArray(entry.layout)))
1397
+ throw new Error("Folder layout must be a path-to-mode object.");
1398
+ const layout = {};
1399
+ if (role === "web" || role === "server" || role === "runner") {
1400
+ layout.services = "domain";
1401
+ }
1402
+ if (role === "web") {
1403
+ layout.hooks = "flat";
1404
+ }
1405
+ for (const [folder, mode] of Object.entries(entry.layout ?? {})) {
1406
+ if (folder.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
1407
+ throw new Error("Layout folders must be literal source-relative paths without escapes, globs or empty segments.");
1408
+ if (mode !== "flat" && mode !== "domain")
1409
+ throw new Error("Folder layout mode must be flat or domain.");
1410
+ layout[folder] = mode;
1411
+ }
1412
+ const folders2 = Object.keys(layout);
1413
+ for (const [index, folder] of folders2.entries()) {
1414
+ const overlaps = folders2.slice(0, index).some((other) => folder.startsWith(other + "/") || other.startsWith(folder + "/"));
1415
+ if (overlaps) {
1416
+ throw new Error("Layout folders must not overlap.");
1417
+ }
1418
+ }
1419
+ return [
1420
+ normalized,
1421
+ {
1422
+ role,
1423
+ layout,
1424
+ ...framework ? { framework } : {},
1425
+ ...entry.sourceDir === undefined ? {} : { sourceDir }
1426
+ }
1427
+ ];
1428
+ });
1429
+ for (const [index, [path]] of entries.entries()) {
1430
+ const overlaps = entries.slice(0, index).some(([other]) => other === path || other === "." || path === "." || other.startsWith(path + "/") || path.startsWith(other + "/"));
1431
+ if (overlaps) {
1432
+ throw new Error("Architecture roots must not overlap.");
1433
+ }
1434
+ }
1435
+ const architecture = Object.fromEntries(entries);
1436
+ if (options.modules?.customFileRoles?.some((role) => !/^[a-z][a-z0-9-]*$/.test(role)))
1437
+ throw new Error("File roles must be lowercase kebab-case names.");
1438
+ if (options.modules?.customFileRoles?.some((role) => [
1439
+ "service",
1440
+ "client",
1441
+ "server",
1442
+ "rsc",
1443
+ "utils",
1444
+ "action",
1445
+ "query",
1446
+ "types",
1447
+ "constants",
1448
+ "handler",
1449
+ "test",
1450
+ "spec"
1451
+ ].includes(role)))
1452
+ throw new Error("File roles must not replace built-in roles.");
1453
+ if (options.framework !== undefined && options.framework !== "tanstack-start" && options.framework !== "next")
1454
+ throw new Error(`Unknown framework: ${options.framework}`);
1455
+ const source = (path, entry) => posix2.join(path, entry.sourceDir ?? "src");
1456
+ const web = entries.filter(([, { role }]) => role === "web").map(([path, entry]) => source(path, entry));
1457
+ const webFor = (framework) => entries.filter(([, entry]) => entry.framework === framework).map(([path, entry]) => source(path, entry));
1458
+ const tanstackWeb = webFor("tanstack-start");
1459
+ const nextWeb = webFor("next");
1460
+ const backendEntries = entries.filter(([, { role }]) => role === "server" || role === "runner");
1461
+ const backendRoots = backendEntries.map(([path]) => path);
1462
+ const backend = backendEntries.map(([path, entry]) => source(path, entry));
1463
+ const packages = entries.filter(([, { role }]) => role === "packages").map(([path]) => path);
1464
+ const scripts = entries.filter(([, { role }]) => role === "scripts").map(([path]) => path);
1465
+ const folders = entries.flatMap(([path, entry]) => Object.entries(entry.layout).map(([folder, mode]) => ({
1466
+ path: posix2.join(["web", "server", "runner"].includes(entry.role) ? source(path, entry) : path, folder),
1467
+ folder,
1468
+ mode
1469
+ })));
1470
+ const rpcClients = entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => posix2.join(source(path, entry), "services", entry.layout.services === "domain" ? "rpc/rpc.client.ts" : "rpc.client.ts"));
1471
+ const app = [...web, ...backend];
1472
+ const appFiles = presetScopes(app);
1473
+ const webFiles = presetScopes(web);
1474
+ const backendFiles = presetScopes(backend);
1475
+ const services = presetScopes(app, `services/**/${presetExtensions}`);
1476
+ const utilities = ["**/*.utils.ts", "**/*.constants.ts", "**/*.types.ts"];
1477
+ const types = presetScopes(app, "types/**");
1478
+ const ui = presetScopes(web, "components/ui/**");
1479
+ const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
1480
+ const safetyFiles = [...appFiles, ...testFiles];
1481
+ const components = presetScopes(web, "components/**/*.tsx");
1482
+ const hooks = presetScopes(web, "hooks/**/*.ts");
1483
+ const level = options.severity ?? "error";
1484
+ const publicEntrypoints = options.imports?.backendEntryPoints ?? backendRoots.flatMap((directory) => {
1485
+ const name = presetPackageName(root, directory);
1486
+ return name ? [`${name}/client`] : [];
1487
+ });
1488
+ const baseImports = [
1489
+ {
1490
+ group: ["zod/v4", "zod/v4/**"],
1491
+ message: "Import from zod, not its transition entry point."
1492
+ }
1493
+ ];
1494
+ const tailwind = options.tailwind;
1495
+ const entryPoints = Object.fromEntries(Object.entries(tailwind === false ? {} : tailwind?.cssEntryPointsByRoot ?? {}).map(([path, entry]) => [
1496
+ posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
1497
+ entry
1498
+ ]));
1499
+ const css = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
1500
+ const sourcePath = source(path, entry);
1501
+ const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
1502
+ return [
1503
+ sourcePath,
1504
+ resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
1505
+ ];
1506
+ }));
1507
+ return {
1508
+ options,
1509
+ root,
1510
+ architecture,
1511
+ folders,
1512
+ rpcClients,
1513
+ packages,
1514
+ scripts,
1515
+ web,
1516
+ tanstackWeb,
1517
+ nextWeb,
1518
+ backend,
1519
+ backendRoots,
1520
+ app,
1521
+ appFiles,
1522
+ webFiles,
1523
+ backendFiles,
1524
+ services,
1525
+ utilities,
1526
+ types,
1527
+ ui,
1528
+ generated,
1529
+ testFiles,
1530
+ safetyFiles,
1531
+ components,
1532
+ hooks,
1533
+ level,
1534
+ publicEntrypoints,
1535
+ baseImports,
1536
+ css
1537
+ };
1538
+ }
1539
+
1540
+ // src/services/preset-compose.ts
1541
+ function presetCompose(context, policies, plugins) {
1542
+ const { options, level, safetyFiles, ui, generated } = context;
1543
+ const severities = options.ruleSeverities ?? {};
1544
+ const levels = (rules) => presetRuleSeverities(presetSeverity(rules, level), severities);
1545
+ const overrideList = [];
1546
+ for (const name of presetPolicyOrder) {
1547
+ const setting = options.policies?.[name];
1548
+ if (setting === false || !policies[name]) {
1549
+ continue;
1550
+ }
1551
+ const defaults = structuredClone(policies[name]);
1552
+ const entries = typeof setting === "function" ? setting(defaults) : defaults;
1553
+ for (const entry of entries) {
1554
+ overrideList.push({
1555
+ ...entry,
1556
+ rules: entry.rules ? levels(entry.rules) : undefined
1557
+ });
1558
+ }
1559
+ }
1560
+ overrideList.push(presetOverride(safetyFiles, levels({
1561
+ "max-lines": ["error", { max: options.limits?.maxFileLines ?? 400, skipBlankLines: true, skipComments: true }],
1562
+ complexity: ["error", { max: options.limits?.maxFunctionComplexity ?? 32 }]
1563
+ }), ui));
1564
+ const named = new Set([...Object.keys(presetCommonRules), ...overrideList.flatMap((entry) => Object.keys(entry.rules ?? {}))]);
1565
+ const unnamedLevels = Object.fromEntries(Object.entries(severities).filter(([rule]) => !named.has(rule)));
1566
+ for (const [rule, files] of Object.entries(options.ruleExclusions ?? {})) {
1567
+ overrideList.push({ files, rules: { [rule]: "off" } });
1568
+ }
1569
+ return {
1570
+ options: { typeAware: true },
1571
+ categories: { correctness: level },
1572
+ plugins: ["typescript", "react", "react-perf", "jsx-a11y", "import", "unicorn", "promise", "vitest", "node", "oxc"],
1573
+ ignorePatterns: [
1574
+ "**/node_modules/**",
1575
+ "**/dist/**",
1576
+ "**/build/**",
1577
+ "**/coverage/**",
1578
+ "**/.output/**",
1579
+ ...context.nextWeb.length ? ["**/.next/**", "**/next-env.d.ts"] : [],
1580
+ ...generated,
1581
+ ...options.ignorePatterns ?? []
1582
+ ],
1583
+ ...plugins,
1584
+ rules: { ...levels(presetCommonRules), ...unnamedLevels },
1585
+ overrides: overrideList.filter((entry) => entry.files.length > 0)
1586
+ };
1587
+ }
1588
+
1589
+ // src/presets/react-modules-preset.ts
1590
+ function reactModulesPreset(options = {}) {
1591
+ const context = presetResolveContext(options);
1592
+ return presetCompose(context, {
1593
+ ...presetSourceConfig(context),
1594
+ ...presetTypesConfig(context),
1595
+ ...presetModulesConfig(context),
1596
+ ...presetTestsConfig(context),
1597
+ ...presetReactConfig(context),
1598
+ ...presetBoundariesConfig(context)
1599
+ }, presetPluginsConfig(context));
1600
+ }
1601
+ export {
1602
+ reactModulesPreset as default
1603
+ };