@zedtech/zedkit-quality 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zedtech-fr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,77 @@
1
- # Temporary Holding Version
1
+ # @zedtech/zedkit-quality
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@zedtech/zedkit-quality.svg)](https://www.npmjs.com/package/@zedtech/zedkit-quality)
4
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/zedtech-fr/zedkit/blob/main/LICENSE)
5
+
6
+ Shared quality setup: UI lint preset, route/view/data layer boundaries,
7
+ and one strict TypeScript base. Consume it from any repo — do not copy
8
+ it. Original work by the same author.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ pnpm add -D @zedtech/zedkit-quality
14
+ ```
15
+
16
+ Peer dependencies (`eslint`, `eslint-plugin-jsx-a11y`,
17
+ `eslint-plugin-better-tailwindcss`) install alongside it.
18
+
19
+ ## Usage
20
+
21
+ ```js
22
+ // eslint.config.mjs
23
+ import { defineLayers, uiPreset } from "@zedtech/zedkit-quality";
24
+
25
+ export default [
26
+ ...uiPreset({ entryPoint: "./app/globals.css" }),
27
+ ...defineLayers({
28
+ kitPackage: "@acme/ui",
29
+ viewDirs: ["app/**/ui/**"],
30
+ dataDirs: ["lib/**"],
31
+ actionPatterns: ["@/lib/*", "**/actions", "**/actions/**"],
32
+ }),
33
+ ];
34
+ ```
35
+
36
+ ```jsonc
37
+ // tsconfig.json
38
+ {
39
+ "extends": "@zedtech/zedkit-quality/tsconfig",
40
+ "compilerOptions": { "outDir": "dist", "rootDir": "src" },
41
+ "include": ["src"]
42
+ }
43
+ ```
44
+
45
+ ## When to use
46
+
47
+ Reach for this package when several repos must enforce the same gates:
48
+ UI lint rules, layer boundaries, one strict `tsconfig` base. One import
49
+ per need, versioned updates everywhere. For AI agent setup (skills,
50
+ subagents, commands) see `@zedtech/zedkit-ai` instead.
51
+
52
+ ## API
53
+
54
+ - `uiPreset({ entryPoint })` — flat-config blocks for `.tsx`/`.jsx`:
55
+ jsx-a11y recommended, Tailwind hygiene, tokens-only classes, no `style`
56
+ attributes, no raw `try`/`catch`. `entryPoint` (your Tailwind CSS
57
+ entry) is required: it is the one thing that varies per repo.
58
+ - `defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns })` —
59
+ `no-restricted-imports` blocks for routes -> views -> data: the UI kit
60
+ is only imported from views, views never import data clients or
61
+ actions, data files import no UI.
62
+ - `@zedtech/zedkit-quality/tsconfig` — the strict base (`strict`,
63
+ `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`,
64
+ `noUnusedLocals`, and more). Extend it, do not copy it.
65
+
66
+ Find helpers by need in [CATALOG.md](CATALOG.md).
67
+
68
+ ## Install or copy?
69
+
70
+ Install it: these configs must stay identical across repos, and a copy
71
+ drifts silently. Versioned updates propagate through normal bump PRs.
72
+
73
+ ## Verify
74
+
75
+ ```sh
76
+ pnpm --filter @zedtech/zedkit-quality typecheck && pnpm --filter @zedtech/zedkit-quality lint && pnpm --filter @zedtech/zedkit-quality test
77
+ ```
package/package.json CHANGED
@@ -1,6 +1,77 @@
1
1
  {
2
2
  "name": "@zedtech/zedkit-quality",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "registry": "https://registry.npmjs.org/"
7
+ },
8
+ "description": "Shared quality setup: UI lint preset, route/view/data layer boundaries, and one strict TypeScript base — consume, do not copy.",
9
+ "keywords": [
10
+ "eslint",
11
+ "eslint-config",
12
+ "tsconfig",
13
+ "typescript",
14
+ "strict",
15
+ "layers",
16
+ "architecture",
17
+ "a11y",
18
+ "tailwindcss",
19
+ "code-quality"
20
+ ],
21
+ "homepage": "https://github.com/zedtech-fr/zedkit/tree/main/packages/quality",
22
+ "bugs": {
23
+ "url": "https://github.com/zedtech-fr/zedkit/issues"
24
+ },
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/zedtech-fr/zedkit",
29
+ "directory": "packages/quality"
30
+ },
31
+ "type": "module",
32
+ "sideEffects": false,
33
+ "imports": {
34
+ "#*": "./src/*"
35
+ },
36
+ "main": "./src/index.mjs",
37
+ "types": "./src/index.d.mts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./src/index.d.mts",
41
+ "import": "./src/index.mjs"
42
+ },
43
+ "./eslint": {
44
+ "types": "./src/eslint.d.mts",
45
+ "import": "./src/eslint.mjs"
46
+ },
47
+ "./tsconfig": "./tsconfig.base.json",
48
+ "./package.json": "./package.json"
49
+ },
50
+ "files": [
51
+ "src/index.mjs",
52
+ "src/index.d.mts",
53
+ "src/eslint.mjs",
54
+ "src/eslint.d.mts",
55
+ "tsconfig.base.json"
56
+ ],
57
+ "zedkit": {
58
+ "noBuild": true
59
+ },
60
+ "peerDependencies": {
61
+ "eslint": "9.39.5",
62
+ "eslint-plugin-better-tailwindcss": "4.10.0",
63
+ "eslint-plugin-jsx-a11y": "6.10.2"
64
+ },
65
+ "devDependencies": {
66
+ "eslint": "9.39.5",
67
+ "eslint-plugin-better-tailwindcss": "4.10.0",
68
+ "eslint-plugin-jsx-a11y": "6.10.2",
69
+ "typescript": "5.9.3",
70
+ "vitest": "2.1.9"
71
+ },
72
+ "scripts": {
73
+ "test": "vitest run",
74
+ "typecheck": "tsc --noEmit -p tsconfig.json",
75
+ "lint": "eslint src tests"
76
+ }
6
77
  }
@@ -0,0 +1,21 @@
1
+ import type { Linter } from "eslint";
2
+
3
+ export interface UiPresetOptions {
4
+ /** Path to the repo's Tailwind CSS entry. Required, no default. */
5
+ readonly entryPoint: string;
6
+ }
7
+
8
+ export function uiPreset(options: UiPresetOptions): Linter.Config[];
9
+
10
+ export interface DefineLayersOptions {
11
+ /** UI kit specifier (e.g. `@acme/ui`). */
12
+ readonly kitPackage: string;
13
+ /** Minimatch globs for view folders. */
14
+ readonly viewDirs: readonly string[];
15
+ /** Minimatch globs for data-access folders. */
16
+ readonly dataDirs: readonly string[];
17
+ /** Import specifiers for data clients and server actions. */
18
+ readonly actionPatterns: readonly string[];
19
+ }
20
+
21
+ export function defineLayers(options: DefineLayersOptions): Linter.Config[];
package/src/eslint.mjs ADDED
@@ -0,0 +1,188 @@
1
+ // Shareable UI lint preset: enforces docs/frontend-charter.md by tools.
2
+ //
3
+ // What it enforces on every .tsx/.jsx file:
4
+ // - jsx-a11y recommended (labels, keyboard, focus, ARIA)
5
+ // - Tailwind class hygiene (order, no duplicates, no conflicts, no unknown)
6
+ // - Tokens only: no raw palette colors (bg-red-500), no arbitrary hex/rgb
7
+ // colors, no raw px font sizes — use the semantic theme tokens
8
+ // - No `style` attributes; no raw try/catch (use a tryCatch wrapper)
9
+ //
10
+ // Consumer setup (no copy: install the package, pass your Tailwind entry):
11
+ // `import { uiPreset } from "@zedtech/zedkit-quality"` then
12
+ // `export default [...uiPreset({ entryPoint: "./app/globals.css" })]`.
13
+ import jsxA11y from "eslint-plugin-jsx-a11y";
14
+ import betterTailwindcss from "eslint-plugin-better-tailwindcss";
15
+
16
+ const PALETTE =
17
+ "slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|fuchsia|pink|rose";
18
+ const COLOR_UTILS =
19
+ "bg|text|border|ring|ring-offset|divide|from|via|to|decoration|outline|accent|caret|fill|stroke|shadow|placeholder";
20
+
21
+ /**
22
+ * UI lint preset for .tsx/.jsx files.
23
+ *
24
+ * @param {{ entryPoint: string }} options - Path to the repo's Tailwind CSS
25
+ * entry, so the plugin knows the theme tokens. Required, no default: the
26
+ * per-repo CSS path is the one thing that varies.
27
+ * @returns {import("eslint").Linter.Config[]} Flat config blocks.
28
+ */
29
+ export function uiPreset({ entryPoint }) {
30
+ if (typeof entryPoint !== "string" || entryPoint === "") {
31
+ throw new Error("uiPreset: `entryPoint` is required (path to your Tailwind CSS entry).");
32
+ }
33
+ return [
34
+ {
35
+ name: "zedkit/ui-a11y",
36
+ files: ["**/*.tsx", "**/*.jsx"],
37
+ ...jsxA11y.flatConfigs.recommended,
38
+ },
39
+ {
40
+ name: "zedkit/ui-tailwind",
41
+ files: ["**/*.tsx", "**/*.jsx"],
42
+ plugins: { "better-tailwindcss": betterTailwindcss },
43
+ settings: {
44
+ "better-tailwindcss": {
45
+ entryPoint,
46
+ },
47
+ },
48
+ rules: {
49
+ "better-tailwindcss/enforce-consistent-class-order": "error",
50
+ "better-tailwindcss/no-duplicate-classes": "error",
51
+ "better-tailwindcss/no-conflicting-classes": "error",
52
+ "better-tailwindcss/no-unknown-classes": "error",
53
+ "better-tailwindcss/no-restricted-classes": [
54
+ "error",
55
+ {
56
+ restrict: [
57
+ {
58
+ pattern: `(^|:)(${COLOR_UTILS})-(${PALETTE}|white|black)(-|/|$)`,
59
+ message:
60
+ "Raw palette colors are banned. Use a semantic theme token (see docs/frontend-charter.md).",
61
+ },
62
+ {
63
+ pattern: "\\[(#|rgb|hsl)",
64
+ message:
65
+ "Arbitrary color values are banned. Use a semantic theme token (see docs/frontend-charter.md).",
66
+ },
67
+ {
68
+ pattern: "text-\\[[^\\]]*px",
69
+ message: "Raw px font sizes are banned. Use the type scale (see docs/frontend-charter.md).",
70
+ },
71
+ ],
72
+ },
73
+ ],
74
+ // NOTE: flat-config rules replace (not merge) by key, so this block
75
+ // restates the root TryStatement ban alongside the style-attribute
76
+ // ban; without the restatement, .tsx files would lose the ban.
77
+ "no-restricted-syntax": [
78
+ "error",
79
+ {
80
+ selector: "TryStatement",
81
+ message: "Raw try/catch is banned. Use tryCatch/tryCatchAsync from @zedtech/zedkit-core.",
82
+ },
83
+ {
84
+ selector: 'JSXAttribute[name.name="style"]',
85
+ message: "Inline style attributes are banned. Use theme tokens (see docs/frontend-charter.md).",
86
+ },
87
+ ],
88
+ },
89
+ },
90
+ ];
91
+ }
92
+
93
+ // JS files the kit-scope block applies to (minus the view/data ignores).
94
+ const JS_FILES = ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts", "**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"];
95
+ const JS_BRACE = "{ts,tsx,mts,cts,js,jsx,mjs,cjs}";
96
+
97
+ // Flat-config `files` never matches a bare directory (`app/ui/**` matches
98
+ // nothing): dir-style entries become `dir/**/*.{exts}`. Entries that
99
+ // already name files (`**/*.tsx`, braces) pass through untouched. A leaf
100
+ // containing a dot counts as a file pattern, so a directory whose own
101
+ // name has a dot must use a trailing `/**` to be treated as a folder.
102
+ function asFiles(patterns) {
103
+ return patterns.map((entry) => {
104
+ const leaf = entry.slice(entry.lastIndexOf("/") + 1);
105
+ if (entry.endsWith("}") || leaf.includes(".")) return entry;
106
+ const stripped = entry.endsWith("/**") ? entry.slice(0, -3) : entry;
107
+ const dir = stripped.endsWith("/") ? stripped.slice(0, -1) : stripped;
108
+ return `${dir}/**/*.${JS_BRACE}`;
109
+ });
110
+ }
111
+
112
+ /**
113
+ * Route/view/data layer boundaries as `no-restricted-imports` blocks.
114
+ * Dependencies run one way (routes -> views -> data):
115
+ * - the UI kit is only imported from view files;
116
+ * - views receive data as props and never import data clients or actions;
117
+ * - data files import no UI (no React runtime, no UI kit).
118
+ *
119
+ * @param {{ kitPackage: string, viewDirs: string[], dataDirs: string[], actionPatterns: string[] }} options
120
+ * - `kitPackage`: UI kit specifier (e.g. `@acme/ui`).
121
+ * - `viewDirs`: view folders (e.g. `app/ui/**`) or file globs.
122
+ * - `dataDirs`: data-access folders or file globs, same shapes.
123
+ * - `actionPatterns`: import specifiers for data clients and server actions.
124
+ * @returns {import("eslint").Linter.Config[]} Flat config blocks.
125
+ */
126
+ export function defineLayers({ kitPackage, viewDirs, dataDirs, actionPatterns }) {
127
+ const kitPatterns = [kitPackage, `${kitPackage}/*`];
128
+ const views = asFiles(viewDirs);
129
+ const data = asFiles(dataDirs);
130
+ return [
131
+ {
132
+ name: "zedkit/layers-kit-scope",
133
+ files: JS_FILES,
134
+ ignores: [...views, ...data],
135
+ rules: {
136
+ "no-restricted-imports": [
137
+ "error",
138
+ {
139
+ patterns: [
140
+ {
141
+ group: kitPatterns,
142
+ message: "UI kit imports are only allowed from view files. Move this import into a view.",
143
+ },
144
+ ],
145
+ },
146
+ ],
147
+ },
148
+ },
149
+ {
150
+ name: "zedkit/layers-view-data",
151
+ files: views,
152
+ rules: {
153
+ "no-restricted-imports": [
154
+ "error",
155
+ {
156
+ patterns: [
157
+ {
158
+ group: [...actionPatterns],
159
+ message: "Views receive data as props and never import data clients or server actions.",
160
+ },
161
+ ],
162
+ },
163
+ ],
164
+ },
165
+ },
166
+ {
167
+ name: "zedkit/layers-data-ui",
168
+ files: data,
169
+ rules: {
170
+ "no-restricted-imports": [
171
+ "error",
172
+ {
173
+ patterns: [
174
+ {
175
+ group: ["react", "react-dom", "react-dom/*", "react/jsx-runtime"],
176
+ message: "Data code is UI-free: no React runtime imports.",
177
+ },
178
+ {
179
+ group: kitPatterns,
180
+ message: "Data code is UI-free: no UI kit imports.",
181
+ },
182
+ ],
183
+ },
184
+ ],
185
+ },
186
+ },
187
+ ];
188
+ }
@@ -0,0 +1,2 @@
1
+ export { defineLayers, uiPreset } from "./eslint.mjs";
2
+ export type { DefineLayersOptions, UiPresetOptions } from "./eslint.mjs";
package/src/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export { defineLayers, uiPreset } from "./eslint.mjs";
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "lib": ["ES2022"],
7
+ "strict": true,
8
+ "exactOptionalPropertyTypes": true,
9
+ "noUncheckedIndexedAccess": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noFallthroughCasesInSwitch": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "skipLibCheck": true,
17
+ "esModuleInterop": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "resolveJsonModule": true
20
+ }
21
+ }