orbital-ds 0.1.1 → 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/contract.json +38 -0
- package/dist/styles.css +89 -9
- package/dist/themes/zyro.css +189 -0
- package/dist/ui/accordion.d.ts +9 -0
- package/dist/ui/accordion.js +79 -0
- package/dist/ui/alert-dialog.d.ts +23 -0
- package/dist/ui/alert-dialog.js +173 -0
- package/dist/ui/alert.d.ts +13 -0
- package/dist/ui/alert.js +77 -0
- package/dist/ui/aspect-ratio.d.ts +7 -0
- package/dist/ui/aspect-ratio.js +22 -0
- package/dist/ui/attachment.d.ts +26 -0
- package/dist/ui/attachment.js +197 -0
- package/dist/ui/avatar.js +1 -1
- package/dist/ui/badge.d.ts +1 -1
- package/dist/ui/badge.js +1 -1
- package/dist/ui/bubble.d.ts +19 -0
- package/dist/ui/bubble.js +115 -0
- package/dist/ui/button-group.d.ts +15 -0
- package/dist/ui/button-group.js +82 -0
- package/dist/ui/button.d.ts +5 -3
- package/dist/ui/button.js +9 -6
- package/dist/ui/calendar.d.ts +15 -0
- package/dist/ui/calendar.js +190 -0
- package/dist/ui/card.js +4 -4
- package/dist/ui/carousel.d.ts +33 -0
- package/dist/ui/carousel.js +199 -0
- package/dist/ui/checkbox.js +1 -1
- package/dist/ui/combobox.d.ts +26 -0
- package/dist/ui/combobox.js +281 -0
- package/dist/ui/command.d.ts +22 -0
- package/dist/ui/command.js +183 -0
- package/dist/ui/context-menu.d.ts +31 -0
- package/dist/ui/context-menu.js +242 -0
- package/dist/ui/dialog.js +2 -2
- package/dist/ui/direction.d.ts +1 -0
- package/dist/ui/direction.js +8 -0
- package/dist/ui/drawer.d.ts +18 -0
- package/dist/ui/drawer.js +208 -0
- package/dist/ui/dropdown-menu.d.ts +1 -1
- package/dist/ui/dropdown-menu.js +13 -9
- package/dist/ui/empty.d.ts +15 -0
- package/dist/ui/empty.js +102 -0
- package/dist/ui/field.d.ts +28 -0
- package/dist/ui/field.js +217 -0
- package/dist/ui/hover-card.d.ts +8 -0
- package/dist/ui/hover-card.js +45 -0
- package/dist/ui/input-group.d.ts +22 -0
- package/dist/ui/input-group.js +145 -0
- package/dist/ui/input-otp.d.ts +13 -0
- package/dist/ui/input-otp.js +82 -0
- package/dist/ui/input.js +1 -1
- package/dist/ui/item.d.ts +26 -0
- package/dist/ui/item.js +195 -0
- package/dist/ui/kbd.d.ts +6 -0
- package/dist/ui/kbd.js +29 -0
- package/dist/ui/marker.d.ts +13 -0
- package/dist/ui/marker.js +71 -0
- package/dist/ui/menubar.d.ts +31 -0
- package/dist/ui/menubar.js +259 -0
- package/dist/ui/message-scroller.d.ts +16 -0
- package/dist/ui/message-scroller.js +120 -0
- package/dist/ui/message.d.ts +12 -0
- package/dist/ui/message.js +90 -0
- package/dist/ui/native-select.d.ts +10 -0
- package/dist/ui/native-select.js +63 -0
- package/dist/ui/navigation-menu.d.ts +15 -0
- package/dist/ui/navigation-menu.js +162 -0
- package/dist/ui/pagination.d.ts +22 -0
- package/dist/ui/pagination.js +128 -0
- package/dist/ui/popover.js +2 -2
- package/dist/ui/questionnaire.d.ts +24 -0
- package/dist/ui/questionnaire.js +331 -0
- package/dist/ui/radio-group.d.ts +8 -0
- package/dist/ui/radio-group.js +39 -0
- package/dist/ui/resizable.d.ts +10 -0
- package/dist/ui/resizable.js +46 -0
- package/dist/ui/scroll-area.js +1 -1
- package/dist/ui/select.js +5 -5
- package/dist/ui/sheet.js +4 -4
- package/dist/ui/sidebar.js +4 -4
- package/dist/ui/slider.d.ts +6 -0
- package/dist/ui/slider.js +53 -0
- package/dist/ui/spinner.d.ts +5 -0
- package/dist/ui/spinner.js +9 -0
- package/dist/ui/switch.d.ts +8 -0
- package/dist/ui/switch.js +32 -0
- package/dist/ui/table.js +2 -2
- package/dist/ui/tabs.js +1 -1
- package/dist/ui/textarea.js +1 -1
- package/dist/ui/toast.d.ts +19 -0
- package/dist/ui/toast.js +193 -0
- package/dist/ui/toggle-group.d.ts +14 -0
- package/dist/ui/toggle-group.js +77 -0
- package/dist/ui/toggle.d.ts +12 -0
- package/dist/ui/toggle.js +43 -0
- package/dist/ui/tooltip.js +3 -3
- package/docs/CONTRACT.md +113 -0
- package/package.json +133 -95
- package/scripts/doctor.mjs +142 -0
package/package.json
CHANGED
|
@@ -1,95 +1,133 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "orbital-ds",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sistema de diseño genérico: estructura común (
|
|
5
|
-
"keywords": [
|
|
6
|
-
"design-system",
|
|
7
|
-
"design-tokens",
|
|
8
|
-
"theme",
|
|
9
|
-
"tailwindcss",
|
|
10
|
-
"base-ui",
|
|
11
|
-
"shadcn",
|
|
12
|
-
"react"
|
|
13
|
-
],
|
|
14
|
-
"license": "MIT",
|
|
15
|
-
"type": "module",
|
|
16
|
-
"sideEffects": [
|
|
17
|
-
"*.css"
|
|
18
|
-
],
|
|
19
|
-
"files": [
|
|
20
|
-
"dist",
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"react
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"next-themes":
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
"
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "orbital-ds",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Sistema de diseño genérico: estructura común + temas intercambiables (qratia, zyro) + ~60 componentes sobre Base UI y Tailwind v4, con contrato de tema verificable.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"design-system",
|
|
7
|
+
"design-tokens",
|
|
8
|
+
"theme",
|
|
9
|
+
"tailwindcss",
|
|
10
|
+
"base-ui",
|
|
11
|
+
"shadcn",
|
|
12
|
+
"react"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"sideEffects": [
|
|
17
|
+
"*.css"
|
|
18
|
+
],
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"scripts/doctor.mjs",
|
|
22
|
+
"contract.json",
|
|
23
|
+
"docs",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE"
|
|
26
|
+
],
|
|
27
|
+
"exports": {
|
|
28
|
+
"./styles.css": "./dist/styles.css",
|
|
29
|
+
"./themes/*.css": "./dist/themes/*.css",
|
|
30
|
+
"./ui/*": {
|
|
31
|
+
"types": "./dist/ui/*.d.ts",
|
|
32
|
+
"import": "./dist/ui/*.js"
|
|
33
|
+
},
|
|
34
|
+
"./lib/*": {
|
|
35
|
+
"types": "./dist/lib/*.d.ts",
|
|
36
|
+
"import": "./dist/lib/*.js"
|
|
37
|
+
},
|
|
38
|
+
"./hooks/*": {
|
|
39
|
+
"types": "./dist/hooks/*.d.ts",
|
|
40
|
+
"import": "./dist/hooks/*.js"
|
|
41
|
+
},
|
|
42
|
+
"./package.json": "./package.json",
|
|
43
|
+
"./contract.json": "./contract.json"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsup && node scripts/copy-css.mjs",
|
|
47
|
+
"dev": "tsup --watch --onSuccess \"node scripts/copy-css.mjs\"",
|
|
48
|
+
"typecheck": "tsc --noEmit",
|
|
49
|
+
"verify": "pnpm doctor && pnpm typecheck && pnpm build",
|
|
50
|
+
"doctor": "node scripts/doctor.mjs"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@base-ui/react": ">=1.9",
|
|
54
|
+
"class-variance-authority": ">=0.7",
|
|
55
|
+
"clsx": ">=2",
|
|
56
|
+
"lucide-react": ">=0.400",
|
|
57
|
+
"react": ">=19",
|
|
58
|
+
"react-dom": ">=19",
|
|
59
|
+
"tailwind-merge": ">=3",
|
|
60
|
+
"tailwindcss": ">=4.1",
|
|
61
|
+
"tw-animate-css": ">=1.3",
|
|
62
|
+
"next-themes": ">=0.4",
|
|
63
|
+
"recharts": ">=3",
|
|
64
|
+
"sonner": ">=2",
|
|
65
|
+
"@shadcn/react": ">=0.3",
|
|
66
|
+
"cmdk": ">=1.1",
|
|
67
|
+
"date-fns": ">=4",
|
|
68
|
+
"embla-carousel-react": ">=8.6",
|
|
69
|
+
"input-otp": ">=1.5",
|
|
70
|
+
"react-day-picker": ">=10",
|
|
71
|
+
"react-resizable-panels": ">=4"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"next-themes": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"recharts": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"sonner": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"@shadcn/react": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"cmdk": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"date-fns": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"embla-carousel-react": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"input-otp": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"react-day-picker": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"react-resizable-panels": {
|
|
102
|
+
"optional": true
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
"devDependencies": {
|
|
106
|
+
"@base-ui/react": "^1.9.0",
|
|
107
|
+
"@types/react": "^19.2.0",
|
|
108
|
+
"@types/react-dom": "^19.2.0",
|
|
109
|
+
"class-variance-authority": "^0.7.1",
|
|
110
|
+
"clsx": "^2.1.1",
|
|
111
|
+
"lucide-react": "^0.577.0",
|
|
112
|
+
"next-themes": "^0.4.6",
|
|
113
|
+
"react": "^19.2.0",
|
|
114
|
+
"react-dom": "^19.2.0",
|
|
115
|
+
"recharts": "3.8.0",
|
|
116
|
+
"sonner": "^2.0.8",
|
|
117
|
+
"tailwind-merge": "^3.0.2",
|
|
118
|
+
"tailwindcss": "^4.1.18",
|
|
119
|
+
"tsup": "^8.5.0",
|
|
120
|
+
"tw-animate-css": "^1.3.6",
|
|
121
|
+
"typescript": "^5.9.0",
|
|
122
|
+
"@shadcn/react": "^0.3.1",
|
|
123
|
+
"cmdk": "^1.1.1",
|
|
124
|
+
"date-fns": "^4.4.0",
|
|
125
|
+
"embla-carousel-react": "^8.6.0",
|
|
126
|
+
"input-otp": "^1.5.0",
|
|
127
|
+
"react-day-picker": "^10.0.2",
|
|
128
|
+
"react-resizable-panels": "^4.14.4"
|
|
129
|
+
},
|
|
130
|
+
"bin": {
|
|
131
|
+
"orbital-ds-doctor": "./scripts/doctor.mjs"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* orbital-ds doctor — comprueba que el sistema se cumple.
|
|
4
|
+
*
|
|
5
|
+
* 1. CONTRATO: cada tema (themes/*.css) define en :root todos los tokens de
|
|
6
|
+
* contract.json (y la fuente `--font-sans`). En .dark solo avisa.
|
|
7
|
+
* 2. REPETIDOS: ninguna variable CSS se define dos veces en el mismo bloque.
|
|
8
|
+
* 3. VALORES FIJOS: ningún componente (ui/*.tsx) escribe colores, z-index,
|
|
9
|
+
* duraciones ni sombras a mano; todo sale de tokens.
|
|
10
|
+
*
|
|
11
|
+
* Uso:
|
|
12
|
+
* node scripts/doctor.mjs # temas y componentes del paquete
|
|
13
|
+
* node scripts/doctor.mjs --css app.css # además, un CSS de una app (contrato + repetidos)
|
|
14
|
+
*
|
|
15
|
+
* Sale con código 1 si hay errores (los avisos no fallan).
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
18
|
+
import { join, dirname, resolve } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
|
|
21
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
+
const contract = JSON.parse(readFileSync(join(ROOT, 'contract.json'), 'utf8'));
|
|
23
|
+
|
|
24
|
+
const errors = [];
|
|
25
|
+
const warnings = [];
|
|
26
|
+
const read = (p) => readFileSync(p, 'utf8');
|
|
27
|
+
const stripComments = (css) => css.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
28
|
+
|
|
29
|
+
/** Devuelve el cuerpo de cada bloque `selector { ... }` (respeta llaves anidadas). */
|
|
30
|
+
function blocks(css, selectorRe) {
|
|
31
|
+
const out = [];
|
|
32
|
+
const re = new RegExp(selectorRe.source + '\\s*\\{', 'g');
|
|
33
|
+
let m;
|
|
34
|
+
while ((m = re.exec(css))) {
|
|
35
|
+
let depth = 1;
|
|
36
|
+
let i = re.lastIndex;
|
|
37
|
+
const start = i;
|
|
38
|
+
while (i < css.length && depth > 0) {
|
|
39
|
+
if (css[i] === '{') depth++;
|
|
40
|
+
else if (css[i] === '}') depth--;
|
|
41
|
+
i++;
|
|
42
|
+
}
|
|
43
|
+
out.push(css.slice(start, i - 1));
|
|
44
|
+
re.lastIndex = i;
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const definedVars = (body) => [...body.matchAll(/(^|[;{\s])(--[a-z0-9-]+)\s*:/gi)].map((m) => m[2]);
|
|
50
|
+
|
|
51
|
+
function checkTheme(file, label) {
|
|
52
|
+
const css = stripComments(read(file));
|
|
53
|
+
const roots = blocks(css, /(?:^|[\s}])\:root/);
|
|
54
|
+
const darks = blocks(css, /(?:^|[\s}])\.dark/);
|
|
55
|
+
const themeBlocks = blocks(css, /@theme\s+inline/);
|
|
56
|
+
|
|
57
|
+
const rootVars = new Set(roots.flatMap(definedVars));
|
|
58
|
+
const themeVars = new Set(themeBlocks.flatMap(definedVars));
|
|
59
|
+
const darkVars = new Set(darks.flatMap(definedVars));
|
|
60
|
+
|
|
61
|
+
for (const t of contract.root) {
|
|
62
|
+
if (!rootVars.has(`--${t}`)) errors.push(`${label}: falta el token obligatorio --${t} en :root`);
|
|
63
|
+
}
|
|
64
|
+
for (const f of contract.fonts) {
|
|
65
|
+
if (!rootVars.has(`--${f}`) && !themeVars.has(`--${f}`)) {
|
|
66
|
+
errors.push(`${label}: falta la fuente --${f} (en :root o @theme)`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (darks.length) {
|
|
70
|
+
for (const t of contract.dark.avisar_si_falta) {
|
|
71
|
+
if (!darkVars.has(`--${t}`)) warnings.push(`${label}: .dark no redefine --${t} (hereda el valor claro)`);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
warnings.push(`${label}: no define piel .dark`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Repetidos dentro de un mismo bloque
|
|
78
|
+
for (const [name, list] of [['@theme', themeBlocks], [':root', roots], ['.dark', darks]]) {
|
|
79
|
+
list.forEach((body, idx) => {
|
|
80
|
+
const seen = new Map();
|
|
81
|
+
for (const v of definedVars(body)) seen.set(v, (seen.get(v) ?? 0) + 1);
|
|
82
|
+
for (const [v, n] of seen) {
|
|
83
|
+
if (n > 1) errors.push(`${label}: ${v} definido ${n} veces en el mismo bloque ${name} (#${idx + 1})`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 1+2) temas del paquete
|
|
90
|
+
const themesDir = join(ROOT, existsSync(join(ROOT, 'src/themes')) ? 'src/themes' : 'dist/themes');
|
|
91
|
+
for (const f of readdirSync(themesDir).filter((n) => n.endsWith('.css'))) {
|
|
92
|
+
checkTheme(join(themesDir, f), `tema ${f}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// CSS de una app (opcional)
|
|
96
|
+
const cssArgIdx = process.argv.indexOf('--css');
|
|
97
|
+
if (cssArgIdx > -1 && process.argv[cssArgIdx + 1]) {
|
|
98
|
+
const f = resolve(process.argv[cssArgIdx + 1]);
|
|
99
|
+
const css = stripComments(read(f));
|
|
100
|
+
const roots = blocks(css, /(?:^|[\s}])\:root/);
|
|
101
|
+
roots.forEach((body, idx) => {
|
|
102
|
+
const seen = new Map();
|
|
103
|
+
for (const v of definedVars(body)) seen.set(v, (seen.get(v) ?? 0) + 1);
|
|
104
|
+
for (const [v, n] of seen) if (n > 1) errors.push(`app css: ${v} definido ${n} veces en :root (#${idx + 1})`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3) valores fijos en componentes
|
|
109
|
+
const NO_HARDCODE = [
|
|
110
|
+
{ re: /#[0-9a-fA-F]{3,8}\b/g, why: 'color hexadecimal', skip: ['chart.tsx'] },
|
|
111
|
+
{ re: /\b(?:rgb|rgba|hsl|hsla|oklch)\((?!from_)/g, why: 'color literal' },
|
|
112
|
+
{ re: /(?<![\w-])z-(?!0(?![\w-]))\d+(?![\w-])/g, why: 'z-index numérico (usa --ds-z-*)' },
|
|
113
|
+
{ re: /(?<![\w-])duration-(?!0(?![\w-]))\d+(?![\w-])/g, why: 'duración fija (usa --ds-motion-*)' },
|
|
114
|
+
{ re: /(?<![\w-])(?:bg|text|border)-(?:black|white)(?:\/\d+)?(?![\w-])/g, why: 'color fijo (usa un token de color)' },
|
|
115
|
+
{ re: /(?<![\w-])shadow-(?:xs|sm|md|lg|xl|2xl)(?![\w-])/g, why: 'sombra de la escala Tailwind (usa shadow-card/control/raised/overlay/popover)' },
|
|
116
|
+
{ re: /(?<![\w-])text-\[\d+px\]/g, why: 'tamaño de texto arbitrario (usa la escala estándar)' },
|
|
117
|
+
{ re: /(?<![\w-])rounded-(?:3xl|4xl)(?![\w-])/g, why: 'radio fuera de la rampa (usa rounded-control/overlay o la rampa sm…2xl)' },
|
|
118
|
+
{ re: /(?<![\w-])(?:bg|text|border|ring|fill|stroke)-(?:red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose|slate|gray|zinc|neutral|stone)-\d{2,3}(?![\w-])/g, why: 'color de la paleta Tailwind (usa tokens del tema)' },
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
const uiDir = join(ROOT, 'src/ui');
|
|
122
|
+
if (existsSync(uiDir)) {
|
|
123
|
+
for (const f of readdirSync(uiDir).filter((n) => n.endsWith('.tsx'))) {
|
|
124
|
+
const lines = read(join(uiDir, f)).split(/\r?\n/);
|
|
125
|
+
lines.forEach((line, i) => {
|
|
126
|
+
const code = line.replace(/\/\/.*$/, '');
|
|
127
|
+
for (const rule of NO_HARDCODE) {
|
|
128
|
+
if (rule.skip?.includes(f)) continue;
|
|
129
|
+
const hits = code.match(rule.re);
|
|
130
|
+
if (hits) errors.push(`ui/${f}:${i + 1}: ${rule.why} → ${[...new Set(hits)].join(', ')}`);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const w of warnings) console.log(`AVISO ${w}`);
|
|
137
|
+
for (const e of errors) console.log(`ERROR ${e}`);
|
|
138
|
+
if (errors.length) {
|
|
139
|
+
console.log(`\ndoctor: ${errors.length} error(es), ${warnings.length} aviso(s)`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
console.log(`doctor: contrato, repetidos y valores fijos correctos (${warnings.length} aviso(s)).`);
|