@mocoto/mahoraga 0.14.7 → 0.14.8
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/README.md +5 -13
- package/dist/analysts/js-ts/registrar.js +1 -1
- package/dist/core/config/auto/auto-fix-config.js +9 -4
- package/dist/core/config/config.js +1 -1
- package/dist/core/messages/shared/icons.js +3 -3
- package/dist/core/parsing/langs/json.js +2 -2
- package/dist/core/parsing/langs/typescript.js +6 -0
- package/dist/core/schema/version.js +1 -1
- package/dist/lib/api.js +14 -0
- package/dist/shared/data-processing/occurrences.js +3 -0
- package/dist/shared/formatters/code-min-formatter.js +1 -1
- package/dist/shared/formatters/formatters/commons.js +19 -5
- package/dist/shared/formatters/formatters/shell.js +128 -9
- package/dist/shared/helpers/ast-visitor-utils.js +3 -0
- package/dist/shared/helpers/imports.js +1 -1
- package/dist/shared/helpers/reader-report.js +13 -0
- package/dist/shared/helpers/structure.js +2 -2
- package/dist/types/guardian/result.js +38 -0
- package/dist/types/reports/processing.js +12 -0
- package/dist/types/shared/validation.js +111 -0
- package/dist/vulnerabilities/scanner.js +7 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -8,10 +8,7 @@ Proveniência e Autoria: Este documento integra o projeto @mocoto/mahoraga (lice
|
|
|
8
8
|
|
|
9
9
|
</div>
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<p> Desisti de desistir do projeto, até parece que vou aceitar perder pra vocês 🤪 </p>
|
|
14
|
-
<p> Caso tenha receio em ajudar com codigo você pode me ajudar ao usar e encontrar falsos postivos/negativos e me mandar relatorios por meio desse e-mail ai em baixo, me ajudaria muito, pois nem todos os erros e bugs eu consigo resolver em ambiente de desenvolvimento sendo preciso uso em produção para serem encontrados</p>
|
|
11
|
+
Caso tenha receio em ajudar com codigo você pode me ajudar ao usar e encontrar falsos postivos/negativos e me mandar relatorios por meio desse e-mail ai em baixo, me ajudaria muito, pois nem todos os erros e bugs eu consigo resolver em ambiente de desenvolvimento sendo preciso uso em produção para serem encontrados
|
|
15
12
|
|
|
16
13
|
[Gmail](mailto:mocoto.persona@gmail.com)
|
|
17
14
|
|
|
@@ -178,7 +175,6 @@ mahoraga atualizar --global # auto-update
|
|
|
178
175
|
|
|
179
176
|
### Relatórios Profissionais
|
|
180
177
|
|
|
181
|
-
- **Múltiplos formatos**: JSON, Markdown
|
|
182
178
|
- **Métricas detalhadas**: complexidade ciclomática, duplicação, cobertura, performance com histórico
|
|
183
179
|
- **Baseline de performance**: snapshots e comparação ao longo do tempo
|
|
184
180
|
- **Scan de licenças**: verificação de dependências e geração de THIRD-PARTY-NOTICES
|
|
@@ -187,8 +183,6 @@ mahoraga atualizar --global # auto-update
|
|
|
187
183
|
|
|
188
184
|
### Integrações CI/CD
|
|
189
185
|
|
|
190
|
-
- **GitHub Actions**: scan de workflows, score 0-100, check runs, quality gate
|
|
191
|
-
- **GitHub App**: análise automática em PRs e pushes
|
|
192
186
|
- **GitLab CI / CircleCI / Jenkins / Azure Pipelines**: análise de pipelines
|
|
193
187
|
- **Conversão** entre plataformas de CI/CD
|
|
194
188
|
|
|
@@ -252,9 +246,9 @@ O Mahoraga lê um arquivo `mahoraga.config.json` na raiz do seu projeto (ou vari
|
|
|
252
246
|
|----------|-----------|--------|
|
|
253
247
|
| `LOG_LEVEL` | Nível de log | `info` |
|
|
254
248
|
| `LOG_ESTRUTURADO` | Logs em JSON | `false` |
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
257
|
-
| `
|
|
249
|
+
| `MAHORAGA_DEBUG` | Modo debug | não definido |
|
|
250
|
+
| `MAHORAGA_STATE_DIR` | Diretório de estado | `.mahoraga` |
|
|
251
|
+
| `MAHORAGA_API_PORT` | Porta do GitHub App | `3100` |
|
|
258
252
|
| `GITHUB_APP_ID` | ID do GitHub App | não definido |
|
|
259
253
|
| `GITHUB_PRIVATE_KEY` | Chave RSA do GitHub App | não definido |
|
|
260
254
|
| `GITHUB_WEBHOOK_SECRET` | Webhook secret | não definido |
|
|
@@ -281,7 +275,7 @@ npm run lint # ESLint
|
|
|
281
275
|
| JavaScript | Babel | Nativo |
|
|
282
276
|
| TypeScript | Babel | Nativo |
|
|
283
277
|
| HTML | htmlparser2 | Nativo |
|
|
284
|
-
| CSS |
|
|
278
|
+
| CSS | postcss | Nativo |
|
|
285
279
|
| XML | fast-xml-parser | Nativo |
|
|
286
280
|
| Python | Heurístico | Nativo |
|
|
287
281
|
| PHP | Heurístico | Nativo |
|
|
@@ -336,5 +330,3 @@ Dependências de terceiros listadas em [THIRD-PARTY-NOTICES.txt](./THIRD-PARTY-N
|
|
|
336
330
|
---
|
|
337
331
|
|
|
338
332
|
**Feito para os iniciantes**
|
|
339
|
-
|
|
340
|
-
**_Estou testando otypescript 7.0.2 Até o momento parece muito bom. o problema é que tive que remover o eslint porque ele nao roda com ts 7, vou esperar eles atualizar e voltar o eslint para o projeto._**
|
|
@@ -35,6 +35,9 @@ export const AGRESSIVA_AUTO_CORRECAO_CONFIGURACAO = {
|
|
|
35
35
|
// Backwards compat constant name used elsewhere in the codebase
|
|
36
36
|
export const AUTO_CORRECAO_CONFIGURACAO_PADROES = PADRAO_AUTO_CORRECAO_CONFIGURACAO;
|
|
37
37
|
export function shouldExcludeFile(fileCaminho, config) {
|
|
38
|
+
if (!config.excludePadroes || !Array.isArray(config.excludePadroes)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
38
41
|
return config.excludePadroes.some(pattern => {
|
|
39
42
|
const regexPadrao = pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*').replace(/\?/g, '.');
|
|
40
43
|
return new RegExp(regexPadrao).test(fileCaminho);
|
|
@@ -48,7 +51,10 @@ export function shouldExcludeFunction(functionName, config) {
|
|
|
48
51
|
}
|
|
49
52
|
export function isCategoryAllowed(category, config) {
|
|
50
53
|
// allowedCategories is an array of known category strings - coerce to string[] for safe includes check
|
|
51
|
-
|
|
54
|
+
if (!config.allowedCategories || !Array.isArray(config.allowedCategories)) {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return config.allowedCategories.includes(category);
|
|
52
58
|
}
|
|
53
59
|
export function hasMinimumConfidence(confidence, config) {
|
|
54
60
|
if (typeof config.minConfidence !== 'number') {
|
|
@@ -63,9 +69,8 @@ export function getAutoFixConfig(mode) {
|
|
|
63
69
|
case 'aggressive':
|
|
64
70
|
return AGRESSIVA_AUTO_CORRECAO_CONFIGURACAO;
|
|
65
71
|
case 'balanced':
|
|
66
|
-
case undefined:
|
|
67
|
-
|
|
68
|
-
}
|
|
72
|
+
case undefined:
|
|
73
|
+
return PADRAO_AUTO_CORRECAO_CONFIGURACAO;
|
|
69
74
|
default:
|
|
70
75
|
return PADRAO_AUTO_CORRECAO_CONFIGURACAO;
|
|
71
76
|
}
|
|
@@ -110,7 +110,7 @@ export const configPadrao = {
|
|
|
110
110
|
ANALISE_INCREMENTAL_ENABLED: false,
|
|
111
111
|
ANALISE_INCREMENTAL_STATE_PATH: path.join(SUKUNA_ESTADO, 'incremental-analise.json'),
|
|
112
112
|
ANALISE_INCREMENTAL_VERSION: 1,
|
|
113
|
-
ANALISE_CACHE_ENABLED:
|
|
113
|
+
ANALISE_CACHE_ENABLED: false,
|
|
114
114
|
ANALISE_CACHE_STATE_PATH: path.join(SUKUNA_ESTADO, 'analysis-cache.json'),
|
|
115
115
|
ANALISE_CACHE_VERSION: 1,
|
|
116
116
|
ANALISE_CACHE_TTL_MS: 86400000,
|
|
@@ -163,11 +163,11 @@ export const ICONES_ZELADOR = {
|
|
|
163
163
|
export function getIcone(categoria, nome) {
|
|
164
164
|
switch (categoria) {
|
|
165
165
|
case 'nivel':
|
|
166
|
-
return ICONES_NIVEL[nome];
|
|
166
|
+
return ICONES_NIVEL[nome] ?? ICONES_NIVEL.info;
|
|
167
167
|
case 'status':
|
|
168
|
-
return ICONES_STATUS[nome];
|
|
168
|
+
return ICONES_STATUS[nome] ?? ICONES_STATUS.pendente;
|
|
169
169
|
case 'acao':
|
|
170
|
-
return ICONES_ACAO[nome];
|
|
170
|
+
return ICONES_ACAO[nome] ?? '[*]';
|
|
171
171
|
default:
|
|
172
172
|
return '[*]';
|
|
173
173
|
}
|
|
@@ -6,9 +6,9 @@ export function parseComJson(codigo) {
|
|
|
6
6
|
try {
|
|
7
7
|
const parsed = JSON.parse(codigo);
|
|
8
8
|
const info = {
|
|
9
|
-
type: Array.isArray(parsed) ? 'array' : typeof parsed,
|
|
9
|
+
type: parsed === null ? 'null' : (Array.isArray(parsed) ? 'array' : typeof parsed),
|
|
10
10
|
};
|
|
11
|
-
if (typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
11
|
+
if (typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed)) {
|
|
12
12
|
const keys = Object.keys(parsed);
|
|
13
13
|
info.keys = keys;
|
|
14
14
|
info.depth = estimateDepth(parsed, 0);
|
|
@@ -27,6 +27,12 @@ export function parseComTypeScript(codigo, tsx = false) {
|
|
|
27
27
|
errorRecovery: true,
|
|
28
28
|
plugins: plugins
|
|
29
29
|
});
|
|
30
|
+
if (ast.errors && ast.errors.length > 0) {
|
|
31
|
+
for (const err of ast.errors) {
|
|
32
|
+
logCore.erroTs(err.message ?? String(err), getCurrentParsingFile());
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
30
36
|
return wrapMinimal(tsx ? 'tsx-tsc' : 'ts-tsc', {
|
|
31
37
|
kind: 'SourceFile',
|
|
32
38
|
statements: ast.program.body.length
|
|
@@ -40,7 +40,7 @@ export function criarSchemaMetadata(versao = VERSAO_ATUAL, descricaoPersonalizad
|
|
|
40
40
|
export function validarSchema(relatorio) {
|
|
41
41
|
const erros = [];
|
|
42
42
|
// Verificar estrutura básica
|
|
43
|
-
if (typeof relatorio !== 'object') {
|
|
43
|
+
if (typeof relatorio !== 'object' || relatorio === null) {
|
|
44
44
|
erros.push('Relatório deve ser um objeto');
|
|
45
45
|
return {
|
|
46
46
|
valido: false,
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
async function request(path) {
|
|
3
|
+
const response = await fetch(path);
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
const body = await response.json();
|
|
6
|
+
throw new Error(body.erro ?? `HTTP ${response.status}`);
|
|
7
|
+
}
|
|
8
|
+
return response.json();
|
|
9
|
+
}
|
|
10
|
+
export const api = {
|
|
11
|
+
getAppInfo() {
|
|
12
|
+
return request('/api/github/app-info');
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* A chave de deduplicação é composta por: relPath|linha|tipo|mensagem.
|
|
10
10
|
*/
|
|
11
11
|
export function dedupeOcorrencias(arr) {
|
|
12
|
+
if (!arr || !Array.isArray(arr)) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
12
15
|
const seen = new Map();
|
|
13
16
|
for (const ocorrencia of arr) {
|
|
14
17
|
const key = `${ocorrencia.relPath ?? ''}|${String(ocorrencia.linha ?? '')}|${ocorrencia.tipo ?? ''}|${ocorrencia.mensagem ?? ''}`;
|
|
@@ -10,7 +10,7 @@ export function formatarCodeMinimo(code, opts) {
|
|
|
10
10
|
const formatted = normalizarNewlinesFinais(removerEspacosFinaisPorLinha(normalized));
|
|
11
11
|
return {
|
|
12
12
|
ok: true,
|
|
13
|
-
parser: opts?.parser,
|
|
13
|
+
parser: (opts?.parser ?? 'code'),
|
|
14
14
|
formatted,
|
|
15
15
|
changed: formatted !== normalizarNewlinesFinais(normalized),
|
|
16
16
|
};
|
|
@@ -93,13 +93,19 @@ const REPEATABLE_TO_SINGLE = new Set([',', '.', '!', '?', ';']);
|
|
|
93
93
|
const REPEATABLE_KEEP_UP_TO_3 = new Set(['.', '!', '?']);
|
|
94
94
|
const MULTI_PUNCT_RE = /([,\.!?:;_\-])\1+/g;
|
|
95
95
|
const SPACE_BEFORE_PUNCT_RE = /\s+([,.:;!?])/g;
|
|
96
|
-
const NO_SPACE_AFTER_NON_DOT_PUNCT_RE = /([,:;!?])([^\s\)\]\}\[!,;:?])/g;
|
|
96
|
+
const NO_SPACE_AFTER_NON_DOT_PUNCT_RE = /([,:;!?])([^\s\)\]\}\[!,;:?"'])/g;
|
|
97
97
|
const SPACE_BEFORE_ELLIPSIS_RE = /([^0-9])(\.{3})/g;
|
|
98
98
|
const PROTECTED_PATTERNS = [/https?:\/\/[^\s"'<>]+/gi, /ftp:\/\/[^\s"'<>]+/gi, /file:\/\/[^\s"'<>]+/gi, /www\.[^\s"'<>]+/gi, /[a-zA-Z0-9](?:\.[a-zA-Z0-9-]+)+\.[a-zA-Z]{2,}/g, /(?:^|(?<=\s|["'=(\[,]))\.\//g, /(?:^|(?<=\s|["'=(\[,]))\.\.\//g, /(?<=[/\\])[\w-]+\.[\w]+/g, /\d+\.\d+(?:\.\d+)*/g, /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, /[a-f0-9]{7,}\.[a-f0-9]+/gi, /[\w-]+\.[\w-]+\.[\w]+/g,
|
|
99
99
|
// Proteger paths e nomes de arquivo com extensão
|
|
100
100
|
/(?:^|[\s"'=(\[,])([\/\\][\w-]+(?:[\/\\][\w-]+)*[\/\\]?[\w-]+\.[a-zA-Z0-9]+)/g, /(?:^|[\s"'=(\[,])([\w-]+(?:[\/\\][\w-]+)+\.[a-zA-Z0-9]+)/g,
|
|
101
101
|
// Proteger imports/require paths
|
|
102
|
-
/(?:from\s+|require\(|import\s+)(["'])([^"']+\.[a-zA-Z0-9]+)\1/g
|
|
102
|
+
/(?:from\s+|require\(|import\s+)(["'])([^"']+\.[a-zA-Z0-9]+)\1/g,
|
|
103
|
+
// Proteger expansões de parâmetro shell ${...} - operadores :- := :? :+ e slicing não devem ser tocados
|
|
104
|
+
/\$\{[^}]*\}/g,
|
|
105
|
+
// Proteger terminadores de case shell: ;; ;;& ;&
|
|
106
|
+
/;;[;&]?|;&/g,
|
|
107
|
+
// Proteger expressões de teste shell [[ ... ]] e (( ... ))
|
|
108
|
+
/\[\[[^\]]*\]\]|\(\([^)]*\)\)/g];
|
|
103
109
|
function protectSequences(code) {
|
|
104
110
|
const placeholders = [];
|
|
105
111
|
let result = code;
|
|
@@ -299,14 +305,22 @@ function fixSpacingAroundPunct(code) {
|
|
|
299
305
|
if (shouldSkipLineForPunctuation(line, idx, lines)) {
|
|
300
306
|
return line;
|
|
301
307
|
}
|
|
302
|
-
|
|
303
|
-
|
|
308
|
+
// Proteger espaço antes de ! quando é operador de negação shell:
|
|
309
|
+
// [[ ! → preservar o espaço, não remover
|
|
310
|
+
// Also protect (( ! and case ! patterns
|
|
311
|
+
const shellNegationProtected = line
|
|
312
|
+
.replace(/(\[\[)\s+(!)/g, '$1\x00SHL_NEG\x00')
|
|
313
|
+
.replace(/(\(\()\s+(!)/g, '$1\x00SHL_NEG\x00');
|
|
314
|
+
const t1 = shellNegationProtected.replace(SPACE_BEFORE_PUNCT_RE, '$1');
|
|
315
|
+
// Restaurar proteção de negação shell
|
|
316
|
+
const t1restored = t1.replace(/\x00SHL_NEG\x00/g, ' !');
|
|
317
|
+
const t2 = t1restored.replace(NO_SPACE_AFTER_NON_DOT_PUNCT_RE, (m, p1, _p2, off) => fixNonDotPunct(m, p1, _p2, off, t1restored));
|
|
304
318
|
const t3 = t2.replace(NO_SPACE_AFTER_DOT_RE, (m, off) => fixDotPunct(m, off, t2));
|
|
305
319
|
const t4 = t3.replace(SPACE_BEFORE_ELLIPSIS_RE, '$1 $2');
|
|
306
320
|
if (t4 !== line) {
|
|
307
321
|
changed = true;
|
|
308
322
|
const countBefore = (line.match(SPACE_BEFORE_PUNCT_RE) ?? []).length;
|
|
309
|
-
const countNonDot = (
|
|
323
|
+
const countNonDot = (t1restored.match(NO_SPACE_AFTER_NON_DOT_PUNCT_RE) ?? []).length;
|
|
310
324
|
const countDot = (t2.match(NO_SPACE_AFTER_DOT_RE) ?? []).length;
|
|
311
325
|
const countEllipsis = (t3.match(SPACE_BEFORE_ELLIPSIS_RE) ?? []).length;
|
|
312
326
|
totalChanges += countBefore + countNonDot + countDot + countEllipsis;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// @mahoraga-disable ml-deep-nesting
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
3
4
|
import { corrigirPontuacaoECodigo, limitarLinhasEmBranco, normalizarFimDeLinha, normalizarNewlinesFinais, removerBom } from './commons.js';
|
|
4
5
|
function parseShellLine(raw, ctx) {
|
|
5
6
|
const trimmed = raw.trimEnd();
|
|
@@ -129,6 +130,82 @@ function parseShellLine(raw, ctx) {
|
|
|
129
130
|
}
|
|
130
131
|
};
|
|
131
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Verifica se um índice dentro de uma linha está entre aspas (single ou double).
|
|
135
|
+
*/
|
|
136
|
+
function isIndexInQuotes(line, index) {
|
|
137
|
+
let inSingle = false;
|
|
138
|
+
let inDouble = false;
|
|
139
|
+
let escaped = false;
|
|
140
|
+
for (let i = 0; i < index && i < line.length; i++) {
|
|
141
|
+
const ch = line[i];
|
|
142
|
+
if (escaped) {
|
|
143
|
+
escaped = false;
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (ch === '\\') {
|
|
147
|
+
escaped = true;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (inSingle) {
|
|
151
|
+
if (ch === "'")
|
|
152
|
+
inSingle = false;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (inDouble) {
|
|
156
|
+
if (ch === '"')
|
|
157
|
+
inDouble = false;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (ch === "'") {
|
|
161
|
+
inSingle = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (ch === '"') {
|
|
165
|
+
inDouble = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return inSingle || inDouble;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Verifica se um caractere pipe (|) está em posição de pipe shell real
|
|
173
|
+
* (fora de aspas, não dentro de string/regex literal).
|
|
174
|
+
*/
|
|
175
|
+
function isRealShellPipe(line, index) {
|
|
176
|
+
if (isIndexInQuotes(line, index))
|
|
177
|
+
return false;
|
|
178
|
+
// Verificar se o | está precedido por $ ou ` (subshell) - isso é pipe real
|
|
179
|
+
const before = line.slice(0, index);
|
|
180
|
+
// Se | está dentro de expressão $() ou `` , é pipe dentro de subshell - ainda é pipe real
|
|
181
|
+
// Mas se está dentro de '...' ou "..." já foi filtrado acima
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Aplica espaçamento em redirecionamento simples: >file → > file
|
|
186
|
+
* Mas preserva redirecionamentos atômicos (2>&1, &>, etc.)
|
|
187
|
+
*/
|
|
188
|
+
function aplicarEspacamentoRedirecionamento(line) {
|
|
189
|
+
// Primeiro, proteger redirecionamentos atômicos com placeholders
|
|
190
|
+
const atomicos = [];
|
|
191
|
+
let result = line.replace(
|
|
192
|
+
// Captura operadores de redirecionamento atômicos (compostos por 2+ caracteres):
|
|
193
|
+
// &>, &>>, 2>, 2>>, 2>&1, 3>&-, |&, <<, <<-, <>, >|, >>, >&, <&
|
|
194
|
+
// NÃO captura > ou < simples (esses devem ganhar espaço)
|
|
195
|
+
/(?:\d+>&?\d*|&>>?|<<[-]?|\|&|<>|>[\|]|>>|>&|<&)/g, (match) => {
|
|
196
|
+
const id = atomicos.length;
|
|
197
|
+
atomicos.push(match);
|
|
198
|
+
return `\x00ATOMIC_REDIR_${id}\x00`;
|
|
199
|
+
});
|
|
200
|
+
// Agora aplicar espaçamento em redirecionamentos simples restantes
|
|
201
|
+
// cmd>file → cmd > file (espaço antes e depois)
|
|
202
|
+
result = result.replace(/(?<!\x00)([^\s><])([><])([^\s><])/g, '$1 $2 $3');
|
|
203
|
+
// Restaurar placeholders
|
|
204
|
+
result = result.replace(/\x00ATOMIC_REDIR_(\d+)\x00/g, (_m, id) => {
|
|
205
|
+
return atomicos[Number(id)] ?? _m;
|
|
206
|
+
});
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
132
209
|
function corrigirEspacamentoShell(lines) {
|
|
133
210
|
const out = [];
|
|
134
211
|
let ctx = {
|
|
@@ -152,6 +229,11 @@ function corrigirEspacamentoShell(lines) {
|
|
|
152
229
|
out.push(output);
|
|
153
230
|
continue;
|
|
154
231
|
}
|
|
232
|
+
// Shebang (#!): preservar intacto, sem espaços
|
|
233
|
+
if (/^\s*#!/.test(output)) {
|
|
234
|
+
out.push(output);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
155
237
|
// Comentários: garantir espaço após #
|
|
156
238
|
if (/^\s*#\S/.test(output)) {
|
|
157
239
|
out.push(output.replace(/^(\s*)#(\S)/, '$1# $2'));
|
|
@@ -163,23 +245,44 @@ function corrigirEspacamentoShell(lines) {
|
|
|
163
245
|
out.push(output.replace(/^(\s*[a-zA-Z_][\w]*)\s+=\s/, '$1='));
|
|
164
246
|
continue;
|
|
165
247
|
}
|
|
166
|
-
// Espaçamento em pipes: | → |
|
|
248
|
+
// Espaçamento em pipes reais do shell (fora de aspas/strings): cmd|cmd → cmd | cmd
|
|
167
249
|
if (/\S\|\S/.test(output)) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
250
|
+
// Só aplicar espaços em pipes que são reais do shell (fora de aspas)
|
|
251
|
+
const withPipeSpaces = output.replace(/(\S)\|(\S)/g, (match, before, after, offset) => {
|
|
252
|
+
if (isRealShellPipe(output, offset + before.length)) {
|
|
253
|
+
return `${before} | ${after}`;
|
|
254
|
+
}
|
|
255
|
+
return match;
|
|
256
|
+
});
|
|
257
|
+
// Se houve mudança, continue; senão cai nos próximos handlers
|
|
258
|
+
if (withPipeSpaces !== output) {
|
|
259
|
+
out.push(aplicarEspacamentoRedirecionamento(withPipeSpaces));
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
175
262
|
}
|
|
176
|
-
|
|
263
|
+
// Espaçamento em redirecionamentos: >file → > file (preservando atômicos)
|
|
264
|
+
out.push(aplicarEspacamentoRedirecionamento(output));
|
|
177
265
|
}
|
|
178
266
|
return out;
|
|
179
267
|
}
|
|
268
|
+
/* ────────────────────────── Validação de sintaxe ────────────────────────── */
|
|
269
|
+
/**
|
|
270
|
+
* Valida a sintaxe shell usando bash -n.
|
|
271
|
+
* Retorna true se a sintaxe for válida, false caso contrário.
|
|
272
|
+
*/
|
|
273
|
+
function validarSintaxeShell(code) {
|
|
274
|
+
try {
|
|
275
|
+
execSync('bash -n', { input: code, timeout: 5000, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
180
282
|
/* ────────────────────────── Public API ────────────────────────── */
|
|
181
283
|
/**
|
|
182
284
|
* Shell formatter com detecção de contexto para here-docs, quoting e continuação.
|
|
285
|
+
* Inclui gate de segurança: se bash -n falhar, retorna o código original sem formatação.
|
|
183
286
|
*/
|
|
184
287
|
export function formatarShellMinimo(code) {
|
|
185
288
|
const normalized = normalizarFimDeLinha(removerBom(code));
|
|
@@ -193,6 +296,22 @@ export function formatarShellMinimo(code) {
|
|
|
193
296
|
const formatted = normalizarNewlinesFinais(comPontuacao);
|
|
194
297
|
const baseline = normalizarNewlinesFinais(normalized);
|
|
195
298
|
const anyChanged = formatted !== baseline;
|
|
299
|
+
// Gate de segurança: validar sintaxe com bash -n, reverter se falhar
|
|
300
|
+
if (anyChanged && lines.length > 1) {
|
|
301
|
+
try {
|
|
302
|
+
execSync('bash -n', { input: formatted, timeout: 5000, stdio: ['pipe', 'ignore', 'ignore'] });
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
// bash -n falhou → reverter para o código original
|
|
306
|
+
return {
|
|
307
|
+
ok: true,
|
|
308
|
+
parser: 'shell',
|
|
309
|
+
formatted: baseline,
|
|
310
|
+
changed: false,
|
|
311
|
+
reason: 'shell-syntax-error-revert'
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
196
315
|
return {
|
|
197
316
|
ok: true,
|
|
198
317
|
parser: 'shell',
|
|
@@ -30,6 +30,9 @@ export class DedupeManager {
|
|
|
30
30
|
export function runUniqueVisitor(ast, relPath, visitor, initialState = {}) {
|
|
31
31
|
const manager = new DedupeManager(relPath);
|
|
32
32
|
const state = { ...initialState, manager, relPath };
|
|
33
|
+
if (ast === null || ast === undefined) {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
33
36
|
const nodeToTraverse = ast.node ?? ast;
|
|
34
37
|
if (!nodeToTraverse) {
|
|
35
38
|
return [];
|
|
@@ -13,6 +13,13 @@ export async function lerRelatorioVersionado(options) {
|
|
|
13
13
|
try {
|
|
14
14
|
// Ler arquivo
|
|
15
15
|
const conteudo = await lerEstado(caminho);
|
|
16
|
+
// Verificar se arquivo existe
|
|
17
|
+
if (conteudo === null || conteudo === undefined) {
|
|
18
|
+
return {
|
|
19
|
+
sucesso: false,
|
|
20
|
+
erro: 'Arquivo não encontrado ou vazio',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
16
23
|
let relatorioFinal = conteudo;
|
|
17
24
|
let migrado = false;
|
|
18
25
|
// Validar schema se solicitado
|
|
@@ -92,6 +99,12 @@ export async function lerDadosRelatorio(caminho) {
|
|
|
92
99
|
export async function verificarSchemaRelatorio(caminho) {
|
|
93
100
|
try {
|
|
94
101
|
const conteudo = await lerEstado(caminho);
|
|
102
|
+
if (conteudo === null || conteudo === undefined) {
|
|
103
|
+
return {
|
|
104
|
+
valido: false,
|
|
105
|
+
erros: ['Arquivo não encontrado ou vazio'],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
95
108
|
const validacao = validarSchema(conteudo);
|
|
96
109
|
return {
|
|
97
110
|
valido: validacao.valido,
|
|
@@ -268,9 +268,9 @@ export function normalizarRel(relPath) {
|
|
|
268
268
|
export async function carregarConfigEstrategia(baseDir, overrides) {
|
|
269
269
|
const caminho = path.join(baseDir, '.mahoraga', 'estrutura.json');
|
|
270
270
|
const lido = await lerEstado(caminho);
|
|
271
|
-
const cfgObj = !Array.isArray(lido) && typeof lido === 'object' ? lido : {};
|
|
271
|
+
const cfgObj = lido !== null && !Array.isArray(lido) && typeof lido === 'object' ? lido : {};
|
|
272
272
|
const nomePreset = overrides?.preset ?? cfgObj['preset'];
|
|
273
|
-
const preset = nomePreset && PRESETS[nomePreset]
|
|
273
|
+
const preset = nomePreset && PRESETS[nomePreset]?.nome ? PRESETS[nomePreset] : undefined;
|
|
274
274
|
const base = {
|
|
275
275
|
raizCodigo: 'src',
|
|
276
276
|
criarSubpastasPorEntidade: true,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* Type guard: verifica se um valor é um GuardianResult válido.
|
|
4
|
+
* Aceita null/undefined como válido (representa "sem resultado ainda").
|
|
5
|
+
*/
|
|
6
|
+
export function isGuardianResult(value) {
|
|
7
|
+
if (value === null || value === undefined) {
|
|
8
|
+
return true;
|
|
9
|
+
}
|
|
10
|
+
if (typeof value !== 'object') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const obj = value;
|
|
14
|
+
if (typeof obj.status !== 'string') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
const knownStatuses = ['ok', 'erro', 'alteracoes-detectadas', 'baseline-aceito', 'baseline-criado'];
|
|
18
|
+
return knownStatuses.includes(obj.status);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Converte/conforma um GuardianResult.
|
|
22
|
+
* - undefined → null
|
|
23
|
+
* - status 'baseline-aceito' → 'ok'
|
|
24
|
+
* - status desconhecido retorna null
|
|
25
|
+
*/
|
|
26
|
+
export function converterResultadoGuardian(result) {
|
|
27
|
+
if (result === undefined) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
if (result.status === 'baseline-aceito') {
|
|
31
|
+
return { ...result, status: 'ok' };
|
|
32
|
+
}
|
|
33
|
+
const knownStatuses = ['ok', 'erro', 'alteracoes-detectadas'];
|
|
34
|
+
if (!knownStatuses.includes(result.status)) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/**
|
|
3
|
+
* Verifica se um valor é uma PendenciaProcessavel válida.
|
|
4
|
+
* Deve ter `arquivo` (string) e `motivo` (string).
|
|
5
|
+
*/
|
|
6
|
+
export function isPendenciaProcessavel(value) {
|
|
7
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const obj = value;
|
|
11
|
+
return typeof obj.arquivo === 'string' && typeof obj.motivo === 'string';
|
|
12
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
import { SharedCommonMensagens } from '../../core/messages/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extrai mensagem de erro de qualquer valor de forma segura.
|
|
5
|
+
*/
|
|
6
|
+
export function extrairMensagemErro(err) {
|
|
7
|
+
if (err === null || err === undefined) {
|
|
8
|
+
return SharedCommonMensagens?.erroDesconhecido ?? 'Erro desconhecido';
|
|
9
|
+
}
|
|
10
|
+
if (err instanceof Error) {
|
|
11
|
+
return err.message;
|
|
12
|
+
}
|
|
13
|
+
if (typeof err === 'string') {
|
|
14
|
+
return err;
|
|
15
|
+
}
|
|
16
|
+
if (typeof err === 'object') {
|
|
17
|
+
const obj = err;
|
|
18
|
+
if (typeof obj.message === 'string') {
|
|
19
|
+
return obj.message;
|
|
20
|
+
}
|
|
21
|
+
if (obj.message instanceof Error) {
|
|
22
|
+
return obj.message.message;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return SharedCommonMensagens?.erroDesconhecido ?? 'Erro desconhecido';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Valida um valor com type guard e retorna fallback se falhar.
|
|
29
|
+
*/
|
|
30
|
+
export function validarSeguro(validador, valor, fallback) {
|
|
31
|
+
if (validador(valor)) {
|
|
32
|
+
return valor;
|
|
33
|
+
}
|
|
34
|
+
console.warn('validação falhou, usando fallback', valor);
|
|
35
|
+
return fallback;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Converte um valor com type guard e retorna fallback se falhar.
|
|
39
|
+
*/
|
|
40
|
+
export function converterSeguro(valor, validador, fallback) {
|
|
41
|
+
if (validador(valor)) {
|
|
42
|
+
return valor;
|
|
43
|
+
}
|
|
44
|
+
return fallback;
|
|
45
|
+
}
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
export function isErroComMensagem(value) {
|
|
48
|
+
if (value === null || value === undefined) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (typeof value !== 'object') {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return typeof value.message === 'string';
|
|
55
|
+
}
|
|
56
|
+
export function isGlobalComVitest(value) {
|
|
57
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return 'vi' in value;
|
|
61
|
+
}
|
|
62
|
+
export function isIntlComDisplayNames(value) {
|
|
63
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// Aceita tanto se tiver DisplayNames quanto qualquer objeto (fallback)
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
export function isGlobalComImport(value) {
|
|
70
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
// Aceita tanto se tiver import quanto qualquer objeto (fallback)
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
export function isConfigPlugin(value) {
|
|
77
|
+
return value !== null && value !== undefined && typeof value === 'object';
|
|
78
|
+
}
|
|
79
|
+
export function isPlanoSugestaoEstrutura(value) {
|
|
80
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
const obj = value;
|
|
84
|
+
return Array.isArray(obj.mover);
|
|
85
|
+
}
|
|
86
|
+
export function isSnapshotAnalise(value) {
|
|
87
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const obj = value;
|
|
91
|
+
return typeof obj.timestamp === 'string' && Array.isArray(obj.arquivos);
|
|
92
|
+
}
|
|
93
|
+
export function isEntradaMapaReversao(value) {
|
|
94
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const obj = value;
|
|
98
|
+
if (typeof obj.arquivo !== 'string') {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return ['adicionar', 'remover', 'modificar'].includes(obj.operacao);
|
|
102
|
+
}
|
|
103
|
+
export function isStringNaoVazia(value) {
|
|
104
|
+
return typeof value === 'string' && value.trim().length > 0;
|
|
105
|
+
}
|
|
106
|
+
export function isNumeroValido(value) {
|
|
107
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
108
|
+
}
|
|
109
|
+
export function isArrayNaoVazio(value) {
|
|
110
|
+
return Array.isArray(value) && value.length > 0;
|
|
111
|
+
}
|
|
@@ -31,8 +31,13 @@ export async function scanVulnerabilities(opts = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
catch (err) {
|
|
33
33
|
if (isNpmAuditError(err)) {
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
try {
|
|
35
|
+
const parsed = JSON.parse(err.stdout);
|
|
36
|
+
return normalizeAuditResult(parsed);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return emptyResult();
|
|
40
|
+
}
|
|
36
41
|
}
|
|
37
42
|
return emptyResult();
|
|
38
43
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mocoto/mahoraga",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "C.L.I modular para análise, diagnóstico e manutenção de projetos JavaScript/TypeScript e multi-stack leve",
|
|
6
6
|
"author": "rei macaco",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"chalk": "^5.6.2",
|
|
123
123
|
"commander": "^15.0.0",
|
|
124
124
|
"dotenv": "^17.4.2",
|
|
125
|
-
"fast-xml-parser": "^5.
|
|
125
|
+
"fast-xml-parser": "^5.10.0",
|
|
126
126
|
"htmlparser2": "^12.0.0",
|
|
127
127
|
"marked": "^18.0.6",
|
|
128
128
|
"micromatch": "^4.0.8",
|
|
@@ -130,7 +130,7 @@
|
|
|
130
130
|
"openpgp": "^6.3.1",
|
|
131
131
|
"ora": "^9.4.1",
|
|
132
132
|
"p-limit": "^7.3.0",
|
|
133
|
-
"postcss": "^8.5.
|
|
133
|
+
"postcss": "^8.5.17",
|
|
134
134
|
"postcss-sass": "^0.5.0",
|
|
135
135
|
"postcss-scss": "^4.0.9",
|
|
136
136
|
"xxhashjs": "^0.2.2",
|