oxlint-plugin-arch 0.9.0 → 0.10.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 +24 -0
- package/dist/presets/index.js +137 -33
- package/dist/presets/next-react-modules-preset.js +137 -33
- package/dist/presets/react-modules-preset.js +137 -33
- package/dist/presets/tanstack-start-react-modules-preset.js +137 -33
- package/dist/types/preset.types.d.ts +12 -2
- package/dist/utils/helpers/preset.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
**Breaking:** `packages` roots now get the app rules. Until now they only got the shared baseline (formatting, imports, stock TypeScript/JavaScript rules and `modules/import-boundaries`), so expect new errors in package `src` folders.
|
|
6
|
+
|
|
7
|
+
- A packages root with its own `package.json` is one package. Without one, each child folder is a package, so `packages: 'packages'` covers every `packages/*/src`. Per-package entries keep working. Files outside `src` keep the baseline only.
|
|
8
|
+
- Every package file gets the app-wide rules: `arch/no-comments` (with the stricter `*.types.ts`/`*.constants.ts` variant), the `typeSafety` set (including the `dillon-anti-slop/*` rules), `arch/no-type-declarations`, `arch/no-local-schema-construction`, `arch/no-rederive-schema`, `arch/no-module-mutable-state`, `modules/double-negation`, `arch/no-trivial-functions`, `max-lines` and `complexity`.
|
|
9
|
+
- Services, types and file roles in packages follow the server-root rules: `modules/service-functions`, `modules/service-types`, `modules/domain-constants`, `arch/require-object-params`, `arch/no-extra-exports`, `arch/no-inline-types`, `arch/export-file-prefix`, `arch/no-restricted-files`, `arch/no-top-level-functions`, `arch/filename-match`, `arch/filename-export-name`, `arch/folder-prefix` and `arch/no-unescaped-like`. Packages default to the `services: 'domain'` layout, and `layout` keys on a packages root now resolve inside `src`. `entry.*.ts` files stay exempt from `arch/export-file-prefix`, as in server roots.
|
|
10
|
+
- React code in packages (`*.tsx`, `components/**`, `hooks/**`) follows the web-root rules: the React, effects, memoization and forms policies, `arch/jsx-attributes-multiline`, `arch/no-file-level-helpers`, `arch/component-props`, `arch/declaration-name`, `modules/local-type-alias`, `modules/pascal-interface`, `arch/only-export-components` and the hook naming rules. The React plugins now load when packages exist, even without a web root.
|
|
11
|
+
- Package `types/**` and `*.types.ts` files may construct schemas, like backend `types/**`.
|
|
12
|
+
- Never on packages: `modules/backend-switch`, `modules/rpc-database`, `arch/require-orpc-output`, `modules/tanstack-runtime`, `arch/no-restricted-token`, `arch/no-runtime-in-types` and `modules/dynamic-classes`. Routes, Tailwind and shadcn usage rules stay with web roots.
|
|
13
|
+
- Rename `shadcn.uiImportPath` to `shadcn.ui` and `shadcn.componentImportSources` to `shadcn.components`, matching the components.json alias names. The old names throw. Migration: `shadcn: { uiImportPath: '@ui' }` becomes `shadcn: { ui: '@ui' }`.
|
|
14
|
+
- `shadcn.ui` also selects the UI kit files, which keep the existing kit exceptions (type safety, formatting and layout are still checked). Use a workspace package name (`'@app/ui'` → that package's `src/**`) or a tsconfig `paths` alias (`'@/kit'` → `<root>/src/kit/**`). A value that resolves to nothing throws. When unset, the kit stays `<web source>/components/ui/**`. A UI kit in a package needs `shadcn: { ui: '<package name>' }`; without it, the kit gets the full package rules.
|
|
15
|
+
|
|
16
|
+
How to fix what the new rules report:
|
|
17
|
+
|
|
18
|
+
| Fix | Rules |
|
|
19
|
+
| --- | --- |
|
|
20
|
+
| Auto-fix (`--fix`) | `arch/no-comments`, `arch/jsx-attributes-multiline`, the `react-extra/jsx-*` layout rules |
|
|
21
|
+
| Rename | `arch/export-file-prefix`, `arch/filename-match`, `arch/filename-export-name`, `arch/folder-prefix`, `arch/declaration-name`, `arch/export-name-pattern`, `modules/pascal-interface`, `modules/shape-suffix` |
|
|
22
|
+
| Move | `arch/no-type-declarations` (to `types/`), `arch/no-local-schema-construction` (to `types/` or `*.types.ts`), `arch/no-restricted-files`, `arch/no-extra-exports`, `arch/only-export-components`, `arch/no-file-level-helpers`, `modules/domain-constants`, `modules/service-types`, `modules/local-type-alias`, `max-lines` |
|
|
23
|
+
| Reshape | `arch/require-object-params`, `arch/component-props`, `arch/no-inline-types`, `arch/no-trivial-functions`, `arch/no-module-mutable-state`, `arch/no-restricted-constructor`, `modules/service-functions`, `modules/no-unknown`, `modules/memoization`, `typescript/no-explicit-any`, the `dillon-anti-slop/*` rules, the React and effects rules, `complexity` |
|
|
24
|
+
|
|
25
|
+
To defer a package, turn single rules off for it: `ruleExclusions: { '<rule>': ['packages/foo/**'] }`.
|
|
26
|
+
|
|
3
27
|
## 0.9.0
|
|
4
28
|
|
|
5
29
|
- Add `limits.maxTypeFileLines` (default `1000`): the preset applies this `max-lines` limit to `*.types.ts` files in app roots instead of `limits.maxFileLines`. Other files keep `maxFileLines`.
|
package/dist/presets/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/utils/helpers/preset.ts
|
|
2
|
-
import { readFileSync } from "node:fs";
|
|
3
|
-
import { resolve } from "node:path";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { relative, resolve } from "node:path";
|
|
4
4
|
|
|
5
5
|
// src/constants/preset.constants.ts
|
|
6
6
|
var presetExtensions = "*.{ts,tsx,mts,cts}";
|
|
@@ -504,6 +504,61 @@ function presetSeverity(rules, level) {
|
|
|
504
504
|
}
|
|
505
505
|
return result;
|
|
506
506
|
}
|
|
507
|
+
function presetReadJsonc(path) {
|
|
508
|
+
let text;
|
|
509
|
+
try {
|
|
510
|
+
text = readFileSync(path, "utf8");
|
|
511
|
+
} catch {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
let output = "";
|
|
515
|
+
for (let index = 0;index < text.length; index++) {
|
|
516
|
+
const character = text[index];
|
|
517
|
+
if (character === '"') {
|
|
518
|
+
const end = /"(?:[^"\\]|\\.)*"/y;
|
|
519
|
+
end.lastIndex = index;
|
|
520
|
+
const literal = end.exec(text)?.[0] ?? text.slice(index);
|
|
521
|
+
output += literal;
|
|
522
|
+
index += literal.length - 1;
|
|
523
|
+
} else if (character === "/" && text[index + 1] === "/") {
|
|
524
|
+
index = text.indexOf(`
|
|
525
|
+
`, index) === -1 ? text.length : text.indexOf(`
|
|
526
|
+
`, index) - 1;
|
|
527
|
+
} else if (character === "/" && text[index + 1] === "*") {
|
|
528
|
+
index = text.indexOf("*/", index + 2) === -1 ? text.length : text.indexOf("*/", index + 2) + 1;
|
|
529
|
+
} else if (character === "}" || character === "]") {
|
|
530
|
+
output = output.replace(/,\s*$/, "") + character;
|
|
531
|
+
} else {
|
|
532
|
+
output += character;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
return JSON.parse(output);
|
|
537
|
+
} catch {
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function presetTsconfigAliasTargets(root, directory, specifier) {
|
|
542
|
+
const config = presetReadJsonc(resolve(root, directory, "tsconfig.json"));
|
|
543
|
+
if (!config || typeof config !== "object" || !("compilerOptions" in config))
|
|
544
|
+
return [];
|
|
545
|
+
const { compilerOptions } = config;
|
|
546
|
+
if (!compilerOptions || typeof compilerOptions !== "object" || !("paths" in compilerOptions))
|
|
547
|
+
return [];
|
|
548
|
+
const { paths } = compilerOptions;
|
|
549
|
+
const baseUrl = "baseUrl" in compilerOptions && typeof compilerOptions.baseUrl === "string" ? compilerOptions.baseUrl : ".";
|
|
550
|
+
if (!paths || typeof paths !== "object")
|
|
551
|
+
return [];
|
|
552
|
+
return Object.entries(paths).flatMap(([pattern, targets]) => {
|
|
553
|
+
const [prefix, suffix = ""] = pattern.split("*");
|
|
554
|
+
const wildcard = pattern.includes("*");
|
|
555
|
+
const matches = wildcard ? specifier.startsWith(prefix) && specifier.endsWith(suffix) && specifier.length >= prefix.length + suffix.length : specifier === pattern;
|
|
556
|
+
if (!matches || !Array.isArray(targets))
|
|
557
|
+
return [];
|
|
558
|
+
const captured = wildcard ? specifier.slice(prefix.length, specifier.length - suffix.length) : "";
|
|
559
|
+
return targets.filter((target) => typeof target === "string").map((target) => relative(root, resolve(root, directory, baseUrl, target.replace("*", captured))).replaceAll("\\", "/")).filter((target) => target && !target.startsWith("..") && existsSync(resolve(root, target)));
|
|
560
|
+
});
|
|
561
|
+
}
|
|
507
562
|
|
|
508
563
|
// src/configs/source.ts
|
|
509
564
|
function presetSourceConfig(context) {
|
|
@@ -591,6 +646,8 @@ function presetTypesConfig(context) {
|
|
|
591
646
|
options,
|
|
592
647
|
web,
|
|
593
648
|
backend,
|
|
649
|
+
packageSources,
|
|
650
|
+
serviceRoots,
|
|
594
651
|
app,
|
|
595
652
|
appFiles,
|
|
596
653
|
webFiles,
|
|
@@ -640,7 +697,11 @@ function presetTypesConfig(context) {
|
|
|
640
697
|
presetOverride(presetScopes(app, "**/*.{utils,constants}.ts"), {
|
|
641
698
|
"arch/no-type-declarations": "error"
|
|
642
699
|
}, [...ui, ...testFiles]),
|
|
643
|
-
presetOverride([
|
|
700
|
+
presetOverride([
|
|
701
|
+
...presetScopes([...schemaOwners, ...packageSources], "types/**/*.types.ts"),
|
|
702
|
+
...colocatedTypes,
|
|
703
|
+
...presetScopes(app, "**/*.constants.ts")
|
|
704
|
+
], {
|
|
644
705
|
"arch/no-top-level-functions": ["error", { banReExports: false }]
|
|
645
706
|
}),
|
|
646
707
|
...runtimeFreeTypes.length ? [
|
|
@@ -684,8 +745,13 @@ function presetTypesConfig(context) {
|
|
|
684
745
|
policies.schemas = [
|
|
685
746
|
presetOverride(appFiles, {
|
|
686
747
|
"arch/no-local-schema-construction": ["error", { packages: ["zod"], namespaces: ["z"] }]
|
|
687
|
-
}, [
|
|
688
|
-
|
|
748
|
+
}, [
|
|
749
|
+
...presetScopes([...schemaOwners, ...packageSources], "types/**"),
|
|
750
|
+
...presetScopes(packageSources, "**/*.types.ts"),
|
|
751
|
+
...colocatedTypes,
|
|
752
|
+
...ui
|
|
753
|
+
]),
|
|
754
|
+
presetOverride([...webFiles, ...presetScopes(packageSources)], {
|
|
689
755
|
"arch/no-rederive-schema": [
|
|
690
756
|
"error",
|
|
691
757
|
{
|
|
@@ -695,7 +761,7 @@ function presetTypesConfig(context) {
|
|
|
695
761
|
}
|
|
696
762
|
]
|
|
697
763
|
}),
|
|
698
|
-
presetOverride(presetScopes(
|
|
764
|
+
presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), {
|
|
699
765
|
"arch/no-unescaped-like": [
|
|
700
766
|
"error",
|
|
701
767
|
{
|
|
@@ -725,7 +791,7 @@ function presetTypesConfig(context) {
|
|
|
725
791
|
// src/configs/modules.ts
|
|
726
792
|
import { posix } from "node:path";
|
|
727
793
|
function presetModulesConfig(context) {
|
|
728
|
-
const { web, nextWeb,
|
|
794
|
+
const { web, nextWeb, serviceRoots, app, services, utilities, ui, testFiles, components, hooks, options } = context;
|
|
729
795
|
const conceptFiles = (options.modules?.customFileRoles ?? []).map((role) => `**/*.${role}.ts`);
|
|
730
796
|
const migrations = options.modules?.migrationFiles ?? [];
|
|
731
797
|
const frontendEntries = [
|
|
@@ -822,7 +888,7 @@ function presetModulesConfig(context) {
|
|
|
822
888
|
}
|
|
823
889
|
]
|
|
824
890
|
}, testFiles),
|
|
825
|
-
presetOverride(presetScopes(
|
|
891
|
+
presetOverride(presetScopes(serviceRoots, "services/**/*.ts"), { "modules/domain-constants": "error" }, [
|
|
826
892
|
...utilities,
|
|
827
893
|
...testFiles
|
|
828
894
|
]),
|
|
@@ -832,11 +898,11 @@ function presetModulesConfig(context) {
|
|
|
832
898
|
presetOverride(frontendEntries, {
|
|
833
899
|
"modules/domain-constants": ["error", { includeData: true, allowServiceMethods: true }]
|
|
834
900
|
}, testFiles),
|
|
835
|
-
presetOverride([...presetScopes(
|
|
901
|
+
presetOverride([...presetScopes(serviceRoots, "services/**/*-{action,query}.*.ts"), ...frontendEntries], { "arch/require-object-params": ["error", { maxParams: 2 }] }, testFiles),
|
|
836
902
|
presetOverride(frontendEntries, {
|
|
837
903
|
"arch/require-object-params": ["error", { maxParams: 2, serviceMethods: true }]
|
|
838
904
|
}, testFiles),
|
|
839
|
-
presetOverride(presetScopes(
|
|
905
|
+
presetOverride(presetScopes(serviceRoots, "services/**/*.service.ts"), {
|
|
840
906
|
"arch/no-extra-exports": [
|
|
841
907
|
"error",
|
|
842
908
|
{
|
|
@@ -864,7 +930,7 @@ function presetModulesConfig(context) {
|
|
|
864
930
|
}
|
|
865
931
|
]
|
|
866
932
|
}, ["**/*.constants.ts"]),
|
|
867
|
-
presetOverride(presetScopes(app, "**/*.{constants,utils,handler,service,types,client,server,rsc}.ts"), { "arch/export-file-prefix": ["error", presetExportPrefix] }, [...ui, ...presetScopes(
|
|
933
|
+
presetOverride(presetScopes(app, "**/*.{constants,utils,handler,service,types,client,server,rsc}.ts"), { "arch/export-file-prefix": ["error", presetExportPrefix] }, [...ui, ...presetScopes(serviceRoots, "entry.*.ts")]),
|
|
868
934
|
presetOverride(frontendEntries, {
|
|
869
935
|
"arch/export-file-prefix": [
|
|
870
936
|
"error",
|
|
@@ -897,7 +963,7 @@ function presetModulesConfig(context) {
|
|
|
897
963
|
],
|
|
898
964
|
"arch/export-file-prefix": "error",
|
|
899
965
|
"arch/export-name-pattern": ["error", { pattern: "^use[A-Z]", ignoreTypeExports: true }]
|
|
900
|
-
}),
|
|
966
|
+
}, ui),
|
|
901
967
|
presetOverride(components, {
|
|
902
968
|
"arch/only-export-components": ["error", { matchFileName: true, allowTypeExports: true, trailingRoles: ["illustration"] }]
|
|
903
969
|
}, [...ui, ...testFiles]),
|
|
@@ -1043,11 +1109,12 @@ function presetTestsConfig(context) {
|
|
|
1043
1109
|
|
|
1044
1110
|
// src/configs/react.ts
|
|
1045
1111
|
function presetReactConfig(context) {
|
|
1046
|
-
const { options, web, tanstackWeb, nextWeb, webFiles, ui, testFiles, components, css } = context;
|
|
1112
|
+
const { options, web, tanstackWeb, nextWeb, webFiles, reactFiles, uiRoots, ui, testFiles, components, css } = context;
|
|
1047
1113
|
const policies = {};
|
|
1048
|
-
|
|
1114
|
+
const webUi = uiRoots.filter((path) => web.some((root) => root === "." || path === root || path.startsWith(root + "/")));
|
|
1115
|
+
if (reactFiles.length) {
|
|
1049
1116
|
policies.react = [
|
|
1050
|
-
presetOverride(
|
|
1117
|
+
presetOverride(reactFiles, {
|
|
1051
1118
|
...presetReactRules,
|
|
1052
1119
|
...options.react?.compiler === false ? {
|
|
1053
1120
|
"react-perf/jsx-no-new-object-as-prop": "error",
|
|
@@ -1062,8 +1129,8 @@ function presetReactConfig(context) {
|
|
|
1062
1129
|
}, [...ui, ...testFiles])
|
|
1063
1130
|
];
|
|
1064
1131
|
}
|
|
1065
|
-
if (
|
|
1066
|
-
policies.effects = [presetOverride(
|
|
1132
|
+
if (reactFiles.length) {
|
|
1133
|
+
policies.effects = [presetOverride(reactFiles, { ...presetEffectsRules, "modules/empty-effect": "error" }, ui)];
|
|
1067
1134
|
}
|
|
1068
1135
|
const nextPage = { bannedHooks: ["useState", "useEffect", "useMutation"], inspectExports: true };
|
|
1069
1136
|
const routes = [
|
|
@@ -1125,9 +1192,9 @@ function presetReactConfig(context) {
|
|
|
1125
1192
|
if (routes.length) {
|
|
1126
1193
|
policies.routes = routes;
|
|
1127
1194
|
}
|
|
1128
|
-
if (
|
|
1195
|
+
if (reactFiles.length) {
|
|
1129
1196
|
policies.forms = [
|
|
1130
|
-
presetOverride(
|
|
1197
|
+
presetOverride(reactFiles, {
|
|
1131
1198
|
"arch/require-paired-call": [
|
|
1132
1199
|
"error",
|
|
1133
1200
|
{
|
|
@@ -1138,8 +1205,8 @@ function presetReactConfig(context) {
|
|
|
1138
1205
|
}, ui)
|
|
1139
1206
|
];
|
|
1140
1207
|
}
|
|
1141
|
-
if (
|
|
1142
|
-
policies.memoization = [presetOverride(
|
|
1208
|
+
if (reactFiles.length && options.react?.compiler !== false) {
|
|
1209
|
+
policies.memoization = [presetOverride(reactFiles, { "modules/memoization": "error" }, ui)];
|
|
1143
1210
|
}
|
|
1144
1211
|
if (web.length) {
|
|
1145
1212
|
policies.rpcClientOwnership = [
|
|
@@ -1154,7 +1221,7 @@ function presetReactConfig(context) {
|
|
|
1154
1221
|
},
|
|
1155
1222
|
{
|
|
1156
1223
|
token: "createContext",
|
|
1157
|
-
allowPathPatterns: [...presetScopes(web, "context/"), ...
|
|
1224
|
+
allowPathPatterns: [...presetScopes(web, "context/"), ...webUi.map((path) => `${path}/`)]
|
|
1158
1225
|
}
|
|
1159
1226
|
]
|
|
1160
1227
|
}
|
|
@@ -1224,7 +1291,7 @@ function presetReactConfig(context) {
|
|
|
1224
1291
|
presetOverride(webFiles, {
|
|
1225
1292
|
"shadcn/no-unknown-classes": ["error", { allow: [] }]
|
|
1226
1293
|
}),
|
|
1227
|
-
presetOverride(presetScopes(
|
|
1294
|
+
presetOverride(presetScopes(webUi, "sonner.tsx"), {
|
|
1228
1295
|
"shadcn/no-unknown-classes": ["error", { allow: ["toaster", "toast"] }]
|
|
1229
1296
|
})
|
|
1230
1297
|
];
|
|
@@ -1307,7 +1374,7 @@ function presetBoundariesConfig(context) {
|
|
|
1307
1374
|
import { createRequire } from "node:module";
|
|
1308
1375
|
var require2 = createRequire(import.meta.url);
|
|
1309
1376
|
function presetPluginsConfig(context) {
|
|
1310
|
-
const { options,
|
|
1377
|
+
const { options, reactFiles, css } = context;
|
|
1311
1378
|
const jsPlugins = [
|
|
1312
1379
|
{ name: "eslint-js", specifier: require2.resolve("oxlint-plugin-eslint") },
|
|
1313
1380
|
{
|
|
@@ -1336,7 +1403,7 @@ function presetPluginsConfig(context) {
|
|
|
1336
1403
|
react: { version: "19" },
|
|
1337
1404
|
"import/resolver": { node: { extensions: [".js", ".jsx", ".ts", ".tsx", ".mts", ".cts"] } }
|
|
1338
1405
|
};
|
|
1339
|
-
if (
|
|
1406
|
+
if (reactFiles.length) {
|
|
1340
1407
|
jsPlugins.push({
|
|
1341
1408
|
name: "react-extra",
|
|
1342
1409
|
specifier: require2.resolve("eslint-plugin-react")
|
|
@@ -1348,8 +1415,8 @@ function presetPluginsConfig(context) {
|
|
|
1348
1415
|
if (options.shadcn !== false) {
|
|
1349
1416
|
jsPlugins.push(require2.resolve("@shadcn/lint"));
|
|
1350
1417
|
settings.shadcn = {
|
|
1351
|
-
ui: options.shadcn?.
|
|
1352
|
-
componentImports: options.shadcn?.
|
|
1418
|
+
ui: options.shadcn?.ui ?? "@/components/ui",
|
|
1419
|
+
componentImports: options.shadcn?.components ?? ["^@/components/"]
|
|
1353
1420
|
};
|
|
1354
1421
|
}
|
|
1355
1422
|
}
|
|
@@ -1357,6 +1424,7 @@ function presetPluginsConfig(context) {
|
|
|
1357
1424
|
}
|
|
1358
1425
|
|
|
1359
1426
|
// src/services/preset-context.ts
|
|
1427
|
+
import { existsSync as existsSync2, readdirSync } from "node:fs";
|
|
1360
1428
|
import { resolve as resolve3, posix as posix2 } from "node:path";
|
|
1361
1429
|
function presetResolveContext(options) {
|
|
1362
1430
|
const generated = presetGeneratedFiles;
|
|
@@ -1396,7 +1464,7 @@ function presetResolveContext(options) {
|
|
|
1396
1464
|
if (entry.layout !== undefined && (!entry.layout || typeof entry.layout !== "object" || Array.isArray(entry.layout)))
|
|
1397
1465
|
throw new Error("Folder layout must be a path-to-mode object.");
|
|
1398
1466
|
const layout = {};
|
|
1399
|
-
if (role
|
|
1467
|
+
if (role !== "scripts") {
|
|
1400
1468
|
layout.services = "domain";
|
|
1401
1469
|
}
|
|
1402
1470
|
if (role === "web") {
|
|
@@ -1453,6 +1521,8 @@ function presetResolveContext(options) {
|
|
|
1453
1521
|
if (options.framework !== undefined && options.framework !== "tanstack-start" && options.framework !== "next")
|
|
1454
1522
|
throw new Error(`Unknown framework: ${options.framework}`);
|
|
1455
1523
|
const source = (path, entry) => posix2.join(path, entry.sourceDir ?? "src");
|
|
1524
|
+
const isPackage = (path) => existsSync2(resolve3(root, path, "package.json"));
|
|
1525
|
+
const packageSource = (path) => posix2.join(path, isPackage(path) ? "src" : "*/src");
|
|
1456
1526
|
const web = entries.filter(([, { role }]) => role === "web").map(([path, entry]) => source(path, entry));
|
|
1457
1527
|
const webFor = (framework) => entries.filter(([, entry]) => entry.framework === framework).map(([path, entry]) => source(path, entry));
|
|
1458
1528
|
const tanstackWeb = webFor("tanstack-start");
|
|
@@ -1461,25 +1531,55 @@ function presetResolveContext(options) {
|
|
|
1461
1531
|
const backendRoots = backendEntries.map(([path]) => path);
|
|
1462
1532
|
const backend = backendEntries.map(([path, entry]) => source(path, entry));
|
|
1463
1533
|
const packages = entries.filter(([, { role }]) => role === "packages").map(([path]) => path);
|
|
1534
|
+
const packageSources = packages.map(packageSource);
|
|
1535
|
+
const serviceRoots = [...backend, ...packageSources];
|
|
1464
1536
|
const scripts = entries.filter(([, { role }]) => role === "scripts").map(([path]) => path);
|
|
1465
1537
|
const folders = entries.flatMap(([path, entry]) => Object.entries(entry.layout).map(([folder, mode]) => ({
|
|
1466
|
-
path: posix2.join(
|
|
1538
|
+
path: posix2.join(entry.role === "scripts" ? path : entry.role === "packages" ? packageSource(path) : source(path, entry), folder),
|
|
1467
1539
|
folder,
|
|
1468
1540
|
mode
|
|
1469
1541
|
})));
|
|
1470
1542
|
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];
|
|
1543
|
+
const app = [...web, ...backend, ...packageSources];
|
|
1472
1544
|
const appFiles = presetScopes(app);
|
|
1473
1545
|
const webFiles = presetScopes(web);
|
|
1546
|
+
const reactFiles = [
|
|
1547
|
+
...webFiles,
|
|
1548
|
+
...presetScopes(packageSources, "**/*.tsx"),
|
|
1549
|
+
...presetScopes(packageSources, `components/**/${presetExtensions}`),
|
|
1550
|
+
...presetScopes(packageSources, `hooks/**/${presetExtensions}`)
|
|
1551
|
+
];
|
|
1474
1552
|
const backendFiles = presetScopes(backend);
|
|
1475
1553
|
const services = presetScopes(app, `services/**/${presetExtensions}`);
|
|
1476
1554
|
const utilities = ["**/*.utils.ts", "**/*.constants.ts", "**/*.types.ts"];
|
|
1477
1555
|
const types = presetScopes(app, "types/**");
|
|
1478
|
-
const
|
|
1556
|
+
const resolveUi = (value) => {
|
|
1557
|
+
if (typeof value !== "string" || !value)
|
|
1558
|
+
throw new Error("shadcn.ui must be a nonempty import path.");
|
|
1559
|
+
const manifests = entries.flatMap(([path, entry]) => {
|
|
1560
|
+
const children = entry.role === "packages" && existsSync2(resolve3(root, path)) && !isPackage(path) ? readdirSync(resolve3(root, path), { withFileTypes: true }).filter((child) => child.isDirectory() && isPackage(posix2.join(path, child.name))).map((child) => ({ directory: posix2.join(path, child.name), sources: posix2.join(path, child.name, "src") })) : [];
|
|
1561
|
+
const sources = ["web", "server", "runner"].includes(entry.role) ? source(path, entry) : posix2.join(path, "src");
|
|
1562
|
+
return [{ directory: path, sources }, ...children];
|
|
1563
|
+
});
|
|
1564
|
+
const named = manifests.filter(({ directory }) => presetPackageName(root, directory) === value);
|
|
1565
|
+
if (named.length)
|
|
1566
|
+
return named.map(({ sources }) => sources);
|
|
1567
|
+
const aliased = [...new Set([".", ...manifests.map(({ directory }) => directory)].flatMap((directory) => presetTsconfigAliasTargets(root, directory, value)))];
|
|
1568
|
+
if (aliased.length)
|
|
1569
|
+
return aliased;
|
|
1570
|
+
throw new Error(`shadcn.ui ${JSON.stringify(value)} matches no workspace package name or existing tsconfig paths alias target.`);
|
|
1571
|
+
};
|
|
1572
|
+
const shadcn = options.shadcn || {};
|
|
1573
|
+
if ("uiImportPath" in shadcn)
|
|
1574
|
+
throw new Error("shadcn.uiImportPath was renamed to shadcn.ui.");
|
|
1575
|
+
if ("componentImportSources" in shadcn)
|
|
1576
|
+
throw new Error("shadcn.componentImportSources was renamed to shadcn.components.");
|
|
1577
|
+
const uiRoots = shadcn.ui === undefined ? web.map((path) => posix2.join(path, "components/ui")) : resolveUi(shadcn.ui);
|
|
1578
|
+
const ui = presetScopes(uiRoots, "**");
|
|
1479
1579
|
const testFiles = ["**/{test,tests,e2e}/**/*.{ts,tsx,mts,cts}", "**/*.{test,spec}.{ts,tsx,mts,cts}"];
|
|
1480
1580
|
const safetyFiles = [...appFiles, ...testFiles];
|
|
1481
|
-
const components = presetScopes(web, "components/**/*.tsx");
|
|
1482
|
-
const hooks = presetScopes(web, "hooks/**/*.ts");
|
|
1581
|
+
const components = presetScopes([...web, ...packageSources], "components/**/*.tsx");
|
|
1582
|
+
const hooks = presetScopes([...web, ...packageSources], "hooks/**/*.ts");
|
|
1483
1583
|
const level = options.severity ?? "error";
|
|
1484
1584
|
const publicEntrypoints = options.imports?.backendEntryPoints ?? backendRoots.flatMap((directory) => {
|
|
1485
1585
|
const name = presetPackageName(root, directory);
|
|
@@ -1517,13 +1617,17 @@ function presetResolveContext(options) {
|
|
|
1517
1617
|
nextWeb,
|
|
1518
1618
|
backend,
|
|
1519
1619
|
backendRoots,
|
|
1620
|
+
packageSources,
|
|
1621
|
+
serviceRoots,
|
|
1520
1622
|
app,
|
|
1521
1623
|
appFiles,
|
|
1522
1624
|
webFiles,
|
|
1625
|
+
reactFiles,
|
|
1523
1626
|
backendFiles,
|
|
1524
1627
|
services,
|
|
1525
1628
|
utilities,
|
|
1526
1629
|
types,
|
|
1630
|
+
uiRoots,
|
|
1527
1631
|
ui,
|
|
1528
1632
|
generated,
|
|
1529
1633
|
testFiles,
|