@rendra-ui/web 2.3.0 → 2.5.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.
Files changed (85) hide show
  1. package/README.md +107 -61
  2. package/bin/rendra.mjs +19 -7
  3. package/dist/base.css +33 -1
  4. package/dist/{button-DWhICtIG.js → button-BzJgR2gH.js} +86 -21
  5. package/dist/button-BzJgR2gH.js.map +1 -0
  6. package/dist/chart.js +61 -15
  7. package/dist/chart.js.map +1 -1
  8. package/dist/cli/auditar.js +66 -16
  9. package/dist/cli/auditar.js.map +1 -1
  10. package/dist/cli/codigos.js +2 -2
  11. package/dist/cli/executar-auditar.js +634 -0
  12. package/dist/cli/executar-auditar.js.map +1 -0
  13. package/dist/cli/help.js +7 -2
  14. package/dist/cli/help.js.map +1 -1
  15. package/dist/cli/trocar.js +2 -2
  16. package/dist/cli/typescript-loader.js +1 -1
  17. package/dist/{cn-DDZQKUVV.js → cn-Cw7M5kEP.js} +2 -2
  18. package/dist/{cn-DDZQKUVV.js.map → cn-Cw7M5kEP.js.map} +1 -1
  19. package/dist/{components-Cq_PmN4y.js → components-DkpspH9h.js} +21 -4
  20. package/dist/components-DkpspH9h.js.map +1 -0
  21. package/dist/components.css +274 -32
  22. package/dist/document-viewer.js +6 -6
  23. package/dist/document-viewer.js.map +1 -1
  24. package/dist/{empty-state-C491qfzf.js → empty-state-Cdb1vkMT.js} +81 -37
  25. package/dist/empty-state-Cdb1vkMT.js.map +1 -0
  26. package/dist/features-max--5VEt6M7.js +3 -0
  27. package/dist/index.js +1170 -390
  28. package/dist/index.js.map +1 -1
  29. package/dist/motion-context-5BRIQAJm.js +107 -0
  30. package/dist/motion-context-5BRIQAJm.js.map +1 -0
  31. package/dist/{rendra-provider-BRtk5Qa1.js → rendra-provider-CRe0XdOU.js} +9 -5
  32. package/dist/rendra-provider-CRe0XdOU.js.map +1 -0
  33. package/dist/rich-text-editor.js +4 -4
  34. package/dist/router-bridge.js +5 -4
  35. package/dist/router-bridge.js.map +1 -1
  36. package/dist/tokens.css +73 -15
  37. package/dist/tooltip-BAMSaGbS.js +224 -0
  38. package/dist/tooltip-BAMSaGbS.js.map +1 -0
  39. package/dist/types/components/app-shell/app-shell.d.ts +22 -2
  40. package/dist/types/components/app-shell/shell-context.d.ts +7 -1
  41. package/dist/types/components/app-shell/types.d.ts +23 -0
  42. package/dist/types/components/layout/page-header.d.ts +17 -1
  43. package/dist/types/components/rendra-provider.d.ts +7 -1
  44. package/dist/types/components/rendra-router-bridge.d.ts +3 -1
  45. package/dist/types/components/ui/accordion.d.ts +1 -1
  46. package/dist/types/components/ui/badge.d.ts +1 -1
  47. package/dist/types/components/ui/brand-feedback-icon.d.ts +20 -3
  48. package/dist/types/components/ui/button-group.d.ts +8 -1
  49. package/dist/types/components/ui/button.d.ts +1 -1
  50. package/dist/types/components/ui/dropdown-menu.d.ts +5 -5
  51. package/dist/types/components/ui/empty-state.d.ts +4 -0
  52. package/dist/types/components/ui/error-page.d.ts +6 -1
  53. package/dist/types/components/ui/modal.d.ts +4 -0
  54. package/dist/types/components/ui/picker-panel.d.ts +1 -1
  55. package/dist/types/components/ui/popover.d.ts +3 -3
  56. package/dist/types/components/ui/radio-group.d.ts +1 -1
  57. package/dist/types/components/ui/skeleton.d.ts +5 -1
  58. package/dist/types/components/ui/stat-card.d.ts +1 -1
  59. package/dist/types/components/ui/table.d.ts +0 -6
  60. package/dist/types/components/ui/tooltip.d.ts +1 -1
  61. package/dist/types/components/ui/wizard.d.ts +14 -1
  62. package/dist/types/hooks/use-reduced-motion.d.ts +3 -0
  63. package/dist/types/index.d.ts +1 -0
  64. package/dist/types/lib/initials.d.ts +2 -0
  65. package/dist/types/lib/motion/count.d.ts +17 -0
  66. package/dist/types/lib/motion/features-max.d.ts +1 -0
  67. package/dist/types/lib/motion/index.d.ts +5 -0
  68. package/dist/types/lib/motion/motion-context.d.ts +43 -0
  69. package/dist/types/lib/motion/native-events.d.ts +16 -0
  70. package/dist/types/lib/motion/presets.d.ts +92 -0
  71. package/dist/types/lib/motion/reorder.d.ts +7 -0
  72. package/dist/types/lib/motion/tokens.d.ts +66 -0
  73. package/dist/types/lib/motion/use-motion-presets.d.ts +8 -0
  74. package/dist/{use-breakpoint-C3ivDt2R.js → use-breakpoint-O9eqFmsF.js} +2 -2
  75. package/dist/{use-breakpoint-C3ivDt2R.js.map → use-breakpoint-O9eqFmsF.js.map} +1 -1
  76. package/dist/use-motion-presets-BrkgL8lG.js +401 -0
  77. package/dist/use-motion-presets-BrkgL8lG.js.map +1 -0
  78. package/dist/widget-grid.js +3 -3
  79. package/package.json +4 -1
  80. package/dist/button-DWhICtIG.js.map +0 -1
  81. package/dist/components-Cq_PmN4y.js.map +0 -1
  82. package/dist/empty-state-C491qfzf.js.map +0 -1
  83. package/dist/rendra-provider-BRtk5Qa1.js.map +0 -1
  84. package/dist/tooltip-DsNlnBSY.js +0 -105
  85. package/dist/tooltip-DsNlnBSY.js.map +0 -1
@@ -0,0 +1,634 @@
1
+ /*! Rendra Design System v2.5.0 | MIT | © 2026 Bruno Magalhaes | brunomagalhaes.me */
2
+ import { EXTENSAO_VARRIDA, IDS_REGRAS_GENERICAS, auditLines, auditar, casaPrefixo, coletarArquivos, percorrer, relativoA } from "./auditar.js";
3
+ import { formatHelp } from "./help.js";
4
+ import { existsSync, readFileSync } from "node:fs";
5
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ //#region src/cli/help-length.ts
7
+ /** Limite de caracteres da orientação abaixo do campo, pela largura (span) do Field. */
8
+ var HELP_LIMITS = {
9
+ full: 150,
10
+ xl: 100,
11
+ lg: 70,
12
+ md: 40,
13
+ half: 40,
14
+ sm: 30,
15
+ xs: 20
16
+ };
17
+ /** Verbos no imperativo que denunciam instrução num subtítulo. */
18
+ var INSTRUCTION_VERBS = [
19
+ "Comece",
20
+ "Clique",
21
+ "Toque",
22
+ "Arraste",
23
+ "Preencha",
24
+ "Use",
25
+ "Escolha",
26
+ "Selecione",
27
+ "Digite",
28
+ "Informe",
29
+ "Veja",
30
+ "Confira"
31
+ ];
32
+ /** Limite do span; sem span (ou span desconhecido), vale o padrão md. */
33
+ function helpLimit(span) {
34
+ return span && span in HELP_LIMITS ? HELP_LIMITS[span] : HELP_LIMITS.md;
35
+ }
36
+ /** Quantidade de caracteres (por ponto de código, para acento e emoji contarem 1). */
37
+ function textLength(text) {
38
+ return [...text.trim()].length;
39
+ }
40
+ var QUOTES = /* @__PURE__ */ new Set([
41
+ "\"",
42
+ "'",
43
+ "`"
44
+ ]);
45
+ /** Pula uma string que começa em `i` (aspas); devolve o índice logo após o fechamento. */
46
+ function skipString(text, i) {
47
+ const quote = text[i];
48
+ let j = i + 1;
49
+ while (j < text.length && text[j] !== quote) j += text[j] === "\\" ? 2 : 1;
50
+ return j + 1;
51
+ }
52
+ /**
53
+ * Lê a tag de abertura JSX que começa em `start` (no "<") até o ">" que a fecha, ignorando o
54
+ * que está dentro de chaves (render={(f) => <Input />}) e de strings.
55
+ */
56
+ function readTag(text, start) {
57
+ let i = start + 1;
58
+ while (i < text.length && /[\w.]/.test(text[i])) i++;
59
+ if (text[i] === "<") {
60
+ let angle = 0;
61
+ for (; i < text.length; i++) if (text[i] === "<") angle++;
62
+ else if (text[i] === ">" && --angle === 0) {
63
+ i++;
64
+ break;
65
+ }
66
+ }
67
+ let depth = 0;
68
+ while (i < text.length) {
69
+ const c = text[i];
70
+ if (QUOTES.has(c)) {
71
+ i = skipString(text, i);
72
+ continue;
73
+ }
74
+ if (c === "{") depth++;
75
+ else if (c === "}") depth--;
76
+ else if (c === ">" && depth === 0) {
77
+ i++;
78
+ break;
79
+ }
80
+ i++;
81
+ }
82
+ return {
83
+ start,
84
+ end: i,
85
+ text: text.slice(start, i)
86
+ };
87
+ }
88
+ /** Atributo `name` no nível de topo da tag (fora de chaves), literal ou dinâmico. */
89
+ function readAttr(tag, name) {
90
+ let depth = 0;
91
+ let i = 0;
92
+ while (i < tag.length) {
93
+ const c = tag[i];
94
+ if (depth > 0 && QUOTES.has(c)) {
95
+ i = skipString(tag, i);
96
+ continue;
97
+ }
98
+ if (c === "{") depth++;
99
+ else if (c === "}") depth--;
100
+ else if (depth === 0 && QUOTES.has(c)) {
101
+ i = skipString(tag, i);
102
+ continue;
103
+ } else if (depth === 0 && /\s/.test(tag[i - 1] ?? "") && tag.startsWith(`${name}=`, i)) return parseValue(tag, i + name.length + 1);
104
+ i++;
105
+ }
106
+ }
107
+ function parseValue(tag, i) {
108
+ const c = tag[i];
109
+ if (c === "\"" || c === "'") return { literal: tag.slice(i + 1, skipString(tag, i) - 1) };
110
+ if (c !== "{") return { dynamic: true };
111
+ let depth = 0;
112
+ let j = i;
113
+ for (; j < tag.length; j++) {
114
+ if (depth > 0 && QUOTES.has(tag[j])) {
115
+ j = skipString(tag, j) - 1;
116
+ continue;
117
+ }
118
+ if (tag[j] === "{") depth++;
119
+ else if (tag[j] === "}" && --depth === 0) break;
120
+ }
121
+ const inner = tag.slice(i + 1, j).trim();
122
+ const quoted = /^(["'`])([\s\S]*)\1$/.exec(inner);
123
+ if (quoted && !(quoted[1] === "`" && quoted[2].includes("${"))) return { literal: quoted[2] };
124
+ return { dynamic: true };
125
+ }
126
+ function lineOf(text, index) {
127
+ return text.slice(0, index).split("\n").length;
128
+ }
129
+ function firstLine(text) {
130
+ return text.split("\n")[0].trim();
131
+ }
132
+ /** Tags de abertura de um componente (nome exato, sem pegar <FieldGroup> por <Field>). */
133
+ function tagsOf(text, names, from = 0, to = text.length) {
134
+ const re = new RegExp(`<(?:${names.join("|")})(?=[\\s<>/])`, "g");
135
+ re.lastIndex = from;
136
+ const tags = [];
137
+ for (let m = re.exec(text); m && m.index < to; m = re.exec(text)) {
138
+ const tag = readTag(text, m.index);
139
+ tags.push(tag);
140
+ re.lastIndex = tag.end;
141
+ }
142
+ return tags;
143
+ }
144
+ /** help literal (não vazio) de uma tag de Field ou FormField, ou undefined. */
145
+ function literalHelp(tag) {
146
+ const help = readAttr(tag, "help");
147
+ return help && "literal" in help && help.literal.trim() ? help.literal : void 0;
148
+ }
149
+ /** Orientação abaixo do campo acima do limite do span. */
150
+ function checkFieldHelp(text) {
151
+ const found = [];
152
+ for (const tag of tagsOf(text, ["Field", "FormField"])) {
153
+ const help = literalHelp(tag.text);
154
+ if (!help) continue;
155
+ const span = readAttr(tag.text, "span");
156
+ if (span && !("literal" in span)) continue;
157
+ const spanName = span && "literal" in span ? span.literal : "md";
158
+ const limit = helpLimit(spanName);
159
+ const size = textLength(help);
160
+ if (size <= limit) continue;
161
+ found.push({
162
+ line: lineOf(text, tag.start),
163
+ id: "texto-orientativo",
164
+ message: `Orientação do campo com ${size} caracteres; o limite para span="${spanName}" é ${limit}. Encurte, ou mova para help da seção (modal) ou um bloco recolhido.`,
165
+ src: firstLine(tag.text)
166
+ });
167
+ }
168
+ return found;
169
+ }
170
+ /** Por FormSection: no máximo metade dos campos com orientação literal. */
171
+ function checkSectionHelp(text) {
172
+ const found = [];
173
+ for (const section of tagsOf(text, ["FormSection"])) {
174
+ const close = text.indexOf("</FormSection>", section.end);
175
+ const end = close === -1 ? text.length : close;
176
+ const fields = tagsOf(text, ["Field", "FormField"], section.end, end);
177
+ const withHelp = fields.filter((f) => literalHelp(f.text)).length;
178
+ if (withHelp * 2 <= fields.length) continue;
179
+ found.push({
180
+ line: lineOf(text, section.start),
181
+ id: "texto-orientativo",
182
+ message: `Seção com orientação em ${withHelp} de ${fields.length} campos. No máximo metade dos campos de uma seção tem orientação.`,
183
+ src: firstLine(section.text)
184
+ });
185
+ }
186
+ return found;
187
+ }
188
+ /** description literal do PageHeader acima de 150 caracteres. */
189
+ function checkPageDescription(text) {
190
+ const found = [];
191
+ for (const tag of tagsOf(text, ["PageHeader"])) {
192
+ const description = readAttr(tag.text, "description");
193
+ if (!description || !("literal" in description)) continue;
194
+ const size = textLength(description.literal);
195
+ if (size <= 150) continue;
196
+ found.push({
197
+ line: lineOf(text, tag.start),
198
+ id: "texto-orientativo",
199
+ message: `Descrição da tela com ${size} caracteres; o limite é 150. Ela diz o que a tela é; orientação vai em PageHeader help.`,
200
+ src: firstLine(tag.text)
201
+ });
202
+ }
203
+ return found;
204
+ }
205
+ var INSTRUCTION = new RegExp(`(?:description=\\{?\\s*["'\`]|<CardDescription>\\s*)(?:${INSTRUCTION_VERBS.join("|")})\\b`, "g");
206
+ /** Subtítulo que começa com verbo de instrução (description e CardDescription). */
207
+ function checkInstruction(text) {
208
+ return [...text.matchAll(INSTRUCTION)].map((m) => ({
209
+ line: lineOf(text, m.index),
210
+ id: "texto-orientativo",
211
+ message: "Instrução no subtítulo. Use help (PageHeader, CardTitle ou FormSection): ícone de informação que abre um modal.",
212
+ src: firstLine(m[0])
213
+ }));
214
+ }
215
+ /** Todas as verificações de texto orientativo de uma tela, em ordem de linha. */
216
+ function checkGuidance(text) {
217
+ return [
218
+ ...checkFieldHelp(text),
219
+ ...checkSectionHelp(text),
220
+ ...checkPageDescription(text),
221
+ ...checkInstruction(text)
222
+ ].sort((a, b) => a.line - b.line);
223
+ }
224
+ //#endregion
225
+ //#region src/cli/var-prefix.ts
226
+ var RENDRA_VAR_EXACT = /* @__PURE__ */ new Set([
227
+ "primary",
228
+ "primary-foreground",
229
+ "primary-hover",
230
+ "primary-hover-foreground",
231
+ "secondary",
232
+ "secondary-foreground",
233
+ "secondary-hover",
234
+ "secondary-hover-foreground",
235
+ "primary-soft",
236
+ "primary-soft-foreground",
237
+ "primary-text",
238
+ "ring",
239
+ "accent",
240
+ "accent-foreground",
241
+ "background",
242
+ "background-image",
243
+ "foreground",
244
+ "card",
245
+ "card-foreground",
246
+ "popover",
247
+ "popover-foreground",
248
+ "muted",
249
+ "muted-foreground",
250
+ "border",
251
+ "input",
252
+ "field",
253
+ "overlay",
254
+ "destructive",
255
+ "destructive-hover",
256
+ "destructive-foreground",
257
+ "destructive-soft",
258
+ "destructive-soft-foreground",
259
+ "success",
260
+ "success-foreground",
261
+ "success-soft",
262
+ "success-soft-foreground",
263
+ "warning",
264
+ "warning-foreground",
265
+ "warning-soft",
266
+ "warning-soft-foreground",
267
+ "info",
268
+ "info-foreground",
269
+ "info-soft",
270
+ "info-soft-foreground",
271
+ "shadow-color",
272
+ "brand-font",
273
+ "radius",
274
+ "sidebar"
275
+ ]);
276
+ var RENDRA_VAR_PREFIXES = [
277
+ "sidebar-",
278
+ "gradient-",
279
+ "chart-",
280
+ "elevation-",
281
+ "shape-",
282
+ "meter-",
283
+ "label-",
284
+ "help-"
285
+ ];
286
+ /** Fonte da verdade: `name` (sem os `--`) é vocabulário próprio do tema do Rendra? */
287
+ function isRendraOwnVar(name) {
288
+ if (RENDRA_VAR_EXACT.has(name)) return true;
289
+ return RENDRA_VAR_PREFIXES.some((p) => name.startsWith(p));
290
+ }
291
+ /**
292
+ * Varre `var(--x)` do arquivo inteiro, pulando o bloco @theme / @theme inline (a ponte
293
+ * documentada entre o nome do Tailwind e o nome do Rendra, ex.:
294
+ * --color-primary: var(--rendra-primary)) e comentários.
295
+ */
296
+ function checkVarPrefix(text) {
297
+ const found = [];
298
+ const lines = text.split("\n");
299
+ let themeDepth = 0;
300
+ lines.forEach((line, i) => {
301
+ if (themeDepth === 0 && /^\s*@theme\b/.test(line)) {
302
+ themeDepth += (line.match(/\{/g)?.length ?? 0) - (line.match(/\}/g)?.length ?? 0);
303
+ return;
304
+ }
305
+ if (themeDepth > 0) {
306
+ themeDepth += (line.match(/\{/g)?.length ?? 0) - (line.match(/\}/g)?.length ?? 0);
307
+ if (themeDepth < 0) themeDepth = 0;
308
+ return;
309
+ }
310
+ if (/^\s*(\/\/|\*|\/\*)/.test(line)) return;
311
+ for (const m of line.matchAll(/var\(--([a-zA-Z][\w-]*)\)/g)) {
312
+ const name = m[1];
313
+ if (name.startsWith("rendra-")) continue;
314
+ if (!isRendraOwnVar(name)) continue;
315
+ found.push({
316
+ line: i + 1,
317
+ name,
318
+ message: `Variável própria do Rendra sem o prefixo --rendra-. Use var(--rendra-${name}).`,
319
+ src: line.trim()
320
+ });
321
+ }
322
+ });
323
+ return found;
324
+ }
325
+ //#endregion
326
+ //#region src/cli/regras-extras.ts
327
+ /** Identificadores das regras extras, na ordem em que a documentação as lista. */
328
+ var IDS_REGRAS_EXTRAS = [
329
+ "var-inline",
330
+ "svg-marca",
331
+ "classe-dinamica",
332
+ "texto-estreito",
333
+ "primaria-como-texto",
334
+ "componente-duplicado",
335
+ "css-fora-do-lugar",
336
+ "botao-solto",
337
+ "texto-orientativo",
338
+ "variavel-sem-prefixo-rendra"
339
+ ];
340
+ /** As cinco regras de uma linha só, na ordem em que eram verificadas. */
341
+ var REGRAS_POR_LINHA = [
342
+ {
343
+ id: "var-inline",
344
+ test: /var\(--(?:color|radius|shadow|font|spacing)-/g,
345
+ message: "Variáveis --color-*, --radius-* etc. do Tailwind são inline e não existem no CSS. Em JS use as do tema: var(--rendra-primary), var(--rendra-chart-1), var(--rendra-shape-control)."
346
+ },
347
+ {
348
+ id: "svg-marca",
349
+ test: /from\s+['"][^'"]*brand\/assets[^'"]*['"]/g,
350
+ message: "SVG de marca importado fora de src/brand. Leia o logotipo e o símbolo via useBrand()."
351
+ },
352
+ {
353
+ id: "classe-dinamica",
354
+ test: /(?<![\w-])(?:[a-z]+:)*-?(?:p[xytrbl]?|m[xytrbl]?|gap(?:-[xy])?|w|h|size|min-[wh]|max-[wh]|inset|top|left|right|bottom|z|text|bg|border(?:-[xytrbl])?|rounded(?:-[a-z]+)?|grid-cols|grid-rows|col-span|row-span|space-[xy]|translate-[xy]|opacity|font|leading|tracking|shadow|ring|fill|stroke|line-clamp|basis|order)-\$\{/g,
355
+ message: "Classe montada por template string não é gerada pelo Tailwind. Escreva a classe inteira (mapa de valores) ou use variável CSS.",
356
+ skipLine: (line) => /^\s*(\/\/|\*|\/\*)/.test(line)
357
+ },
358
+ {
359
+ id: "texto-estreito",
360
+ test: /<p\s[^>]*className=["'{][^>]*max-w-/g,
361
+ message: "Texto ocupa 100% da largura disponível. Limite de largura (max-w-*) em parágrafo só quando o layout pedir outra medida."
362
+ },
363
+ {
364
+ id: "primaria-como-texto",
365
+ test: /(?<![\w-])(?:[\w-]+:)*text-primary(?![\w-])/g,
366
+ message: "Primária como texto usa text-primary-text (tom que passa AA no claro e no escuro). text-primary é só para o preenchimento."
367
+ }
368
+ ];
369
+ /**
370
+ * Varre o texto linha a linha contra as cinco regras de uma linha (`var-inline`, `svg-marca`,
371
+ * `classe-dinamica`, `texto-estreito`, `primaria-como-texto`). Por linha, as regras saem na
372
+ * ordem da lista acima.
373
+ */
374
+ function regrasPorLinha(text) {
375
+ const found = [];
376
+ text.split("\n").forEach((line, i) => {
377
+ for (const regra of REGRAS_POR_LINHA) {
378
+ if (regra.skipLine?.(line)) continue;
379
+ regra.test.lastIndex = 0;
380
+ if (regra.test.test(line)) found.push({
381
+ line: i + 1,
382
+ rule: regra.id,
383
+ message: regra.message,
384
+ source: line.trim()
385
+ });
386
+ }
387
+ });
388
+ return found;
389
+ }
390
+ var NOME_PROIBIDO = /(Mobile|Simples|Simple|ComBusca|Grande|Pequeno|Small|Large)\.(t|j)sx?$/i;
391
+ var NOME_PROIBIDO_KEBAB = /-(mobile|simples|simple|com-busca|grande|pequeno)\.(t|j)sx?$/i;
392
+ /** `componente-duplicado`: pelo NOME do arquivo (variante mobile ou paralela). Linha 0. */
393
+ function componenteDuplicado(caminho) {
394
+ if (!NOME_PROIBIDO.test(caminho) && !NOME_PROIBIDO_KEBAB.test(caminho)) return void 0;
395
+ return {
396
+ line: 0,
397
+ rule: "componente-duplicado",
398
+ message: "Arquivo de variante paralela ou mobile. Resolva com props no componente único.",
399
+ source: ""
400
+ };
401
+ }
402
+ /** `css-fora-do-lugar`: quem chama já decidiu que o `.css` está fora das pastas de marca. */
403
+ function cssForaDoLugar() {
404
+ return {
405
+ line: 0,
406
+ rule: "css-fora-do-lugar",
407
+ message: "CSS fora de src/styles e src/brand. Estilo de componente é classe do Tailwind; token vai em globals.css; marca, em theme.css.",
408
+ source: ""
409
+ };
410
+ }
411
+ var BOTAO_SOLTO = /<CardContent\b[^>]*>\s*(?:<Stack\b[^>]*>\s*)?<Button\b|<Button\b[^>]*\bself-(?:start|end|center)\b/g;
412
+ /** `botao-solto`: olha o arquivo inteiro (JSX em várias linhas); só nas telas do sistema. */
413
+ function botaoSolto(text) {
414
+ return [...text.matchAll(BOTAO_SOLTO)].map((m) => ({
415
+ line: text.slice(0, m.index).split("\n").length,
416
+ rule: "botao-solto",
417
+ message: "Botão solto no conteúdo. Ação de card vai em CardHeader actions; da tela, no PageHeader actions ou na ActionBar.",
418
+ source: m[0].split("\n")[0].trim()
419
+ }));
420
+ }
421
+ /** `texto-orientativo` (regra C7, em help-length.ts), no formato de `AuditViolation`. */
422
+ function textoOrientativo(text) {
423
+ return checkGuidance(text).map((p) => ({
424
+ line: p.line,
425
+ rule: p.id,
426
+ message: p.message,
427
+ source: p.src
428
+ }));
429
+ }
430
+ /** `variavel-sem-prefixo-rendra` (em var-prefix.ts), no formato de `AuditViolation`. */
431
+ function variavelSemPrefixo(text) {
432
+ return checkVarPrefix(text).map((p) => ({
433
+ line: p.line,
434
+ rule: "variavel-sem-prefixo-rendra",
435
+ message: p.message,
436
+ source: p.src
437
+ }));
438
+ }
439
+ //#endregion
440
+ //#region src/cli/auditar-tudo.ts
441
+ /** Os 17 identificadores de regra, na ordem da documentação (genéricas e depois extras). */
442
+ var IDS_TODAS_AS_REGRAS = [...IDS_REGRAS_GENERICAS, ...IDS_REGRAS_EXTRAS];
443
+ /** Padrões do boilerplate Rendra, usados quando o projeto não diz outra coisa. */
444
+ var PASTAS_MARCA_PADRAO = ["src/brand", "src/styles"];
445
+ var PASTAS_TELAS_PADRAO = ["src/pages/app"];
446
+ var PASTAS_MARCA_REPOSITORIO = [...PASTAS_MARCA_PADRAO, "src/cli"];
447
+ /** Arquivos do modo `repositorio`: tudo sob `src`, sem pular nada, na ordem do sistema. */
448
+ function arquivosDoRepositorio(cwd) {
449
+ const src = join(cwd, "src");
450
+ if (!existsSync(src)) return [];
451
+ return percorrer(src, () => false).filter((f) => EXTENSAO_VARRIDA.test(f));
452
+ }
453
+ /**
454
+ * Varre `cwd` com as 17 regras. Não lê `rendra.config.json` (quem lê é `executarAuditar`),
455
+ * não imprime e não lança.
456
+ */
457
+ function auditarTudo(cwd, opcoes = {}) {
458
+ const repositorio = opcoes.modo === "repositorio";
459
+ const marca = opcoes.pastasMarca ?? (repositorio ? PASTAS_MARCA_REPOSITORIO : PASTAS_MARCA_PADRAO);
460
+ const telas = opcoes.pastasTelas ?? PASTAS_TELAS_PADRAO;
461
+ const ligada = (id) => opcoes.regras?.[id] !== false;
462
+ const dentro = (arquivo, rel, pastas) => repositorio ? pastas.some((p) => arquivo.startsWith(join(cwd, p))) : casaPrefixo(rel, pastas);
463
+ const arquivos = repositorio ? arquivosDoRepositorio(cwd) : coletarArquivos(cwd, opcoes);
464
+ const problemas = [];
465
+ const emitir = (file, achados) => {
466
+ for (const a of achados) if (ligada(a.rule)) problemas.push({
467
+ file,
468
+ ...a
469
+ });
470
+ };
471
+ for (const arquivo of arquivos) {
472
+ const rel = relativoA(cwd, arquivo);
473
+ const duplicado = componenteDuplicado(repositorio ? arquivo : rel);
474
+ if (duplicado) emitir(rel, [duplicado]);
475
+ const texto = readFileSync(arquivo, "utf8");
476
+ emitir(rel, variavelSemPrefixo(texto));
477
+ if (dentro(arquivo, rel, marca)) continue;
478
+ if (arquivo.endsWith(".css")) {
479
+ emitir(rel, [cssForaDoLugar()]);
480
+ continue;
481
+ }
482
+ if (dentro(arquivo, rel, telas)) {
483
+ emitir(rel, textoOrientativo(texto));
484
+ emitir(rel, botaoSolto(texto));
485
+ }
486
+ emitir(rel, auditLines(texto));
487
+ emitir(rel, regrasPorLinha(texto));
488
+ }
489
+ if (!repositorio) problemas.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line);
490
+ return {
491
+ violacoes: problemas,
492
+ arquivos: arquivos.length
493
+ };
494
+ }
495
+ //#endregion
496
+ //#region src/cli/argumentos.ts
497
+ /** As flags que `rendra auditar` entende. */
498
+ var FLAGS_AUDITAR = [
499
+ "--all",
500
+ "--ignore",
501
+ "--help"
502
+ ];
503
+ var MENSAGEM_SEM_VALOR = "Falta o valor de --ignore. Use --ignore a,b, --ignore a --ignore b ou --ignore=a,b.";
504
+ var itens = (valor) => valor.split(",").map((s) => s.trim()).filter((s) => s !== "");
505
+ /** Lê os argumentos que vêm depois de `auditar`. Erro volta como `{ erro }`, nunca lança. */
506
+ function lerArgumentosAuditar(rest) {
507
+ const lido = {
508
+ pastas: [],
509
+ ignorar: [],
510
+ todas: false,
511
+ ajuda: false
512
+ };
513
+ for (let i = 0; i < rest.length; i++) {
514
+ const arg = rest[i];
515
+ if (arg === "--all") lido.todas = true;
516
+ else if (arg === "--help" || arg === "-h") lido.ajuda = true;
517
+ else if (arg === "--ignore" || arg.startsWith("--ignore=")) {
518
+ let bruto;
519
+ if (arg === "--ignore") {
520
+ const proximo = rest[i + 1];
521
+ if (proximo !== void 0 && !proximo.startsWith("-")) {
522
+ bruto = proximo;
523
+ i++;
524
+ }
525
+ } else bruto = arg.slice(9);
526
+ const valores = bruto === void 0 ? [] : itens(bruto);
527
+ if (valores.length === 0) return { erro: MENSAGEM_SEM_VALOR };
528
+ lido.ignorar.push(...valores);
529
+ } else if (arg.startsWith("-")) return { erro: `Flag desconhecida: ${arg}. Flags aceitas: ${FLAGS_AUDITAR.join(", ")}.` };
530
+ else lido.pastas.push(arg);
531
+ }
532
+ return lido;
533
+ }
534
+ //#endregion
535
+ //#region src/cli/config.ts
536
+ var NOME_ARQUIVO_CONFIG = "rendra.config.json";
537
+ var CHAVES_CONFIG = [
538
+ "pastas",
539
+ "ignorar",
540
+ "regras",
541
+ "pastasMarca",
542
+ "pastasTelas"
543
+ ];
544
+ var CHAVES_LISTA = [
545
+ "pastas",
546
+ "ignorar",
547
+ "pastasMarca",
548
+ "pastasTelas"
549
+ ];
550
+ var ehObjeto = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
551
+ var erro = (mensagem) => ({ erro: `${NOME_ARQUIVO_CONFIG}: ${mensagem}` });
552
+ /** Lê e valida `rendra.config.json` em `base`. Ausente devolve `{ config: {} }`. */
553
+ function carregarConfig(base) {
554
+ const caminho = join(base, NOME_ARQUIVO_CONFIG);
555
+ let texto;
556
+ try {
557
+ texto = readFileSync(caminho, "utf8");
558
+ } catch (e) {
559
+ if (e.code === "ENOENT") return { config: {} };
560
+ return erro(`não consegui ler o arquivo (${e.message}).`);
561
+ }
562
+ let dados;
563
+ try {
564
+ dados = JSON.parse(texto.charCodeAt(0) === 65279 ? texto.slice(1) : texto);
565
+ } catch (e) {
566
+ return erro(`JSON inválido (${e.message}).`);
567
+ }
568
+ if (!ehObjeto(dados)) return erro("o conteúdo precisa ser um objeto JSON ({ ... }).");
569
+ for (const chave of Object.keys(dados)) if (!CHAVES_CONFIG.includes(chave)) return erro(`chave desconhecida "${chave}". Chaves aceitas: ${CHAVES_CONFIG.join(", ")}.`);
570
+ const config = {};
571
+ for (const chave of CHAVES_LISTA) {
572
+ const valor = dados[chave];
573
+ if (valor === void 0) continue;
574
+ if (!Array.isArray(valor) || !valor.every((x) => typeof x === "string")) return erro(`"${chave}" precisa ser uma lista de textos.`);
575
+ config[chave] = valor;
576
+ }
577
+ if (dados.regras !== void 0) {
578
+ if (!ehObjeto(dados.regras)) return erro("\"regras\" precisa ser um objeto ({ \"<id>\": false }).");
579
+ const regras = {};
580
+ for (const [id, ligada] of Object.entries(dados.regras)) {
581
+ if (!IDS_TODAS_AS_REGRAS.includes(id)) return erro(`regra desconhecida "${id}". Regras válidas: ${IDS_TODAS_AS_REGRAS.join(", ")}.`);
582
+ if (typeof ligada !== "boolean") return erro(`"regras.${id}" precisa ser true ou false.`);
583
+ regras[id] = ligada;
584
+ }
585
+ config.regras = regras;
586
+ }
587
+ return { config };
588
+ }
589
+ //#endregion
590
+ //#region src/cli/executar-auditar.ts
591
+ /**
592
+ * Pasta relativa à base, com `/`, ou `undefined` quando resolve fora dela. Pasta absoluta
593
+ * dentro da base vale; a própria base vira `.`.
594
+ */
595
+ function pastaNaBase(base, pasta) {
596
+ const rel = relative(base, resolve(base, pasta));
597
+ if (rel === ".." || rel.startsWith(".." + sep) || isAbsolute(rel)) return void 0;
598
+ return rel === "" ? "." : rel.split(sep).join("/");
599
+ }
600
+ /** Roda `rendra auditar` com os argumentos de `rest` (o que vem depois de `auditar`). */
601
+ function executarAuditar(rest, cwdProcesso) {
602
+ const args = lerArgumentosAuditar(rest);
603
+ if ("erro" in args) return { erro: args.erro };
604
+ if (args.ajuda) return { ajuda: formatHelp() };
605
+ const base = args.pastas.length === 1 ? resolve(cwdProcesso, args.pastas[0]) : cwdProcesso;
606
+ const lida = carregarConfig(base);
607
+ if ("erro" in lida) return { erro: lida.erro };
608
+ const config = lida.config;
609
+ const pedidas = args.pastas.length >= 2 ? args.pastas : config.pastas ?? [];
610
+ const pastas = [];
611
+ for (const pasta of pedidas) {
612
+ const dentro = pastaNaBase(base, pasta);
613
+ if (dentro === void 0) return { erro: `A pasta "${pasta}" fica fora de ${base}. Use só pastas dentro do projeto.` };
614
+ pastas.push(dentro);
615
+ }
616
+ const opcoes = {
617
+ ...pastas.length > 0 ? { pastas } : {},
618
+ ignorar: args.ignorar.length > 0 ? args.ignorar : config.ignorar ?? [],
619
+ ...config.regras ? { regras: config.regras } : {}
620
+ };
621
+ if (args.todas) {
622
+ const { violacoes } = auditarTudo(base, {
623
+ ...opcoes,
624
+ ...config.pastasMarca ? { pastasMarca: config.pastasMarca } : {},
625
+ ...config.pastasTelas ? { pastasTelas: config.pastasTelas } : {}
626
+ });
627
+ return { violacoes };
628
+ }
629
+ return { violacoes: auditar(base, opcoes) };
630
+ }
631
+ //#endregion
632
+ export { executarAuditar };
633
+
634
+ //# sourceMappingURL=executar-auditar.js.map