oxlint-plugin-arch 0.6.2 → 0.7.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/CHANGELOG.md +21 -0
- package/README.md +101 -515
- package/dist/constants/preset.constants.d.ts +6 -0
- package/dist/index.js +354 -102
- package/dist/presets/index.d.ts +3 -1
- package/dist/presets/index.js +247 -69
- package/dist/presets/next-react-modules-preset.d.ts +4 -0
- package/dist/presets/next-react-modules-preset.js +1570 -0
- package/dist/presets/react-modules-preset.d.ts +4 -0
- package/dist/presets/react-modules-preset.js +1565 -0
- package/dist/presets/tanstack-start-react-modules-preset.d.ts +1 -0
- package/dist/presets/tanstack-start-react-modules-preset.js +239 -68
- package/dist/rules/module-policy/index.js +202 -34
- package/dist/rules/module-policy/next-runtime.d.ts +7 -0
- package/dist/types/module-policy.types.d.ts +5 -0
- package/dist/types/preset.types.d.ts +29 -0
- package/dist/utils/helpers/declarations.d.ts +1 -1
- package/dist/utils/helpers/index.d.ts +1 -0
- package/dist/utils/helpers/layout.d.ts +1 -1
- package/dist/utils/helpers/naming.d.ts +1 -1
- package/dist/utils/helpers/preset.d.ts +1 -0
- package/dist/utils/helpers/runtime.d.ts +14 -0
- package/package.json +29 -20
package/dist/presets/index.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
+
export { default as reactModulesPreset } from './react-modules-preset.ts';
|
|
2
|
+
export { default as nextReactModulesPreset } from './next-react-modules-preset.ts';
|
|
1
3
|
export { default as tanstackStartReactModulesPreset } from './tanstack-start-react-modules-preset.ts';
|
|
2
|
-
export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetTestsOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';
|
|
4
|
+
export type { PresetOptions, PresetArchitecture, PresetArchitectureEntry, PresetFolderLayout, PresetFormattingOptions, PresetFramework, PresetNextOptions, PresetTanstackStartOptions, PresetLimitsOptions, PresetModulesOptions, PresetImportsOptions, PresetOrpcOptions, PresetSqlOptions, PresetFormsOptions, PresetReactOptions, PresetTestsOptions, PresetPolicyOptions, PresetPolicy, PresetPolicyName, PresetTailwindOptions, PresetShadcnOptions, } from '../types/preset.types.ts';
|
package/dist/presets/index.js
CHANGED
|
@@ -418,6 +418,24 @@ var presetTestRules = {
|
|
|
418
418
|
"vitest/no-conditional-expect": "error",
|
|
419
419
|
"vitest/require-mock-type-parameters": "error"
|
|
420
420
|
};
|
|
421
|
+
var presetNextHandlerMethods = ["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
|
|
422
|
+
var presetNextSegmentConfig = [
|
|
423
|
+
"generateStaticParams",
|
|
424
|
+
"dynamic",
|
|
425
|
+
"dynamicParams",
|
|
426
|
+
"revalidate",
|
|
427
|
+
"fetchCache",
|
|
428
|
+
"runtime",
|
|
429
|
+
"preferredRegion",
|
|
430
|
+
"maxDuration"
|
|
431
|
+
];
|
|
432
|
+
var presetNextSegmentExports = [
|
|
433
|
+
"generateMetadata",
|
|
434
|
+
"metadata",
|
|
435
|
+
"generateViewport",
|
|
436
|
+
"viewport",
|
|
437
|
+
...presetNextSegmentConfig
|
|
438
|
+
];
|
|
421
439
|
var presetExportPrefix = {
|
|
422
440
|
stem: "before-first-dot",
|
|
423
441
|
trailingRoles: ["constants", "utils", "handler", "service", "types"],
|
|
@@ -436,7 +454,7 @@ var presetGeneratedFiles = [
|
|
|
436
454
|
|
|
437
455
|
// src/utils/helpers/preset.ts
|
|
438
456
|
function presetScopes(roots, suffix = `**/${presetExtensions}`) {
|
|
439
|
-
return roots.map((root) => `${root}/${suffix}`);
|
|
457
|
+
return roots.map((root) => root === "." ? suffix : `${root}/${suffix}`);
|
|
440
458
|
}
|
|
441
459
|
function presetOverride(files, rules, excludeFiles = []) {
|
|
442
460
|
return {
|
|
@@ -445,6 +463,18 @@ function presetOverride(files, rules, excludeFiles = []) {
|
|
|
445
463
|
rules
|
|
446
464
|
};
|
|
447
465
|
}
|
|
466
|
+
function presetRuleSeverities(rules, severities) {
|
|
467
|
+
const result = structuredClone(rules);
|
|
468
|
+
for (const [name, level] of Object.entries(severities)) {
|
|
469
|
+
const value = result[name];
|
|
470
|
+
const current = Array.isArray(value) ? value[0] : value;
|
|
471
|
+
if (value === undefined || current === "off" || current === "allow" || current === 0) {
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
result[name] = Array.isArray(value) && level !== "off" ? [level, ...value.slice(1)] : level;
|
|
475
|
+
}
|
|
476
|
+
return result;
|
|
477
|
+
}
|
|
448
478
|
function presetPackageName(root, directory) {
|
|
449
479
|
try {
|
|
450
480
|
const manifest = JSON.parse(readFileSync(resolve(root, directory, "package.json"), "utf8"));
|
|
@@ -469,7 +499,14 @@ function presetSeverity(rules, level) {
|
|
|
469
499
|
function presetSourceConfig(context) {
|
|
470
500
|
const { options, web, scripts, app, appFiles, backendFiles, ui, safetyFiles, publicEntrypoints, baseImports } = context;
|
|
471
501
|
const policies = {};
|
|
472
|
-
|
|
502
|
+
const { semi = "always", quotes = "single" } = options.formatting ?? {};
|
|
503
|
+
policies.formatting = [
|
|
504
|
+
presetOverride([`**/${presetExtensions}`], {
|
|
505
|
+
...presetFormattingRules,
|
|
506
|
+
"eslint-js/semi": ["error", semi],
|
|
507
|
+
"eslint-js/quotes": ["error", quotes, { avoidEscape: true }]
|
|
508
|
+
})
|
|
509
|
+
];
|
|
473
510
|
policies.statementLayout = [
|
|
474
511
|
presetOverride([`**/${presetExtensions}`], {
|
|
475
512
|
"arch/padding-between-statements": ["error", { multilineVariables: true }],
|
|
@@ -555,12 +592,18 @@ function presetTypesConfig(context) {
|
|
|
555
592
|
safetyFiles,
|
|
556
593
|
components,
|
|
557
594
|
hooks,
|
|
558
|
-
publicEntrypoints
|
|
595
|
+
publicEntrypoints,
|
|
596
|
+
tanstackWeb,
|
|
597
|
+
nextWeb
|
|
559
598
|
} = context;
|
|
599
|
+
const schemaOwners = backend.length ? backend : nextWeb;
|
|
600
|
+
const runtimeFreeTypes = backend.length ? web : tanstackWeb;
|
|
601
|
+
const colocatedTypes = options.modules?.colocatedTypeFiles ?? [];
|
|
560
602
|
const policies = {};
|
|
561
603
|
policies.typePlacement = [
|
|
562
604
|
presetOverride(appFiles, { "arch/no-type-declarations": "error" }, [
|
|
563
605
|
...types,
|
|
606
|
+
...colocatedTypes,
|
|
564
607
|
...components,
|
|
565
608
|
...hooks,
|
|
566
609
|
...services,
|
|
@@ -583,16 +626,18 @@ function presetTypesConfig(context) {
|
|
|
583
626
|
...options.react?.componentProps === false ? [] : [presetOverride(components, { "arch/component-props": ["error", { filePrefix: true }] }, [...ui, ...testFiles])],
|
|
584
627
|
presetOverride(presetScopes(app, "**/*.types.ts"), {
|
|
585
628
|
"arch/no-restricted-files": ["error", { message: "Domain types belong in src/types." }]
|
|
586
|
-
}, [...types, ...ui]),
|
|
629
|
+
}, [...types, ...colocatedTypes, ...ui]),
|
|
587
630
|
presetOverride(presetScopes(app, "**/*.{utils,constants}.ts"), {
|
|
588
631
|
"arch/no-type-declarations": "error"
|
|
589
632
|
}, [...ui, ...testFiles]),
|
|
590
|
-
presetOverride([...presetScopes(
|
|
633
|
+
presetOverride([...presetScopes(schemaOwners, "types/**/*.types.ts"), ...colocatedTypes, ...presetScopes(app, "**/*.constants.ts")], {
|
|
591
634
|
"arch/no-top-level-functions": ["error", { banReExports: false }]
|
|
592
635
|
}),
|
|
593
|
-
|
|
594
|
-
"
|
|
595
|
-
|
|
636
|
+
...runtimeFreeTypes.length ? [
|
|
637
|
+
presetOverride(presetScopes(runtimeFreeTypes, "types/**/*.types.ts"), {
|
|
638
|
+
"arch/no-runtime-in-types": ["error", { runtimeImports: "ban", allowImportSources: [], banReExports: true }]
|
|
639
|
+
})
|
|
640
|
+
] : []
|
|
596
641
|
];
|
|
597
642
|
policies.typeSafety = [
|
|
598
643
|
presetOverride(safetyFiles, {
|
|
@@ -629,7 +674,7 @@ function presetTypesConfig(context) {
|
|
|
629
674
|
policies.schemas = [
|
|
630
675
|
presetOverride(appFiles, {
|
|
631
676
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
632
|
-
}, [...presetScopes(
|
|
677
|
+
}, [...presetScopes(schemaOwners, "types/**"), ...colocatedTypes, ...ui]),
|
|
633
678
|
presetOverride(webFiles, {
|
|
634
679
|
"arch/no-rederive-schema": [
|
|
635
680
|
"error",
|
|
@@ -668,8 +713,9 @@ function presetTypesConfig(context) {
|
|
|
668
713
|
}
|
|
669
714
|
|
|
670
715
|
// src/configs/modules.ts
|
|
716
|
+
import { posix } from "node:path";
|
|
671
717
|
function presetModulesConfig(context) {
|
|
672
|
-
const { web, backend, app, services, utilities, ui, testFiles, components, hooks, options } = context;
|
|
718
|
+
const { web, nextWeb, backend, app, services, utilities, ui, testFiles, components, hooks, options } = context;
|
|
673
719
|
const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
|
|
674
720
|
const migrations = options.modules?.migrationFiles ?? [];
|
|
675
721
|
const frontendEntries = [
|
|
@@ -703,7 +749,7 @@ function presetModulesConfig(context) {
|
|
|
703
749
|
})
|
|
704
750
|
] : []
|
|
705
751
|
]),
|
|
706
|
-
presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === path
|
|
752
|
+
presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), `components/*/*/**/${presetExtensions}`), {
|
|
707
753
|
"arch/no-restricted-files": ["error", { message: "Components allow one group folder." }]
|
|
708
754
|
}, ui),
|
|
709
755
|
presetOverride([...presetScopes(web, "components/**/*.ts"), ...presetScopes(web, "hooks/**/*.tsx")], {
|
|
@@ -719,7 +765,13 @@ function presetModulesConfig(context) {
|
|
|
719
765
|
message: "Frontend services use .client.ts, .server.ts or .rsc.{ts,tsx}; shared domain helpers use .utils.ts and values use .constants.ts."
|
|
720
766
|
}
|
|
721
767
|
]
|
|
722
|
-
}, [
|
|
768
|
+
}, [
|
|
769
|
+
"**/*.{client,server,rsc,utils,constants}.ts",
|
|
770
|
+
"**/*.rsc.tsx",
|
|
771
|
+
...presetScopes(nextWeb, "services/**/*.action.ts"),
|
|
772
|
+
...conceptFiles,
|
|
773
|
+
...options.modules?.colocatedTypeFiles ?? []
|
|
774
|
+
])
|
|
723
775
|
];
|
|
724
776
|
policies.serviceStructure = [
|
|
725
777
|
presetOverride(services, {
|
|
@@ -837,9 +889,9 @@ function presetModulesConfig(context) {
|
|
|
837
889
|
"arch/export-name-pattern": ["error", { pattern: "^use[A-Z]", ignoreTypeExports: true }]
|
|
838
890
|
}),
|
|
839
891
|
presetOverride(components, {
|
|
840
|
-
"arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true }]
|
|
892
|
+
"arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true, trailingRoles: ["illustration"] }]
|
|
841
893
|
}, [...ui, ...testFiles]),
|
|
842
|
-
presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === path
|
|
894
|
+
presetOverride(presetScopes(web.filter((path) => !context.folders.some((scope) => scope.path === posix.join(path, "components"))), "components/**/*.tsx"), {
|
|
843
895
|
"arch/folder-prefix": ["error", { singularize: "trailing-s", separators: ["-"] }]
|
|
844
896
|
}, [...ui, ...testFiles]),
|
|
845
897
|
...["action", "query"].map((role) => presetOverride([
|
|
@@ -899,7 +951,7 @@ function presetModulesConfig(context) {
|
|
|
899
951
|
|
|
900
952
|
// src/configs/tests.ts
|
|
901
953
|
function presetTestsConfig(context) {
|
|
902
|
-
const {
|
|
954
|
+
const { backendRoots, testFiles, baseImports, options } = context;
|
|
903
955
|
const profile = options.tests?.profile ?? "fixtures";
|
|
904
956
|
if (profile !== "standard" && profile !== "fixtures")
|
|
905
957
|
throw new Error("Unknown test profile.");
|
|
@@ -930,7 +982,7 @@ function presetTestsConfig(context) {
|
|
|
930
982
|
{ callees: ["describe", ...testFunctions], forbid: "^should\\b", flags: "i" }
|
|
931
983
|
]
|
|
932
984
|
}),
|
|
933
|
-
presetOverride(presetScopes(
|
|
985
|
+
presetOverride(presetScopes(backendRoots, "tests/**/*.ts"), {
|
|
934
986
|
"modules/concurrent-db": "error",
|
|
935
987
|
...profile === "fixtures" ? {
|
|
936
988
|
"no-restricted-imports": [
|
|
@@ -953,7 +1005,7 @@ function presetTestsConfig(context) {
|
|
|
953
1005
|
|
|
954
1006
|
// src/configs/react.ts
|
|
955
1007
|
function presetReactConfig(context) {
|
|
956
|
-
const { options, web, webFiles, ui, testFiles, components, css } = context;
|
|
1008
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, ui, testFiles, components, css } = context;
|
|
957
1009
|
const policies = {};
|
|
958
1010
|
if (web.length) {
|
|
959
1011
|
policies.react = [
|
|
@@ -975,9 +1027,10 @@ function presetReactConfig(context) {
|
|
|
975
1027
|
if (web.length) {
|
|
976
1028
|
policies.effects = [presetOverride(webFiles, { ...presetEffectsRules, "modules/empty-effect": "error" }, ui)];
|
|
977
1029
|
}
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1030
|
+
const nextPage = { bannedHooks: ["useState", "useEffect", "useMutation"], inspectExports: true };
|
|
1031
|
+
const routes = [
|
|
1032
|
+
...tanstackWeb.length ? [
|
|
1033
|
+
presetOverride(presetScopes(tanstackWeb, "routes/**/*.{ts,tsx}"), {
|
|
981
1034
|
"arch/route-surface": [
|
|
982
1035
|
"error",
|
|
983
1036
|
{
|
|
@@ -987,7 +1040,7 @@ function presetReactConfig(context) {
|
|
|
987
1040
|
}
|
|
988
1041
|
]
|
|
989
1042
|
}, testFiles),
|
|
990
|
-
presetOverride(presetScopes(
|
|
1043
|
+
presetOverride(presetScopes(tanstackWeb, "routes/handlers/**/*.ts"), {
|
|
991
1044
|
"arch/no-file-level-helpers": [
|
|
992
1045
|
"error",
|
|
993
1046
|
{
|
|
@@ -996,7 +1049,43 @@ function presetReactConfig(context) {
|
|
|
996
1049
|
}
|
|
997
1050
|
]
|
|
998
1051
|
})
|
|
999
|
-
]
|
|
1052
|
+
] : [],
|
|
1053
|
+
...nextWeb.length ? [
|
|
1054
|
+
presetOverride(presetScopes(nextWeb, "app/**/{page,template,default,not-found,forbidden,unauthorized,loading}.tsx"), {
|
|
1055
|
+
"arch/route-surface": [
|
|
1056
|
+
"error",
|
|
1057
|
+
{ exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: true, ...nextPage }
|
|
1058
|
+
]
|
|
1059
|
+
}, [...testFiles, ...presetScopes(nextWeb, "app/not-found.tsx")]),
|
|
1060
|
+
presetOverride([...presetScopes(nextWeb, "app/**/{layout,global-not-found}.tsx"), ...presetScopes(nextWeb, "app/not-found.tsx")], {
|
|
1061
|
+
"arch/route-surface": [
|
|
1062
|
+
"error",
|
|
1063
|
+
{ exportName: "default", allowedExports: presetNextSegmentExports, banIntrinsicJsx: false, ...nextPage }
|
|
1064
|
+
]
|
|
1065
|
+
}, testFiles),
|
|
1066
|
+
presetOverride(presetScopes(nextWeb, "app/**/route.ts"), {
|
|
1067
|
+
"arch/route-surface": [
|
|
1068
|
+
"error",
|
|
1069
|
+
{
|
|
1070
|
+
exportName: "GET",
|
|
1071
|
+
allowedExports: [...presetNextHandlerMethods.slice(1), ...presetNextSegmentConfig],
|
|
1072
|
+
bannedHooks: [],
|
|
1073
|
+
banIntrinsicJsx: false
|
|
1074
|
+
}
|
|
1075
|
+
],
|
|
1076
|
+
"arch/no-file-level-helpers": [
|
|
1077
|
+
"error",
|
|
1078
|
+
{
|
|
1079
|
+
detectComponents: false,
|
|
1080
|
+
allowPattern: `^(${[...presetNextHandlerMethods, ...presetNextSegmentConfig].join("|")})$`,
|
|
1081
|
+
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."
|
|
1082
|
+
}
|
|
1083
|
+
]
|
|
1084
|
+
})
|
|
1085
|
+
] : []
|
|
1086
|
+
];
|
|
1087
|
+
if (routes.length) {
|
|
1088
|
+
policies.routes = routes;
|
|
1000
1089
|
}
|
|
1001
1090
|
if (web.length) {
|
|
1002
1091
|
policies.forms = [
|
|
@@ -1049,23 +1138,51 @@ function presetReactConfig(context) {
|
|
|
1049
1138
|
`${group}-[env(safe-area-inset-${side})]`,
|
|
1050
1139
|
...group === "p" ? [] : [`${group}-[max(--spacing(*),env(safe-area-inset-${side}))]`]
|
|
1051
1140
|
]));
|
|
1141
|
+
const illustrationPalette = [
|
|
1142
|
+
"text-color",
|
|
1143
|
+
"bg-color",
|
|
1144
|
+
"gradient-from",
|
|
1145
|
+
"gradient-via",
|
|
1146
|
+
"gradient-to",
|
|
1147
|
+
"border-color",
|
|
1148
|
+
"divide-color",
|
|
1149
|
+
"outline-color",
|
|
1150
|
+
"ring-color",
|
|
1151
|
+
"ring-offset-color",
|
|
1152
|
+
"shadow",
|
|
1153
|
+
"shadow-color",
|
|
1154
|
+
"inset-shadow",
|
|
1155
|
+
"inset-shadow-color",
|
|
1156
|
+
"drop-shadow",
|
|
1157
|
+
"drop-shadow-color",
|
|
1158
|
+
"fill",
|
|
1159
|
+
"stroke",
|
|
1160
|
+
"bg-image",
|
|
1161
|
+
"bg-size",
|
|
1162
|
+
"bg-position",
|
|
1163
|
+
"mask-image",
|
|
1164
|
+
"blur",
|
|
1165
|
+
"saturate"
|
|
1166
|
+
];
|
|
1167
|
+
const restyle = (base) => ({
|
|
1168
|
+
allow: base,
|
|
1169
|
+
contracts: [
|
|
1170
|
+
{ pattern: "^(CardContent|PopoverContent)$", allow: [...base, ...padding] },
|
|
1171
|
+
{ pattern: "^TabsContent$", allow: [...base, ...gap, ...padding] },
|
|
1172
|
+
{ pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...base, ...gap] }
|
|
1173
|
+
]
|
|
1174
|
+
});
|
|
1052
1175
|
policies.shadcn = [
|
|
1053
1176
|
presetOverride(webFiles, {
|
|
1054
|
-
"shadcn/no-restyle": [
|
|
1055
|
-
"error",
|
|
1056
|
-
{
|
|
1057
|
-
allow,
|
|
1058
|
-
contracts: [
|
|
1059
|
-
{ pattern: "^(CardContent|PopoverContent)$", allow: [...allow, ...padding] },
|
|
1060
|
-
{ pattern: "^TabsContent$", allow: [...allow, ...gap, ...padding] },
|
|
1061
|
-
{ pattern: "^(Tabs|HoverCardContent|SidebarHeader|BreadcrumbList)$", allow: [...allow, ...gap] }
|
|
1062
|
-
]
|
|
1063
|
-
}
|
|
1064
|
-
],
|
|
1177
|
+
"shadcn/no-restyle": ["error", restyle(allow)],
|
|
1065
1178
|
"shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea] }],
|
|
1066
1179
|
"shadcn/require-static-classes": "error",
|
|
1067
1180
|
"modules/dynamic-classes": "error"
|
|
1068
1181
|
}, ui),
|
|
1182
|
+
presetOverride(presetScopes(web, "components/**/*.illustration.tsx"), {
|
|
1183
|
+
"shadcn/no-restyle": ["error", restyle([...allow, ...illustrationPalette])],
|
|
1184
|
+
"shadcn/no-arbitrary-values": ["error", { allow: ["layout", ...safeArea, ...illustrationPalette] }]
|
|
1185
|
+
}, ui),
|
|
1069
1186
|
presetOverride(webFiles, {
|
|
1070
1187
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1071
1188
|
}),
|
|
@@ -1080,7 +1197,16 @@ function presetReactConfig(context) {
|
|
|
1080
1197
|
// src/configs/boundaries.ts
|
|
1081
1198
|
import { resolve as resolve2 } from "node:path";
|
|
1082
1199
|
function presetBoundariesConfig(context) {
|
|
1083
|
-
const { options, root, architecture, packages, web, backend, appFiles, publicEntrypoints, baseImports } = context;
|
|
1200
|
+
const { options, root, architecture, packages, web, tanstackWeb, nextWeb, backend, appFiles, publicEntrypoints, baseImports } = context;
|
|
1201
|
+
const runtimeBoundary = {
|
|
1202
|
+
web: web.map((path) => resolve2(root, path)),
|
|
1203
|
+
backend: backend.map((path) => resolve2(root, path)),
|
|
1204
|
+
aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve2(root, path)])),
|
|
1205
|
+
backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
|
|
1206
|
+
const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
|
|
1207
|
+
return name ? [name] : [];
|
|
1208
|
+
})
|
|
1209
|
+
};
|
|
1084
1210
|
const policies = {};
|
|
1085
1211
|
policies.boundaries = [
|
|
1086
1212
|
presetOverride([...appFiles, ...presetScopes(packages, "**/" + presetExtensions)], {
|
|
@@ -1108,20 +1234,11 @@ function presetBoundariesConfig(context) {
|
|
|
1108
1234
|
}
|
|
1109
1235
|
]
|
|
1110
1236
|
}),
|
|
1111
|
-
presetOverride([...presetScopes(
|
|
1237
|
+
presetOverride([...presetScopes(tanstackWeb, "services/**/*.{ts,tsx}"), ...presetScopes(tanstackWeb, "lib/**/*.{ts,tsx}")], {
|
|
1112
1238
|
"modules/tanstack-runtime": [
|
|
1113
1239
|
"error",
|
|
1114
1240
|
{
|
|
1115
|
-
|
|
1116
|
-
backend: backend.map((path) => resolve2(root, path)),
|
|
1117
|
-
aliases: Object.fromEntries(Object.entries(options.imports?.aliases ?? {}).map(([alias, path]) => [
|
|
1118
|
-
alias.replace(/\/$/, ""),
|
|
1119
|
-
resolve2(root, path)
|
|
1120
|
-
])),
|
|
1121
|
-
backendPackages: Object.entries(architecture).flatMap(([directory, { role }]) => {
|
|
1122
|
-
const name = role === "server" || role === "runner" ? presetPackageName(root, directory) : undefined;
|
|
1123
|
-
return name ? [name] : [];
|
|
1124
|
-
}),
|
|
1241
|
+
...runtimeBoundary,
|
|
1125
1242
|
rpcClients: ((options.orpc?.clientOwnerFile) ? [options.orpc?.clientOwnerFile] : context.rpcClients).map((path) => resolve2(root, path)),
|
|
1126
1243
|
serverImports: options.tanstackStart?.additionalServerOnlyImports ?? [],
|
|
1127
1244
|
clientImports: options.tanstackStart?.additionalClientOnlyImports ?? [],
|
|
@@ -1129,6 +1246,17 @@ function presetBoundariesConfig(context) {
|
|
|
1129
1246
|
}
|
|
1130
1247
|
]
|
|
1131
1248
|
}),
|
|
1249
|
+
presetOverride([...presetScopes(nextWeb, "services/**/*.{ts,tsx}"), ...presetScopes(nextWeb, "lib/**/*.{ts,tsx}")], {
|
|
1250
|
+
"modules/next-runtime": [
|
|
1251
|
+
"error",
|
|
1252
|
+
{
|
|
1253
|
+
...runtimeBoundary,
|
|
1254
|
+
serverImports: options.next?.additionalServerOnlyImports ?? [],
|
|
1255
|
+
clientImports: options.next?.additionalClientOnlyImports ?? [],
|
|
1256
|
+
allowComputedImportsIn: options.next?.computedImportAllowedFiles?.map((path) => resolve2(root, path)) ?? []
|
|
1257
|
+
}
|
|
1258
|
+
]
|
|
1259
|
+
}),
|
|
1132
1260
|
presetOverride(presetScopes(backend, "rpc/**/*.ts"), {
|
|
1133
1261
|
"modules/rpc-database": "error",
|
|
1134
1262
|
"no-restricted-imports": ["error", { paths: ["kysely"], patterns: baseImports }]
|
|
@@ -1191,7 +1319,7 @@ function presetPluginsConfig(context) {
|
|
|
1191
1319
|
}
|
|
1192
1320
|
|
|
1193
1321
|
// src/services/preset-context.ts
|
|
1194
|
-
import { resolve as resolve3, posix } from "node:path";
|
|
1322
|
+
import { resolve as resolve3, posix as posix2 } from "node:path";
|
|
1195
1323
|
function presetResolveContext(options) {
|
|
1196
1324
|
const generated = presetGeneratedFiles;
|
|
1197
1325
|
const root = resolve3(options.root ?? process.cwd());
|
|
@@ -1205,13 +1333,24 @@ function presetResolveContext(options) {
|
|
|
1205
1333
|
const entry = typeof configuredRoot === "string" ? { role: configuredRoot } : configuredRoot;
|
|
1206
1334
|
if (!entry || typeof entry !== "object")
|
|
1207
1335
|
throw new Error("Architecture entries must be roles or role/layout objects.");
|
|
1208
|
-
if (Object.keys(entry).some((key) => !["role", "layout"].includes(key)))
|
|
1336
|
+
if (Object.keys(entry).some((key) => !["role", "layout", "framework", "sourceDir"].includes(key)))
|
|
1209
1337
|
throw new Error("Unknown architecture entry option.");
|
|
1210
1338
|
const role = entry.role;
|
|
1339
|
+
if ("framework" in entry && entry.framework !== undefined && role !== "web")
|
|
1340
|
+
throw new Error("Only web architecture roots accept a framework.");
|
|
1341
|
+
const framework = role === "web" ? entry.framework ?? options.framework ?? "tanstack-start" : undefined;
|
|
1342
|
+
if (framework !== undefined && framework !== "tanstack-start" && framework !== "next")
|
|
1343
|
+
throw new Error(`Unknown framework: ${framework}`);
|
|
1344
|
+
const sourced = role === "web" || role === "server" || role === "runner";
|
|
1345
|
+
if ("sourceDir" in entry && entry.sourceDir !== undefined && !sourced)
|
|
1346
|
+
throw new Error("Only web, server and runner architecture roots accept a sourceDir.");
|
|
1347
|
+
const sourceDir = entry.sourceDir ?? "src";
|
|
1348
|
+
if (typeof sourceDir !== "string" || sourceDir !== "." && sourceDir.split("/").some((segment) => !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(segment)))
|
|
1349
|
+
throw new Error("Source folders must be `.` or literal root-relative paths without escapes, globs or empty segments.");
|
|
1211
1350
|
const portable = path.replaceAll("\\", "/");
|
|
1212
|
-
if (!portable.trim() ||
|
|
1351
|
+
if (!portable.trim() || posix2.isAbsolute(portable) || /^[a-z]:/i.test(portable))
|
|
1213
1352
|
throw new Error("Architecture roots must be nonempty project-relative paths.");
|
|
1214
|
-
const normalized =
|
|
1353
|
+
const normalized = posix2.normalize(portable).replace(/\/$/, "");
|
|
1215
1354
|
if (normalized === ".." || normalized.startsWith("../"))
|
|
1216
1355
|
throw new Error("Architecture roots must stay inside the project.");
|
|
1217
1356
|
if (!["web", "server", "runner", "scripts", "packages"].includes(role))
|
|
@@ -1239,7 +1378,15 @@ function presetResolveContext(options) {
|
|
|
1239
1378
|
throw new Error("Layout folders must not overlap.");
|
|
1240
1379
|
}
|
|
1241
1380
|
}
|
|
1242
|
-
return [
|
|
1381
|
+
return [
|
|
1382
|
+
normalized,
|
|
1383
|
+
{
|
|
1384
|
+
role,
|
|
1385
|
+
layout,
|
|
1386
|
+
...framework ? { framework } : {},
|
|
1387
|
+
...entry.sourceDir === undefined ? {} : { sourceDir }
|
|
1388
|
+
}
|
|
1389
|
+
];
|
|
1243
1390
|
});
|
|
1244
1391
|
for (const [index, [path]] of entries.entries()) {
|
|
1245
1392
|
const overlaps = entries.slice(0, index).some(([other]) => other === path || other === "." || path === "." || other.startsWith(path + "/") || path.startsWith(other + "/"));
|
|
@@ -1265,17 +1412,24 @@ function presetResolveContext(options) {
|
|
|
1265
1412
|
"spec"
|
|
1266
1413
|
].includes(role)))
|
|
1267
1414
|
throw new Error("File roles must not replace built-in roles.");
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
const
|
|
1415
|
+
if (options.framework !== undefined && options.framework !== "tanstack-start" && options.framework !== "next")
|
|
1416
|
+
throw new Error(`Unknown framework: ${options.framework}`);
|
|
1417
|
+
const source = (path, entry) => posix2.join(path, entry.sourceDir ?? "src");
|
|
1418
|
+
const web = entries.filter(([, { role }]) => role === "web").map(([path, entry]) => source(path, entry));
|
|
1419
|
+
const webFor = (framework) => entries.filter(([, entry]) => entry.framework === framework).map(([path, entry]) => source(path, entry));
|
|
1420
|
+
const tanstackWeb = webFor("tanstack-start");
|
|
1421
|
+
const nextWeb = webFor("next");
|
|
1422
|
+
const backendEntries = entries.filter(([, { role }]) => role === "server" || role === "runner");
|
|
1423
|
+
const backendRoots = backendEntries.map(([path]) => path);
|
|
1424
|
+
const backend = backendEntries.map(([path, entry]) => source(path, entry));
|
|
1271
1425
|
const packages = entries.filter(([, { role }]) => role === "packages").map(([path]) => path);
|
|
1272
1426
|
const scripts = entries.filter(([, { role }]) => role === "scripts").map(([path]) => path);
|
|
1273
1427
|
const folders = entries.flatMap(([path, entry]) => Object.entries(entry.layout).map(([folder, mode]) => ({
|
|
1274
|
-
path:
|
|
1428
|
+
path: posix2.join(["web", "server", "runner"].includes(entry.role) ? source(path, entry) : path, folder),
|
|
1275
1429
|
folder,
|
|
1276
1430
|
mode
|
|
1277
1431
|
})));
|
|
1278
|
-
const rpcClients = entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) =>
|
|
1432
|
+
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"));
|
|
1279
1433
|
const app = [...web, ...backend];
|
|
1280
1434
|
const appFiles = presetScopes(app);
|
|
1281
1435
|
const webFiles = presetScopes(web);
|
|
@@ -1301,13 +1455,17 @@ function presetResolveContext(options) {
|
|
|
1301
1455
|
];
|
|
1302
1456
|
const tailwind = options.tailwind;
|
|
1303
1457
|
const entryPoints = Object.fromEntries(Object.entries(tailwind === false ? {} : tailwind?.cssEntryPointsByRoot ?? {}).map(([path, entry]) => [
|
|
1304
|
-
|
|
1458
|
+
posix2.normalize(path.replaceAll("\\", "/")).replace(/\/$/, ""),
|
|
1305
1459
|
entry
|
|
1306
1460
|
]));
|
|
1307
|
-
const css = tailwind === false ? {} : Object.fromEntries(web.map((path) =>
|
|
1308
|
-
path,
|
|
1309
|
-
|
|
1310
|
-
|
|
1461
|
+
const css = tailwind === false ? {} : Object.fromEntries(entries.filter(([, entry]) => entry.role === "web").map(([path, entry]) => {
|
|
1462
|
+
const sourcePath = source(path, entry);
|
|
1463
|
+
const fallback = entry.framework === "next" ? "app/globals.css" : "application/styles.css";
|
|
1464
|
+
return [
|
|
1465
|
+
sourcePath,
|
|
1466
|
+
resolve3(root, entryPoints[path] ?? tailwind?.cssEntryPoint ?? posix2.join(sourcePath, fallback))
|
|
1467
|
+
];
|
|
1468
|
+
}));
|
|
1311
1469
|
return {
|
|
1312
1470
|
options,
|
|
1313
1471
|
root,
|
|
@@ -1317,7 +1475,10 @@ function presetResolveContext(options) {
|
|
|
1317
1475
|
packages,
|
|
1318
1476
|
scripts,
|
|
1319
1477
|
web,
|
|
1478
|
+
tanstackWeb,
|
|
1479
|
+
nextWeb,
|
|
1320
1480
|
backend,
|
|
1481
|
+
backendRoots,
|
|
1321
1482
|
app,
|
|
1322
1483
|
appFiles,
|
|
1323
1484
|
webFiles,
|
|
@@ -1341,6 +1502,8 @@ function presetResolveContext(options) {
|
|
|
1341
1502
|
// src/services/preset-compose.ts
|
|
1342
1503
|
function presetCompose(context, policies, plugins) {
|
|
1343
1504
|
const { options, level, safetyFiles, ui, generated } = context;
|
|
1505
|
+
const severities = options.ruleSeverities ?? {};
|
|
1506
|
+
const levels = (rules) => presetRuleSeverities(presetSeverity(rules, level), severities);
|
|
1344
1507
|
const overrideList = [];
|
|
1345
1508
|
for (const name of presetPolicyOrder) {
|
|
1346
1509
|
const setting = options.policies?.[name];
|
|
@@ -1352,14 +1515,16 @@ function presetCompose(context, policies, plugins) {
|
|
|
1352
1515
|
for (const entry of entries) {
|
|
1353
1516
|
overrideList.push({
|
|
1354
1517
|
...entry,
|
|
1355
|
-
rules: entry.rules ?
|
|
1518
|
+
rules: entry.rules ? levels(entry.rules) : undefined
|
|
1356
1519
|
});
|
|
1357
1520
|
}
|
|
1358
1521
|
}
|
|
1359
|
-
overrideList.push(presetOverride(safetyFiles, {
|
|
1360
|
-
"max-lines": [
|
|
1361
|
-
complexity: [
|
|
1362
|
-
}, ui));
|
|
1522
|
+
overrideList.push(presetOverride(safetyFiles, levels({
|
|
1523
|
+
"max-lines": ["error", { max: options.limits?.maxFileLines ?? 400, skipBlankLines: true, skipComments: true }],
|
|
1524
|
+
complexity: ["error", { max: options.limits?.maxFunctionComplexity ?? 32 }]
|
|
1525
|
+
}), ui));
|
|
1526
|
+
const named = new Set([...Object.keys(presetCommonRules), ...overrideList.flatMap((entry) => Object.keys(entry.rules ?? {}))]);
|
|
1527
|
+
const unnamedLevels = Object.fromEntries(Object.entries(severities).filter(([rule]) => !named.has(rule)));
|
|
1363
1528
|
for (const [rule, files] of Object.entries(options.ruleExclusions ?? {})) {
|
|
1364
1529
|
overrideList.push({ files, rules: { [rule]: "off" } });
|
|
1365
1530
|
}
|
|
@@ -1373,17 +1538,18 @@ function presetCompose(context, policies, plugins) {
|
|
|
1373
1538
|
"**/build/**",
|
|
1374
1539
|
"**/coverage/**",
|
|
1375
1540
|
"**/.output/**",
|
|
1541
|
+
...context.nextWeb.length ? ["**/.next/**", "**/next-env.d.ts"] : [],
|
|
1376
1542
|
...generated,
|
|
1377
1543
|
...options.ignorePatterns ?? []
|
|
1378
1544
|
],
|
|
1379
1545
|
...plugins,
|
|
1380
|
-
rules:
|
|
1546
|
+
rules: { ...levels(presetCommonRules), ...unnamedLevels },
|
|
1381
1547
|
overrides: overrideList.filter((entry) => entry.files.length > 0)
|
|
1382
1548
|
};
|
|
1383
1549
|
}
|
|
1384
1550
|
|
|
1385
|
-
// src/presets/
|
|
1386
|
-
function
|
|
1551
|
+
// src/presets/react-modules-preset.ts
|
|
1552
|
+
function reactModulesPreset(options = {}) {
|
|
1387
1553
|
const context = presetResolveContext(options);
|
|
1388
1554
|
return presetCompose(context, {
|
|
1389
1555
|
...presetSourceConfig(context),
|
|
@@ -1394,6 +1560,18 @@ function tanstackStartReactModulesPreset(options = {}) {
|
|
|
1394
1560
|
...presetBoundariesConfig(context)
|
|
1395
1561
|
}, presetPluginsConfig(context));
|
|
1396
1562
|
}
|
|
1563
|
+
|
|
1564
|
+
// src/presets/next-react-modules-preset.ts
|
|
1565
|
+
function nextReactModulesPreset(options = {}) {
|
|
1566
|
+
return reactModulesPreset({ ...options, framework: options.framework ?? "next" });
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
// src/presets/tanstack-start-react-modules-preset.ts
|
|
1570
|
+
function tanstackStartReactModulesPreset(options = {}) {
|
|
1571
|
+
return reactModulesPreset({ ...options, framework: options.framework ?? "tanstack-start" });
|
|
1572
|
+
}
|
|
1397
1573
|
export {
|
|
1398
|
-
tanstackStartReactModulesPreset
|
|
1574
|
+
tanstackStartReactModulesPreset,
|
|
1575
|
+
reactModulesPreset,
|
|
1576
|
+
nextReactModulesPreset
|
|
1399
1577
|
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { OxlintConfig } from 'oxlint';
|
|
2
|
+
import type { PresetOptions } from '../types/preset.types.ts';
|
|
3
|
+
/** The React modules preset with Next.js App Router as the default web framework. */
|
|
4
|
+
export default function nextReactModulesPreset(options?: PresetOptions): OxlintConfig;
|