@zedtech/zedkit-quality 0.3.1 → 0.4.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/README.md +11 -3
- package/package.json +5 -3
- package/src/eslint.d.mts +35 -20
- package/src/eslint.mjs +215 -32
package/README.md
CHANGED
|
@@ -14,7 +14,8 @@ pnpm add -D @zedtech/zedkit-quality
|
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
Peer dependencies (`eslint`, `eslint-plugin-jsx-a11y`,
|
|
17
|
-
`eslint-plugin-better-tailwindcss`) install
|
|
17
|
+
`eslint-plugin-better-tailwindcss`, `typescript-eslint`) install
|
|
18
|
+
alongside it.
|
|
18
19
|
|
|
19
20
|
## Usage
|
|
20
21
|
|
|
@@ -53,8 +54,10 @@ subagents, commands) see `@zedtech/zedkit-ai` instead.
|
|
|
53
54
|
|
|
54
55
|
- `uiPreset({ entryPoint })` — flat-config blocks for `.tsx`/`.jsx`:
|
|
55
56
|
jsx-a11y recommended, Tailwind hygiene, tokens-only classes, no `style`
|
|
56
|
-
attributes, no
|
|
57
|
-
|
|
57
|
+
attributes, no `forwardRef` (ref is a prop; see `packages/ui/src/button.tsx`),
|
|
58
|
+
no explicit `any`, files ≤ 300 lines and functions ≤ 60, no raw `try`/`catch`.
|
|
59
|
+
`entryPoint` (your Tailwind CSS entry) is required: it is the one thing
|
|
60
|
+
that varies per repo.
|
|
58
61
|
- `defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns, extraPatterns })` —
|
|
59
62
|
`no-restricted-imports` blocks for routes -> views -> data: the UI kit
|
|
60
63
|
is only imported from views, views never import data clients or
|
|
@@ -62,6 +65,11 @@ subagents, commands) see `@zedtech/zedkit-ai` instead.
|
|
|
62
65
|
`{ kitScope?, viewData?, dataUi? }`) merges your own bans into each
|
|
63
66
|
block — use it instead of a second `no-restricted-imports` block,
|
|
64
67
|
which would replace these per flat-config key semantics.
|
|
68
|
+
- `defineLayers({ preset: "feature-folders", kitPackage, featureRoot })` —
|
|
69
|
+
same helper, feature-folder mapping: UI imports ports (not adapters),
|
|
70
|
+
adapters are injected at the composition root, model is pure. Bans
|
|
71
|
+
apply only under `featureRoot`. Live example:
|
|
72
|
+
`apps/docs/features/note-list` (running at `/examples/note-list`).
|
|
65
73
|
- `@zedtech/zedkit-quality/tsconfig` — the strict base (`strict`,
|
|
66
74
|
`exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`,
|
|
67
75
|
`noUnusedLocals`, and more). Extend it, do not copy it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zedtech/zedkit-quality",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public",
|
|
6
6
|
"registry": "https://registry.npmjs.org/"
|
|
@@ -60,14 +60,16 @@
|
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"eslint": "9.39.5",
|
|
62
62
|
"eslint-plugin-better-tailwindcss": "4.10.0",
|
|
63
|
-
"eslint-plugin-jsx-a11y": "6.10.2"
|
|
63
|
+
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
64
|
+
"typescript-eslint": "8.72.0"
|
|
64
65
|
},
|
|
65
66
|
"devDependencies": {
|
|
66
67
|
"eslint": "9.39.5",
|
|
67
68
|
"eslint-plugin-better-tailwindcss": "4.10.0",
|
|
68
69
|
"eslint-plugin-jsx-a11y": "6.10.2",
|
|
69
70
|
"typescript": "5.9.3",
|
|
70
|
-
"
|
|
71
|
+
"typescript-eslint": "8.72.0",
|
|
72
|
+
"vitest": "5.0.5"
|
|
71
73
|
},
|
|
72
74
|
"scripts": {
|
|
73
75
|
"test": "vitest run",
|
package/src/eslint.d.mts
CHANGED
|
@@ -15,25 +15,40 @@ export interface RestrictedImportPattern {
|
|
|
15
15
|
readonly allowTypeImports?: boolean;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
export type DefineLayersOptions =
|
|
19
|
+
| {
|
|
20
|
+
readonly preset?: undefined;
|
|
21
|
+
/** UI kit specifier (e.g. `@acme/ui`). */
|
|
22
|
+
readonly kitPackage: string;
|
|
23
|
+
/** Minimatch globs for view folders. */
|
|
24
|
+
readonly viewDirs: readonly string[];
|
|
25
|
+
/** Minimatch globs for data-access folders. */
|
|
26
|
+
readonly dataDirs: readonly string[];
|
|
27
|
+
/** Import specifiers for data clients and server actions. May be empty. */
|
|
28
|
+
readonly actionPatterns: readonly string[];
|
|
29
|
+
/**
|
|
30
|
+
* Consumer bans merged into each block's own patterns (flat-config
|
|
31
|
+
* rules replace per key, so a consumer block setting
|
|
32
|
+
* `no-restricted-imports` would otherwise drop these).
|
|
33
|
+
*/
|
|
34
|
+
readonly extraPatterns?: {
|
|
35
|
+
readonly kitScope?: readonly RestrictedImportPattern[];
|
|
36
|
+
readonly viewData?: readonly RestrictedImportPattern[];
|
|
37
|
+
readonly dataUi?: readonly RestrictedImportPattern[];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
readonly preset: "feature-folders";
|
|
42
|
+
/** UI kit specifier (e.g. `@acme/ui`). */
|
|
43
|
+
readonly kitPackage: string;
|
|
44
|
+
/** Feature-folder root (e.g. `features`). Bans apply only under this tree. */
|
|
45
|
+
readonly featureRoot: string;
|
|
46
|
+
readonly extraPatterns?: {
|
|
47
|
+
readonly kitScope?: readonly RestrictedImportPattern[];
|
|
48
|
+
readonly viewData?: readonly RestrictedImportPattern[];
|
|
49
|
+
readonly dataUi?: readonly RestrictedImportPattern[];
|
|
50
|
+
readonly model?: readonly RestrictedImportPattern[];
|
|
51
|
+
};
|
|
52
|
+
};
|
|
38
53
|
|
|
39
54
|
export function defineLayers(options: DefineLayersOptions): Linter.Config[];
|
package/src/eslint.mjs
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
// Shareable UI lint preset:
|
|
1
|
+
// Shareable UI lint preset: charter rule ids live in docs/frontend-charter.md;
|
|
2
|
+
// the messages below carry the explanation.
|
|
2
3
|
//
|
|
3
4
|
// What it enforces on every .tsx/.jsx file:
|
|
4
5
|
// - jsx-a11y recommended (labels, keyboard, focus, ARIA)
|
|
5
6
|
// - Tailwind class hygiene (order, no duplicates, no conflicts, no unknown)
|
|
6
7
|
// - Tokens only: no raw palette colors (bg-red-500), no arbitrary hex/rgb
|
|
7
8
|
// colors, no raw px font sizes — use the semantic theme tokens
|
|
8
|
-
// - No `style` attributes; no
|
|
9
|
+
// - No `style` attributes; no forwardRef (ref is a prop); no explicit `any`;
|
|
10
|
+
// files ≤ 300 lines, functions ≤ 60; no raw try/catch (use a tryCatch wrapper)
|
|
9
11
|
//
|
|
10
12
|
// Consumer setup (no copy: install the package, pass your Tailwind entry):
|
|
11
13
|
// `import { uiPreset } from "@zedtech/zedkit-quality"` then
|
|
12
14
|
// `export default [...uiPreset({ entryPoint: "./app/globals.css" })]`.
|
|
13
15
|
import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
14
16
|
import betterTailwindcss from "eslint-plugin-better-tailwindcss";
|
|
17
|
+
import { plugin as tsPlugin } from "typescript-eslint";
|
|
15
18
|
|
|
16
19
|
const PALETTE =
|
|
17
20
|
"slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|fuchsia|pink|rose";
|
|
@@ -57,36 +60,67 @@ export function uiPreset({ entryPoint }) {
|
|
|
57
60
|
{
|
|
58
61
|
pattern: `(^|:)(${COLOR_UTILS})-(${PALETTE}|white|black)(-|/|$)`,
|
|
59
62
|
message:
|
|
60
|
-
"
|
|
63
|
+
"tokens-color: raw palette classes (bg-red-500) are banned. Use a semantic token (background, foreground, primary, destructive, …) so light and dark stay in sync.",
|
|
61
64
|
},
|
|
62
65
|
{
|
|
63
66
|
pattern: "\\[(#|rgb|hsl)",
|
|
64
67
|
message:
|
|
65
|
-
"
|
|
68
|
+
"tokens-color: arbitrary hex/rgb/hsl colors are banned. Use a semantic token (background, foreground, primary, …).",
|
|
66
69
|
},
|
|
67
70
|
{
|
|
68
71
|
pattern: "text-\\[[^\\]]*px",
|
|
69
|
-
message:
|
|
72
|
+
message:
|
|
73
|
+
"tokens-style: raw px font sizes are banned. Use the type scale (text-xs … text-2xl).",
|
|
70
74
|
},
|
|
71
75
|
],
|
|
72
76
|
},
|
|
73
77
|
],
|
|
74
78
|
// NOTE: flat-config rules replace (not merge) by key, so this block
|
|
75
79
|
// restates the root TryStatement ban alongside the style-attribute
|
|
76
|
-
//
|
|
80
|
+
// and forwardRef bans; without the restatement, .tsx files would lose them.
|
|
77
81
|
"no-restricted-syntax": [
|
|
78
82
|
"error",
|
|
79
83
|
{
|
|
80
84
|
selector: "TryStatement",
|
|
81
|
-
message:
|
|
85
|
+
message:
|
|
86
|
+
"try-catch: raw try/catch is banned. Use tryCatch/tryCatchAsync from @zedtech/zedkit-core so thrown values become Error with cause.",
|
|
82
87
|
},
|
|
83
88
|
{
|
|
84
89
|
selector: 'JSXAttribute[name.name="style"]',
|
|
85
|
-
message:
|
|
90
|
+
message:
|
|
91
|
+
"tokens-style: inline style attributes are banned. Use theme tokens and utility classes.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
selector: "Identifier[name='forwardRef']",
|
|
95
|
+
message:
|
|
96
|
+
"ui-ref: forwardRef is banned. Pass ref as a prop on ComponentProps<\"button\"> (see packages/ui/src/button.tsx).",
|
|
86
97
|
},
|
|
87
98
|
],
|
|
88
99
|
},
|
|
89
100
|
},
|
|
101
|
+
{
|
|
102
|
+
name: "zedkit/ui-types",
|
|
103
|
+
files: ["**/*.tsx", "**/*.jsx"],
|
|
104
|
+
plugins: { "@typescript-eslint": tsPlugin },
|
|
105
|
+
rules: {
|
|
106
|
+
"@typescript-eslint/no-explicit-any": "error",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "zedkit/ui-size",
|
|
111
|
+
files: ["**/*.tsx", "**/*.jsx"],
|
|
112
|
+
ignores: ["**/*.test.*", "**/*.spec.*", "**/tests/**", "**/__tests__/**"],
|
|
113
|
+
rules: {
|
|
114
|
+
"max-lines": [
|
|
115
|
+
"error",
|
|
116
|
+
{ max: 300, skipBlankLines: true, skipComments: true },
|
|
117
|
+
],
|
|
118
|
+
"max-lines-per-function": [
|
|
119
|
+
"error",
|
|
120
|
+
{ max: 60, skipBlankLines: true, skipComments: true, IIFEs: true },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
90
124
|
];
|
|
91
125
|
}
|
|
92
126
|
|
|
@@ -109,6 +143,18 @@ function asFiles(patterns) {
|
|
|
109
143
|
});
|
|
110
144
|
}
|
|
111
145
|
|
|
146
|
+
function assertExtraPatterns(extraPatterns, keys) {
|
|
147
|
+
const want = keys.join(", ").replace(/, ([^,]+)$/, " or $1");
|
|
148
|
+
for (const [key, value] of Object.entries(extraPatterns)) {
|
|
149
|
+
if (!keys.includes(key)) {
|
|
150
|
+
throw new Error(`defineLayers: unknown extraPatterns key "${key}" (want ${want})`);
|
|
151
|
+
}
|
|
152
|
+
if (!Array.isArray(value)) {
|
|
153
|
+
throw new Error(`defineLayers: extraPatterns.${key} must be a list of no-restricted-imports pattern objects`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
112
158
|
/**
|
|
113
159
|
* Route/view/data layer boundaries as `no-restricted-imports` blocks.
|
|
114
160
|
* Dependencies run one way (routes -> views -> data):
|
|
@@ -116,31 +162,50 @@ function asFiles(patterns) {
|
|
|
116
162
|
* - views receive data as props and never import data clients or actions;
|
|
117
163
|
* - data files import no UI (no React runtime, no UI kit).
|
|
118
164
|
*
|
|
119
|
-
* @param {
|
|
120
|
-
* - `kitPackage
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
165
|
+
* @param {object} options
|
|
166
|
+
* - Classic: `{ kitPackage, viewDirs, dataDirs, actionPatterns, extraPatterns? }`.
|
|
167
|
+
* `actionPatterns` may be empty (views legitimately import data): the
|
|
168
|
+
* view ban is then omitted, and the view block is dropped when it
|
|
169
|
+
* would carry no bans at all. `extraPatterns` merges consumer bans
|
|
170
|
+
* into each block (keys: `kitScope`, `viewData`, `dataUi`).
|
|
171
|
+
* - Preset `feature-folders`: `{ preset: "feature-folders", kitPackage,
|
|
172
|
+
* featureRoot, extraPatterns? }`. UI may import ports, not adapters;
|
|
173
|
+
* adapters are injected at the feature composition root; model is
|
|
174
|
+
* pure. Bans apply only under `featureRoot` (extraPatterns also
|
|
175
|
+
* accepts `model`).
|
|
129
176
|
* @returns {import("eslint").Linter.Config[]} Flat config blocks.
|
|
130
177
|
*/
|
|
131
|
-
export function defineLayers(
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
178
|
+
export function defineLayers(options) {
|
|
179
|
+
const { preset, extraPatterns = {} } = options;
|
|
180
|
+
if (preset === "feature-folders") {
|
|
181
|
+
return featureFolderLayers(options);
|
|
182
|
+
}
|
|
183
|
+
if (preset !== undefined) {
|
|
184
|
+
throw new Error(`defineLayers: unknown preset "${preset}" (want feature-folders)`);
|
|
185
|
+
}
|
|
186
|
+
const { kitPackage, viewDirs, dataDirs, actionPatterns } = options;
|
|
187
|
+
assertExtraPatterns(extraPatterns, ["kitScope", "viewData", "dataUi"]);
|
|
188
|
+
if (!Array.isArray(actionPatterns)) {
|
|
189
|
+
throw new Error("defineLayers: actionPatterns must be a list of import specifiers (may be empty)");
|
|
139
190
|
}
|
|
140
191
|
const kitPatterns = [kitPackage, `${kitPackage}/*`];
|
|
141
192
|
const views = asFiles(viewDirs);
|
|
142
193
|
const data = asFiles(dataDirs);
|
|
143
194
|
const extras = { kitScope: [], viewData: [], dataUi: [], ...extraPatterns };
|
|
195
|
+
// An empty group fails ESLint schema validation, so an empty
|
|
196
|
+
// actionPatterns omits the ban; with no viewData extras either,
|
|
197
|
+
// the whole block is dropped (a ban-less block is noise).
|
|
198
|
+
const viewDataPatterns = [
|
|
199
|
+
...(actionPatterns.length > 0
|
|
200
|
+
? [
|
|
201
|
+
{
|
|
202
|
+
group: [...actionPatterns],
|
|
203
|
+
message: "Views receive data as props and never import data clients or server actions.",
|
|
204
|
+
},
|
|
205
|
+
]
|
|
206
|
+
: []),
|
|
207
|
+
...extras.viewData,
|
|
208
|
+
];
|
|
144
209
|
return [
|
|
145
210
|
{
|
|
146
211
|
name: "zedkit/layers-kit-scope",
|
|
@@ -161,6 +226,97 @@ export function defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns, e
|
|
|
161
226
|
],
|
|
162
227
|
},
|
|
163
228
|
},
|
|
229
|
+
...(viewDataPatterns.length > 0
|
|
230
|
+
? [
|
|
231
|
+
{
|
|
232
|
+
name: "zedkit/layers-view-data",
|
|
233
|
+
files: views,
|
|
234
|
+
rules: {
|
|
235
|
+
"no-restricted-imports": ["error", { patterns: viewDataPatterns }],
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
]
|
|
239
|
+
: []),
|
|
240
|
+
{
|
|
241
|
+
name: "zedkit/layers-data-ui",
|
|
242
|
+
files: data,
|
|
243
|
+
rules: {
|
|
244
|
+
"no-restricted-imports": [
|
|
245
|
+
"error",
|
|
246
|
+
{
|
|
247
|
+
patterns: [
|
|
248
|
+
{
|
|
249
|
+
group: ["react", "react-dom", "react-dom/*", "react/jsx-runtime"],
|
|
250
|
+
message: "Data code is UI-free: no React runtime imports.",
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
group: kitPatterns,
|
|
254
|
+
message: "Data code is UI-free: no UI kit imports.",
|
|
255
|
+
},
|
|
256
|
+
...extras.dataUi,
|
|
257
|
+
],
|
|
258
|
+
},
|
|
259
|
+
],
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const ADAPTER_GROUP = ["**/adapters", "**/adapters/**"];
|
|
266
|
+
const REACT_GROUP = ["react", "react-dom", "react-dom/*", "react/jsx-runtime"];
|
|
267
|
+
|
|
268
|
+
function featureFolderLayers({
|
|
269
|
+
kitPackage,
|
|
270
|
+
featureRoot,
|
|
271
|
+
extraPatterns = {},
|
|
272
|
+
viewDirs,
|
|
273
|
+
dataDirs,
|
|
274
|
+
actionPatterns,
|
|
275
|
+
}) {
|
|
276
|
+
if (viewDirs !== undefined || dataDirs !== undefined || actionPatterns !== undefined) {
|
|
277
|
+
throw new Error("defineLayers: preset feature-folders uses featureRoot, not viewDirs/dataDirs/actionPatterns");
|
|
278
|
+
}
|
|
279
|
+
if (typeof featureRoot !== "string" || featureRoot === "") {
|
|
280
|
+
throw new Error("defineLayers: featureRoot is required for preset feature-folders");
|
|
281
|
+
}
|
|
282
|
+
assertExtraPatterns(extraPatterns, ["kitScope", "viewData", "dataUi", "model"]);
|
|
283
|
+
const root = featureRoot.replace(/\/$/, "");
|
|
284
|
+
const kitPatterns = [kitPackage, `${kitPackage}/*`];
|
|
285
|
+
const views = asFiles([`${root}/**/ui/**`]);
|
|
286
|
+
const adapters = asFiles([`${root}/**/adapters/**`]);
|
|
287
|
+
const model = asFiles([`${root}/**/model/**`]);
|
|
288
|
+
const compose = [`${root}/*/index.ts`, `${root}/*/index.tsx`, `${root}/*/compose.ts`, `${root}/*/compose.tsx`];
|
|
289
|
+
const extras = { kitScope: [], viewData: [], dataUi: [], model: [], ...extraPatterns };
|
|
290
|
+
return [
|
|
291
|
+
{
|
|
292
|
+
name: "zedkit/layers-kit-scope",
|
|
293
|
+
files: asFiles([root]),
|
|
294
|
+
ignores: [...views, ...adapters, ...model, ...compose],
|
|
295
|
+
rules: {
|
|
296
|
+
"no-restricted-imports": [
|
|
297
|
+
"error",
|
|
298
|
+
{
|
|
299
|
+
patterns: [
|
|
300
|
+
{
|
|
301
|
+
group: kitPatterns,
|
|
302
|
+
message:
|
|
303
|
+
"layers-features: UI kit imports are only allowed from view files. Move this import into a view.",
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
group: REACT_GROUP,
|
|
307
|
+
message: "layers-features: only ui/ and the composition root import React.",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
group: ADAPTER_GROUP,
|
|
311
|
+
message:
|
|
312
|
+
"layers-features: inject adapters at the feature composition root (index.ts or compose.ts).",
|
|
313
|
+
},
|
|
314
|
+
...extras.kitScope,
|
|
315
|
+
],
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
},
|
|
164
320
|
{
|
|
165
321
|
name: "zedkit/layers-view-data",
|
|
166
322
|
files: views,
|
|
@@ -170,8 +326,9 @@ export function defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns, e
|
|
|
170
326
|
{
|
|
171
327
|
patterns: [
|
|
172
328
|
{
|
|
173
|
-
group:
|
|
174
|
-
message:
|
|
329
|
+
group: ADAPTER_GROUP,
|
|
330
|
+
message:
|
|
331
|
+
"layers-features: UI files import ports, not adapters. Inject the adapter at the feature composition root.",
|
|
175
332
|
},
|
|
176
333
|
...extras.viewData,
|
|
177
334
|
],
|
|
@@ -181,19 +338,19 @@ export function defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns, e
|
|
|
181
338
|
},
|
|
182
339
|
{
|
|
183
340
|
name: "zedkit/layers-data-ui",
|
|
184
|
-
files:
|
|
341
|
+
files: adapters,
|
|
185
342
|
rules: {
|
|
186
343
|
"no-restricted-imports": [
|
|
187
344
|
"error",
|
|
188
345
|
{
|
|
189
346
|
patterns: [
|
|
190
347
|
{
|
|
191
|
-
group:
|
|
192
|
-
message: "
|
|
348
|
+
group: REACT_GROUP,
|
|
349
|
+
message: "layers-features: data code is UI-free: no React runtime imports.",
|
|
193
350
|
},
|
|
194
351
|
{
|
|
195
352
|
group: kitPatterns,
|
|
196
|
-
message: "
|
|
353
|
+
message: "layers-features: data code is UI-free: no UI kit imports.",
|
|
197
354
|
},
|
|
198
355
|
...extras.dataUi,
|
|
199
356
|
],
|
|
@@ -201,5 +358,31 @@ export function defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns, e
|
|
|
201
358
|
],
|
|
202
359
|
},
|
|
203
360
|
},
|
|
361
|
+
{
|
|
362
|
+
name: "zedkit/layers-model-pure",
|
|
363
|
+
files: model,
|
|
364
|
+
rules: {
|
|
365
|
+
"no-restricted-imports": [
|
|
366
|
+
"error",
|
|
367
|
+
{
|
|
368
|
+
patterns: [
|
|
369
|
+
{
|
|
370
|
+
group: REACT_GROUP,
|
|
371
|
+
message: "layers-features: model is pure: no React runtime. Keep I/O in adapters.",
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
group: kitPatterns,
|
|
375
|
+
message: "layers-features: model is pure: no UI kit imports.",
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
group: ADAPTER_GROUP,
|
|
379
|
+
message: "layers-features: model is pure: adapters are injected at the composition root.",
|
|
380
|
+
},
|
|
381
|
+
...extras.model,
|
|
382
|
+
],
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
204
387
|
];
|
|
205
388
|
}
|