pumuki-ast-hooks 5.5.53 → 5.5.54

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.
@@ -207,6 +207,9 @@ function runBackendIntelligence(project, findings, platform) {
207
207
 
208
208
  if (platformOf(filePath) !== "backend") return;
209
209
 
210
+ const normalizedBackendPath = filePath.replace(/\\/g, '/');
211
+ const isRealBackendAppFile = normalizedBackendPath.includes('/apps/backend/') || normalizedBackendPath.includes('apps/backend/');
212
+
210
213
  // NO excluir archivos AST - la librería debe auto-auditarse para detectar God classes masivas
211
214
 
212
215
  const fullText = sf.getFullText();
@@ -272,7 +275,7 @@ function runBackendIntelligence(project, findings, platform) {
272
275
  fullTextUpper.includes("ConfigService") ||
273
276
  usesEnvHelper;
274
277
  const hasConfigUsage = /process\.env\b|ConfigService|env\.get(Bool|Number)?\(|\bconfig\s*[\.\[]/i.test(sf.getFullText());
275
- if (!hasEnvSpecific && hasConfigUsage && !isTestFile(filePath)) {
278
+ if (isRealBackendAppFile && !hasEnvSpecific && hasConfigUsage && !isTestFile(filePath)) {
276
279
  pushFinding("backend.config.missing_env_separation", "info", sf, sf, "Missing environment-specific configuration - consider NODE_ENV or ConfigService", findings);
277
280
  }
278
281
 
@@ -287,6 +290,10 @@ function runBackendIntelligence(project, findings, platform) {
287
290
  analyzeGodClasses(sf, findings, { SyntaxKind, pushFinding, godClassBaseline });
288
291
  }
289
292
 
293
+ if (!isRealBackendAppFile) {
294
+ return;
295
+ }
296
+
290
297
  sf.getDescendantsOfKind(SyntaxKind.ClassDeclaration).forEach((cls) => {
291
298
  const name = cls.getName();
292
299
  if (name && /Entity|Model|Domain/.test(name)) {
@@ -349,16 +356,12 @@ function runBackendIntelligence(project, findings, platform) {
349
356
  }
350
357
  });
351
358
 
352
- const filePathNormalizedForMetrics = filePath.replace(/\\/g, "/");
353
- const filePathNormalizedForMetricsLower = filePathNormalizedForMetrics.toLowerCase();
354
- const isInternalAstToolingFileForMetrics = filePathNormalizedForMetricsLower.includes("/infrastructure/ast/") || filePathNormalizedForMetricsLower.includes("infrastructure/ast/") || filePathNormalizedForMetricsLower.includes("/scripts/hooks-system/infrastructure/ast/");
355
-
356
359
  const fullTextLower = fullText.toLowerCase();
357
360
  const hasMetrics = fullTextLower.includes("micrometer") || fullTextLower.includes("prometheus") ||
358
361
  fullTextLower.includes("actuator") || fullTextLower.includes("metrics");
359
362
  const looksLikeServiceOrController = fullTextLower.includes("controller") || fullTextLower.includes("service");
360
363
 
361
- if (!isInternalAstToolingFileForMetrics && !hasMetrics && looksLikeServiceOrController) {
364
+ if (isRealBackendAppFile && !hasMetrics && looksLikeServiceOrController) {
362
365
  pushFinding("backend.metrics.missing_prometheus", "info", sf, sf, "Missing application metrics - consider Spring Boot Actuator or Micrometer for monitoring", findings);
363
366
  }
364
367
 
@@ -478,7 +481,7 @@ function runBackendIntelligence(project, findings, platform) {
478
481
  sf.getDescendantsOfKind(SyntaxKind.ThrowStatement).forEach((throwStmt) => {
479
482
  const expr = throwStmt.getExpression();
480
483
  if (!expr) return;
481
- const exprText = expr.getText();
484
+ const exprText = throwStmt.getExpression().getText();
482
485
  if (exprText.includes("Error(") || exprText.includes("Exception(")) {
483
486
  const isCustom = exprText.includes("Exception") &&
484
487
  (exprText.includes("Validation") ||
@@ -486,7 +489,9 @@ function runBackendIntelligence(project, findings, platform) {
486
489
  exprText.includes("Unauthorized") ||
487
490
  exprText.includes("Forbidden"));
488
491
  if (!isCustom) {
489
- pushFinding("backend.error.custom_exceptions", "info", sf, throwStmt, "Generic Error/Exception thrown - create custom exception classes for better error handling", findings);
492
+ if (isRealBackendAppFile) {
493
+ pushFinding("backend.error.custom_exceptions", "info", sf, throwStmt, "Generic Error/Exception thrown - create custom exception classes for better error handling", findings);
494
+ }
490
495
  }
491
496
  }
492
497
  });
@@ -148,15 +148,22 @@ function runCommonIntelligence(project, findings) {
148
148
  sf.getDescendantsOfKind(SyntaxKind.CatchClause).forEach((clause) => {
149
149
  const block = typeof clause.getBlock === 'function' ? clause.getBlock() : null;
150
150
  const statements = block && typeof block.getStatements === 'function' ? block.getStatements() : [];
151
+
151
152
  if ((statements || []).length === 0) {
152
- pushFinding(
153
- 'common.error.empty_catch',
154
- 'critical',
155
- sf,
156
- clause,
157
- 'Empty catch block detected - always handle errors (log, rethrow, wrap, or return Result)',
158
- findings
159
- );
153
+ const blockText = block ? block.getText() : '';
154
+ const hasTestAssertions = /XCTFail|XCTAssert|guard\s+case|expect\(|assert/i.test(blockText);
155
+ const hasErrorHandling = /throw|console\.|logger\.|log\(|print\(/i.test(blockText);
156
+
157
+ if (!hasTestAssertions && !hasErrorHandling) {
158
+ pushFinding(
159
+ 'common.error.empty_catch',
160
+ 'critical',
161
+ sf,
162
+ clause,
163
+ 'Empty catch block detected - always handle errors (log, rethrow, wrap, or return Result)',
164
+ findings
165
+ );
166
+ }
160
167
  }
161
168
  });
162
169
 
@@ -0,0 +1,385 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const glob = require('glob');
4
+
5
+ function checkFastfileExists(ctx) {
6
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
7
+ if (!fs.existsSync(fastfilePath)) {
8
+ ctx.pushFinding(ctx.findings, {
9
+ ruleId: 'ios.cicd.missing_fastfile',
10
+ severity: 'medium',
11
+ message: 'Proyecto iOS sin Fastfile. Fastlane automatiza builds, tests y deployments.',
12
+ filePath: 'PROJECT_ROOT',
13
+ line: 1,
14
+ suggestion: `Inicializar Fastlane:
15
+
16
+ fastlane init
17
+
18
+ Esto crea:
19
+ - fastlane/Fastfile
20
+ - fastlane/Appfile
21
+ - fastlane/Matchfile`
22
+ });
23
+ return;
24
+ }
25
+ checkFastfileLanes(ctx, fastfilePath);
26
+ }
27
+
28
+ function checkFastfileLanes(ctx, fastfilePath) {
29
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
30
+ const requiredLanes = ['test', 'beta', 'release'];
31
+ const existingLanes = content.match(/lane\s+:(\w+)/g)?.map(l => l.match(/:(\w+)/)?.[1]) || [];
32
+ const missingLanes = requiredLanes.filter(lane => !existingLanes.includes(lane));
33
+
34
+ if (missingLanes.length > 0) {
35
+ ctx.pushFinding(ctx.findings, {
36
+ ruleId: 'ios.cicd.fastfile_missing_lanes',
37
+ severity: 'medium',
38
+ message: `Fastfile sin lanes esenciales: ${missingLanes.join(', ')}`,
39
+ filePath: fastfilePath,
40
+ line: 1,
41
+ suggestion: `Añadir lanes:
42
+
43
+ lane :test do
44
+ scan
45
+ end
46
+
47
+ lane :beta do
48
+ build_app
49
+ upload_to_testflight
50
+ end
51
+
52
+ lane :release do
53
+ build_app
54
+ upload_to_app_store
55
+ end`
56
+ });
57
+ }
58
+
59
+ if (!content.includes('increment_build_number') && !content.includes('increment_version_number')) {
60
+ ctx.pushFinding(ctx.findings, {
61
+ ruleId: 'ios.cicd.missing_version_increment',
62
+ severity: 'medium',
63
+ message: 'Fastfile sin incremento automático de versión/build.',
64
+ filePath: fastfilePath,
65
+ line: 1
66
+ });
67
+ }
68
+ }
69
+
70
+ function checkGitHubActionsWorkflow(ctx) {
71
+ const workflowPath = path.join(ctx.projectRoot, '.github/workflows');
72
+
73
+ if (!fs.existsSync(workflowPath)) {
74
+ ctx.pushFinding(ctx.findings, {
75
+ ruleId: 'ios.cicd.missing_github_actions',
76
+ severity: 'low',
77
+ message: 'Proyecto sin GitHub Actions workflows. Automatizar CI/CD.',
78
+ filePath: 'PROJECT_ROOT',
79
+ line: 1,
80
+ suggestion: 'Crear .github/workflows/ios-ci.yml para tests automáticos'
81
+ });
82
+ return;
83
+ }
84
+
85
+ const workflows = glob.sync('*.yml', { cwd: workflowPath, absolute: true });
86
+ const iosWorkflows = workflows.filter(w => {
87
+ const content = fs.readFileSync(w, 'utf-8');
88
+ return content.includes('macos') || content.includes('ios') || content.includes('xcodebuild');
89
+ });
90
+
91
+ if (iosWorkflows.length === 0) {
92
+ ctx.pushFinding(ctx.findings, {
93
+ ruleId: 'ios.cicd.no_ios_workflow',
94
+ severity: 'medium',
95
+ message: 'GitHub Actions sin workflow de iOS.',
96
+ filePath: '.github/workflows/',
97
+ line: 1
98
+ });
99
+ return;
100
+ }
101
+
102
+ iosWorkflows.forEach(workflow => {
103
+ const content = fs.readFileSync(workflow, 'utf-8');
104
+
105
+ if (!content.includes('xcodebuild test')) {
106
+ ctx.pushFinding(ctx.findings, {
107
+ ruleId: 'ios.cicd.workflow_missing_tests',
108
+ severity: 'high',
109
+ message: 'Workflow de iOS sin tests automáticos.',
110
+ filePath: workflow,
111
+ line: 1
112
+ });
113
+ }
114
+
115
+ if (!content.includes('fastlane')) {
116
+ ctx.pushFinding(ctx.findings, {
117
+ ruleId: 'ios.cicd.workflow_without_fastlane',
118
+ severity: 'low',
119
+ message: 'Workflow sin Fastlane. Considerar para simplificar pipeline.',
120
+ filePath: workflow,
121
+ line: 1
122
+ });
123
+ }
124
+ });
125
+ }
126
+
127
+ function checkTestFlightConfiguration(ctx) {
128
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
129
+ if (!fs.existsSync(fastfilePath)) return;
130
+
131
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
132
+ if (content.includes('beta') && !content.includes('upload_to_testflight')) {
133
+ ctx.pushFinding(ctx.findings, {
134
+ ruleId: 'ios.cicd.beta_without_testflight',
135
+ severity: 'medium',
136
+ message: 'Lane beta sin upload_to_testflight. Automatizar distribución.',
137
+ filePath: fastfilePath,
138
+ line: 1
139
+ });
140
+ }
141
+
142
+ if (content.includes('upload_to_testflight') && !content.includes('changelog')) {
143
+ ctx.pushFinding(ctx.findings, {
144
+ ruleId: 'ios.cicd.testflight_without_changelog',
145
+ severity: 'low',
146
+ message: 'TestFlight sin changelog. Añadir notas de release.',
147
+ filePath: fastfilePath,
148
+ line: 1
149
+ });
150
+ }
151
+ }
152
+
153
+ function checkBuildConfiguration(ctx) {
154
+ const projectFiles = glob.sync('**/*.xcodeproj/project.pbxproj', {
155
+ cwd: ctx.projectRoot,
156
+ absolute: true
157
+ });
158
+ if (projectFiles.length === 0) return;
159
+
160
+ projectFiles.forEach(projectFile => {
161
+ const content = fs.readFileSync(projectFile, 'utf-8');
162
+ const configurations = content.match(/buildConfiguration\s*=\s*(\w+)/g) || [];
163
+ const hasDebug = configurations.some(c => c.includes('Debug'));
164
+ const hasRelease = configurations.some(c => c.includes('Release'));
165
+ const hasStaging = configurations.some(c => c.includes('Staging'));
166
+
167
+ if (!hasStaging && (hasDebug && hasRelease)) {
168
+ ctx.pushFinding(ctx.findings, {
169
+ ruleId: 'ios.cicd.missing_staging_config',
170
+ severity: 'low',
171
+ message: 'Sin configuración Staging. Útil para testing pre-production.',
172
+ filePath: projectFile,
173
+ line: 1
174
+ });
175
+ }
176
+ });
177
+ }
178
+
179
+ function checkCertificateManagement(ctx) {
180
+ const matchfilePath = path.join(ctx.projectRoot, 'fastlane/Matchfile');
181
+ if (fs.existsSync(matchfilePath)) return;
182
+
183
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
184
+ if (fs.existsSync(fastfilePath)) {
185
+ ctx.pushFinding(ctx.findings, {
186
+ ruleId: 'ios.cicd.missing_match_config',
187
+ severity: 'medium',
188
+ message: 'Fastlane sin Match para gestión de certificados.',
189
+ filePath: 'fastlane/',
190
+ line: 1,
191
+ suggestion: 'fastlane match init para gestionar certificados en equipo'
192
+ });
193
+ }
194
+ }
195
+
196
+ function checkCodeSigningConfiguration(ctx) {
197
+ const projectFiles = glob.sync('**/*.xcodeproj/project.pbxproj', {
198
+ cwd: ctx.projectRoot,
199
+ absolute: true
200
+ });
201
+
202
+ projectFiles.forEach(projectFile => {
203
+ const content = fs.readFileSync(projectFile, 'utf-8');
204
+ if (content.includes('CODE_SIGN_STYLE = Manual')) {
205
+ ctx.pushFinding(ctx.findings, {
206
+ ruleId: 'ios.cicd.manual_code_signing',
207
+ severity: 'low',
208
+ message: 'Code signing manual. Considerar Automatic o Match para CI/CD.',
209
+ filePath: projectFile,
210
+ line: 1
211
+ });
212
+ }
213
+ });
214
+ }
215
+
216
+ function checkAutomatedTesting(ctx) {
217
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
218
+ if (!fs.existsSync(fastfilePath)) return;
219
+
220
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
221
+ if (!content.includes('scan') && !content.includes('run_tests')) {
222
+ ctx.pushFinding(ctx.findings, {
223
+ ruleId: 'ios.cicd.no_automated_tests',
224
+ severity: 'high',
225
+ message: 'Fastlane sin tests automatizados. Añadir scan action.',
226
+ filePath: fastfilePath,
227
+ line: 1,
228
+ suggestion: `lane :test do
229
+ scan(scheme: "MyApp")
230
+ end`
231
+ });
232
+ }
233
+ }
234
+
235
+ function checkVersionBumping(ctx) {
236
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
237
+ if (!fs.existsSync(fastfilePath)) return;
238
+
239
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
240
+ if (content.includes('upload_to') && !content.includes('increment_build_number')) {
241
+ ctx.pushFinding(ctx.findings, {
242
+ ruleId: 'ios.cicd.missing_build_increment',
243
+ severity: 'medium',
244
+ message: 'Deployment sin incremento automático de build number.',
245
+ filePath: fastfilePath,
246
+ line: 1
247
+ });
248
+ }
249
+ }
250
+
251
+ function checkReleaseNotes(ctx) {
252
+ const changelogPath = path.join(ctx.projectRoot, 'CHANGELOG.md');
253
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
254
+ if (!fs.existsSync(fastfilePath) || fs.existsSync(changelogPath)) return;
255
+
256
+ ctx.pushFinding(ctx.findings, {
257
+ ruleId: 'ios.cicd.missing_changelog',
258
+ severity: 'low',
259
+ message: 'Proyecto sin CHANGELOG.md. Documentar cambios para releases.',
260
+ filePath: 'PROJECT_ROOT',
261
+ line: 1
262
+ });
263
+ }
264
+
265
+ function checkSlackNotifications(ctx) {
266
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
267
+ if (!fs.existsSync(fastfilePath)) return;
268
+
269
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
270
+ if (content.includes('upload_to') && !content.includes('slack')) {
271
+ ctx.pushFinding(ctx.findings, {
272
+ ruleId: 'ios.cicd.missing_notifications',
273
+ severity: 'low',
274
+ message: 'Deployment sin notificaciones. Añadir Slack para avisar al equipo.',
275
+ filePath: fastfilePath,
276
+ line: 1
277
+ });
278
+ }
279
+ }
280
+
281
+ function checkMatchConfiguration(ctx) {
282
+ const matchfilePath = path.join(ctx.projectRoot, 'fastlane/Matchfile');
283
+ if (!fs.existsSync(matchfilePath)) return;
284
+
285
+ const content = fs.readFileSync(matchfilePath, 'utf-8');
286
+ if (!content.includes('git_url')) {
287
+ ctx.pushFinding(ctx.findings, {
288
+ ruleId: 'ios.cicd.match_missing_git_url',
289
+ severity: 'high',
290
+ message: 'Matchfile sin git_url. Match requiere repositorio para certificados.',
291
+ filePath: matchfilePath,
292
+ line: 1
293
+ });
294
+ }
295
+ }
296
+
297
+ function checkGymConfiguration(ctx) {
298
+ const gymfilePath = path.join(ctx.projectRoot, 'fastlane/Gymfile');
299
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
300
+ if (!fs.existsSync(fastfilePath) || fs.existsSync(gymfilePath)) return;
301
+
302
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
303
+ if (content.includes('build_app') || content.includes('gym')) {
304
+ ctx.pushFinding(ctx.findings, {
305
+ ruleId: 'ios.cicd.missing_gymfile',
306
+ severity: 'low',
307
+ message: 'build_app sin Gymfile. Considerar para centralizar configuración de build.',
308
+ filePath: 'fastlane/',
309
+ line: 1
310
+ });
311
+ }
312
+ }
313
+
314
+ function checkScanConfiguration(ctx) {
315
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
316
+ if (!fs.existsSync(fastfilePath)) return;
317
+
318
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
319
+ if (content.includes('scan') && !content.includes('code_coverage')) {
320
+ ctx.pushFinding(ctx.findings, {
321
+ ruleId: 'ios.cicd.scan_without_coverage',
322
+ severity: 'low',
323
+ message: 'scan sin code_coverage: true. Activar para métricas.',
324
+ filePath: fastfilePath,
325
+ line: 1,
326
+ suggestion: 'scan(code_coverage: true, scheme: "MyApp")'
327
+ });
328
+ }
329
+ }
330
+
331
+ function checkPilotConfiguration(ctx) {
332
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
333
+ if (!fs.existsSync(fastfilePath)) return;
334
+
335
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
336
+ if (!content.includes('pilot') && !content.includes('upload_to_testflight')) return;
337
+
338
+ if (!content.includes('changelog') && !content.includes('whats_new')) {
339
+ ctx.pushFinding(ctx.findings, {
340
+ ruleId: 'ios.cicd.pilot_missing_changelog',
341
+ severity: 'low',
342
+ message: 'TestFlight upload sin changelog/whats_new.',
343
+ filePath: fastfilePath,
344
+ line: 1
345
+ });
346
+ }
347
+ }
348
+
349
+ function checkAppStoreMetadata(ctx) {
350
+ const metadataPath = path.join(ctx.projectRoot, 'fastlane/metadata');
351
+ if (fs.existsSync(metadataPath)) return;
352
+
353
+ const fastfilePath = path.join(ctx.projectRoot, 'fastlane/Fastfile');
354
+ if (!fs.existsSync(fastfilePath)) return;
355
+
356
+ const content = fs.readFileSync(fastfilePath, 'utf-8');
357
+ if (content.includes('upload_to_app_store') || content.includes('deliver')) {
358
+ ctx.pushFinding(ctx.findings, {
359
+ ruleId: 'ios.cicd.missing_metadata',
360
+ severity: 'low',
361
+ message: 'Upload a App Store sin metadata/ folder. Versionar descripciones y screenshots.',
362
+ filePath: 'fastlane/',
363
+ line: 1,
364
+ suggestion: 'fastlane deliver init para crear estructura metadata/'
365
+ });
366
+ }
367
+ }
368
+
369
+ module.exports = {
370
+ checkFastfileExists,
371
+ checkGitHubActionsWorkflow,
372
+ checkTestFlightConfiguration,
373
+ checkBuildConfiguration,
374
+ checkCertificateManagement,
375
+ checkCodeSigningConfiguration,
376
+ checkAutomatedTesting,
377
+ checkVersionBumping,
378
+ checkReleaseNotes,
379
+ checkSlackNotifications,
380
+ checkMatchConfiguration,
381
+ checkGymConfiguration,
382
+ checkScanConfiguration,
383
+ checkPilotConfiguration,
384
+ checkAppStoreMetadata,
385
+ };