oxlint-plugin-arch 0.13.0 → 0.13.2
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 +71 -0
- package/README.md +7 -7
- package/dist/index.js +2154 -504
- package/dist/preset/groups/top.d.ts +2 -0
- package/dist/preset/preset-sets.d.ts +2 -0
- package/dist/preset/preset.constants.d.ts +4 -1
- package/dist/preset/preset.js +299 -57
- package/dist/preset/preset.utils.d.ts +2 -0
- package/dist/rules/code/code-inside.d.ts +1 -0
- package/dist/rules/effect/effect-layer-keys.d.ts +1 -0
- package/dist/rules/effect/effect-service-params.d.ts +1 -0
- package/dist/rules/effect/effect-yield-name.d.ts +1 -0
- package/dist/rules/effect/effect-yields-first.d.ts +1 -0
- package/dist/rules/files/files-siblings.d.ts +1 -0
- package/dist/rules/files/files-top-level.d.ts +1 -0
- package/dist/rules/layout/layout-type-arguments.d.ts +1 -0
- package/dist/rules/naming/naming-imports.d.ts +1 -0
- package/dist/rules/naming/naming-keys.d.ts +1 -0
- package/dist/rules/naming/naming-labels.d.ts +1 -0
- package/dist/rules/naming/naming-pairs.d.ts +1 -0
- package/dist/types/boundaries.types.d.ts +33 -1
- package/dist/types/code.types.d.ts +12 -0
- package/dist/types/comments.types.d.ts +1 -0
- package/dist/types/dillon-anti-slop.types.d.ts +6 -0
- package/dist/types/effect.types.d.ts +21 -0
- package/dist/types/files.types.d.ts +33 -3
- package/dist/types/layout.types.d.ts +16 -0
- package/dist/types/naming.types.d.ts +74 -2
- package/dist/types/preset.types.d.ts +26 -0
- package/dist/types/react.types.d.ts +13 -0
- package/dist/types/types.types.d.ts +4 -0
- package/dist/utils/ast.utils.d.ts +1 -2
- package/dist/utils/boundaries.utils.d.ts +72 -4
- package/dist/utils/naming.utils.d.ts +4 -1
- package/dist/utils/options.utils.d.ts +2 -0
- package/dist/vendor/dillon-anti-slop/index.js +40 -15
- package/package.json +1 -1
- package/src/vendor/dillon-anti-slop/UPSTREAM.md +3 -0
package/dist/preset/preset.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
3
3
|
|
|
4
4
|
// src/preset/preset.ts
|
|
5
|
-
import { globSync } from "node:fs";
|
|
5
|
+
import { globSync as globSync2 } from "node:fs";
|
|
6
6
|
import { createRequire as createRequire3 } from "node:module";
|
|
7
7
|
import { matchesGlob as matchesGlob2, posix as posix9 } from "node:path";
|
|
8
8
|
|
|
@@ -399,6 +399,7 @@ var presetGroupOrder = [
|
|
|
399
399
|
"kit",
|
|
400
400
|
"limits",
|
|
401
401
|
"deny",
|
|
402
|
+
"top",
|
|
402
403
|
"externals"
|
|
403
404
|
];
|
|
404
405
|
var presetDefaultGroups = [
|
|
@@ -412,6 +413,7 @@ var presetDefaultGroups = [
|
|
|
412
413
|
"imports",
|
|
413
414
|
"tests",
|
|
414
415
|
"deny",
|
|
416
|
+
"top",
|
|
415
417
|
"externals"
|
|
416
418
|
];
|
|
417
419
|
var presetExternals = [
|
|
@@ -432,7 +434,7 @@ var presetGroupOptions = {
|
|
|
432
434
|
limits: { level: "error", lines: 400, types: 1000, complexity: 32 },
|
|
433
435
|
style: { level: "error", quotes: "single", semicolons: true, multiline: true, padding: true, dashes: true },
|
|
434
436
|
basics: { level: "error", trivial: true, state: true, switch: true, negation: true },
|
|
435
|
-
naming: { level: "error", files: true, exports: true, folders: true, unique: undefined },
|
|
437
|
+
naming: { level: "error", files: true, exports: true, folders: true, unique: undefined, imports: {} },
|
|
436
438
|
comments: { level: "error", directives: true },
|
|
437
439
|
types: { level: "error", placement: true, safety: true, colocated: [] },
|
|
438
440
|
imports: { level: "error", sort: true, boundaries: true, aliases: {}, internal: undefined },
|
|
@@ -449,9 +451,10 @@ var presetGroupOptions = {
|
|
|
449
451
|
schemas: { level: "error" },
|
|
450
452
|
forms: { level: "error" },
|
|
451
453
|
sql: { level: "error" },
|
|
452
|
-
effect: { level: "error", code: true, width: 140, exports: undefined, skip: undefined, entries: [] },
|
|
454
|
+
effect: { level: "error", code: true, width: 140, exports: undefined, skip: undefined, entries: [], files: [], strict: true },
|
|
453
455
|
console: { level: "error" },
|
|
454
|
-
deny: { level: "error", imports: {}, calls: {} },
|
|
456
|
+
deny: { level: "error", imports: {}, calls: {}, only: [] },
|
|
457
|
+
top: { level: "error", allow: undefined, deny: undefined, message: undefined, stem: undefined, singularize: undefined, roles: undefined, separators: undefined, file: undefined, placeholder: undefined },
|
|
455
458
|
externals: { level: "error" }
|
|
456
459
|
};
|
|
457
460
|
var presetQueryWrites = ["insertInto", "updateTable", "deleteFrom", "mergeInto", "replaceInto", "dispatch", "send"];
|
|
@@ -511,10 +514,13 @@ var presetEffectImports = {
|
|
|
511
514
|
"ts-pattern": "Use effect/Match.",
|
|
512
515
|
dayjs: "Use effect/DateTime."
|
|
513
516
|
};
|
|
517
|
+
var presetEffectServerImports = {
|
|
518
|
+
ai: "Use effect/ai.",
|
|
519
|
+
"@ai-sdk/**": "Use effect/ai."
|
|
520
|
+
};
|
|
514
521
|
var presetRecipeMark = Symbol("recipe");
|
|
515
522
|
var presetDetectBackend = ["@orpc/server", "hono", "kysely", "drizzle-orm", "express", "fastify", "elysia"];
|
|
516
523
|
var presetDetectSkip = ["node_modules", "dist", "build", "coverage", "public", ".output", ".next", ".vinext", ".turbo", ".wrangler", "target"];
|
|
517
|
-
var presetStylesheetNames = ["styles", "globals", "global", "app", "index", "main", "style", "tailwind"];
|
|
518
524
|
var presetRegistryFiles = ["core/core.services.ts", "service.ts"];
|
|
519
525
|
var presetRegistrySkip = ["brand", "database", "db", "math", "security", "tools"];
|
|
520
526
|
var presetServerEntries = ["rpc", "server", "security", "services"];
|
|
@@ -588,7 +594,7 @@ var presetTemplates = {
|
|
|
588
594
|
};
|
|
589
595
|
|
|
590
596
|
// src/preset/preset.utils.ts
|
|
591
|
-
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
597
|
+
import { existsSync, globSync, readFileSync, statSync } from "node:fs";
|
|
592
598
|
import { createRequire as createRequire2 } from "node:module";
|
|
593
599
|
import { posix, resolve } from "node:path";
|
|
594
600
|
class PresetError extends Error {
|
|
@@ -703,6 +709,23 @@ function presetIntersect(glob, scope) {
|
|
|
703
709
|
function presetGlobEscape(path) {
|
|
704
710
|
return path.replaceAll(/[[\]]/g, (bracket) => `[${bracket}]`);
|
|
705
711
|
}
|
|
712
|
+
function presetFileOverlaps(root, build, keys) {
|
|
713
|
+
const matched = new Map;
|
|
714
|
+
for (const key of keys) {
|
|
715
|
+
for (const path of globSync(key.pattern, { cwd: root, exclude: (name) => posix.basename(name) === "node_modules" })) {
|
|
716
|
+
matched.set(path, [...matched.get(path) ?? [], key]);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
const reach = new Map(keys.map((key) => [key, [...matched.values()].filter((found) => found.includes(key)).length]));
|
|
720
|
+
const merged = [...matched].flatMap(([path, found]) => {
|
|
721
|
+
if (found.length < 2) {
|
|
722
|
+
return [];
|
|
723
|
+
}
|
|
724
|
+
const ordered = found.toSorted((left, right) => (reach.get(right) ?? 0) - (reach.get(left) ?? 0));
|
|
725
|
+
return [build(presetGlobEscape(path), ordered.reduce((folder, key) => Object(presetMerge(folder, key.folder)), {}))];
|
|
726
|
+
});
|
|
727
|
+
return [...keys, ...merged];
|
|
728
|
+
}
|
|
706
729
|
function presetAliases(root, aliases) {
|
|
707
730
|
return Object.fromEntries(Object.entries(aliases ?? {}).map(([alias, path]) => [alias.replace(/\/$/, ""), resolve(root, path)]));
|
|
708
731
|
}
|
|
@@ -769,7 +792,8 @@ function presetSource(root, directory, configured) {
|
|
|
769
792
|
}
|
|
770
793
|
function presetFilled(conventions, exists) {
|
|
771
794
|
return Object.fromEntries(Object.entries(conventions).filter(([path, folder]) => {
|
|
772
|
-
const
|
|
795
|
+
const denied = folder instanceof Object && folder.deny instanceof Object ? { ...folder.deny.imports, ...folder.deny.calls } : undefined;
|
|
796
|
+
const empty = denied !== undefined && Object.keys(denied).length === 0;
|
|
773
797
|
return exists(path) && !empty;
|
|
774
798
|
}));
|
|
775
799
|
}
|
|
@@ -793,8 +817,27 @@ function presetMerge(base, top) {
|
|
|
793
817
|
const merged = presetMergeObject(base, top, 2);
|
|
794
818
|
const before = base.layout instanceof Object ? base.layout.files : undefined;
|
|
795
819
|
const after = top.layout instanceof Object ? top.layout.files : undefined;
|
|
820
|
+
const owned = [base.deny, top.deny].flatMap((deny) => deny instanceof Object ? deny.only ?? [] : []);
|
|
821
|
+
if (owned.length > 0 && merged.deny instanceof Object) {
|
|
822
|
+
merged.deny = { ...merged.deny, only: owned };
|
|
823
|
+
}
|
|
796
824
|
return before && after && merged.layout instanceof Object ? { ...merged, layout: { ...merged.layout, files: [...before, ...after] } } : merged;
|
|
797
825
|
}
|
|
826
|
+
function presetNames(inherited, entries, option) {
|
|
827
|
+
if (!Array.isArray(entries) || !entries.every((entry) => typeof entry === "string" && presetLayoutFileName.test(entry))) {
|
|
828
|
+
throw new PresetError(`${option} takes file names such as \`*.prompts.ts\`; \`!*.utils.ts\` removes one.`);
|
|
829
|
+
}
|
|
830
|
+
const names = [...inherited];
|
|
831
|
+
for (const entry of entries) {
|
|
832
|
+
const index = names.indexOf(entry.startsWith("!") ? entry.slice(1) : entry);
|
|
833
|
+
if (entry.startsWith("!") && index !== -1) {
|
|
834
|
+
names.splice(index, 1);
|
|
835
|
+
} else if (!entry.startsWith("!") && index === -1) {
|
|
836
|
+
names.push(entry);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return names;
|
|
840
|
+
}
|
|
798
841
|
|
|
799
842
|
// src/preset/preset-effect.ts
|
|
800
843
|
var presetEffectQueryWrites = Object.fromEntries(presetQueryWrites.map((name) => [name, "A queries file only reads; write in an actions file."]));
|
|
@@ -802,8 +845,11 @@ var presetEffectWorkflowCalls = Object.fromEntries(["selectFrom", ...presetQuery
|
|
|
802
845
|
var presetEffectFrameworks = Object.fromEntries(["@tanstack/*", "react-dom", "react-dom/**", "next", "next/**", "vite"].map((name) => [name, "The server imports no web framework."]));
|
|
803
846
|
var presetEffectRouters = ["@tanstack/react-router", "next/link", "next/navigation", "next/router", "expo-router"];
|
|
804
847
|
var presetEffectErrors = ["Schema.TaggedError", "Schema.ErrorClass", "Data.TaggedError", "Data.Error"];
|
|
805
|
-
var
|
|
806
|
-
var
|
|
848
|
+
var presetEffectContractKinds = ["*.schemas.ts", "*.admin.schemas.ts", "*.contract.ts", "*.admin.contract.ts", "*.errors.ts", "*.layer.types.ts", "*.service.ts"];
|
|
849
|
+
var presetEffectSpans = [
|
|
850
|
+
"error",
|
|
851
|
+
{ labels: [{ callee: "Effect.fn", name: "{name}", drop: ["Action", "Query"], loose: true }] }
|
|
852
|
+
];
|
|
807
853
|
var presetEffectSchemaUses = {
|
|
808
854
|
namespaces: ["Schema"],
|
|
809
855
|
methods: ["Array", "Union", "Record", "Tuple"],
|
|
@@ -848,7 +894,7 @@ function presetEffectServerKeys(at) {
|
|
|
848
894
|
rules: {
|
|
849
895
|
"arch/types-declarations": ["error", { modules: true }],
|
|
850
896
|
"arch/types-inline": ["error", { variables: true, assertions: true, arguments: true }],
|
|
851
|
-
"arch/comments-code": ["error", { why: false, jsdoc: true, allow: ["^\\s*SAFETY: "] }]
|
|
897
|
+
"arch/comments-code": ["error", { why: false, jsdoc: true, allow: ["^\\s*SAFETY: "], steps: true }]
|
|
852
898
|
}
|
|
853
899
|
},
|
|
854
900
|
[at("domains")]: {
|
|
@@ -876,17 +922,18 @@ function presetEffectServerKeys(at) {
|
|
|
876
922
|
},
|
|
877
923
|
[at("domains/*/*.actions.ts")]: {
|
|
878
924
|
limits: { lines: 1000 },
|
|
879
|
-
rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Action[A-Z]\\w*"], types: true }] }
|
|
925
|
+
rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Action[A-Z]\\w*"], types: true }], "arch/naming-labels": presetEffectSpans }
|
|
880
926
|
},
|
|
881
927
|
[at("domains/*/*.queries.ts")]: {
|
|
882
928
|
limits: { lines: 1000 },
|
|
883
929
|
deny: { calls: presetEffectQueryWrites, imports: { "./*.actions{,.ts}": "A queries file imports no actions." } },
|
|
884
|
-
rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Query[A-Z]\\w*"], types: true }] }
|
|
930
|
+
rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Query[A-Z]\\w*"], types: true }], "arch/naming-labels": presetEffectSpans }
|
|
885
931
|
},
|
|
886
932
|
[at("domains/*/*.constants.ts")]: { rules: { "arch/files-no-functions": ["error", { reexports: false }] } },
|
|
887
|
-
[at("domains/*/*.layer.ts")]: { rules: { "arch/effect-layer-file": "error" } },
|
|
888
|
-
[at("domains/*/*.driver.ts")]: { rules: { "arch/effect-layer-file": "error" } },
|
|
933
|
+
[at("domains/*/*.layer.ts")]: { rules: { "arch/effect-layer-file": ["error", { suffix: "Layer" }] } },
|
|
934
|
+
[at("domains/*/*.driver.ts")]: { rules: { "arch/effect-layer-file": ["error", { suffix: "Layer" }] } },
|
|
889
935
|
[at("domains/*/*.workflows.ts")]: {
|
|
936
|
+
rules: { "arch/naming-labels": ["error", { labels: [{ callee: "Workflow.make", name: "{name}", drop: "Workflow$" }] }] },
|
|
890
937
|
deny: {
|
|
891
938
|
calls: presetEffectWorkflowCalls,
|
|
892
939
|
imports: {
|
|
@@ -905,7 +952,14 @@ function presetEffectServerKeys(at) {
|
|
|
905
952
|
[at("rpc")]: { rules: { "arch/code-delegate": "error" } }
|
|
906
953
|
};
|
|
907
954
|
}
|
|
908
|
-
function presetEffectContractKeys(at) {
|
|
955
|
+
function presetEffectContractKeys(at, files) {
|
|
956
|
+
const kinds = presetNames(presetEffectContractKinds, files ?? [], "effect.files");
|
|
957
|
+
if (kinds.some((kind) => !/^\*\.[a-z0-9.-]+\.ts$/.test(kind))) {
|
|
958
|
+
throw new PresetError("effect.files takes contract file kinds such as `*.service.ts`.");
|
|
959
|
+
}
|
|
960
|
+
const pattern = `^[a-z0-9]+(?:-[a-z0-9]+)*(?:${kinds.map((kind) => kind.slice(1, -3).replaceAll(".", "[.]")).join("|")})[.]ts$`;
|
|
961
|
+
const listed = kinds.flatMap((kind) => kind === "*.schemas.ts" ? ["<d>.schemas.ts", "<d>-<part>.schemas.ts"] : [`<d>${kind.slice(1)}`]);
|
|
962
|
+
const message = `Contract files use ${listed.slice(0, -1).join(", ")} or ${listed.at(-1)}.`;
|
|
909
963
|
const schemas = {
|
|
910
964
|
rules: {
|
|
911
965
|
"arch/files-constants-only": ["error", { types: true, functions: true }],
|
|
@@ -918,7 +972,7 @@ function presetEffectContractKeys(at) {
|
|
|
918
972
|
return {
|
|
919
973
|
[at("*/*.ts")]: {
|
|
920
974
|
rules: {
|
|
921
|
-
"arch/naming-file": ["error", { pattern
|
|
975
|
+
"arch/naming-file": ["error", { pattern, message }],
|
|
922
976
|
"arch/naming-folder": ["error", { singularize: "none", separators: [".", "-"] }],
|
|
923
977
|
"arch/files-exports": ["error", { stem: "folder", singularize: "trailing-s", patterns: ["{Domain}\\w*"] }]
|
|
924
978
|
}
|
|
@@ -928,6 +982,7 @@ function presetEffectContractKeys(at) {
|
|
|
928
982
|
[at("*/*.contract.ts")]: schemas,
|
|
929
983
|
[at("*/*.errors.ts")]: noFunctions,
|
|
930
984
|
[at("*/*.layer.types.ts")]: noFunctions,
|
|
985
|
+
[at("*/*.service.ts")]: noFunctions,
|
|
931
986
|
[at("shared/*.ts")]: {
|
|
932
987
|
rules: {
|
|
933
988
|
"arch/naming-file": "off",
|
|
@@ -946,19 +1001,116 @@ function presetEffectContractKeys(at) {
|
|
|
946
1001
|
}
|
|
947
1002
|
};
|
|
948
1003
|
}
|
|
949
|
-
function
|
|
1004
|
+
function presetEffectTop(allow) {
|
|
1005
|
+
return { top: { allow, stem: "folder" } };
|
|
1006
|
+
}
|
|
1007
|
+
function presetEffectStrictServer(at, service) {
|
|
1008
|
+
const operations = presetEffectTop([{ kind: "const", exported: true, from: "^Effect\\.fn" }, { kind: "const", names: "^decode[A-Z]" }]);
|
|
1009
|
+
const layerKeys = service === undefined ? {} : { "arch/effect-layer-keys": ["error", { service }] };
|
|
1010
|
+
return {
|
|
1011
|
+
[at(".")]: {
|
|
1012
|
+
rules: {
|
|
1013
|
+
"arch/effect-yield-name": "error",
|
|
1014
|
+
"arch/effect-yields-first": "error",
|
|
1015
|
+
"arch/code-inside": ["error", { inside: [{ callee: "*.transaction", calls: { "*Workflow.execute": "Start the workflow after the commit." } }] }]
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
[at("domains")]: { naming: { imports: { "./{file}.ts": "{File}" } } },
|
|
1019
|
+
[at("domains/*/*.actions.ts")]: operations,
|
|
1020
|
+
[at("domains/*/*.queries.ts")]: operations,
|
|
1021
|
+
[at("domains/*/*.utils.ts")]: {
|
|
1022
|
+
...operations,
|
|
1023
|
+
rules: { "arch/naming-labels": ["error", { labels: [{ callee: "Effect.fn", name: "{name}", loose: true }] }] }
|
|
1024
|
+
},
|
|
1025
|
+
[at("domains/*/*.layer.ts")]: {
|
|
1026
|
+
rules: { "arch/naming-keys": ["error", { callees: ["*.bind", "*.of"], name: "^{folder}(?:Action|Query){Path}$", inline: false }], ...layerKeys }
|
|
1027
|
+
},
|
|
1028
|
+
[at("domains/*/*.workflows.ts")]: {
|
|
1029
|
+
...presetEffectTop([{ kind: "const", from: "(?:^Workflow\\.make|\\.toLayer|^Layer\\.\\w+|^Schedule\\.\\w+)$" }]),
|
|
1030
|
+
rules: {
|
|
1031
|
+
"arch/files-exports": ["error", { stem: "folder", patterns: ["{Domain}\\w*Workflow", "{Domain}WorkflowsLayer", "{domain}Schedule[A-Z]\\w*"] }],
|
|
1032
|
+
"arch/naming-labels": [
|
|
1033
|
+
"error",
|
|
1034
|
+
{
|
|
1035
|
+
labels: [
|
|
1036
|
+
{ callee: "Workflow.make", name: "{name}", drop: "Workflow$" },
|
|
1037
|
+
{ callee: "Effect.fn", name: "{name}", within: "*.toLayer", loose: true }
|
|
1038
|
+
]
|
|
1039
|
+
}
|
|
1040
|
+
]
|
|
1041
|
+
}
|
|
1042
|
+
},
|
|
1043
|
+
[at("domains/*/*.constants.ts")]: presetEffectTop([{ kind: "const", exported: true, names: "^{DOMAIN}_" }]),
|
|
1044
|
+
[at("domains/*/*.config.ts")]: presetEffectTop([{ kind: "const", exported: true, names: "^{domain}\\w*Config$" }]),
|
|
1045
|
+
[at("domains/*/*.prompts.ts")]: { rules: { "arch/files-exports": ["error", { stem: "folder", patterns: ["{domain}Prompt[A-Z]\\w*"], types: true }] } },
|
|
1046
|
+
[at("rpc/*/*.ts")]: {
|
|
1047
|
+
...presetEffectTop([{ kind: "const", exported: true }]),
|
|
1048
|
+
rules: {
|
|
1049
|
+
"arch/naming-file": ["error", { template: "{group}/rpc.{group}.{area}.ts", message: "Handlers use rpc/<group>/rpc.<group>.<area>.ts." }],
|
|
1050
|
+
"arch/files-exports": ["error", { file: "rpc.{group}.{area}.ts", names: ["rpc{Group}{Area}"] }],
|
|
1051
|
+
"arch/naming-keys": ["error", { exports: "^rpc[A-Z]", delegate: true }]
|
|
1052
|
+
}
|
|
1053
|
+
},
|
|
1054
|
+
[at("mails/*.mail.tsx")]: { rules: { "arch/files-exports": ["error", { file: "{name}.mail.tsx", names: ["{name}Mail", "{Name}Email"], types: true }] } }
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
function presetEffectStrictContract(at, name, files) {
|
|
1058
|
+
const key = name === undefined ? [] : [{ callee: "Context.Service", name: `${name}/{folder}/{file}/{name}` }];
|
|
1059
|
+
const labels = key.length > 0 ? { "arch/naming-labels": ["error", { labels: key }] } : {};
|
|
1060
|
+
const service = presetNames(presetEffectContractKinds, files ?? [], "effect.files").includes("*.service.ts");
|
|
1061
|
+
const sibling = service ? { "arch/files-siblings": ["error", { require: "{folder}.service.ts", message: "A domain is reached through its service." }] } : {};
|
|
1062
|
+
return {
|
|
1063
|
+
[at("*/*.schemas.ts")]: presetEffectTop(["type", "interface", "namespace", { kind: "const", exported: true }]),
|
|
1064
|
+
[at("*/*.contract.ts")]: {
|
|
1065
|
+
...presetEffectTop(["type", { kind: "const", exported: true }, { kind: "const", exported: false, from: "^[A-Z]\\w*\\." }]),
|
|
1066
|
+
deny: { calls: { "Schema.Struct": "Name the Schema in <d>.schemas.ts." } },
|
|
1067
|
+
rules: sibling
|
|
1068
|
+
},
|
|
1069
|
+
[at("*/*.service.ts")]: {
|
|
1070
|
+
...presetEffectTop(["interface", "type", "class", "namespace"]),
|
|
1071
|
+
rules: { ...labels, "arch/effect-service-params": "error" }
|
|
1072
|
+
},
|
|
1073
|
+
[at("*/*.errors.ts")]: {
|
|
1074
|
+
...presetEffectTop([{ kind: "class", exported: true }]),
|
|
1075
|
+
rules: {
|
|
1076
|
+
"arch/naming-labels": [
|
|
1077
|
+
"error",
|
|
1078
|
+
{
|
|
1079
|
+
labels: [
|
|
1080
|
+
{ callee: ["Schema.TaggedErrorClass", "Schema.TaggedError", "Data.TaggedError"], name: "{name}" },
|
|
1081
|
+
{ callee: "*.Tagged", property: "tag", name: "{name}" },
|
|
1082
|
+
{ callee: "*.Tagged", property: "code", pattern: "^[A-Z][A-Z0-9_]*$" }
|
|
1083
|
+
]
|
|
1084
|
+
}
|
|
1085
|
+
]
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
var presetEffectStrictTests = { top: { allow: ["describe*", "it*", "test*", "layer*"] } };
|
|
1091
|
+
function presetEffectUnit(root, key, value, contract) {
|
|
950
1092
|
if (!value.effect) {
|
|
951
1093
|
return {};
|
|
952
1094
|
}
|
|
953
1095
|
const at = presetEffectAt(root, key, value);
|
|
954
|
-
const
|
|
1096
|
+
const own = presetEffectContract(root, key, value);
|
|
955
1097
|
const server = presetEffectDomains(root, key, value);
|
|
956
|
-
const
|
|
1098
|
+
const options = value.effect instanceof Object ? value.effect : {};
|
|
1099
|
+
const service = contract && `${posix2.join(root, contract.source)}/{folder}/{folder}.service.ts`;
|
|
1100
|
+
const base = server ? presetEffectServerKeys(at) : presetEffectContractKeys(at, options.files);
|
|
1101
|
+
const tests = { "tests/**/*.test.ts": presetEffectStrictTests, "test/**/*.test.ts": presetEffectStrictTests };
|
|
1102
|
+
const strict = server ? { ...presetEffectStrictServer(at, service), ...tests } : presetEffectStrictContract(at, presetManifest(root, key).name, options.files);
|
|
1103
|
+
const locked = options.strict === false ? {} : strict;
|
|
1104
|
+
const paths = [...new Set([...Object.keys(base), ...Object.keys(locked)])];
|
|
1105
|
+
const merged = (path) => (path in base) && (path in locked) ? presetMerge(base[path], locked[path]) : base[path] ?? locked[path];
|
|
1106
|
+
const conventions = Object.fromEntries(paths.map((path) => [path, merged(path)]));
|
|
1107
|
+
const keys = server || own ? presetFilled(conventions, presetEffectExists(root, key)) : {};
|
|
957
1108
|
const errors = Object.fromEntries(presetEffectErrors.map((name) => [name, "Tagged errors live in <d>.errors.ts."]));
|
|
958
|
-
const placement = server ||
|
|
1109
|
+
const placement = server || own ? { types: { placement: false } } : {};
|
|
1110
|
+
const imports = server || own ? { ...presetEffectImports, ...presetEffectServerImports } : presetEffectImports;
|
|
959
1111
|
return {
|
|
960
1112
|
...keys,
|
|
961
|
-
".": presetMerge({ ...placement, deny: { imports
|
|
1113
|
+
".": presetMerge({ ...placement, deny: { imports, calls: errors } }, keys["."] ?? {}),
|
|
962
1114
|
[at("**/*.errors.ts")]: { deny: { calls: Object.fromEntries(presetEffectErrors.map((name) => [name, false])) } }
|
|
963
1115
|
};
|
|
964
1116
|
}
|
|
@@ -1015,9 +1167,13 @@ function presetEffectKit(root, key, value, units) {
|
|
|
1015
1167
|
return { ".": { deny: { imports: Object.fromEntries(names.flatMap((name) => [[name, message], [`${name}/**`, message]])) } } };
|
|
1016
1168
|
}
|
|
1017
1169
|
function presetEffectConventions(root, key, value, units) {
|
|
1018
|
-
const
|
|
1170
|
+
const packages = units.flatMap(([other, folder]) => {
|
|
1171
|
+
const name = presetEffectContract(root, other, folder) ? presetManifest(root, other).name : undefined;
|
|
1172
|
+
return name === undefined ? [] : [{ name, source: presetSource(root, other, folder instanceof Object ? folder.source : undefined) }];
|
|
1173
|
+
});
|
|
1174
|
+
const contracts = packages.map((item) => item.name);
|
|
1019
1175
|
return [
|
|
1020
|
-
...Object.entries(presetEffectUnit(root, key, value)),
|
|
1176
|
+
...Object.entries(presetEffectUnit(root, key, value, packages.at(0))),
|
|
1021
1177
|
...Object.entries(presetEffectFrontend(root, key, value)),
|
|
1022
1178
|
...Object.entries(presetEffectWeb(root, key, value, contracts)),
|
|
1023
1179
|
...Object.entries(presetEffectKit(root, key, value, units))
|
|
@@ -1422,8 +1578,9 @@ var denyGroup = (project, settings) => {
|
|
|
1422
1578
|
const imports = kept(settings.imports);
|
|
1423
1579
|
const calls = kept(settings.calls);
|
|
1424
1580
|
const overrides = [];
|
|
1425
|
-
|
|
1426
|
-
|
|
1581
|
+
const only = settings.only.length > 0 ? { only: settings.only } : {};
|
|
1582
|
+
if (Object.keys(imports).length > 0 || Object.keys(calls).length > 0 || settings.only.length > 0) {
|
|
1583
|
+
overrides.push(presetOverride([`**/${presetExtensions}`], { "arch/boundaries-deny": ["error", { imports, calls, ...only }] }));
|
|
1427
1584
|
}
|
|
1428
1585
|
const reads = Object.fromEntries(presetQueryWrites.map((name) => [name, "Query files only read; move writes to an action."]));
|
|
1429
1586
|
for (const unit of project.units.filter((item) => item.kind === "backend")) {
|
|
@@ -1431,7 +1588,7 @@ var denyGroup = (project, settings) => {
|
|
|
1431
1588
|
...presetServiceScopes(project.serviceFolders, [unit.source], "**/*-query.*.ts"),
|
|
1432
1589
|
...presetServiceScopes(project.serviceFolders, project.operations.filter((path) => path === unit.source), "*/queries/*.ts")
|
|
1433
1590
|
];
|
|
1434
|
-
overrides.push(presetOverride(files, { "arch/boundaries-deny": ["error", { imports, calls: kept({ ...reads, ...settings.calls }) }] }));
|
|
1591
|
+
overrides.push(presetOverride(files, { "arch/boundaries-deny": ["error", { imports, calls: kept({ ...reads, ...settings.calls }), ...only }] }));
|
|
1435
1592
|
}
|
|
1436
1593
|
return overrides;
|
|
1437
1594
|
};
|
|
@@ -1500,17 +1657,19 @@ var effectGroup = (project, settings) => {
|
|
|
1500
1657
|
"arch/code-switch": ["error", { message: "Branch with Match." }],
|
|
1501
1658
|
"arch/naming-exports": "off",
|
|
1502
1659
|
"arch/code-function-nesting": ["error", { max: 4 }],
|
|
1503
|
-
"arch/code-inline": ["error", { max, nesting: 2, keep: ["^[A-Z]", "^layer"], allow: "^(?:decode|encode)[A-Z]", services: ["of"] }],
|
|
1660
|
+
"arch/code-inline": ["error", { max, nesting: 2, callbacks: true, keep: ["^[A-Z]", "^layer"], allow: "^(?:decode|encode)[A-Z]", services: ["of"] }],
|
|
1504
1661
|
"arch/code-yield-argument": "error",
|
|
1505
1662
|
"arch/effect-layers": "error",
|
|
1506
1663
|
"arch/effect-merge-pipe": "error",
|
|
1507
1664
|
"arch/effect-values-first": "error",
|
|
1508
1665
|
"arch/layout-arrow-body": ["error", { max }],
|
|
1509
|
-
"arch/layout-call-arguments": ["error", { split: false, hug: { max }, join: { max, nesting: 2, keep: ["Layer.mergeAll"] } }],
|
|
1510
|
-
"arch/layout-call-depth": ["error", { max: 2 }],
|
|
1666
|
+
"arch/layout-call-arguments": ["error", { split: false, pipe: { min: 2 }, hug: { max }, join: { max, nesting: 2, keep: ["Layer.mergeAll"] } }],
|
|
1667
|
+
"arch/layout-call-depth": ["error", { max: 2, callbacks: true }],
|
|
1511
1668
|
"arch/layout-items": "error",
|
|
1669
|
+
"arch/layout-object": ["error", { min: 3, scope: "call-args", params: true, nested: true, siblings: true, inner: 2 }],
|
|
1512
1670
|
"arch/layout-pipe": "error",
|
|
1513
|
-
"arch/layout-ternary": ["error", {
|
|
1671
|
+
"arch/layout-ternary": ["error", { max: 100, branch: { max: 3 } }],
|
|
1672
|
+
"arch/layout-type-arguments": ["error", { min: 3 }]
|
|
1514
1673
|
}),
|
|
1515
1674
|
presetOverride(files, {
|
|
1516
1675
|
"arch/code-justified-call": [
|
|
@@ -1845,6 +2004,10 @@ var namingGroup = (project, settings, owner) => {
|
|
|
1845
2004
|
}
|
|
1846
2005
|
}
|
|
1847
2006
|
add("exports", presetServiceScopes(project.serviceFolders, web, "**/*.rsc.{ts,tsx}"), { "arch/naming-exports": ["error", namingFrontendPrefix, { mode: "pattern", pattern: "^[a-z][a-z0-9]*Rsc[A-Z][a-zA-Z0-9]*$" }] }, ["**/*-{action,query}.*.rsc.{ts,tsx}", ...testFiles]);
|
|
2007
|
+
const imports = Object.entries(settings.imports).flatMap(([source, name]) => name === false ? [] : [{ source, name }]);
|
|
2008
|
+
if (imports.length > 0) {
|
|
2009
|
+
overrides.push(presetOverride([`**/${presetExtensions}`], { "arch/naming-imports": ["error", { imports }] }));
|
|
2010
|
+
}
|
|
1848
2011
|
if (typeof exportsPattern === "string") {
|
|
1849
2012
|
overrides.push(presetOverride([`**/${presetExtensions}`], { "arch/naming-exports": ["error", { mode: "pattern", pattern: exportsPattern, types: true }] }));
|
|
1850
2013
|
}
|
|
@@ -1978,7 +2141,10 @@ var shadcnGroup = (project) => {
|
|
|
1978
2141
|
|
|
1979
2142
|
// src/preset/groups/sql.ts
|
|
1980
2143
|
var sqlGroup = (project) => [
|
|
1981
|
-
presetOverride(presetServiceScopes(project.serviceFolders, project.serviceRoots, "**/*.ts"), {
|
|
2144
|
+
presetOverride(presetServiceScopes(project.serviceFolders, project.serviceRoots, "**/*.ts"), {
|
|
2145
|
+
"arch/backend-sql-like": "error",
|
|
2146
|
+
"arch/layout-chain": ["error", { groups: [{ min: 3, methods: ["selectFrom", "insertInto", "updateTable", "deleteFrom", "mergeInto", "with"] }] }]
|
|
2147
|
+
})
|
|
1982
2148
|
];
|
|
1983
2149
|
|
|
1984
2150
|
// src/preset/groups/style.ts
|
|
@@ -2079,6 +2245,12 @@ function optionsAssertMockingApis(patterns) {
|
|
|
2079
2245
|
throw new OptionsError("Test mocking APIs must be `*` or `<vi|jest|*>.<method|*>`.");
|
|
2080
2246
|
}
|
|
2081
2247
|
}
|
|
2248
|
+
function optionsEscape(value) {
|
|
2249
|
+
return value.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2250
|
+
}
|
|
2251
|
+
function optionsWildcards(names) {
|
|
2252
|
+
return [names ?? []].flat().map((name) => new RegExp(`^${name.split("*").map(optionsEscape).join(".*")}$`));
|
|
2253
|
+
}
|
|
2082
2254
|
|
|
2083
2255
|
// src/preset/groups/tests.ts
|
|
2084
2256
|
var testsGroup = (project, settings) => {
|
|
@@ -2125,6 +2297,16 @@ var testsGroup = (project, settings) => {
|
|
|
2125
2297
|
];
|
|
2126
2298
|
};
|
|
2127
2299
|
|
|
2300
|
+
// src/preset/groups/top.ts
|
|
2301
|
+
var topGroup = (_project, { level: _level, allow, ...rest }) => {
|
|
2302
|
+
if (allow === undefined && rest.deny === undefined) {
|
|
2303
|
+
return [];
|
|
2304
|
+
}
|
|
2305
|
+
const listed = allow === undefined || allow.includes("import") ? allow : ["import", ...allow];
|
|
2306
|
+
const options = Object.fromEntries(Object.entries({ allow: listed, ...rest }).filter(([, value]) => value !== undefined));
|
|
2307
|
+
return [presetOverride([`**/${presetExtensions}`], { "arch/files-top-level": ["error", options] })];
|
|
2308
|
+
};
|
|
2309
|
+
|
|
2128
2310
|
// src/preset/groups/types.ts
|
|
2129
2311
|
var typesGroup = (project, settings) => {
|
|
2130
2312
|
const { web, backend, packageSources, app, appFiles, services, utilities, types, ui, testFiles, components, hooks } = project;
|
|
@@ -2185,9 +2367,69 @@ var typesGroup = (project, settings) => {
|
|
|
2185
2367
|
import { existsSync as existsSync5, readdirSync as readdirSync5, realpathSync as realpathSync2 } from "node:fs";
|
|
2186
2368
|
import { posix as posix8, resolve as resolve8 } from "node:path";
|
|
2187
2369
|
|
|
2370
|
+
// src/preset/preset-sets.ts
|
|
2371
|
+
var presetSetsReserved = ["domain", "stem", "file", "folder", "parent", "name", "key", "value"];
|
|
2372
|
+
function presetSets(folders, sets = {}) {
|
|
2373
|
+
const names = Object.keys(sets);
|
|
2374
|
+
const reserved = names.find((name) => presetSetsReserved.includes(name) || !/^[a-z][A-Za-z0-9]*$/.test(name));
|
|
2375
|
+
if (reserved) {
|
|
2376
|
+
throw new PresetError(`Set \`${reserved}\` needs another name: start with a lowercase letter, use letters and digits, and avoid ${presetSetsReserved.join(", ")}.`);
|
|
2377
|
+
}
|
|
2378
|
+
if (names.length === 0) {
|
|
2379
|
+
return folders;
|
|
2380
|
+
}
|
|
2381
|
+
const token = new RegExp(`\\{(${names.join("|")})\\}`);
|
|
2382
|
+
const pairs = (name) => {
|
|
2383
|
+
if (!Object.hasOwn(sets, name)) {
|
|
2384
|
+
throw new PresetError(`Unknown set \`${name}\`.`);
|
|
2385
|
+
}
|
|
2386
|
+
const set = sets[name];
|
|
2387
|
+
return Array.isArray(set) ? set.map((item) => [item, item]) : Object.entries(set);
|
|
2388
|
+
};
|
|
2389
|
+
const text = (value, key, item) => value.replaceAll("{key}", key).replaceAll("{value}", item);
|
|
2390
|
+
const fill = (value, key, item) => {
|
|
2391
|
+
if (typeof value === "string") {
|
|
2392
|
+
return text(value, key, item);
|
|
2393
|
+
}
|
|
2394
|
+
if (Array.isArray(value)) {
|
|
2395
|
+
return value.map((entry) => fill(entry, key, item));
|
|
2396
|
+
}
|
|
2397
|
+
return value instanceof Object ? Object.fromEntries(Object.entries(value).map(([name, entry]) => [text(name, key, item), fill(entry, key, item)])) : value;
|
|
2398
|
+
};
|
|
2399
|
+
const spread = (value) => {
|
|
2400
|
+
const match = token.exec(value);
|
|
2401
|
+
return match ? pairs(match[1]).flatMap(([key]) => spread(value.replace(match[0], key))) : [value];
|
|
2402
|
+
};
|
|
2403
|
+
const expandEntries = (value) => Object.entries(value).flatMap(([name, entry]) => {
|
|
2404
|
+
const match = token.exec(name);
|
|
2405
|
+
return match ? pairs(match[1]).flatMap(([key, item]) => expandEntries({ [name.replace(match[0], key)]: fill(entry, key, item) })) : [[name, name === "message" ? entry : expand(entry)]];
|
|
2406
|
+
});
|
|
2407
|
+
const expand = (value) => {
|
|
2408
|
+
if (typeof value === "string") {
|
|
2409
|
+
return token.test(value) ? spread(value) : value;
|
|
2410
|
+
}
|
|
2411
|
+
if (!Array.isArray(value)) {
|
|
2412
|
+
const marks = value instanceof Object ? Object.getOwnPropertySymbols(value).map((mark) => [mark, Reflect.get(value, mark)]) : [];
|
|
2413
|
+
return value instanceof Object ? Object.assign(Object.fromEntries(expandEntries(value)), Object.fromEntries(marks)) : value;
|
|
2414
|
+
}
|
|
2415
|
+
return value.flatMap((entry) => {
|
|
2416
|
+
if (typeof entry === "string") {
|
|
2417
|
+
return spread(entry);
|
|
2418
|
+
}
|
|
2419
|
+
if (!(entry instanceof Object) || Array.isArray(entry) || typeof entry.each !== "string") {
|
|
2420
|
+
return [expand(entry)];
|
|
2421
|
+
}
|
|
2422
|
+
const { each, ...rest } = entry;
|
|
2423
|
+
return pairs(each).map(([key, item]) => expand(fill(rest, key, item)));
|
|
2424
|
+
});
|
|
2425
|
+
};
|
|
2426
|
+
return expand(folders);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
2188
2429
|
// src/preset/preset-stylesheet.ts
|
|
2189
2430
|
import { readdirSync as readdirSync3, readFileSync as readFileSync3, realpathSync } from "node:fs";
|
|
2190
2431
|
import { posix as posix6, resolve as resolve6 } from "node:path";
|
|
2432
|
+
var presetStylesheetNames = ["styles", "globals", "global", "app", "index", "main", "style", "tailwind"];
|
|
2191
2433
|
function presetTailwindEntry(css) {
|
|
2192
2434
|
const code = css.replaceAll(/\/\*[\s\S]*?\*\//g, "");
|
|
2193
2435
|
return /(?:^|[;{}])\s*@(?:import\s+(?:url\(\s*(["']?)tailwindcss(?:\/[^"')\s]*)?\1\s*\)|(["'])tailwindcss(?:\/[^"']*)?\2)|tailwind\s)/.test(code);
|
|
@@ -2237,7 +2479,7 @@ function presetUnitSource(root, key, kind, source) {
|
|
|
2237
2479
|
}
|
|
2238
2480
|
return kind === "console" || !existsSync4(resolve7(root, key, "src")) ? key : posix7.join(key, "src");
|
|
2239
2481
|
}
|
|
2240
|
-
function
|
|
2482
|
+
function presetSets2(folder, name) {
|
|
2241
2483
|
return folder[name] !== undefined && folder[name] !== false;
|
|
2242
2484
|
}
|
|
2243
2485
|
function presetKind(key, enabled) {
|
|
@@ -2273,8 +2515,8 @@ function presetUnit(root, key, scope) {
|
|
|
2273
2515
|
const { effective } = scope;
|
|
2274
2516
|
const wildcard = scope.parent && scope.parent.depth === scope.depth && scope.parent.key.includes("*") ? scope.parent.folder : {};
|
|
2275
2517
|
const folder = { ...wildcard, ...scope.folder };
|
|
2276
|
-
const own = presetKindGroups.some((name) =>
|
|
2277
|
-
const { kind, framework } = presetKind(key, (name) => own ?
|
|
2518
|
+
const own = presetKindGroups.some((name) => presetSets2(folder, name)) || presetHasManifest(root, key);
|
|
2519
|
+
const { kind, framework } = presetKind(key, (name) => own ? presetSets2(folder, name) : Boolean(effective[name]));
|
|
2278
2520
|
const layout = {};
|
|
2279
2521
|
const settings = effective.layout;
|
|
2280
2522
|
if (settings) {
|
|
@@ -2297,7 +2539,7 @@ function presetUnits(root, scopes, ignored) {
|
|
|
2297
2539
|
const candidates = [];
|
|
2298
2540
|
for (const scope of scopes) {
|
|
2299
2541
|
const manifest = presetHasManifest(root, scope.key);
|
|
2300
|
-
const own = presetKindGroups.some((name) =>
|
|
2542
|
+
const own = presetKindGroups.some((name) => presetSets2(scope.folder, name)) || manifest;
|
|
2301
2543
|
const top = scope.parent?.key === "." && (rootKind === "library" || own);
|
|
2302
2544
|
const sibling = scope.parent !== null && candidates.includes(scope.parent) && scope.parent.depth === scope.depth && !scope.key.includes("*");
|
|
2303
2545
|
const workspace = scope.parent !== null && scope.parent.key !== "." && manifest;
|
|
@@ -2404,20 +2646,16 @@ function presetDenyEntries(inherited, changes) {
|
|
|
2404
2646
|
return [key, message];
|
|
2405
2647
|
}));
|
|
2406
2648
|
}
|
|
2407
|
-
function
|
|
2408
|
-
|
|
2409
|
-
|
|
2649
|
+
function presetDenyOnly(inherited, entries = []) {
|
|
2650
|
+
const each = entries.find((entry) => entry.each !== undefined)?.each;
|
|
2651
|
+
if (each !== undefined) {
|
|
2652
|
+
throw new PresetError(`Unknown set \`${each}\`.`);
|
|
2410
2653
|
}
|
|
2411
|
-
const
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
if (entry.startsWith("!") && index !== -1) {
|
|
2415
|
-
names.splice(index, 1);
|
|
2416
|
-
} else if (!entry.startsWith("!") && index === -1) {
|
|
2417
|
-
names.push(entry);
|
|
2418
|
-
}
|
|
2654
|
+
const known = (entry) => ("in" in entry) && (("imports" in entry) || ("calls" in entry)) && Object.keys(entry).every((key) => ["imports", "calls", "in", "message"].includes(key));
|
|
2655
|
+
if (!Array.isArray(entries) || !entries.every((entry) => entry instanceof Object && known(entry))) {
|
|
2656
|
+
throw new PresetError("deny.only takes entries such as `{ imports: [...], in: [...], message }`, with `imports` or `calls` and the places that may use them.");
|
|
2419
2657
|
}
|
|
2420
|
-
return
|
|
2658
|
+
return [...inherited, ...entries];
|
|
2421
2659
|
}
|
|
2422
2660
|
function presetEffective(folder, parent) {
|
|
2423
2661
|
const resolveGroup = (name) => {
|
|
@@ -2431,13 +2669,14 @@ function presetEffective(folder, parent) {
|
|
|
2431
2669
|
effective.deny = {
|
|
2432
2670
|
...effective.deny,
|
|
2433
2671
|
imports: presetDenyEntries(inherited.imports, deny.imports),
|
|
2434
|
-
calls: presetDenyEntries(inherited.calls, deny.calls)
|
|
2672
|
+
calls: presetDenyEntries(inherited.calls, deny.calls),
|
|
2673
|
+
only: presetDenyOnly(inherited.only, deny.only)
|
|
2435
2674
|
};
|
|
2436
2675
|
}
|
|
2437
2676
|
const layout = folder.layout;
|
|
2438
2677
|
if (effective.layout && layout instanceof Object && layout.files !== undefined) {
|
|
2439
2678
|
const inherited = (parent ? parent.layout?.files : null) ?? presetGroupOptions.layout.files;
|
|
2440
|
-
effective.layout = { ...effective.layout, files:
|
|
2679
|
+
effective.layout = { ...effective.layout, files: presetNames(inherited, layout.files, "layout.files") };
|
|
2441
2680
|
}
|
|
2442
2681
|
return effective;
|
|
2443
2682
|
}
|
|
@@ -2458,7 +2697,7 @@ function presetFolder(key, value) {
|
|
|
2458
2697
|
if (folder.severity !== undefined && !["error", "warn"].includes(folder.severity)) {
|
|
2459
2698
|
throw new PresetError(`Folder \`${key}\` severity must be 'error' or 'warn'.`);
|
|
2460
2699
|
}
|
|
2461
|
-
return folder;
|
|
2700
|
+
return Array.isArray(folder.top) ? { ...folder, top: { allow: folder.top } } : folder;
|
|
2462
2701
|
}
|
|
2463
2702
|
function presetScopesTree(root, folders) {
|
|
2464
2703
|
const entries = Object.entries(folders).map(([key, value]) => [presetNormalizeKey(key), value]);
|
|
@@ -2503,7 +2742,7 @@ function presetScopesTree(root, folders) {
|
|
|
2503
2742
|
function presetProject(config) {
|
|
2504
2743
|
const given = resolve8(config.root ?? process.cwd());
|
|
2505
2744
|
const root = existsSync5(given) ? realpathSync2(given) : given;
|
|
2506
|
-
const expanded = Object.entries(config.folders ?? {}).flatMap(([key, value]) => presetBraces(key).map((path) => [presetPortableKey(path), value, key.includes("{")]));
|
|
2745
|
+
const expanded = Object.entries(presetSets(config.folders ?? {}, config.sets)).flatMap(([key, value]) => presetBraces(key).map((path) => [presetPortableKey(path), value, key.includes("{")]));
|
|
2507
2746
|
const files = expanded.filter(([key]) => presetIsFileKey(root, key));
|
|
2508
2747
|
const listed = expanded.filter((entry) => !files.includes(entry)).map(([key, value, fromList]) => [presetNormalizeKey(key), value, fromList]);
|
|
2509
2748
|
const twice = listed.find(([key, , fromList], index) => listed.some(([other, , otherFromList], at) => at < index && other === key && !fromList && !otherFromList));
|
|
@@ -2528,7 +2767,11 @@ function presetProject(config) {
|
|
|
2528
2767
|
if (dead) {
|
|
2529
2768
|
throw new PresetError(`Folder \`${dead.key}\` sits inside the skipped folder \`${dead.parent?.key}\`.`);
|
|
2530
2769
|
}
|
|
2531
|
-
const
|
|
2770
|
+
const keyFor = (pattern, folder) => {
|
|
2771
|
+
const reached = live.filter((scope) => presetIntersect(pattern, scope.key).length > 0).map((scope) => ({ key: scope.key, parent: scope, depth: scope.depth, folder, effective: presetEffective(folder, scope.effective) }));
|
|
2772
|
+
return { pattern, folder, scopes: reached };
|
|
2773
|
+
};
|
|
2774
|
+
const fileKeys = presetFileOverlaps(root, keyFor, fileEntries.flatMap(([key, value]) => {
|
|
2532
2775
|
if (value === false) {
|
|
2533
2776
|
return [];
|
|
2534
2777
|
}
|
|
@@ -2540,10 +2783,8 @@ function presetProject(config) {
|
|
|
2540
2783
|
if (Object(folder.layout).operations !== undefined) {
|
|
2541
2784
|
throw new PresetError(`Set layout.operations on an app or package folder, not on \`${key}\`.`);
|
|
2542
2785
|
}
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
return [{ pattern, folder, scopes: reached }];
|
|
2546
|
-
});
|
|
2786
|
+
return [keyFor(presetGlobEscape(key), folder)];
|
|
2787
|
+
}));
|
|
2547
2788
|
const units = presetUnits(root, scopes, ignored);
|
|
2548
2789
|
const unitKeys = new Set(units.map((unit) => unit.scope.key));
|
|
2549
2790
|
const misplaced = live.find((scope) => !unitKeys.has(scope.key) && Object(scope.folder.layout).operations !== undefined);
|
|
@@ -2710,6 +2951,7 @@ var presetBuilders = {
|
|
|
2710
2951
|
shadcn: shadcnGroup,
|
|
2711
2952
|
limits: limitsGroup,
|
|
2712
2953
|
deny: denyGroup,
|
|
2954
|
+
top: topGroup,
|
|
2713
2955
|
externals: externalsGroup
|
|
2714
2956
|
};
|
|
2715
2957
|
function presetLevel(scope) {
|
|
@@ -2845,7 +3087,7 @@ function preset(config) {
|
|
|
2845
3087
|
const level = presetLevel(root);
|
|
2846
3088
|
const expanded = new Map;
|
|
2847
3089
|
const expand = (fileKey) => {
|
|
2848
|
-
const paths = expanded.get(fileKey.pattern) ??
|
|
3090
|
+
const paths = expanded.get(fileKey.pattern) ?? globSync2(fileKey.pattern, { cwd: project.root, exclude: (path) => posix9.basename(path) === "node_modules" });
|
|
2849
3091
|
expanded.set(fileKey.pattern, paths);
|
|
2850
3092
|
return paths;
|
|
2851
3093
|
};
|