@yasainet/eslint 0.0.72 → 0.0.74
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -60
- package/package.json +1 -1
- package/src/common/{constants.mjs → _internal/constants.mjs} +0 -18
- package/src/common/_internal/import-patterns.mjs +16 -0
- package/src/common/{plugins.mjs → _internal/plugins.mjs} +0 -1
- package/src/common/_internal/selectors.mjs +10 -0
- package/src/common/{rules.mjs → base/typescript.mjs} +2 -29
- package/src/common/{entry-points.mjs → boundaries/entry-point.mjs} +0 -1
- package/src/common/cross-cutting/ban-alias.mjs +22 -0
- package/src/common/cross-cutting/feature-default-imports.mjs +26 -0
- package/src/common/cross-cutting/feature-name.mjs +15 -0
- package/src/common/cross-cutting/features-ts-only.mjs +20 -0
- package/src/common/cross-cutting/form-state.mjs +16 -0
- package/src/common/{jsdoc.mjs → cross-cutting/jsdoc.mjs} +2 -3
- package/src/common/cross-cutting/logger.mjs +21 -0
- package/src/common/cross-cutting/namespace-import.mjs +23 -0
- package/src/common/cross-cutting/no-any-return.mjs +18 -0
- package/src/common/cross-cutting/supabase-columns-satisfies.mjs +18 -0
- package/src/common/index.mjs +42 -24
- package/src/common/layers/constants.mjs +36 -0
- package/src/common/layers/entries.mjs +168 -0
- package/src/common/layers/lib.mjs +18 -0
- package/src/common/layers/queries.mjs +183 -0
- package/src/common/layers/schemas.mjs +45 -0
- package/src/common/layers/services.mjs +115 -0
- package/src/common/layers/top-level-utils.mjs +18 -0
- package/src/common/layers/types.mjs +44 -0
- package/src/common/layers/utils.mjs +50 -0
- package/src/common/local-plugins/entry-single-service-call.mjs +0 -30
- package/src/common/local-plugins/entry-template.mjs +33 -72
- package/src/common/local-plugins/feature-name.mjs +8 -22
- package/src/common/local-plugins/form-state-naming.mjs +0 -10
- package/src/common/local-plugins/form-state-shape.mjs +0 -34
- package/src/common/local-plugins/import-path-style.mjs +0 -7
- package/src/common/local-plugins/index.mjs +0 -1
- package/src/common/local-plugins/layout-main-structural-only.mjs +0 -21
- package/src/common/local-plugins/namespace-import-name.mjs +0 -26
- package/src/common/local-plugins/no-any-return.mjs +0 -8
- package/src/common/local-plugins/queries-export.mjs +0 -8
- package/src/common/local-plugins/queries-namespace-import.mjs +0 -10
- package/src/common/local-plugins/schema-naming.mjs +0 -6
- package/src/common/local-plugins/supabase-columns-satisfies.mjs +0 -24
- package/src/common/local-plugins/supabase-select-typed-columns.mjs +0 -32
- package/src/deno/boundaries/entry-point.mjs +44 -0
- package/src/deno/boundaries/lib.mjs +28 -0
- package/src/deno/boundaries/utils.mjs +25 -0
- package/src/deno/index.mjs +9 -13
- package/src/deno/local-plugins/flat-entry-point.mjs +0 -5
- package/src/deno/local-plugins/index.mjs +0 -1
- package/src/next/boundaries/components.mjs +36 -0
- package/src/next/boundaries/hooks.mjs +36 -0
- package/src/next/boundaries/lib.mjs +23 -0
- package/src/next/boundaries/page.mjs +36 -0
- package/src/next/boundaries/route.mjs +36 -0
- package/src/next/boundaries/sitemap.mjs +36 -0
- package/src/next/directives.mjs +0 -1
- package/src/next/imports.mjs +0 -1
- package/src/next/index.mjs +12 -15
- package/src/next/layers/components.mjs +30 -0
- package/src/next/layers/hooks.mjs +31 -0
- package/src/next/layers/layouts.mjs +12 -0
- package/src/next/tailwindcss.mjs +0 -21
- package/src/node/index.mjs +1 -2
- package/src/common/imports.mjs +0 -457
- package/src/common/layers.mjs +0 -158
- package/src/common/naming.mjs +0 -347
- package/src/deno/imports.mjs +0 -90
- package/src/next/layouts.mjs +0 -18
- package/src/next/naming.mjs +0 -58
package/src/common/imports.mjs
DELETED
|
@@ -1,457 +0,0 @@
|
|
|
1
|
-
const LAYER_PATTERNS = {
|
|
2
|
-
queries: [
|
|
3
|
-
{
|
|
4
|
-
group: ["**/services/*", "**/services"],
|
|
5
|
-
message: "queries cannot import services (layer violation)",
|
|
6
|
-
},
|
|
7
|
-
{
|
|
8
|
-
group: ["**/entries/*", "**/entries"],
|
|
9
|
-
message: "queries cannot import entries (layer violation)",
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
group: ["**/hooks/*", "**/hooks"],
|
|
13
|
-
message: "queries cannot import hooks (layer violation)",
|
|
14
|
-
},
|
|
15
|
-
],
|
|
16
|
-
services: [
|
|
17
|
-
{
|
|
18
|
-
group: ["**/entries/*", "**/entries"],
|
|
19
|
-
message: "services cannot import entries (layer violation)",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
group: ["**/hooks/*", "**/hooks"],
|
|
23
|
-
message: "services cannot import hooks (layer violation)",
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
entries: [
|
|
27
|
-
{
|
|
28
|
-
group: ["**/queries/*", "**/queries"],
|
|
29
|
-
message: "entries cannot import queries (layer violation)",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
group: ["**/hooks/*", "**/hooks"],
|
|
33
|
-
message: "entries cannot import hooks (layer violation)",
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const LATERAL_PATTERNS = {
|
|
39
|
-
queries: [
|
|
40
|
-
{
|
|
41
|
-
group: ["@/features/*/queries/*", "@/features/*/queries"],
|
|
42
|
-
message:
|
|
43
|
-
"queries cannot import other feature's queries (lateral violation)",
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
services: [
|
|
47
|
-
{
|
|
48
|
-
group: ["@/features/*/services/*", "@/features/*/services"],
|
|
49
|
-
message:
|
|
50
|
-
"services cannot import other feature's services (lateral violation)",
|
|
51
|
-
},
|
|
52
|
-
],
|
|
53
|
-
entries: [
|
|
54
|
-
{
|
|
55
|
-
group: ["@/features/*/entries/*", "@/features/*/entries"],
|
|
56
|
-
message:
|
|
57
|
-
"entries cannot import other feature's entries (lateral violation)",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
group: [
|
|
61
|
-
"@/features/*/services/*",
|
|
62
|
-
"@/features/*/services",
|
|
63
|
-
"!@/features/shared/services/*",
|
|
64
|
-
"!@/features/shared/services",
|
|
65
|
-
],
|
|
66
|
-
message:
|
|
67
|
-
"entries cannot import other feature's services. Use the same feature's service (1:1) or move orchestration into the service layer. `shared/services/*` is exempt for cross-cutting side effects (notifications etc.).",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const CARDINALITY_PATTERNS = {
|
|
73
|
-
server: [
|
|
74
|
-
{
|
|
75
|
-
group: ["**/services/client", "**/services/admin"],
|
|
76
|
-
message:
|
|
77
|
-
"server entry can only import server service (cardinality violation)",
|
|
78
|
-
},
|
|
79
|
-
],
|
|
80
|
-
client: [
|
|
81
|
-
{
|
|
82
|
-
group: ["**/services/server", "**/services/admin"],
|
|
83
|
-
message:
|
|
84
|
-
"client entry can only import client service (cardinality violation)",
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
admin: [
|
|
88
|
-
{
|
|
89
|
-
group: ["**/services/server", "**/services/client"],
|
|
90
|
-
message:
|
|
91
|
-
"admin entry can only import admin service (cardinality violation)",
|
|
92
|
-
},
|
|
93
|
-
],
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
function prefixLibPatterns(prefix, mapping) {
|
|
97
|
-
const prefixes = Object.keys(mapping);
|
|
98
|
-
const allowedLib = mapping[prefix];
|
|
99
|
-
return prefixes
|
|
100
|
-
.filter((p) => p !== prefix)
|
|
101
|
-
.map((p) => ({
|
|
102
|
-
group: [`**/lib/${mapping[p]}`, `**/lib/${mapping[p]}/*`],
|
|
103
|
-
message: `queries/${prefix}.ts can only import from lib/${allowedLib}. Use the correct query file for this lib.`,
|
|
104
|
-
}));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const LIB_BOUNDARY_PATTERNS = [
|
|
108
|
-
{
|
|
109
|
-
group: ["@/lib/*", "@/lib/**"],
|
|
110
|
-
message:
|
|
111
|
-
"lib/* can only be imported from queries (lib-boundary violation)",
|
|
112
|
-
},
|
|
113
|
-
];
|
|
114
|
-
|
|
115
|
-
const MAPPING_PATTERNS = [
|
|
116
|
-
{
|
|
117
|
-
group: ["@/utils/mapping.util"],
|
|
118
|
-
importNames: ["mapSnakeToCamel", "mapCamelToSnake"],
|
|
119
|
-
message:
|
|
120
|
-
"Mapping functions are only allowed in services. Snake/camel conversion belongs at the service boundary.",
|
|
121
|
-
},
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
const PAGE_BOUNDARY_PATTERNS = [
|
|
125
|
-
{
|
|
126
|
-
group: ["**/queries/*", "**/queries"],
|
|
127
|
-
message:
|
|
128
|
-
"page.tsx can only import entries, not queries (page-boundary violation)",
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
group: ["**/services/*", "**/services"],
|
|
132
|
-
message:
|
|
133
|
-
"page.tsx can only import entries, not services (page-boundary violation)",
|
|
134
|
-
},
|
|
135
|
-
];
|
|
136
|
-
|
|
137
|
-
const ROUTE_BOUNDARY_PATTERNS = [
|
|
138
|
-
{
|
|
139
|
-
group: ["**/queries/*", "**/queries"],
|
|
140
|
-
message:
|
|
141
|
-
"route.ts can only import entries, not queries (route-boundary violation)",
|
|
142
|
-
},
|
|
143
|
-
{
|
|
144
|
-
group: ["**/services/*", "**/services"],
|
|
145
|
-
message:
|
|
146
|
-
"route.ts can only import entries, not services (route-boundary violation)",
|
|
147
|
-
},
|
|
148
|
-
];
|
|
149
|
-
|
|
150
|
-
const SITEMAP_BOUNDARY_PATTERNS = [
|
|
151
|
-
{
|
|
152
|
-
group: ["**/queries/*", "**/queries"],
|
|
153
|
-
message:
|
|
154
|
-
"sitemap.ts can only import entries, not queries (sitemap-boundary violation)",
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
group: ["**/services/*", "**/services"],
|
|
158
|
-
message:
|
|
159
|
-
"sitemap.ts can only import entries, not services (sitemap-boundary violation)",
|
|
160
|
-
},
|
|
161
|
-
];
|
|
162
|
-
|
|
163
|
-
const HOOKS_BOUNDARY_PATTERNS = [
|
|
164
|
-
{
|
|
165
|
-
group: ["**/queries/*", "**/queries"],
|
|
166
|
-
message:
|
|
167
|
-
"hooks can only import entries, not queries (hooks-boundary violation)",
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
group: ["**/services/*", "**/services"],
|
|
171
|
-
message:
|
|
172
|
-
"hooks can only import entries, not services (hooks-boundary violation)",
|
|
173
|
-
},
|
|
174
|
-
];
|
|
175
|
-
|
|
176
|
-
const COMPONENTS_BOUNDARY_PATTERNS = [
|
|
177
|
-
{
|
|
178
|
-
group: ["**/queries/*", "**/queries"],
|
|
179
|
-
message:
|
|
180
|
-
"components can only import entries or hooks, not queries (components-boundary violation)",
|
|
181
|
-
},
|
|
182
|
-
{
|
|
183
|
-
group: ["**/services/*", "**/services"],
|
|
184
|
-
message:
|
|
185
|
-
"components can only import entries or hooks, not services (components-boundary violation)",
|
|
186
|
-
},
|
|
187
|
-
];
|
|
188
|
-
|
|
189
|
-
function makeConfig(name, files, ...patternArrays) {
|
|
190
|
-
const patterns = patternArrays.flat();
|
|
191
|
-
if (patterns.length === 0) return null;
|
|
192
|
-
return {
|
|
193
|
-
name: `imports/${name}`,
|
|
194
|
-
files,
|
|
195
|
-
rules: {
|
|
196
|
-
"no-restricted-imports": ["error", { patterns }],
|
|
197
|
-
},
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// In ESLint flat config, when multiple matching configs set the same rule
|
|
202
|
-
// (`no-restricted-imports`), the later config's options REPLACE the earlier
|
|
203
|
-
// ones — patterns are not merged. Page / hooks / components boundary configs
|
|
204
|
-
// run after libBoundaryConfigs and would silently drop lib + mapping bans
|
|
205
|
-
// unless we re-include those patterns explicitly.
|
|
206
|
-
/** Next.js-only: restrict page.tsx to only import entries. */
|
|
207
|
-
export const pageBoundaryConfigs = [
|
|
208
|
-
{
|
|
209
|
-
name: "imports/page-boundary",
|
|
210
|
-
files: ["src/app/**/page.tsx"],
|
|
211
|
-
rules: {
|
|
212
|
-
"no-restricted-imports": [
|
|
213
|
-
"error",
|
|
214
|
-
{
|
|
215
|
-
patterns: [
|
|
216
|
-
...PAGE_BOUNDARY_PATTERNS,
|
|
217
|
-
...LIB_BOUNDARY_PATTERNS,
|
|
218
|
-
...MAPPING_PATTERNS,
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
],
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
];
|
|
225
|
-
|
|
226
|
-
/** Next.js-only: restrict route.ts to only import entries. */
|
|
227
|
-
export const routeBoundaryConfigs = [
|
|
228
|
-
{
|
|
229
|
-
name: "imports/route-boundary",
|
|
230
|
-
files: ["src/app/**/route.ts"],
|
|
231
|
-
rules: {
|
|
232
|
-
"no-restricted-imports": [
|
|
233
|
-
"error",
|
|
234
|
-
{
|
|
235
|
-
patterns: [
|
|
236
|
-
...ROUTE_BOUNDARY_PATTERNS,
|
|
237
|
-
...LIB_BOUNDARY_PATTERNS,
|
|
238
|
-
...MAPPING_PATTERNS,
|
|
239
|
-
],
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
];
|
|
245
|
-
|
|
246
|
-
/** Next.js-only: restrict sitemap.ts to only import entries. */
|
|
247
|
-
export const sitemapBoundaryConfigs = [
|
|
248
|
-
{
|
|
249
|
-
name: "imports/sitemap-boundary",
|
|
250
|
-
files: ["src/app/sitemap.ts", "src/app/**/sitemap.ts"],
|
|
251
|
-
rules: {
|
|
252
|
-
"no-restricted-imports": [
|
|
253
|
-
"error",
|
|
254
|
-
{
|
|
255
|
-
patterns: [
|
|
256
|
-
...SITEMAP_BOUNDARY_PATTERNS,
|
|
257
|
-
...LIB_BOUNDARY_PATTERNS,
|
|
258
|
-
...MAPPING_PATTERNS,
|
|
259
|
-
],
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
},
|
|
263
|
-
},
|
|
264
|
-
];
|
|
265
|
-
|
|
266
|
-
/** Next.js-only: restrict hooks to only import entries (not queries or services). */
|
|
267
|
-
export const hooksBoundaryConfigs = [
|
|
268
|
-
{
|
|
269
|
-
name: "imports/hooks-boundary",
|
|
270
|
-
files: ["src/features/**/hooks/*.ts"],
|
|
271
|
-
rules: {
|
|
272
|
-
"no-restricted-imports": [
|
|
273
|
-
"error",
|
|
274
|
-
{
|
|
275
|
-
patterns: [
|
|
276
|
-
...HOOKS_BOUNDARY_PATTERNS,
|
|
277
|
-
...LIB_BOUNDARY_PATTERNS,
|
|
278
|
-
...MAPPING_PATTERNS,
|
|
279
|
-
],
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
},
|
|
283
|
-
},
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
/** Next.js-only: restrict components to only import entries or hooks (not queries or services). */
|
|
287
|
-
export const componentsBoundaryConfigs = [
|
|
288
|
-
{
|
|
289
|
-
name: "imports/components-boundary",
|
|
290
|
-
files: ["src/components/**/*.{ts,tsx}"],
|
|
291
|
-
rules: {
|
|
292
|
-
"no-restricted-imports": [
|
|
293
|
-
"error",
|
|
294
|
-
{
|
|
295
|
-
patterns: [
|
|
296
|
-
...COMPONENTS_BOUNDARY_PATTERNS,
|
|
297
|
-
...LIB_BOUNDARY_PATTERNS,
|
|
298
|
-
...MAPPING_PATTERNS,
|
|
299
|
-
],
|
|
300
|
-
},
|
|
301
|
-
],
|
|
302
|
-
},
|
|
303
|
-
},
|
|
304
|
-
];
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Next.js-only: restrict @/lib imports and mapping imports outside features.
|
|
308
|
-
* Per-feature subdir rules live in createImportsConfigs to avoid clobbering each other.
|
|
309
|
-
*/
|
|
310
|
-
export const libBoundaryConfigs = [
|
|
311
|
-
{
|
|
312
|
-
name: "imports/lib-boundary",
|
|
313
|
-
files: ["src/**/*.{ts,tsx}"],
|
|
314
|
-
ignores: [
|
|
315
|
-
"src/lib/**",
|
|
316
|
-
"src/proxy.ts",
|
|
317
|
-
"src/app/**/route.ts",
|
|
318
|
-
"src/features/**",
|
|
319
|
-
],
|
|
320
|
-
rules: {
|
|
321
|
-
"no-restricted-imports": [
|
|
322
|
-
"error",
|
|
323
|
-
{ patterns: [...LIB_BOUNDARY_PATTERNS, ...MAPPING_PATTERNS] },
|
|
324
|
-
],
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
];
|
|
328
|
-
|
|
329
|
-
/** Scope import restriction rules to the given feature root. */
|
|
330
|
-
export function createImportsConfigs(
|
|
331
|
-
featureRoot,
|
|
332
|
-
prefixLibMapping,
|
|
333
|
-
{ banAliasImports = false } = {},
|
|
334
|
-
) {
|
|
335
|
-
const configs = [];
|
|
336
|
-
|
|
337
|
-
if (banAliasImports) {
|
|
338
|
-
configs.push({
|
|
339
|
-
name: "imports/ban-alias",
|
|
340
|
-
files: [`${featureRoot}/**/*.ts`],
|
|
341
|
-
rules: {
|
|
342
|
-
"no-restricted-imports": [
|
|
343
|
-
"error",
|
|
344
|
-
{
|
|
345
|
-
patterns: [
|
|
346
|
-
{
|
|
347
|
-
group: ["@/*", "@/**"],
|
|
348
|
-
message:
|
|
349
|
-
"Alias imports (@/) are not available in this environment. Use relative paths.",
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
});
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
configs.push(
|
|
359
|
-
makeConfig(
|
|
360
|
-
"queries",
|
|
361
|
-
[`${featureRoot}/**/queries/*.ts`],
|
|
362
|
-
LAYER_PATTERNS.queries,
|
|
363
|
-
LATERAL_PATTERNS.queries,
|
|
364
|
-
MAPPING_PATTERNS,
|
|
365
|
-
),
|
|
366
|
-
);
|
|
367
|
-
|
|
368
|
-
for (const prefix of Object.keys(prefixLibMapping)) {
|
|
369
|
-
const patterns = prefixLibPatterns(prefix, prefixLibMapping);
|
|
370
|
-
if (patterns.length === 0) continue;
|
|
371
|
-
configs.push(
|
|
372
|
-
makeConfig(
|
|
373
|
-
`queries/${prefix}`,
|
|
374
|
-
[`${featureRoot}/**/queries/${prefix}.ts`],
|
|
375
|
-
LAYER_PATTERNS.queries,
|
|
376
|
-
LATERAL_PATTERNS.queries,
|
|
377
|
-
patterns,
|
|
378
|
-
MAPPING_PATTERNS,
|
|
379
|
-
),
|
|
380
|
-
);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
configs.push(
|
|
384
|
-
makeConfig(
|
|
385
|
-
"services",
|
|
386
|
-
[`${featureRoot}/**/services/*.ts`],
|
|
387
|
-
LAYER_PATTERNS.services,
|
|
388
|
-
LATERAL_PATTERNS.services,
|
|
389
|
-
LIB_BOUNDARY_PATTERNS,
|
|
390
|
-
),
|
|
391
|
-
);
|
|
392
|
-
|
|
393
|
-
configs.push(
|
|
394
|
-
makeConfig(
|
|
395
|
-
"entries",
|
|
396
|
-
[`${featureRoot}/**/entries/*.ts`],
|
|
397
|
-
LAYER_PATTERNS.entries,
|
|
398
|
-
LATERAL_PATTERNS.entries,
|
|
399
|
-
LIB_BOUNDARY_PATTERNS,
|
|
400
|
-
MAPPING_PATTERNS,
|
|
401
|
-
),
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
configs.push(
|
|
405
|
-
makeConfig(
|
|
406
|
-
"utils",
|
|
407
|
-
[`${featureRoot}/**/utils/*.ts`],
|
|
408
|
-
LIB_BOUNDARY_PATTERNS,
|
|
409
|
-
MAPPING_PATTERNS,
|
|
410
|
-
),
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
// Catch-all for feature subdirs without a per-layer config
|
|
414
|
-
// (constants, hooks, schemas). lib-boundary + mapping ban.
|
|
415
|
-
configs.push({
|
|
416
|
-
name: "imports/feature-other",
|
|
417
|
-
files: [`${featureRoot}/**/*.ts`],
|
|
418
|
-
ignores: [
|
|
419
|
-
`${featureRoot}/**/services/*.ts`,
|
|
420
|
-
`${featureRoot}/**/queries/*.ts`,
|
|
421
|
-
`${featureRoot}/**/entries/*.ts`,
|
|
422
|
-
`${featureRoot}/**/utils/*.ts`,
|
|
423
|
-
`${featureRoot}/**/types/*.ts`,
|
|
424
|
-
],
|
|
425
|
-
rules: {
|
|
426
|
-
"no-restricted-imports": [
|
|
427
|
-
"error",
|
|
428
|
-
{ patterns: [...LIB_BOUNDARY_PATTERNS, ...MAPPING_PATTERNS] },
|
|
429
|
-
],
|
|
430
|
-
},
|
|
431
|
-
});
|
|
432
|
-
|
|
433
|
-
// Types: mapping ban only. lib is allowed (Database/Tables type imports).
|
|
434
|
-
configs.push({
|
|
435
|
-
name: "imports/feature-types",
|
|
436
|
-
files: [`${featureRoot}/**/types/*.ts`],
|
|
437
|
-
rules: {
|
|
438
|
-
"no-restricted-imports": ["error", { patterns: MAPPING_PATTERNS }],
|
|
439
|
-
},
|
|
440
|
-
});
|
|
441
|
-
|
|
442
|
-
for (const prefix of ["server", "client", "admin"]) {
|
|
443
|
-
configs.push(
|
|
444
|
-
makeConfig(
|
|
445
|
-
`entries/${prefix}`,
|
|
446
|
-
[`${featureRoot}/**/entries/${prefix}.ts`],
|
|
447
|
-
LAYER_PATTERNS.entries,
|
|
448
|
-
LATERAL_PATTERNS.entries,
|
|
449
|
-
CARDINALITY_PATTERNS[prefix],
|
|
450
|
-
LIB_BOUNDARY_PATTERNS,
|
|
451
|
-
MAPPING_PATTERNS,
|
|
452
|
-
),
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
return configs.filter(Boolean);
|
|
457
|
-
}
|
package/src/common/layers.mjs
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { localPlugin } from "./local-plugins/index.mjs";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Scope layer rules to the given feature root:
|
|
5
|
-
*
|
|
6
|
-
* - `typeAware: true` (default) includes `layers/no-any-return`, which uses
|
|
7
|
-
* the TypeScript checker to inspect inferred return types
|
|
8
|
-
* - `typeAware: false` skips it for environments where the checker cannot run
|
|
9
|
-
* (e.g., Deno files outside the project tsconfig)
|
|
10
|
-
*/
|
|
11
|
-
export function createLayersConfigs(featureRoot, { typeAware = true } = {}) {
|
|
12
|
-
const loggerSelector = "CallExpression[callee.object.name='logger']";
|
|
13
|
-
const loggerMessage =
|
|
14
|
-
"logger is not allowed outside entries. Logging belongs in entries.";
|
|
15
|
-
|
|
16
|
-
const aliasDynamicImportSelector =
|
|
17
|
-
"ImportExpression[source.type='Literal'][source.value=/^@\\//]";
|
|
18
|
-
const aliasDynamicImportMessage =
|
|
19
|
-
"Dynamic imports of `@/` aliased paths are not allowed in features layers. They bypass prefix-lib and lateral cardinality (e.g. `await import('@/lib/supabase/admin')` from queries/server.ts escapes the lib-boundary check). Create the correct queries/<prefix>.ts or services/<prefix>.ts file instead. External npm packages can still be lazy-loaded for cold-start optimization.";
|
|
20
|
-
|
|
21
|
-
const noAnyReturnConfig = {
|
|
22
|
-
name: "layers/no-any-return",
|
|
23
|
-
files: [
|
|
24
|
-
`${featureRoot}/**/queries/*.ts`,
|
|
25
|
-
`${featureRoot}/**/services/*.ts`,
|
|
26
|
-
],
|
|
27
|
-
plugins: { local: localPlugin },
|
|
28
|
-
rules: {
|
|
29
|
-
"local/no-any-return": "error",
|
|
30
|
-
},
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
return [
|
|
34
|
-
// Logger/console: all features except entries
|
|
35
|
-
{
|
|
36
|
-
name: "layers/logger",
|
|
37
|
-
files: [`${featureRoot}/**/*.ts`],
|
|
38
|
-
ignores: [`${featureRoot}/**/entries/*.ts`],
|
|
39
|
-
rules: {
|
|
40
|
-
"no-console": "error",
|
|
41
|
-
"no-restricted-syntax": [
|
|
42
|
-
"error",
|
|
43
|
-
{ selector: loggerSelector, message: loggerMessage },
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
// Queries: try-catch + if + loops + logger
|
|
48
|
-
{
|
|
49
|
-
name: "layers/queries",
|
|
50
|
-
files: [`${featureRoot}/**/queries/*.ts`],
|
|
51
|
-
rules: {
|
|
52
|
-
"no-restricted-syntax": [
|
|
53
|
-
"error",
|
|
54
|
-
{
|
|
55
|
-
selector: "TryStatement",
|
|
56
|
-
message:
|
|
57
|
-
"try-catch is not allowed in queries. Error handling belongs in entries.",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
selector: "IfStatement",
|
|
61
|
-
message:
|
|
62
|
-
"if statements are not allowed in queries. Conditional logic belongs in services.",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
selector: "ForStatement",
|
|
66
|
-
message:
|
|
67
|
-
"Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
selector: "ForOfStatement",
|
|
71
|
-
message:
|
|
72
|
-
"Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
selector: "ForInStatement",
|
|
76
|
-
message:
|
|
77
|
-
"Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
selector: "WhileStatement",
|
|
81
|
-
message:
|
|
82
|
-
"Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
selector: "DoWhileStatement",
|
|
86
|
-
message:
|
|
87
|
-
"Loops are not allowed in queries. Queries should be thin CRUD wrappers — iteration belongs in services.",
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
selector: "ThrowStatement",
|
|
91
|
-
message:
|
|
92
|
-
"throw is not allowed in queries. Queries must return Supabase's { data, error } shape as-is. Error handling belongs in entries.",
|
|
93
|
-
},
|
|
94
|
-
{ selector: loggerSelector, message: loggerMessage },
|
|
95
|
-
{
|
|
96
|
-
selector: aliasDynamicImportSelector,
|
|
97
|
-
message: aliasDynamicImportMessage,
|
|
98
|
-
},
|
|
99
|
-
],
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
// Boundary type safety: queries & services must not leak `any`
|
|
103
|
-
// into their public API. Uses type-aware inspection of the inferred
|
|
104
|
-
// return type so unannotated functions are still checked.
|
|
105
|
-
...(typeAware ? [noAnyReturnConfig] : []),
|
|
106
|
-
// Services: try-catch + logger + throw + dead error fallbacks
|
|
107
|
-
{
|
|
108
|
-
name: "layers/services",
|
|
109
|
-
files: [`${featureRoot}/**/services/*.ts`],
|
|
110
|
-
rules: {
|
|
111
|
-
"no-restricted-syntax": [
|
|
112
|
-
"error",
|
|
113
|
-
{
|
|
114
|
-
selector: "TryStatement",
|
|
115
|
-
message:
|
|
116
|
-
"try-catch is not allowed in services. Error handling belongs in entries.",
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
selector: "ThrowStatement",
|
|
120
|
-
message:
|
|
121
|
-
"throw is not allowed in services. Communicate failures via T | null / { data, error } / empty default. Native exceptions from libs auto-propagate to entry's catch.",
|
|
122
|
-
},
|
|
123
|
-
{ selector: loggerSelector, message: loggerMessage },
|
|
124
|
-
{
|
|
125
|
-
selector:
|
|
126
|
-
"LogicalExpression[operator='??'][left.type='ChainExpression'][left.expression.property.name='message'][right.type='Literal']",
|
|
127
|
-
message:
|
|
128
|
-
"Dead fallback for error message. If you reached this branch the error is known — return the error directly. Unhandled exceptions belong in entries.",
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
selector:
|
|
132
|
-
"LogicalExpression[operator='??'][left.type='MemberExpression'][left.property.name='error'][right.type='ObjectExpression']",
|
|
133
|
-
message:
|
|
134
|
-
"Dead fallback for nullable error. Check `if (error)` and return the error directly. Unhandled exceptions belong in entries.",
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
selector: aliasDynamicImportSelector,
|
|
138
|
-
message: aliasDynamicImportMessage,
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
// Entries: ban dynamic `@/` imports that bypass cardinality / lateral rules
|
|
144
|
-
{
|
|
145
|
-
name: "layers/entries",
|
|
146
|
-
files: [`${featureRoot}/**/entries/*.ts`],
|
|
147
|
-
rules: {
|
|
148
|
-
"no-restricted-syntax": [
|
|
149
|
-
"error",
|
|
150
|
-
{
|
|
151
|
-
selector: aliasDynamicImportSelector,
|
|
152
|
-
message: aliasDynamicImportMessage,
|
|
153
|
-
},
|
|
154
|
-
],
|
|
155
|
-
},
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
}
|