@mocoto/mahoraga 0.14.2 → 0.14.4

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.
Files changed (105) hide show
  1. package/README.md +2 -32
  2. package/dist/analysts/architects/analyst-structure.js +19 -19
  3. package/dist/analysts/architects/archetypes-custom.js +8 -6
  4. package/dist/analysts/architects/detector-archetypes.js +30 -27
  5. package/dist/analysts/architects/signals-project-advanced.js +24 -26
  6. package/dist/analysts/architects/signals-project.js +1 -1
  7. package/dist/analysts/architects/strategist-structure.js +47 -66
  8. package/dist/analysts/azure-pipelines/analysts/analyst-azure.js +21 -14
  9. package/dist/analysts/azure-pipelines/corrections/correction-azure.js +22 -19
  10. package/dist/analysts/azure-pipelines/detectors/detector-azure-performance.js +21 -18
  11. package/dist/analysts/azure-pipelines/detectors/detector-azure-security.js +25 -20
  12. package/dist/analysts/azure-pipelines/detectors/detector-azure-structure.js +39 -37
  13. package/dist/analysts/azure-pipelines/scorers/scorer-azure.js +10 -7
  14. package/dist/analysts/circleci/analysts/analyst-circleci.js +23 -15
  15. package/dist/analysts/circleci/corrections/correction-circleci.js +22 -20
  16. package/dist/analysts/circleci/detectors/detector-circleci-performance.js +23 -19
  17. package/dist/analysts/circleci/detectors/detector-circleci-security.js +78 -82
  18. package/dist/analysts/circleci/detectors/detector-circleci-structure.js +20 -17
  19. package/dist/analysts/converters/engines/converter-gha-to-gitlab.js +17 -15
  20. package/dist/analysts/corrections/analyst-quick-fixes.js +25 -28
  21. package/dist/analysts/corrections/analyst-scoring.js +24 -21
  22. package/dist/analysts/corrections/corrector-structure.js +35 -35
  23. package/dist/analysts/corrections/fix-alias-imports.js +10 -6
  24. package/dist/analysts/corrections/fix-md-fences.js +23 -23
  25. package/dist/analysts/corrections/map-reversion.js +54 -51
  26. package/dist/analysts/corrections/pruning.js +19 -16
  27. package/dist/analysts/corrections/quick-fixes/fix-any-to-proper-type.js +57 -61
  28. package/dist/analysts/corrections/quick-fixes/fix-unknown-to-specific-type.js +30 -30
  29. package/dist/analysts/corrections/reorganize-barrels.js +10 -10
  30. package/dist/analysts/corrections/scoring-shared.js +19 -18
  31. package/dist/analysts/corrections/type-safety/context-analyzer.js +15 -10
  32. package/dist/analysts/corrections/type-safety/type-validator.js +2 -1
  33. package/dist/analysts/corrections/type-safety/usage-analyzer.js +36 -39
  34. package/dist/analysts/css/analysts/analyst-css.js +16 -15
  35. package/dist/analysts/css/corrections/correction-css.js +19 -11
  36. package/dist/analysts/css/detectors/detector-css-best-practices.js +129 -118
  37. package/dist/analysts/css/scorers/scorer-css.js +23 -18
  38. package/dist/analysts/css-in-js/analysts/analyst-css-in-js.js +24 -17
  39. package/dist/analysts/css-in-js/corrections/correction-css-in-js.js +31 -16
  40. package/dist/analysts/css-in-js/detectors/detector-css-in-js-best-practices.js +20 -15
  41. package/dist/analysts/css-in-js/scorers/scorer-css-in-js.js +79 -54
  42. package/dist/analysts/css-in-js/shared.js +7 -0
  43. package/dist/analysts/detectors/detector-architecture.js +89 -83
  44. package/dist/analysts/detectors/detector-bugs-ml.js +21 -19
  45. package/dist/analysts/detectors/detector-code-fragile.js +30 -26
  46. package/dist/analysts/detectors/detector-duplications.js +21 -18
  47. package/dist/analysts/detectors/detector-monorepo.js +41 -36
  48. package/dist/analysts/detectors/detector-performance.js +22 -16
  49. package/dist/analysts/detectors/detector-recommendation-architecture.js +39 -35
  50. package/dist/analysts/detectors/detector-security.js +43 -42
  51. package/dist/analysts/detectors/detector-structure.js +29 -26
  52. package/dist/analysts/js-ts/detectors/detector-anti-patterns-async.js +4 -1
  53. package/dist/analysts/js-ts/detectors/detector-constructions-syntactic.js +10 -16
  54. package/dist/analysts/plugins/detector-documentation.js +1 -0
  55. package/dist/analysts/react/analysts/analyst-react-hooks.js +4 -2
  56. package/dist/analysts/react/analysts/analyst-react.js +1 -1
  57. package/dist/analysts/react/corrections/correction-react.js +2 -2
  58. package/dist/analysts/react/detectors/detector-react-best-practices.js +1 -1
  59. package/dist/analysts/registry/autodiscovery.js +2 -2
  60. package/dist/analysts/tailwind/corrections/correction-tailwind.js +0 -8
  61. package/dist/analysts/tailwind/scorers/scorer-tailwind.js +1 -2
  62. package/dist/cli/commands/command-analysts.js +3 -2
  63. package/dist/cli/commands/command-formatters.js +6 -5
  64. package/dist/cli/commands/command-github-actions.js +1 -1
  65. package/dist/cli/commands/command-guardian.js +1 -1
  66. package/dist/cli/commands/command-marketplace.js +4 -4
  67. package/dist/cli/diagnostic/filters.js +9 -9
  68. package/dist/core/config/chalk-safe.js +4 -4
  69. package/dist/core/config/config.js +5 -5
  70. package/dist/core/execution/analysis-cache.js +1 -1
  71. package/dist/core/execution/ast-cache.js +1 -3
  72. package/dist/core/execution/executor.js +2 -4
  73. package/dist/core/execution/scanner.js +1 -1
  74. package/dist/core/messages/en/analysts/report-circleci-messages.js +7 -7
  75. package/dist/core/messages/en/caretakers/caretaker-messages.js +1 -7
  76. package/dist/core/messages/ja/analysts/report-circleci-messages.js +7 -7
  77. package/dist/core/messages/ja/caretakers/caretaker-messages.js +1 -7
  78. package/dist/core/messages/pt/analysts/report-circleci-messages.js +7 -7
  79. package/dist/core/messages/pt/caretakers/caretaker-messages.js +1 -7
  80. package/dist/core/messages/zh/analysts/report-circleci-messages.js +7 -7
  81. package/dist/core/messages/zh/caretakers/caretaker-messages.js +1 -7
  82. package/dist/core/parsing/parser.js +2 -3
  83. package/dist/core/parsing/utils.js +1 -0
  84. package/dist/core/workers/worker-pool.js +0 -0
  85. package/dist/guardian/records.js +1 -0
  86. package/dist/guardian/sentinel.js +1 -0
  87. package/dist/guardian/watcher-hidden.js +1 -0
  88. package/dist/licenses/fs-utils.js +2 -1
  89. package/dist/licenses/header-options.js +1 -0
  90. package/dist/licenses/index.js +1 -1
  91. package/dist/reports/generator-report.js +3 -3
  92. package/dist/reports/report-patterns-usage.js +1 -1
  93. package/dist/shared/data-processing/fragment-report.js +1 -1
  94. package/dist/shared/formatters/engines/stylelint.js +40 -34
  95. package/dist/shared/formatters/formatters/markdown.js +1 -1
  96. package/dist/shared/helpers/framework-detector.js +5 -1
  97. package/dist/shared/persistence/persistence.js +13 -5
  98. package/dist/shared/plugins/core-plugin.js +3 -1
  99. package/dist/types/analysts/tailwind-correction.js +2 -0
  100. package/dist/types/index.js +3 -3
  101. package/dist/types/processing/filters.js +5 -4
  102. package/dist/vulnerabilities/npm-audit.js +1 -0
  103. package/dist/vulnerabilities/scanner.js +1 -1
  104. package/dist/vulnerabilities/vulnerabilities.js +1 -0
  105. package/package.json +5 -10
@@ -2,6 +2,44 @@
2
2
  import { parseDocument } from 'yaml';
3
3
  import { messages } from '../../../core/messages/index.js';
4
4
  import { criarOcorrencia } from '../../../types/index.js';
5
+ function processarJobsAzure(jobs, relPath, ocorrencias) {
6
+ const jobNames = jobs.map(j => j.job ?? '').filter(Boolean);
7
+ for (const job of jobs) {
8
+ if (job.dependsOn && !jobNames.includes(job.dependsOn)) {
9
+ ocorrencias.push(criarOcorrencia({
10
+ tipo: 'azure-job-orfao',
11
+ nivel: 'erro',
12
+ mensagem: `Job '${job.job ?? job.displayName}' depende de '${job.dependsOn}' que não existe`,
13
+ relPath, linha: 1,
14
+ sugestao: 'Verifique se o nome do job no dependsOn está correto',
15
+ }));
16
+ }
17
+ }
18
+ if (jobs.length > 3 && jobs.every(j => !j.dependsOn || jobNames.indexOf(j.dependsOn) === jobNames.indexOf(j.job ?? '') - 1)) {
19
+ const hasParallel = jobs.some(j => !j.dependsOn && j !== jobs[0]);
20
+ if (!hasParallel) {
21
+ ocorrencias.push(criarOcorrencia({
22
+ tipo: 'azure-sem-paralelismo',
23
+ nivel: 'info',
24
+ mensagem: 'Pipeline com jobs estritamente sequenciais',
25
+ relPath, linha: 1,
26
+ sugestao: 'Considere paralelizar jobs independentes',
27
+ }));
28
+ }
29
+ }
30
+ for (const job of jobs) {
31
+ if (job.job && !job.displayName) {
32
+ ocorrencias.push(criarOcorrencia({
33
+ tipo: 'azure-missing-job-displayname',
34
+ nivel: 'info',
35
+ mensagem: messages.RelatorioAzureMensagens.missingJobDisplayName,
36
+ relPath,
37
+ linha: 1,
38
+ sugestao: messages.RelatorioAzureMensagens.missingJobDisplayNameSugestao,
39
+ }));
40
+ }
41
+ }
42
+ }
5
43
  export const detectorAzureEstrutura = {
6
44
  nome: 'detector-azure-estrutura',
7
45
  categoria: 'workflows',
@@ -17,43 +55,7 @@ export const detectorAzureEstrutura = {
17
55
  const pipeline = doc.toJS();
18
56
  const jobs = pipeline.jobs;
19
57
  if (jobs && Array.isArray(jobs)) {
20
- const jobNames = jobs.map(j => j.job ?? '').filter(Boolean);
21
- for (const job of jobs) {
22
- if (job.dependsOn && !jobNames.includes(job.dependsOn)) {
23
- ocorrencias.push(criarOcorrencia({
24
- tipo: 'azure-job-orfao',
25
- nivel: 'erro',
26
- mensagem: `Job '${job.job ?? job.displayName}' depende de '${job.dependsOn}' que não existe`,
27
- relPath, linha: 1,
28
- sugestao: 'Verifique se o nome do job no dependsOn está correto',
29
- }));
30
- }
31
- }
32
- if (jobs.length > 3 && jobs.every(j => !j.dependsOn || jobNames.indexOf(j.dependsOn) === jobNames.indexOf(j.job ?? '') - 1)) {
33
- const hasParallel = jobs.some(j => !j.dependsOn && j !== jobs[0]);
34
- if (!hasParallel) {
35
- ocorrencias.push(criarOcorrencia({
36
- tipo: 'azure-sem-paralelismo',
37
- nivel: 'info',
38
- mensagem: 'Pipeline com jobs estritamente sequenciais',
39
- relPath, linha: 1,
40
- sugestao: 'Considere paralelizar jobs independentes',
41
- }));
42
- }
43
- }
44
- // Display name conventions
45
- for (const job of jobs) {
46
- if (job.job && !job.displayName) {
47
- ocorrencias.push(criarOcorrencia({
48
- tipo: 'azure-missing-job-displayname',
49
- nivel: 'info',
50
- mensagem: messages.RelatorioAzureMensagens.missingJobDisplayName,
51
- relPath,
52
- linha: 1,
53
- sugestao: messages.RelatorioAzureMensagens.missingJobDisplayNameSugestao,
54
- }));
55
- }
56
- }
58
+ processarJobsAzure(jobs, relPath, ocorrencias);
57
59
  }
58
60
  const container = pipeline;
59
61
  const containers = container.resources?.containers;
@@ -1,6 +1,15 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { messages } from '../../../core/messages/index.js';
3
3
  import { splitLines } from '../../../shared/helpers/index.js';
4
+ function detectarVulnerableTask(srcCompleto) {
5
+ return Object.keys({
6
+ 'Npm@1': 'Npm@1',
7
+ 'NuGetCommand@2': 'NuGetCommand@2',
8
+ 'DotNetCoreCLI@1': 'DotNetCoreCLI@1',
9
+ 'VSBuild@1': 'VSBuild@1',
10
+ 'VsTest@1': 'VsTest@1',
11
+ }).some(task => new RegExp(`task:\\s*${task.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(srcCompleto));
12
+ }
4
13
  export function pontuarAzure(src, _relPath) {
5
14
  let score = 0;
6
15
  const maxScore = 100;
@@ -16,13 +25,7 @@ export function pontuarAzure(src, _relPath) {
16
25
  else {
17
26
  issues.push(messages.RelatorioAzureMensagens.vulnerableTask);
18
27
  }
19
- const isVulnerableTask = Object.keys({
20
- 'Npm@1': 'Npm@1',
21
- 'NuGetCommand@2': 'NuGetCommand@2',
22
- 'DotNetCoreCLI@1': 'DotNetCoreCLI@1',
23
- 'VSBuild@1': 'VSBuild@1',
24
- 'VsTest@1': 'VsTest@1',
25
- }).some(task => new RegExp(`task:\\s*${task.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`, 'i').test(srcCompleto));
28
+ const isVulnerableTask = detectarVulnerableTask(srcCompleto);
26
29
  if (!isVulnerableTask) {
27
30
  score += 5;
28
31
  strengths.push('Tasks com versões seguras');
@@ -3,6 +3,23 @@ import { parseDocument } from 'yaml';
3
3
  import { detectorCircleCIPerformance } from '../detectors/detector-circleci-performance.js';
4
4
  import { detectorCircleCISecurity, obterDetectoresCircleCI } from '../detectors/detector-circleci-security.js';
5
5
  import { detectorCircleCIEstrutura } from '../detectors/detector-circleci-structure.js';
6
+ function processarJobsCircleCI(jobs, relPath, ocorrencias) {
7
+ if (!jobs) {
8
+ return;
9
+ }
10
+ for (const [jobName, job] of Object.entries(jobs)) {
11
+ if (job.steps && job.steps.length > 20) {
12
+ ocorrencias.push({
13
+ tipo: 'CIRCLECI_JOB_TOO_MANY_STEPS',
14
+ mensagem: `Job '${jobName}' possui muitos steps (${job.steps.length})`,
15
+ relPath,
16
+ linha: 1,
17
+ nivel: 'info',
18
+ sugestao: 'Agrupe steps repetitivos em "commands" para maior legibilidade'
19
+ });
20
+ }
21
+ }
22
+ }
6
23
  export const analistaCircleCI = {
7
24
  nome: 'circleci',
8
25
  categoria: 'workflows',
@@ -14,7 +31,11 @@ export const analistaCircleCI = {
14
31
  const securityResults = await Promise.resolve(detectorCircleCISecurity.aplicar(conteudo, relPath, null, _fc, contexto));
15
32
  const performanceResults = await Promise.resolve(detectorCircleCIPerformance.aplicar(conteudo, relPath, null, _fc, contexto));
16
33
  const structureResults = await Promise.resolve(detectorCircleCIEstrutura.aplicar(conteudo, relPath, null, _fc, contexto));
17
- ocorrencias.push(...securityResults, ...performanceResults, ...structureResults);
34
+ for (const r of [securityResults, performanceResults, structureResults]) {
35
+ if (r) {
36
+ ocorrencias.push(...(Array.isArray(r) ? r : [r]));
37
+ }
38
+ }
18
39
  // Run registered plugins
19
40
  const caminhos = contexto?.arquivos.map(arquivo => arquivo.relPath) ?? [];
20
41
  const plugResults = await Promise.all(obterDetectoresCircleCI().map(d => Promise.resolve(d.testar({ content: conteudo }, { conteudo, caminhos })).catch(() => [])));
@@ -43,20 +64,7 @@ export const analistaCircleCI = {
43
64
  sugestao: 'Atualize para a versão 2.1 para usar Orbs, Commands e outras melhorias modernas'
44
65
  });
45
66
  }
46
- if (configData.jobs) {
47
- for (const [jobName, job] of Object.entries(configData.jobs)) {
48
- if (job.steps && job.steps.length > 20) {
49
- ocorrencias.push({
50
- tipo: 'CIRCLECI_JOB_TOO_MANY_STEPS',
51
- mensagem: `Job '${jobName}' possui muitos steps (${job.steps.length})`,
52
- relPath,
53
- linha: 1,
54
- nivel: 'info',
55
- sugestao: 'Agrupe steps repetitivos em "commands" para maior legibilidade'
56
- });
57
- }
58
- }
59
- }
67
+ processarJobsCircleCI(configData.jobs, relPath, ocorrencias);
60
68
  }
61
69
  catch {
62
70
  // Falha silenciosa no parse
@@ -2,30 +2,32 @@
2
2
  import { messages } from '../../../core/messages/index.js';
3
3
  import { splitLines } from '../../../shared/helpers/index.js';
4
4
  import { criarOcorrencia } from '../../../types/index.js';
5
+ function processarLinhaCircleCI(linha, numeroLinha, correcoes) {
6
+ if (/\bimage:\s*[^\s@]+:latest\b/.test(linha)) {
7
+ correcoes.push({
8
+ tipo: 'circleci-fix-latest-image',
9
+ original: linha,
10
+ sugerido: linha.replace(/:latest\b/, ':3.18'),
11
+ linha: numeroLinha,
12
+ confianca: 90
13
+ });
14
+ }
15
+ if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
16
+ const replaced = linha.replace(/['"][^'"]+['"]/, '"$VAR_NAME"');
17
+ correcoes.push({
18
+ tipo: 'circleci-use-context',
19
+ original: linha,
20
+ sugerido: replaced,
21
+ linha: numeroLinha,
22
+ confianca: 90
23
+ });
24
+ }
25
+ }
5
26
  export function detectarCorrecoesCircleCI(src, _relPath) {
6
27
  const correcoes = [];
7
28
  const linhas = splitLines(src);
8
29
  linhas.forEach((linha, index) => {
9
- const numeroLinha = index + 1;
10
- if (/\bimage:\s*[^\s@]+:latest\b/.test(linha)) {
11
- correcoes.push({
12
- tipo: 'circleci-fix-latest-image',
13
- original: linha,
14
- sugerido: linha.replace(/:latest\b/, ':3.18'),
15
- linha: numeroLinha,
16
- confianca: 90
17
- });
18
- }
19
- if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
20
- const replaced = linha.replace(/['"][^'"]+['"]/, '"$VAR_NAME"');
21
- correcoes.push({
22
- tipo: 'circleci-use-context',
23
- original: linha,
24
- sugerido: replaced,
25
- linha: numeroLinha,
26
- confianca: 90
27
- });
28
- }
30
+ processarLinhaCircleCI(linha, index + 1, correcoes);
29
31
  });
30
32
  if (!/save_cache:/.test(src) && !/restore_cache:/.test(src)) {
31
33
  const firstJobLine = linhas.findIndex(l => /steps:/.test(l));
@@ -2,6 +2,28 @@
2
2
  import { messages } from '../../../core/messages/index.js';
3
3
  import { criarOcorrencia } from '../../../types/index.js';
4
4
  const MSG = messages.RelatorioCircleciMensagens;
5
+ function analisarJobsSequenciais(src, relPath, ocorrencias) {
6
+ const jobSections = src.split(/^jobs:/m);
7
+ if (jobSections.length <= 1) {
8
+ return 0;
9
+ }
10
+ const jobsBlock = jobSections[1].split(/^workflows:|^orbs:|^commands:|^executors:/m)[0];
11
+ const jobNames = jobsBlock.match(/^\s{2}\w[^:]+:/gm);
12
+ const jobCountEstimate = jobNames ? jobNames.length : 0;
13
+ if (jobCountEstimate > 2) {
14
+ const needsCount = (jobsBlock.match(/needs:/g) ?? []).length;
15
+ if (needsCount >= jobCountEstimate - 1) {
16
+ ocorrencias.push(criarOcorrencia({
17
+ tipo: 'circleci-sequential-test-jobs',
18
+ nivel: 'info',
19
+ mensagem: typeof MSG.sequentialTestJobs === 'function' ? (MSG.sequentialTestJobs)() : MSG.sequentialTestJobs,
20
+ relPath, linha: 1,
21
+ sugestao: typeof MSG.sequentialTestJobsSugestao === 'function' ? (MSG.sequentialTestJobsSugestao)() : MSG.sequentialTestJobsSugestao
22
+ }));
23
+ }
24
+ }
25
+ return jobCountEstimate;
26
+ }
5
27
  export const detectorCircleCIPerformance = {
6
28
  nome: 'detector-circleci-performance',
7
29
  categoria: 'performance',
@@ -73,25 +95,7 @@ export const detectorCircleCIPerformance = {
73
95
  }));
74
96
  }
75
97
  // 6. Sequential test jobs analysis
76
- let jobCountEstimate = 0;
77
- const jobSections = src.split(/^jobs:/m);
78
- if (jobSections.length > 1) {
79
- const jobsBlock = jobSections[1].split(/^workflows:|^orbs:|^commands:|^executors:/m)[0];
80
- const jobNames = jobsBlock.match(/^\s{2}\w[^:]+:/gm);
81
- jobCountEstimate = jobNames ? jobNames.length : 0;
82
- if (jobCountEstimate > 2) {
83
- const needsCount = (jobsBlock.match(/needs:/g) ?? []).length;
84
- if (needsCount >= jobCountEstimate - 1) {
85
- ocorrencias.push(criarOcorrencia({
86
- tipo: 'circleci-sequential-test-jobs',
87
- nivel: 'info',
88
- mensagem: typeof MSG.sequentialTestJobs === 'function' ? (MSG.sequentialTestJobs)() : MSG.sequentialTestJobs,
89
- relPath, linha: 1,
90
- sugestao: typeof MSG.sequentialTestJobsSugestao === 'function' ? (MSG.sequentialTestJobsSugestao)() : MSG.sequentialTestJobsSugestao
91
- }));
92
- }
93
- }
94
- }
98
+ const jobCountEstimate = analisarJobsSequenciais(src, relPath, ocorrencias);
95
99
  // 7. Single executor type for all jobs
96
100
  const executorTypes = new Set();
97
101
  const machineMatch = src.match(/machine:/g);
@@ -13,6 +13,83 @@ function _isProblemaWorkflowArray(val) {
13
13
  return Array.isArray(val);
14
14
  }
15
15
  const MSG = messages.RelatorioCircleciMensagens;
16
+ function processarLinhaSegurancaCircleCI(linha, index, linhas, relPath, ocorrencias) {
17
+ const numeroLinha = index + 1;
18
+ if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
19
+ ocorrencias.push(criarOcorrencia({
20
+ tipo: 'circleci-hardcoded-secret',
21
+ nivel: 'erro',
22
+ mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
23
+ relPath,
24
+ linha: numeroLinha,
25
+ sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
26
+ }));
27
+ }
28
+ if (/\bimage:\s*[^\s@]+:latest\b/.test(linha) || (/\bimage:\s*[^\s@:]+$/.test(linha.trim()) && linha.includes('image:'))) {
29
+ ocorrencias.push(criarOcorrencia({
30
+ tipo: 'circleci-image-latest',
31
+ nivel: 'aviso',
32
+ mensagem: 'Uso de imagem :latest ou sem tag específica',
33
+ relPath,
34
+ linha: numeroLinha,
35
+ sugestao: 'Use um digest (SHA) ou uma versão fixa para garantir segurança e reprodutibilidade'
36
+ }));
37
+ }
38
+ if (/resource_class\s*:\s*(medium|small)\s*$/.test(linha)) {
39
+ ocorrencias.push(criarOcorrencia({
40
+ tipo: 'circleci-default-resource-class',
41
+ nivel: 'info',
42
+ mensagem: typeof MSG.missingResourceClass === 'function' ? (MSG.missingResourceClass)() : MSG.missingResourceClass,
43
+ relPath,
44
+ linha: numeroLinha,
45
+ sugestao: typeof MSG.missingResourceClassSugestao === 'function' ? (MSG.missingResourceClassSugestao)() : MSG.missingResourceClassSugestao
46
+ }));
47
+ }
48
+ if (/context\s*:/.test(linha)) {
49
+ const nextLines = linhas.slice(index + 1, index + 5);
50
+ const hasBranchFilter = nextLines.some(l => /branches\s*:/.test(l));
51
+ if (!hasBranchFilter) {
52
+ ocorrencias.push(criarOcorrencia({
53
+ tipo: 'circleci-context-no-branch-restriction',
54
+ nivel: 'aviso',
55
+ mensagem: typeof MSG.unsafeContextUsage === 'function' ? (MSG.unsafeContextUsage)() : MSG.unsafeContextUsage,
56
+ relPath,
57
+ linha: numeroLinha,
58
+ sugestao: typeof MSG.unsafeContextUsageSugestao === 'function' ? (MSG.unsafeContextUsageSugestao)() : MSG.unsafeContextUsageSugestao
59
+ }));
60
+ }
61
+ }
62
+ if (/fingerprint\s*:\s*['"][^'"]+['"]/.test(linha) || /\bkey\s*:\s*['"][A-Za-z0-9+/]{20,}['"]/.test(linha)) {
63
+ ocorrencias.push(criarOcorrencia({
64
+ tipo: 'circleci-hardcoded-fingerprint',
65
+ nivel: 'erro',
66
+ mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
67
+ relPath,
68
+ linha: numeroLinha,
69
+ sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
70
+ }));
71
+ }
72
+ if (/checkout\s*:/.test(linha) && /path\s*:/.test(linha)) {
73
+ ocorrencias.push(criarOcorrencia({
74
+ tipo: 'circleci-custom-checkout-path',
75
+ nivel: 'info',
76
+ mensagem: typeof MSG.dangerousCheckoutPath === 'function' ? (MSG.dangerousCheckoutPath)() : MSG.dangerousCheckoutPath,
77
+ relPath,
78
+ linha: numeroLinha,
79
+ sugestao: typeof MSG.dangerousCheckoutPathSugestao === 'function' ? (MSG.dangerousCheckoutPathSugestao)() : MSG.dangerousCheckoutPathSugestao
80
+ }));
81
+ }
82
+ if (/\bsudo\s+/.test(linha)) {
83
+ ocorrencias.push(criarOcorrencia({
84
+ tipo: 'circleci-sudo-usage',
85
+ nivel: 'aviso',
86
+ mensagem: typeof MSG.sudoWithoutPasswordless === 'function' ? (MSG.sudoWithoutPasswordless)() : MSG.sudoWithoutPasswordless,
87
+ relPath,
88
+ linha: numeroLinha,
89
+ sugestao: typeof MSG.sudoWithoutPasswordlessSugestao === 'function' ? (MSG.sudoWithoutPasswordlessSugestao)() : MSG.sudoWithoutPasswordlessSugestao
90
+ }));
91
+ }
92
+ }
16
93
  export const detectorCircleCISecurity = {
17
94
  nome: 'detector-circleci-security',
18
95
  categoria: 'security',
@@ -25,88 +102,7 @@ export const detectorCircleCISecurity = {
25
102
  const ocorrencias = [];
26
103
  const linhas = splitLines(src);
27
104
  linhas.forEach((linha, index) => {
28
- const numeroLinha = index + 1;
29
- // 1. Secrets hardcoded em environment
30
- if (/\b(TOKEN|SECRET|PASSWORD|KEY|ACCESS_KEY)\b.*:\s*['"][^'"]+['"]/i.test(linha) && !/\$/.test(linha)) {
31
- ocorrencias.push(criarOcorrencia({
32
- tipo: 'circleci-hardcoded-secret',
33
- nivel: 'erro',
34
- mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
35
- relPath,
36
- linha: numeroLinha,
37
- sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
38
- }));
39
- }
40
- // 2. Uso de imagem :latest
41
- if (/\bimage:\s*[^\s@]+:latest\b/.test(linha) || (/\bimage:\s*[^\s@:]+$/.test(linha.trim()) && linha.includes('image:'))) {
42
- ocorrencias.push(criarOcorrencia({
43
- tipo: 'circleci-image-latest',
44
- nivel: 'aviso',
45
- mensagem: 'Uso de imagem :latest ou sem tag específica',
46
- relPath,
47
- linha: numeroLinha,
48
- sugestao: 'Use um digest (SHA) ou uma versão fixa para garantir segurança e reprodutibilidade'
49
- }));
50
- }
51
- // 3. Resource class analysis
52
- if (/resource_class\s*:\s*(medium|small)\s*$/.test(linha)) {
53
- ocorrencias.push(criarOcorrencia({
54
- tipo: 'circleci-default-resource-class',
55
- nivel: 'info',
56
- mensagem: typeof MSG.missingResourceClass === 'function' ? (MSG.missingResourceClass)() : MSG.missingResourceClass,
57
- relPath,
58
- linha: numeroLinha,
59
- sugestao: typeof MSG.missingResourceClassSugestao === 'function' ? (MSG.missingResourceClassSugestao)() : MSG.missingResourceClassSugestao
60
- }));
61
- }
62
- // 4. Context usage without branch restriction
63
- if (/context\s*:/.test(linha)) {
64
- const nextLines = linhas.slice(index + 1, index + 5);
65
- const hasBranchFilter = nextLines.some(l => /branches\s*:/.test(l));
66
- if (!hasBranchFilter) {
67
- ocorrencias.push(criarOcorrencia({
68
- tipo: 'circleci-context-no-branch-restriction',
69
- nivel: 'aviso',
70
- mensagem: typeof MSG.unsafeContextUsage === 'function' ? (MSG.unsafeContextUsage)() : MSG.unsafeContextUsage,
71
- relPath,
72
- linha: numeroLinha,
73
- sugestao: typeof MSG.unsafeContextUsageSugestao === 'function' ? (MSG.unsafeContextUsageSugestao)() : MSG.unsafeContextUsageSugestao
74
- }));
75
- }
76
- }
77
- // 5. Hardcoded key/fingerprint
78
- if (/fingerprint\s*:\s*['"][^'"]+['"]/.test(linha) || /\bkey\s*:\s*['"][A-Za-z0-9+/]{20,}['"]/.test(linha)) {
79
- ocorrencias.push(criarOcorrencia({
80
- tipo: 'circleci-hardcoded-fingerprint',
81
- nivel: 'erro',
82
- mensagem: typeof MSG.hardcodedKeyInConfig === 'function' ? (MSG.hardcodedKeyInConfig)() : MSG.hardcodedKeyInConfig,
83
- relPath,
84
- linha: numeroLinha,
85
- sugestao: typeof MSG.hardcodedKeyInConfigSugestao === 'function' ? (MSG.hardcodedKeyInConfigSugestao)() : MSG.hardcodedKeyInConfigSugestao
86
- }));
87
- }
88
- // 6. Dangerous checkout path
89
- if (/checkout\s*:/.test(linha) && /path\s*:/.test(linha)) {
90
- ocorrencias.push(criarOcorrencia({
91
- tipo: 'circleci-custom-checkout-path',
92
- nivel: 'info',
93
- mensagem: typeof MSG.dangerousCheckoutPath === 'function' ? (MSG.dangerousCheckoutPath)() : MSG.dangerousCheckoutPath,
94
- relPath,
95
- linha: numeroLinha,
96
- sugestao: typeof MSG.dangerousCheckoutPathSugestao === 'function' ? (MSG.dangerousCheckoutPathSugestao)() : MSG.dangerousCheckoutPathSugestao
97
- }));
98
- }
99
- // 7. Sudo usage
100
- if (/\bsudo\s+/.test(linha)) {
101
- ocorrencias.push(criarOcorrencia({
102
- tipo: 'circleci-sudo-usage',
103
- nivel: 'aviso',
104
- mensagem: typeof MSG.sudoWithoutPasswordless === 'function' ? (MSG.sudoWithoutPasswordless)() : MSG.sudoWithoutPasswordless,
105
- relPath,
106
- linha: numeroLinha,
107
- sugestao: typeof MSG.sudoWithoutPasswordlessSugestao === 'function' ? (MSG.sudoWithoutPasswordlessSugestao)() : MSG.sudoWithoutPasswordlessSugestao
108
- }));
109
- }
105
+ processarLinhaSegurancaCircleCI(linha, index, linhas, relPath, ocorrencias);
110
106
  });
111
107
  return ocorrencias;
112
108
  }
@@ -127,23 +127,7 @@ export const detectorCircleCIEstrutura = {
127
127
  }
128
128
  // Orphaned job detection
129
129
  if (jobs.jobs && workflows) {
130
- const wfValues = Object.values(workflows);
131
- const usedJobs = new Set();
132
- for (const wf of wfValues) {
133
- if (typeof wf === 'object') {
134
- const wfJobs = wf.jobs;
135
- if (Array.isArray(wfJobs)) {
136
- for (const wfJob of wfJobs) {
137
- if (typeof wfJob === 'string') {
138
- usedJobs.add(wfJob);
139
- }
140
- else if (typeof wfJob === 'object') {
141
- Object.keys(wfJob).forEach(k => usedJobs.add(k));
142
- }
143
- }
144
- }
145
- }
146
- }
130
+ const usedJobs = collectUsedJobsFromWorkflows(Object.values(workflows));
147
131
  for (const jn of Object.keys(jobs.jobs)) {
148
132
  if (!usedJobs.has(jn)) {
149
133
  ocorrencias.push(criarOcorrencia({
@@ -217,3 +201,22 @@ export const detectorCircleCIEstrutura = {
217
201
  return ocorrencias;
218
202
  }
219
203
  };
204
+ function collectUsedJobsFromWorkflows(wfValues) {
205
+ const usedJobs = new Set();
206
+ for (const wf of wfValues) {
207
+ if (typeof wf === 'object' && wf !== null) {
208
+ const wfJobs = wf.jobs;
209
+ if (Array.isArray(wfJobs)) {
210
+ for (const wfJob of wfJobs) {
211
+ if (typeof wfJob === 'string') {
212
+ usedJobs.add(wfJob);
213
+ }
214
+ else if (typeof wfJob === 'object' && wfJob !== null) {
215
+ Object.keys(wfJob).forEach(k => usedJobs.add(k));
216
+ }
217
+ }
218
+ }
219
+ }
220
+ }
221
+ return usedJobs;
222
+ }
@@ -10,6 +10,7 @@ export class ConversorGhaToGitlab extends ConversorBase {
10
10
  de = 'github-actions';
11
11
  para = 'gitlab-ci';
12
12
  async converter(conteudo) {
13
+ await Promise.resolve();
13
14
  const avisos = [];
14
15
  const erros = [];
15
16
  try {
@@ -49,21 +50,7 @@ export class ConversorGhaToGitlab extends ConversorBase {
49
50
  if (job.steps && Array.isArray(job.steps)) {
50
51
  const steps = job.steps;
51
52
  for (const step of steps) {
52
- if (step.run) {
53
- // Limpar comandos de run (remover multi-line pipe se necessário)
54
- const cmd = step.run.trim();
55
- gitlabJob.script.push(cmd);
56
- }
57
- else if (step.uses) {
58
- // Tratamento especial para checkout
59
- if (step.uses.includes('actions/checkout')) {
60
- gitlabJob.script.push('# Checkout automático pelo GitLab Runner');
61
- }
62
- else {
63
- avisos.push(`Job '${jobId}': Action '${step.uses}' detectada. Considere usar um componente equivalente do GitLab ou um script customizado.`);
64
- gitlabJob.script.push(`# TODO: Implementar lógica da action: ${step.uses}`);
65
- }
66
- }
53
+ processWorkflowStep(step, jobId, gitlabJob, avisos);
67
54
  }
68
55
  }
69
56
  // Mapear dependências (needs)
@@ -85,3 +72,18 @@ export class ConversorGhaToGitlab extends ConversorBase {
85
72
  }
86
73
  }
87
74
  }
75
+ function processWorkflowStep(step, jobId, gitlabJob, avisos) {
76
+ if (step.run) {
77
+ const cmd = step.run.trim();
78
+ gitlabJob.script.push(cmd);
79
+ }
80
+ else if (step.uses) {
81
+ if (step.uses.includes('actions/checkout')) {
82
+ gitlabJob.script.push('# Checkout automático pelo GitLab Runner');
83
+ }
84
+ else {
85
+ avisos.push(`Job '${jobId}': Action '${step.uses}' detectada. Considere usar um componente equivalente do GitLab ou um script customizado.`);
86
+ gitlabJob.script.push(`# TODO: Implementar lógica da action: ${step.uses}`);
87
+ }
88
+ }
89
+ }
@@ -24,39 +24,36 @@ export const analistaQuickFixes = {
24
24
  const uniqueFixes = quickFixes.filter((fix, index, arr) => arr.findIndex(arquivo => arquivo.id === fix.id) === index);
25
25
  for (const fixResultado of uniqueFixes) {
26
26
  for (const match of fixResultado.matches) {
27
- // Calcular linha aproximada do match
28
- const beforeMatch = src.substring(0, match.index ?? 0);
29
- const linha = beforeMatch.split('\n').length;
30
- // Gerar preview da correção
31
- const previewCorrecao = fixResultado.fix(match, src);
32
- const originalLine = src.split('\n')[linha - 1] || '';
33
- const fixedLine = previewCorrecao.split('\n')[linha - 1] || '';
34
- // Criar sugestão com preview mais detalhado
35
- const sugestao = [fixResultado.description, '', `[CHAVE-INGLESA] Correção sugerida:`, `[ERRO] Antes: ${originalLine.trim()}`, `[OK] Depois: ${fixedLine.trim()}`, '', `Confiança: ${fixResultado.confidence}%`, `Categoria: ${fixResultado.category}`, `ID do Fix: ${fixResultado.id}`].join('\n');
36
- // Mapear categoria para nível
37
- const nivel = mapearCategoriaNivel(fixResultado.category);
38
- // Criar ocorrência base
39
- const ocorrencia = criarOcorrencia({
40
- tipo: 'auto-fix-disponivel',
41
- nivel,
42
- mensagem: fixResultado.title,
43
- relPath,
44
- linha
45
- });
46
- // Adicionar campos extras como propriedades do objeto genérico
47
- const ocorrenciaGenerica = ocorrencia; // OcorrenciaGenerica allows extra properties
48
- ocorrenciaGenerica.sugestao = sugestao;
49
- ocorrenciaGenerica.quickFixId = fixResultado.id;
50
- ocorrenciaGenerica.confidence = fixResultado.confidence;
51
- ocorrenciaGenerica.category = fixResultado.category;
52
- ocorrenciaGenerica.matchIndex = match.index;
53
- ocorrenciaGenerica.matchLength = match[0].length;
54
- ocorrencias.push(ocorrencia);
27
+ processQuickFixMatch(match, fixResultado, src, relPath, ocorrencias);
55
28
  }
56
29
  }
57
30
  return ocorrencias;
58
31
  }
59
32
  };
33
+ function processQuickFixMatch(match, fixResultado, src, relPath, ocorrencias) {
34
+ const beforeMatch = src.substring(0, match.index ?? 0);
35
+ const linha = beforeMatch.split('\n').length;
36
+ const previewCorrecao = fixResultado.fix(match, src);
37
+ const originalLine = src.split('\n')[linha - 1] || '';
38
+ const fixedLine = previewCorrecao.split('\n')[linha - 1] || '';
39
+ const sugestao = [fixResultado.description, '', `[CHAVE-INGLESA] Correção sugerida:`, `[ERRO] Antes: ${originalLine.trim()}`, `[OK] Depois: ${fixedLine.trim()}`, '', `Confiança: ${fixResultado.confidence}%`, `Categoria: ${fixResultado.category}`, `ID do Fix: ${fixResultado.id}`].join('\n');
40
+ const nivel = mapearCategoriaNivel(fixResultado.category);
41
+ const ocorrencia = criarOcorrencia({
42
+ tipo: 'auto-fix-disponivel',
43
+ nivel,
44
+ mensagem: fixResultado.title,
45
+ relPath,
46
+ linha
47
+ });
48
+ const ocorrenciaGenerica = ocorrencia;
49
+ ocorrenciaGenerica.sugestao = sugestao;
50
+ ocorrenciaGenerica.quickFixId = fixResultado.id;
51
+ ocorrenciaGenerica.confidence = fixResultado.confidence;
52
+ ocorrenciaGenerica.category = fixResultado.category;
53
+ ocorrenciaGenerica.matchIndex = match.index;
54
+ ocorrenciaGenerica.matchLength = match[0].length;
55
+ ocorrencias.push(ocorrencia);
56
+ }
60
57
  function mapearCategoriaNivel(category) {
61
58
  switch (category) {
62
59
  case 'security':