~/blog/estatisticas-privadas-do-github-sem-vazar-dados

cat estatisticas-privadas-do-github-sem-vazar-dados.mdx

Mostrando estatísticas de repositórios privados sem vazar dados

3 min de leitura

Como exibo linguagens e contribuições de repos privados no portfólio sem publicar nome de projeto, sem expor token no navegador e sem depender de serviço de terceiro.

A maior parte do que eu escrevo em código está em repositórios privados de clientes. Um gráfico de linguagens que só conta repositórios públicos não diz quase nada sobre o que eu realmente faço no dia a dia.

Eu queria contar os privados. Sem expor nada deles.

As três regras que me impus

  1. Nenhum token chega ao navegador.
  2. Nenhum nome, descrição ou URL de repositório privado é publicado.
  3. Nenhum serviço de terceiro recebe meu token.

A terceira regra elimina os geradores de badge prontos. As duas primeiras eliminam qualquer chamada à API feita pelo cliente.

A forma do dado importa mais que o acesso

A decisão que resolve o problema não é sobre autenticação, é sobre o que fica salvo. Em vez de guardar uma lista de repositórios, eu guardo só o agregado:

{
  "summary": { "repositoryCount": 32, "stars": 1 },
  "activity": { "commits": 94, "pullRequests": 6, "issues": 0, "reviews": 0 },
  "languages": [
    { "name": "TypeScript", "color": "#3178c6", "bytes": 141815, "percentage": 62.71 }
  ]
}

Um repositório privado contribui com bytes para o total de uma linguagem e com +1 na contagem. Ele não aparece em lugar nenhum de forma identificável: nem o nome, nem quando foi atualizado, nem quantas estrelas tem. O número agregado não é reversível.

Onde o token vive

A coleta roda em um GitHub Action diário, nunca no navegador:

- name: Generate GitHub statistics
  run: node scripts/update-github-stats.mjs
  env:
    GITHUB_STATS_TOKEN: ${{ secrets.GH_STATS_TOKEN || github.token }}
    INCLUDE_PRIVATE: ${{ secrets.GH_STATS_TOKEN != '' }}

Duas coisas acontecem aqui que vale destacar.

A primeira é o fallback: se o secret GH_STATS_TOKEN não existe, o workflow usa o GITHUB_TOKEN temporário do Actions e produz apenas dados públicos. O site nunca quebra por falta de configuração; ele só fica menos completo.

A segunda é que INCLUDE_PRIVATE é derivado da existência do secret, não escrito à mão. Não existe estado em que eu peça dados privados sem ter credencial para isso.

Uma verificação que vale o esforço

A query GraphQL pede viewer { login } junto com os dados. Com isso o script confere se o token pertence mesmo ao usuário configurado:

if (includePrivate && data.viewer.login.toLowerCase() !== username.toLowerCase()) {
  throw new Error("GH_STATS_TOKEN não pertence ao usuário configurado.");
}

Parece paranoia até você trocar um token e passar a publicar silenciosamente a atividade de outra conta. Falhar o build é muito melhor que acertar por acidente.

O que o navegador recebe

Um arquivo JSON estático, commitado no repositório e servido junto com o site. O cliente faz um fetch e, se algo der errado, cai em um fallback compilado no bundle:

export function useGitHubStats() {
  const [stats, setStats] = useState<GitHubStats>(fallbackGitHubStats);
  // ...
}

Nenhuma chave, nenhuma chamada autenticada, nenhuma dependência de terceiro em tempo de execução. A página carrega igual se a API do GitHub estiver fora do ar.

O custo disso

O dado tem até 24 horas de atraso. Para um portfólio, isso é irrelevante, e é um preço pequeno por não ter segredo nenhum rodando no navegador de quem visita.

LER~/blog/estatisticas-privadas-do-github-sem-vazar-dados.mdxmdx · utf-8topo
cd ../blog