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.
- package/README.md +0 -9
- package/docs/MCP_SERVERS.md +16 -2
- package/docs/RELEASE_NOTES.md +34 -0
- package/package.json +1 -1
- package/scripts/hooks-system/.audit_tmp/hook-metrics.jsonl +40 -0
- package/scripts/hooks-system/application/services/installation/McpConfigurator.js +9 -205
- package/scripts/hooks-system/application/services/installation/mcp/McpGlobalConfigCleaner.js +49 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpProjectConfigWriter.js +59 -0
- package/scripts/hooks-system/application/services/installation/mcp/McpServerConfigBuilder.js +103 -0
- package/scripts/hooks-system/infrastructure/ast/backend/ast-backend.js +13 -8
- package/scripts/hooks-system/infrastructure/ast/common/ast-common.js +15 -8
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDChecks.js +385 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSCICDRules.js +38 -408
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMChecks.js +408 -0
- package/scripts/hooks-system/infrastructure/ast/ios/analyzers/iOSSPMRules.js +36 -442
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenExtractor.js +146 -0
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenParser.js +22 -190
- package/scripts/hooks-system/infrastructure/ast/ios/parsers/SourceKittenRunner.js +62 -0
- package/scripts/hooks-system/infrastructure/mcp/ast-intelligence-automation.js +27 -9
|
@@ -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 (
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
+
};
|