@ksmv/ui-checks
v0.21.1
Published
Static and computed quality checks for semantic UI token contracts.
Maintainers
Readme
@ksmv/ui-checks
Verificações de qualidade para o contrato semântico --ui-* de @ksmv/ui-react: completude dos temas, contraste dos pares do contrato (ações, estados, campos, controles e, desde o contrato de contraste 3.0.0, o texto base sobre as superfícies e, desde o 4.0.0, a cor de link e, desde o 5.0.0, a borda das ações e, desde o 6.0.0, as variantes de ação da 0.17.0 e o botão de texto e, desde o 7.0.0, os botões da casa da 0.18.1 e o hover por tom do botão de texto e, desde o 7.1.0, o texto de hover dos botões suaves e do contorno de aviso e, desde o 9.0.0, o texto, o limite de controle, o link, o botão de texto e o fantasma sobre a superfície inversa, a paleta categórica, a descrição do diálogo, o giro contra a superfície, o aviso e o rótulo de seção, o item de menu e o cartão de rádio, o cabeçalho da tabela e o grupo de alternância, e o selo cheio, a bolinha sobre a superfície inversa e a barra e, desde o 10.0.0, o fundo do botão de ícone contornado, a cor da ajuda e a caixa de seleção), prefixo dos tokens, cores literais fora dos arquivos de tema e evidência calculada vinculada às fontes.
Antes de depender deste pacote
Os pacotes são publicados no registro público do npm para que as aplicações do próprio mantenedor os instalem sem autenticação. O repositório de origem é privado. Não há equipe de suporte, canal público de issues nem compromisso de atendimento a terceiros. Este README é a documentação completa para quem está de fora.
Vulnerabilidades não devem ser publicadas em nenhum canal público. Quem não tem acesso ao repositório relata pelo suporte do próprio npm, que encaminha relatos sobre pacotes publicados: https://www.npmjs.com/support
Instalação e execução
npm install --save-dev @ksmv/ui-checks
npx ksmv-ui-checks ksmv-ui.config.jsRequer Node.js 22.14 ou superior. npx ksmv-ui-checks --help mostra os comandos; sem argumento, a configuração lida é ksmv-ui.config.js no diretório atual. --json troca o relatório legível por JSON.
Configuração
// ksmv-ui.config.js
import { defineConfig } from "@ksmv/ui-checks/config";
export default defineConfig({
contractSource: "@ksmv/ui-react",
root: ".",
include: ["src/**/*.{css,tsx}"],
themes: [
{ name: "light", files: ["src/theme.css"], selector: '[data-theme="light"]' },
{ name: "dark", files: ["src/theme.css"], selector: '[data-theme="dark"]' },
],
baseline: ".ksmv-ui-baseline.json",
failOnUncovered: true,
});contractSource é obrigatório e explícito:
"@ksmv/ui-react": use quando a aplicação importa o pacote React. O verificador resolve os contratos da versão realmente instalada, então uma atualização do pacote é verificada contra os contratos novos;"bundled": usa os contratos embutidos neste pacote, apenas para análises que não usam o pacote React, e falha fechado se encontrar essa importação.
O valor de selector deve ser textualmente idêntico ao seletor escrito no CSS, inclusive nas aspas. O verificador não tenta provar que grafias diferentes são equivalentes.
include precisa encontrar arquivos: nenhum arquivo analisado é falha, não sucesso. exclude e suppressions são opcionais.
Baseline
baseline é opcional. Quando configurado, o arquivo precisa existir. Crie-o somente depois de revisar a análise completa:
npx ksmv-ui-checks baseline ksmv-ui.config.js --write --first-seen-version=0.1.2 --owner-role=frontend --reduction-target=2099-12-31O arquivo guarda fingerprints, regra, papel responsável e meta de redução, sem copiar o trecho encontrado. Uma atualização que aumente a dívida exige --allow-growth e uma justificativa em arquivo via --reason-file. Erro operacional e cobertura obrigatória ausente nunca entram na baseline.
O fingerprint segue o conteúdo do achado, como seletor, propriedade e valor, e a ordem entre achados idênticos no mesmo arquivo, não a linha. Linhas inseridas ou removidas em outro ponto do arquivo não transformam dívida conhecida em achado novo; mudar o próprio trecho, sim. Baselines gravadas pela 0.1.4 ou anterior usavam a posição e precisam ser regeneradas uma vez com a 0.1.5.
Códigos de saída
0: análise completa, sem violações novas;1: existe violação nova ou regressão em relação à baseline;2: configuração inválida, leitura ou operação falhou, nenhum arquivo encontrado, ou há cobertura obrigatória não resolvida.
Trate 2 como falha do pipeline, não como violação comum.
Evidência calculada
Seletores disjuntos, como [data-theme="light"] e [data-theme="dark"], são verificados estaticamente e dispensam evidência. Quando a cascata de um tema legado se sobrepõe, como .theme e .theme.active, só o navegador sabe o valor final de cada token. Nesse caso o tema declara computedEvidence:
{
name: "legacy",
files: ["src/theme.css"],
selector: ".theme.active",
computedEvidence: { file: "artifacts/theme-evidence.json", theme: "legacy" },
}A evidência não é uma exceção manual. O arquivo é recusado, com código 2, quando:
- o arquivo não existe, não pode ser lido, não é JSON válido ou não tem a estrutura da receita abaixo;
capturedAtnão é um horário UTC ou está no futuro;- os seletores diferem dos configurados, ou o tema nomeado em
computedEvidence.themenão está no arquivo ou foi capturado para outro seletor; - 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);
- a identidade de um dos contratos diverge da instalada, ou seja, o pacote React mudou depois da captura;
- falta algum token exigido, ou algum valor ainda contém
var(...).
O erro operacional diz qual desses motivos ocorreu e, quando há, o remédio. Por exemplo, depois de alterar um arquivo de tema sem capturar de novo:
config Invalid computed theme evidence in artifacts/theme-evidence.json: the theme source hash differs, so a theme file changed after the capture. Capture the evidence again.Depois de atualizar o pacote React, a mensagem traz a versão do contrato da captura e a do contrato instalado; quando faltam tokens ou resta var(...), traz os nomes dos tokens, em ordem, até dez, e a contagem dos demais. A mensagem não repete valores do produto: aparecem só o caminho da evidência e o nome do tema como estão na configuração, nomes de tokens e versões de contrato. Falha ao resolver os contratos também é relatada com a própria causa (Unable to resolve the configured contract source., Unsupported theme contract. ou Unsupported contrast manifest.).
Tokens opcionais, os derivados e os de marca, podem ficar vazios na captura: é assim que o navegador informa um token que o tema não declarou. Um derivado vazio assume o valor do token de origem, que é o padrão documentado no contrato. Quando a origem também é um derivado, como no texto do hover do contorno de aviso (--ui-action-warning-outline-text-hover deriva de --ui-action-warning-outline-text, que deriva do contorno de perigo), a cadeia é seguida até um token declarado. Um derivado que é ponta de um par de contraste, como --ui-control-checked-bg, também pode ficar vazio; a captura precisa então do token em que a cadeia dele termina. Sem evidência, tokens/contrast segue a mesma cadeia: um derivado que o tema não declara é avaliado pelo valor da origem, ou pelo padrão literal do contrato (transparent, uma cor). Um par com over mede um fundo translúcido composto sobre aquela superfície; sem over, um fundo translúcido continua cobertura pendente.
Desde o contrato de tema 3.0.0, a forma é da biblioteca: a regra themes/no-shape aponta, como violação, cada token da lista shape do contrato que um seletor de tema declarar. Quem migra de um tema que declarava forma pode carregar essas violações na baseline enquanto tira as linhas.
A captura usa a API pública deste pacote e um navegador à sua escolha. Com Playwright, e a aplicação servida localmente:
// capture-theme-evidence.mjs
import { mkdir, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";
import { computeThemeSourceHash, resolveContractSet } from "@ksmv/ui-checks";
import { chromium } from "playwright";
// Tudo aqui espelha ksmv-ui.config.js: o mesmo `root`, o mesmo
// `computedEvidence.file`, os mesmos arquivos de tema, e os mesmos nomes de
// `computedEvidence.theme` com seus seletores, cada um com uma URL em que o
// seletor está aplicado. O verificador resolve os caminhos a partir de `root`.
const root = resolve(".");
const evidenceFile = "artifacts/theme-evidence.json";
const themes = {
legacy: { selector: ".theme.active", url: "http://localhost:4173/" },
};
const files = ["src/theme.css"];
const contracts = await resolveContractSet({ root, source: "@ksmv/ui-react" });
const tokens = [...new Set([
...contracts.theme.required,
...Object.keys(contracts.theme.derived),
...Object.values(contracts.theme.derived),
...contracts.theme.brandComposition,
...contracts.contrast.pairs.flatMap(({ background, foreground }) => [background, foreground]),
])];
const browser = await chromium.launch();
const captured = {};
for (const [name, { selector, url }] of Object.entries(themes)) {
const page = await browser.newPage();
await page.goto(url);
const values = await page.evaluate(({ selector, tokens }) => {
const style = getComputedStyle(document.querySelector(selector));
return Object.fromEntries(tokens.map((token) => [token, style.getPropertyValue(token).trim()]));
}, { selector, tokens });
captured[name] = { selector, tokens: values };
await page.close();
}
await browser.close();
const target = resolve(root, evidenceFile);
await mkdir(dirname(target), { recursive: true });
await writeFile(target, `${JSON.stringify({
capturedAt: new Date().toISOString(),
contracts: { contrast: contracts.identities.contrast, theme: contracts.identities.theme },
schemaVersion: 1,
selectors: [...new Set(Object.values(themes).map(({ selector }) => selector))],
sourceHash: await computeThemeSourceHash(root, files),
themes: captured,
}, null, 2)}\n`);Capture de novo sempre que um arquivo de tema mudar ou o pacote React for atualizado; a verificação recusa a evidência antiga, que é exatamente o objetivo.
Ajustes em componentes
A regra components/override confere como o produto ajusta os componentes da biblioteca. Ela só roda quando a configuração tem o bloco components:
components: {
include: ["src/**/*.tsx"],
exclude: ["src/**/*.test.tsx"],
sources: ["@ksmv/ui-react", "@/ui"],
classHelpers: ["tw"],
}- Livre: o que encaixa o componente no pai — margem, largura, item de flex/grid (
flex-1,self-*,col-span-*…), posição,z-*, visibilidade (hidden,sr-only) e alinhamento do texto. Nostyle, as propriedades equivalentes. - Exige
data-ui-override="motivo"no mesmo elemento: aparência (cor, fundo, borda, raio, sombra, fonte, altura, padding,gap-*,overflow-*), arranjo interno (flex,grid-cols-*,justify-*,items-*,block) e qualquer classe fora do catálogo, inclusive classes próprias do produto. Também exigem a marcaclassNameoustyleque a regra não consegue ler e props repassadas depois declassNameoustyle. - Relatório: sem marca, é uma violação (saída 1), que pode entrar em baseline durante a migração. Com marca, o elemento aparece em
overrides, com o motivo. - Arquivos: os do
components.includerecebem só esta regra. Um arquivo que também está noincludeprincipal recebe as duas coisas, e aí a cobertura dinâmica volta a valer nele. - O que a regra não vê: um componente do produto que repassa props para um componente da biblioteca, e CSS do produto que mira as classes internas
.ui-*; props repassadas sozinhas ou antes declassName/style(<Button {...props} />); componente usado como valor (const X = Button; <X />); arquivos que não compilam — aparecem como erro operacional de parse (saída 2) e não são analisados pela regra.
Detalhes
sourcessubstitui o padrão: mantenha@ksmv/ui-reactna lista ao acrescentar um barril; a comparação é pelo especificador exato do import (subcaminhos e imports relativos precisam de entrada própria).classHelperssoma aos padrões (cn,clsx,cx,classnames,classNames,twMerge,twJoin,cva) e entende chamadas no estilo clsx; decva(...)só os textos literais são lidos. Template marcado (tw\...`) e funções de variante geradas porcva` contam como ilegíveis.undefined,null,true,falsee0dentro de uma expressão de classe não contribuem classe.overrides[]tempath,line,column,component,reasoneclasses;classeslista as classes visuais autorizadas, as propriedades destylecomostyle:<propriedade>e os tipos de achado (dynamic-class,dynamic-style,spread-after).- componente do produto com
forwardRefque faz<Button className={cn("mt-2", className)} {...props} />: geradynamic-classespread-after; passe o spread antes doclassNamee marque o elemento com o motivo ("repassa className do chamador"), ou use a baseline durante a migração. - o padrão responsivo
hidden md:inline-flexexige marca (inline-flexé arranjo interno). - um
components.includeque não casa nenhum arquivo é erro operacional (saída 2).
API programática
O ponto de entrada @ksmv/ui-checks exporta runChecks, as regras, resolveContractSet, computeThemeSourceHash, loadComputedEvidence, as funções de baseline e os formatadores de relatório, com os tipos TypeScript correspondentes. Os contratos embutidos estão em @ksmv/ui-checks/theme-contract.json e @ksmv/ui-checks/contrast-contract.json.
Licença
Apache-2.0.
