@ksmv/ui-checks 0.3.0 → 0.3.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 - 2026-10-01
4
+
5
+ - **O hash das fontes do tema deixa de depender do fim de linha do checkout.** Num repositório sem regra de fim de linha, o mesmo commit sai com CRLF no Windows e com LF no Linux. Na 0.3.1 e anteriores o hash cobria os bytes do arquivo, então a evidência capturada num sistema era recusada no outro, com código 2: verde na máquina de quem capturou e vermelho no CI. `CRLF` passa a contar como `LF`; qualquer outra diferença continua mudando o hash.
6
+ - **Quem tem fontes de tema em CRLF precisa capturar a evidência de novo uma vez**, porque o hash delas mudou. Evidência de fontes em LF continua válida.
7
+
8
+ ## 0.3.1 - 2026-09-30
9
+
10
+ - **O CLI volta a rodar quando é chamado por um link simbólico.** Na 0.3.0 e anteriores, pelo `npx`, por um script npm ou pelo `node_modules/.bin` no Linux e no macOS, e em qualquer sistema quando `node_modules` é um link ou uma junção, o comando não executava nada e saía com código `0`: uma verificação verde que não tinha acontecido. A detecção do ponto de entrada passa a comparar os caminhos reais. **Quem roda o verificador num CI Linux deve atualizar e conferir que o passo de fato imprime o relatório.**
11
+ - **Um `#` que não inicia um nome deixa de travar a análise.** Um arquivo TypeScript ou TSX com `#` dentro de uma expressão regular literal, como `/^#\/rota/`, fazia a varredura nunca terminar, até o processo esgotar a memória. A varredura agora sempre avança. Um `#` solto que não compila continua sendo erro operacional de parse (saída 2).
12
+
3
13
  ## 0.3.0 - 2026-09-30
4
14
 
5
15
  - **Regra `components/override`**, opt-in pelo bloco `components` da configuração. Num componente da biblioteca, ajuste de encaixe por `className` ou `style` (margem, largura, item de flex/grid, posição, visibilidade, alinhamento do texto) é livre; ajuste de aparência ou de arranjo interno exige `data-ui-override="motivo"` no mesmo elemento. Classes fora do catálogo contam como aparência. `className` ou `style` que o checker não consegue ler e props repassadas depois de `className`/`style` também exigem a marca.
package/README.md CHANGED
@@ -82,7 +82,7 @@ A evidência não é uma exceção manual. O arquivo é recusado, com código `2
82
82
 
83
83
  - `capturedAt` está no futuro;
84
84
  - os seletores diferem dos configurados;
85
- - o hash das fontes diverge, ou seja, o tema mudou depois da captura;
85
+ - o hash das fontes diverge, ou seja, o tema mudou depois da captura (o fim de linha não conta: o mesmo arquivo em CRLF e em LF tem o mesmo hash);
86
86
  - a identidade de um dos contratos diverge da instalada, ou seja, o pacote React mudou depois da captura;
87
87
  - falta algum token exigido, ou algum valor ainda contém `var(...)`.
88
88
 
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  export declare function main(argv?: string[]): Promise<import("./result.js").ExitCode>;
3
+ /**
4
+ * Whether this module is the script Node was started with. Compared by real
5
+ * path: through a symbolic link (npm's `.bin` on POSIX, a linked node_modules)
6
+ * `process.argv[1]` names the link while `import.meta.url` names the file, and
7
+ * comparing them as written made the CLI do nothing and exit 0.
8
+ */
9
+ export declare function isEntryPoint(scriptPath: string | undefined, moduleUrl: string): boolean;
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { realpathSync } from "node:fs";
2
3
  import { readFile } from "node:fs/promises";
3
4
  import { resolve } from "node:path";
4
- import { pathToFileURL } from "node:url";
5
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
6
  import { approveBaselineGrowth, baselineFor, validateBaseline, writeBaselineFile, } from "./baseline.js";
6
7
  import { applyConfiguredSuppressions } from "./governance.js";
7
8
  import { formatHumanReport, formatJsonReport, } from "./result.js";
@@ -169,9 +170,23 @@ export async function main(argv = process.argv.slice(2)) {
169
170
  return 2;
170
171
  }
171
172
  }
172
- const entryPoint = process.argv[1]
173
- ? pathToFileURL(resolve(process.argv[1])).href
174
- : undefined;
175
- if (entryPoint === import.meta.url) {
173
+ /**
174
+ * Whether this module is the script Node was started with. Compared by real
175
+ * path: through a symbolic link (npm's `.bin` on POSIX, a linked node_modules)
176
+ * `process.argv[1]` names the link while `import.meta.url` names the file, and
177
+ * comparing them as written made the CLI do nothing and exit 0.
178
+ */
179
+ export function isEntryPoint(scriptPath, moduleUrl) {
180
+ if (!scriptPath)
181
+ return false;
182
+ try {
183
+ return (realpathSync.native(resolve(scriptPath)) ===
184
+ realpathSync.native(fileURLToPath(moduleUrl)));
185
+ }
186
+ catch {
187
+ return false;
188
+ }
189
+ }
190
+ if (isEntryPoint(process.argv[1], import.meta.url)) {
176
191
  process.exitCode = await main();
177
192
  }
package/dist/evidence.js CHANGED
@@ -16,12 +16,23 @@ function normalizedFiles(root, files) {
16
16
  return { absolute, path: fromRoot };
17
17
  });
18
18
  }
19
+ /**
20
+ * The same commit is checked out with CRLF on one machine and LF on another
21
+ * when the repository has no end-of-line rule. The hash says whether the theme
22
+ * changed since the capture, and a line ending is not a change: CRLF counts as
23
+ * LF. A lone CR is left alone, and so is everything else.
24
+ */
25
+ function withLineFeeds(content) {
26
+ return content.includes("\r\n")
27
+ ? Buffer.from(content.toString("latin1").replaceAll("\r\n", "\n"), "latin1")
28
+ : content;
29
+ }
19
30
  export async function computeThemeSourceHash(root, files) {
20
31
  const hash = createHash("sha256");
21
32
  for (const file of normalizedFiles(root, files)) {
22
33
  hash.update(file.path);
23
34
  hash.update("\0");
24
- hash.update(await readFile(file.absolute));
35
+ hash.update(withLineFeeds(await readFile(file.absolute)));
25
36
  hash.update("\0");
26
37
  }
27
38
  return `sha256:${hash.digest("hex")}`;
package/dist/scan.js CHANGED
@@ -24,6 +24,19 @@ function scanTypeScript(content, path) {
24
24
  const uncovered = [];
25
25
  let kind = scanner.scan();
26
26
  while (kind !== SyntaxKind.EndOfFile) {
27
+ // This scan has no parser behind it, so a "#" that starts no name (the one
28
+ // in /#/, read here as a division) comes back as a zero-width token and
29
+ // the scanner stays where it was. Read it as the hash it is; if a token
30
+ // still has no width, step over one character so the scan always ends.
31
+ if (scanner.getTokenEnd() <= scanner.getTokenStart()) {
32
+ if (kind === SyntaxKind.PrivateIdentifier)
33
+ kind = scanner.reScanHashToken();
34
+ if (scanner.getTokenEnd() <= scanner.getTokenStart()) {
35
+ scanner.resetTokenState(scanner.getTokenStart() + 1);
36
+ kind = scanner.scan();
37
+ continue;
38
+ }
39
+ }
27
40
  tokens.push({
28
41
  end: scanner.getTokenEnd(),
29
42
  kind,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ksmv/ui-checks",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Static and computed quality checks for semantic UI token contracts.",
5
5
  "keywords": ["accessibility", "design-tokens", "quality", "static-analysis"],
6
6
  "license": "Apache-2.0",