From 2cb4b2f97535c534c7667d26bba87bf4047135f0 Mon Sep 17 00:00:00 2001 From: leonardo-toniolo Date: Thu, 11 Jun 2026 11:51:47 -0300 Subject: [PATCH] log de saida --- README.md | 35 +++++++++++++- src/index.ts | 19 ++++++++ src/log/run-log.ts | 118 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/log/run-log.ts diff --git a/README.md b/README.md index f8f7bae..fd984a9 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,42 @@ Campos **nullable** permitem limpar o valor escolhendo "Limpar campo (null)" ou O app também tenta carregar `../clarizen-lib/.env` se não houver `.env` local. +## Log de execução + +Após cada atualização concluída, o app gera `run-log-.json` na pasta do projeto com o registro completo da execução: + +- data/hora e duração +- usuário autenticado +- tipo de entidade, filtro, campo e valor aplicado +- resumo (total, atualizados, erros) +- lista de cada objeto com `status: "updated"` ou `status: "error"` + +Exemplo resumido: + +```json +{ + "executedAt": "2026-06-09T15:30:00.000Z", + "durationMs": 45230, + "userId": "/User/abc123", + "operation": { + "typeName": "Task", + "filter": "Todos", + "fieldName": "C_MeuCampo", + "value": true + }, + "summary": { "total": 10, "updated": 9, "errors": 1 }, + "results": [ + { "id": "/Task/...", "displayName": "Tarefa 1", "status": "updated" }, + { "id": "/Task/...", "displayName": "Tarefa 2", "status": "error", "message": "..." } + ] +} +``` + +O caminho do arquivo é exibido no terminal ao final: `Log da execução: ...` + ## Erros -Objetos que falharem na atualização são listados ao final. Um arquivo `errors-.json` é gerado na pasta do projeto com os detalhes. +Objetos que falharem na atualização também aparecem no `run-log-*.json`. Além disso, um arquivo `errors-.json` é gerado quando há falhas, contendo só os erros para reprocessamento. ## Observações diff --git a/src/index.ts b/src/index.ts index ef074ea..f99b5e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,7 @@ import { import { formatFilterSummary } from "./query/filter.js"; import { fetchAllEntities } from "./query/fetch-all.js"; import { promptEntityFilter } from "./query/prompt-filter.js"; +import { buildRunLog, writeRunLog } from "./log/run-log.js"; import { bulkUpdateField } from "./update/bulk-field.js"; import type { FieldDescription, JsonValue } from "@arco/clarizen-lib"; @@ -148,6 +149,8 @@ Resumo da operação: console.log("\nAtualizando...\n"); + const startedAt = Date.now(); + const result = await bulkUpdateField( clarizen, entityChoice.typeName, @@ -156,6 +159,20 @@ Resumo da operação: parsedValue, ); + const logPath = writeRunLog( + buildRunLog({ + startedAt, + userId: auth.userId, + entityChoice, + entityFilter, + field, + fieldName, + parsedValue, + records, + result, + }), + ); + if (result.errors.length > 0) { const errorFile = resolve( process.cwd(), @@ -165,6 +182,8 @@ Resumo da operação: console.log(`\n${result.errors.length} erro(s) — detalhes em: ${errorFile}`); } + console.log(`\nLog da execução: ${logPath}`); + outro( `Concluído: ${result.updated.toLocaleString("pt-BR")} atualizado(s), ${result.errors.length.toLocaleString("pt-BR")} erro(s).`, ); diff --git a/src/log/run-log.ts b/src/log/run-log.ts new file mode 100644 index 0000000..d9857bf --- /dev/null +++ b/src/log/run-log.ts @@ -0,0 +1,118 @@ +import { writeFileSync } from "node:fs"; +import { resolve } from "node:path"; +import type { FieldDescription, JsonValue } from "@arco/clarizen-lib"; +import type { EntityChoice } from "../metadata/picker.js"; +import { formatFilterSummary, type EntityFilter } from "../query/filter.js"; +import type { EntityRecord } from "../query/fetch-all.js"; +import type { BulkUpdateResult } from "../update/bulk-field.js"; + +export interface RunLogEntry { + id: string; + displayName?: string; + status: "updated" | "error"; + message?: string; + errorCode?: string; +} + +export interface RunLog { + executedAt: string; + durationMs: number; + userId: string; + operation: { + typeName: string; + typeLabel?: string; + filter: string; + fieldName: string; + fieldLabel?: string; + fieldType?: string; + value: JsonValue; + }; + summary: { + total: number; + updated: number; + errors: number; + }; + results: RunLogEntry[]; +} + +export interface BuildRunLogParams { + startedAt: number; + userId: string; + entityChoice: EntityChoice; + entityFilter: EntityFilter; + field: FieldDescription; + fieldName: string; + parsedValue: JsonValue; + records: EntityRecord[]; + result: BulkUpdateResult; +} + +function logTimestamp(): string { + return new Date().toISOString().replace(/[:.]/g, "-"); +} + +export function buildRunLog(params: BuildRunLogParams): RunLog { + const { + startedAt, + userId, + entityChoice, + entityFilter, + field, + fieldName, + parsedValue, + records, + result, + } = params; + + const errorsById = new Map( + result.errors.map((e) => [String(e.id), e] as const), + ); + + const results: RunLogEntry[] = records.map((record) => { + const id = String(record.id); + const error = errorsById.get(id); + + if (error) { + return { + id, + displayName: record.displayName, + status: "error", + message: error.message, + errorCode: error.errorCode, + }; + } + + return { + id, + displayName: record.displayName, + status: "updated", + }; + }); + + return { + executedAt: new Date().toISOString(), + durationMs: Date.now() - startedAt, + userId, + operation: { + typeName: entityChoice.typeName, + typeLabel: entityChoice.label, + filter: formatFilterSummary(entityFilter), + fieldName, + fieldLabel: field.label, + fieldType: field.type, + value: parsedValue, + }, + summary: { + total: records.length, + updated: result.updated, + errors: result.errors.length, + }, + results, + }; +} + +export function writeRunLog(log: RunLog, cwd = process.cwd()): string { + const filePath = resolve(cwd, `run-log-${logTimestamp()}.json`); + writeFileSync(filePath, JSON.stringify(log, null, 2), "utf-8"); + return filePath; +}