create-qa-architect 5.0.7 → 5.4.3
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/.github/workflows/auto-release.yml +49 -0
- package/.github/workflows/quality.yml +11 -11
- package/.github/workflows/shell-ci.yml.example +82 -0
- package/.github/workflows/shell-quality.yml.example +148 -0
- package/README.md +165 -12
- package/config/shell-ci.yml +82 -0
- package/config/shell-quality.yml +148 -0
- package/docs/ADOPTION-SUMMARY.md +41 -0
- package/docs/ARCHITECTURE-REVIEW.md +67 -0
- package/docs/ARCHITECTURE.md +29 -45
- package/docs/CI-COST-ANALYSIS.md +323 -0
- package/docs/CODE-REVIEW.md +100 -0
- package/docs/REQUIREMENTS.md +148 -0
- package/docs/SECURITY-AUDIT.md +68 -0
- package/docs/test-trace-matrix.md +28 -0
- package/eslint.config.cjs +2 -0
- package/lib/commands/analyze-ci.js +616 -0
- package/lib/commands/deps.js +293 -0
- package/lib/commands/index.js +29 -0
- package/lib/commands/validate.js +85 -0
- package/lib/config-validator.js +28 -45
- package/lib/error-reporter.js +14 -2
- package/lib/github-api.js +138 -13
- package/lib/license-signing.js +125 -0
- package/lib/license-validator.js +359 -71
- package/lib/licensing.js +434 -106
- package/lib/package-utils.js +9 -9
- package/lib/prelaunch-validator.js +828 -0
- package/lib/project-maturity.js +58 -6
- package/lib/quality-tools-generator.js +495 -0
- package/lib/result-types.js +112 -0
- package/lib/security-enhancements.js +1 -1
- package/lib/smart-strategy-generator.js +46 -10
- package/lib/telemetry.js +1 -1
- package/lib/template-loader.js +52 -19
- package/lib/ui-helpers.js +1 -1
- package/lib/validation/cache-manager.js +36 -6
- package/lib/validation/config-security.js +100 -33
- package/lib/validation/index.js +68 -97
- package/lib/validation/workflow-validation.js +28 -7
- package/package.json +4 -6
- package/scripts/check-test-coverage.sh +46 -0
- package/scripts/validate-claude-md.js +80 -0
- package/setup.js +923 -301
- package/create-saas-monetization.js +0 -1513
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency monitoring command handler
|
|
3
|
+
*
|
|
4
|
+
* Extracted from setup.js to improve maintainability.
|
|
5
|
+
* Handles --deps, --dependency-monitoring commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
hasNpmProject,
|
|
13
|
+
generateBasicDependabotConfig,
|
|
14
|
+
writeBasicDependabotConfig,
|
|
15
|
+
} = require('../dependency-monitoring-basic')
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
generatePremiumDependabotConfig,
|
|
19
|
+
writePremiumDependabotConfig,
|
|
20
|
+
} = require('../dependency-monitoring-premium')
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
getLicenseInfo,
|
|
24
|
+
showUpgradeMessage,
|
|
25
|
+
checkUsageCaps,
|
|
26
|
+
incrementUsage,
|
|
27
|
+
} = require('../licensing')
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect Python project
|
|
31
|
+
* @param {string} projectPath - Path to project
|
|
32
|
+
* @returns {boolean} True if Python project detected
|
|
33
|
+
*/
|
|
34
|
+
function detectPythonProject(projectPath) {
|
|
35
|
+
const pythonFiles = [
|
|
36
|
+
'pyproject.toml',
|
|
37
|
+
'requirements.txt',
|
|
38
|
+
'setup.py',
|
|
39
|
+
'Pipfile',
|
|
40
|
+
]
|
|
41
|
+
return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Detect Rust project
|
|
46
|
+
* @param {string} projectPath - Path to project
|
|
47
|
+
* @returns {boolean} True if Rust project detected
|
|
48
|
+
*/
|
|
49
|
+
function detectRustProject(projectPath) {
|
|
50
|
+
return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Detect Ruby project
|
|
55
|
+
* @param {string} projectPath - Path to project
|
|
56
|
+
* @returns {boolean} True if Ruby project detected
|
|
57
|
+
*/
|
|
58
|
+
function detectRubyProject(projectPath) {
|
|
59
|
+
return fs.existsSync(path.join(projectPath, 'Gemfile'))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handle dependency monitoring command (Free/Pro/Team/Enterprise)
|
|
64
|
+
*/
|
|
65
|
+
async function handleDependencyMonitoring() {
|
|
66
|
+
const projectPath = process.cwd()
|
|
67
|
+
const license = getLicenseInfo()
|
|
68
|
+
|
|
69
|
+
// Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
|
|
70
|
+
const hasNpm = hasNpmProject(projectPath)
|
|
71
|
+
const hasPython = detectPythonProject(projectPath)
|
|
72
|
+
const hasRust = detectRustProject(projectPath)
|
|
73
|
+
const hasRuby = detectRubyProject(projectPath)
|
|
74
|
+
|
|
75
|
+
if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
|
|
76
|
+
console.error(
|
|
77
|
+
'❌ No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
|
|
78
|
+
)
|
|
79
|
+
console.log("💡 Make sure you're in a directory with dependency files.")
|
|
80
|
+
process.exit(1)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (hasNpm) console.log('📦 Detected: npm project')
|
|
84
|
+
if (hasPython) console.log('🐍 Detected: Python project')
|
|
85
|
+
if (hasRust) console.log('🦀 Detected: Rust project')
|
|
86
|
+
if (hasRuby) console.log('💎 Detected: Ruby project')
|
|
87
|
+
console.log(`📋 License tier: ${license.tier.toUpperCase()}`)
|
|
88
|
+
|
|
89
|
+
// Use sentinel value instead of null for consistent access patterns
|
|
90
|
+
const capCheck =
|
|
91
|
+
license.tier === 'FREE'
|
|
92
|
+
? checkUsageCaps('dependency-pr')
|
|
93
|
+
: { allowed: true, usage: {}, caps: {} }
|
|
94
|
+
|
|
95
|
+
if (!capCheck.allowed) {
|
|
96
|
+
console.error(`❌ ${capCheck.reason}`)
|
|
97
|
+
console.error(
|
|
98
|
+
' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qa-architect'
|
|
99
|
+
)
|
|
100
|
+
process.exit(1)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
|
|
104
|
+
|
|
105
|
+
// Use premium or basic config based on license tier
|
|
106
|
+
const shouldUsePremium =
|
|
107
|
+
license.tier === 'PRO' ||
|
|
108
|
+
license.tier === 'TEAM' ||
|
|
109
|
+
license.tier === 'ENTERPRISE'
|
|
110
|
+
|
|
111
|
+
// Free tier only supports npm projects. Fail fast with a clear message.
|
|
112
|
+
if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
|
|
113
|
+
console.error(
|
|
114
|
+
'❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
|
|
115
|
+
)
|
|
116
|
+
console.error(
|
|
117
|
+
' Free tier supports npm projects only. Detected non-npm ecosystems.'
|
|
118
|
+
)
|
|
119
|
+
console.error(
|
|
120
|
+
' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
|
|
121
|
+
)
|
|
122
|
+
process.exit(1)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (shouldUsePremium) {
|
|
126
|
+
console.log(
|
|
127
|
+
'\n🚀 Setting up framework-aware dependency monitoring (Premium)...\n'
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
const configData = generatePremiumDependabotConfig({
|
|
131
|
+
projectPath,
|
|
132
|
+
schedule: 'weekly',
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
if (configData) {
|
|
136
|
+
const { ecosystems } = configData
|
|
137
|
+
const ecosystemNames = Object.keys(ecosystems)
|
|
138
|
+
|
|
139
|
+
if (ecosystemNames.length > 0) {
|
|
140
|
+
console.log('🔍 Detected ecosystems:')
|
|
141
|
+
|
|
142
|
+
let primaryEcosystem = null
|
|
143
|
+
ecosystemNames.forEach(ecoName => {
|
|
144
|
+
const eco = ecosystems[ecoName]
|
|
145
|
+
const frameworks = Object.keys(eco.detected || {})
|
|
146
|
+
const totalPackages = frameworks.reduce((sum, fw) => {
|
|
147
|
+
return sum + (eco.detected[fw]?.count || 0)
|
|
148
|
+
}, 0)
|
|
149
|
+
|
|
150
|
+
console.log(` • ${ecoName}: ${totalPackages} packages`)
|
|
151
|
+
|
|
152
|
+
if (eco.primary) {
|
|
153
|
+
primaryEcosystem = ecoName
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
if (primaryEcosystem) {
|
|
158
|
+
console.log(`\n🎯 Primary ecosystem: ${primaryEcosystem}`)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
writePremiumDependabotConfig(configData, dependabotPath)
|
|
163
|
+
console.log('\n✅ Created .github/dependabot.yml with framework grouping')
|
|
164
|
+
|
|
165
|
+
console.log('\n🎉 Premium dependency monitoring setup complete!')
|
|
166
|
+
console.log('\n📋 What was added (Pro Tier):')
|
|
167
|
+
console.log(' • Framework-aware dependency grouping')
|
|
168
|
+
console.log(
|
|
169
|
+
` • ${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
|
|
170
|
+
)
|
|
171
|
+
console.log(' • Intelligent update batching (reduces PRs by 60%+)')
|
|
172
|
+
console.log(' • GitHub Actions dependency monitoring')
|
|
173
|
+
}
|
|
174
|
+
} else {
|
|
175
|
+
console.log('\n🔍 Setting up basic dependency monitoring (Free Tier)...\n')
|
|
176
|
+
|
|
177
|
+
const dependabotConfig = generateBasicDependabotConfig({
|
|
178
|
+
projectPath,
|
|
179
|
+
schedule: 'weekly',
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
if (dependabotConfig) {
|
|
183
|
+
writeBasicDependabotConfig(dependabotConfig, dependabotPath)
|
|
184
|
+
console.log('✅ Created .github/dependabot.yml')
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('\n🎉 Basic dependency monitoring setup complete!')
|
|
188
|
+
console.log('\n📋 What was added (Free Tier):')
|
|
189
|
+
console.log(' • Basic Dependabot configuration for npm packages')
|
|
190
|
+
console.log(' • Weekly dependency updates on Monday 9am')
|
|
191
|
+
console.log(' • GitHub Actions dependency monitoring')
|
|
192
|
+
|
|
193
|
+
// Show upgrade message for premium features
|
|
194
|
+
console.log('\n🔒 Premium features now available:')
|
|
195
|
+
console.log(' ✅ Framework-aware package grouping (React, Vue, Angular)')
|
|
196
|
+
console.log(' • Coming soon: Multi-language support (Python, Rust, Go)')
|
|
197
|
+
console.log(' • Planned: Advanced security audit workflows')
|
|
198
|
+
console.log(' • Planned: Custom update schedules and notifications')
|
|
199
|
+
|
|
200
|
+
showUpgradeMessage('Framework-Aware Dependency Grouping')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (license.tier === 'FREE') {
|
|
204
|
+
const increment = incrementUsage('dependency-pr')
|
|
205
|
+
const usage = increment.usage || capCheck.usage
|
|
206
|
+
const caps = capCheck.caps
|
|
207
|
+
if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
|
|
208
|
+
console.log(
|
|
209
|
+
`🧮 Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Auto-enable Dependabot on GitHub if token available
|
|
215
|
+
console.log('\n🔧 Attempting to enable Dependabot on GitHub...')
|
|
216
|
+
try {
|
|
217
|
+
const { setupDependabot } = require('../github-api')
|
|
218
|
+
const result = await setupDependabot(projectPath, { verbose: true })
|
|
219
|
+
|
|
220
|
+
if (result.success) {
|
|
221
|
+
console.log('✅ Dependabot alerts and security updates enabled!')
|
|
222
|
+
} else if (result.errors.length > 0) {
|
|
223
|
+
console.log('⚠️ Could not auto-enable Dependabot:')
|
|
224
|
+
result.errors.forEach(err => console.log(` • ${err}`))
|
|
225
|
+
console.log('\n💡 Manual steps needed:')
|
|
226
|
+
console.log(' • Go to GitHub repo → Settings → Code security')
|
|
227
|
+
console.log(
|
|
228
|
+
' • Enable "Dependabot alerts" and "Dependabot security updates"'
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
} catch (error) {
|
|
232
|
+
const errorId = 'DEPENDABOT_AUTO_ENABLE_FAILED'
|
|
233
|
+
|
|
234
|
+
// Diagnose specific error types
|
|
235
|
+
let diagnosis = 'Unknown error'
|
|
236
|
+
let remedy = 'Check the error message for details'
|
|
237
|
+
|
|
238
|
+
if (
|
|
239
|
+
error.message.includes('401') ||
|
|
240
|
+
error.message.includes('authentication')
|
|
241
|
+
) {
|
|
242
|
+
diagnosis = 'GitHub token is invalid or missing'
|
|
243
|
+
remedy = 'Set GITHUB_TOKEN environment variable with a valid token'
|
|
244
|
+
} else if (error.message.includes('404')) {
|
|
245
|
+
diagnosis = 'Repository not found or insufficient permissions'
|
|
246
|
+
remedy = 'Ensure token has repo:write access to this repository'
|
|
247
|
+
} else if (error.message.includes('rate limit')) {
|
|
248
|
+
diagnosis = 'GitHub API rate limit exceeded'
|
|
249
|
+
remedy = 'Wait 1 hour or use authenticated token for higher limits'
|
|
250
|
+
} else if (
|
|
251
|
+
error.code === 'ENOTFOUND' ||
|
|
252
|
+
error.message.includes('network')
|
|
253
|
+
) {
|
|
254
|
+
diagnosis = 'Network connectivity issue'
|
|
255
|
+
remedy = 'Check internet connection and GitHub API status'
|
|
256
|
+
} else if (error.message.includes('timeout')) {
|
|
257
|
+
diagnosis = 'Request timed out'
|
|
258
|
+
remedy = 'Retry the operation or check network connectivity'
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
console.error(`\n❌ [${errorId}] Could not auto-enable Dependabot`)
|
|
262
|
+
console.error(` Diagnosis: ${diagnosis}`)
|
|
263
|
+
console.error(` Error: ${error.message}`)
|
|
264
|
+
console.error(`\n 🔧 Recommended fix: ${remedy}`)
|
|
265
|
+
|
|
266
|
+
if (process.env.DEBUG) {
|
|
267
|
+
console.error(`\n Debug info:`)
|
|
268
|
+
console.error(` • Error code: ${error.code || 'N/A'}`)
|
|
269
|
+
console.error(` • Stack: ${error.stack}`)
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log('\n💡 Alternative: Enable manually:')
|
|
273
|
+
console.log(' 1. Go to GitHub repo → Settings → Code security')
|
|
274
|
+
console.log(' 2. Enable "Dependabot alerts"')
|
|
275
|
+
console.log(' 3. Enable "Dependabot security updates"')
|
|
276
|
+
console.log(
|
|
277
|
+
`\n • Report issue: https://github.com/vibebuildlab/qa-architect/issues/new?title=${errorId}`
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log('\n💡 Next steps:')
|
|
282
|
+
console.log(' • Review and commit .github/dependabot.yml')
|
|
283
|
+
console.log(
|
|
284
|
+
' • Dependabot will start monitoring weekly for dependency updates'
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
module.exports = {
|
|
289
|
+
handleDependencyMonitoring,
|
|
290
|
+
detectPythonProject,
|
|
291
|
+
detectRustProject,
|
|
292
|
+
detectRubyProject,
|
|
293
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command handlers index
|
|
3
|
+
*
|
|
4
|
+
* Centralizes CLI command handlers for better maintainability.
|
|
5
|
+
* Each command has its own module with focused functionality.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { handleValidationCommands } = require('./validate')
|
|
9
|
+
const {
|
|
10
|
+
handleDependencyMonitoring,
|
|
11
|
+
detectPythonProject,
|
|
12
|
+
detectRustProject,
|
|
13
|
+
detectRubyProject,
|
|
14
|
+
} = require('./deps')
|
|
15
|
+
const { handleAnalyzeCi } = require('./analyze-ci')
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
// Validation commands
|
|
19
|
+
handleValidationCommands,
|
|
20
|
+
|
|
21
|
+
// Dependency monitoring commands
|
|
22
|
+
handleDependencyMonitoring,
|
|
23
|
+
detectPythonProject,
|
|
24
|
+
detectRustProject,
|
|
25
|
+
detectRubyProject,
|
|
26
|
+
|
|
27
|
+
// CI/CD optimization commands
|
|
28
|
+
handleAnalyzeCi,
|
|
29
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation command handlers
|
|
3
|
+
*
|
|
4
|
+
* Extracted from setup.js to improve maintainability.
|
|
5
|
+
* Handles --validate, --comprehensive, --security-config, --validate-docs commands.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { ValidationRunner } = require('../validation')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handle validation-only commands
|
|
12
|
+
*
|
|
13
|
+
* @param {Object} options - Validation options
|
|
14
|
+
* @param {boolean} options.isConfigSecurityMode - Run config security check only
|
|
15
|
+
* @param {boolean} options.isDocsValidationMode - Run docs validation only
|
|
16
|
+
* @param {boolean} options.isComprehensiveMode - Run comprehensive validation
|
|
17
|
+
* @param {boolean} options.isValidationMode - Run validation mode
|
|
18
|
+
* @param {boolean} options.disableNpmAudit - Skip npm audit
|
|
19
|
+
* @param {boolean} options.disableGitleaks - Skip gitleaks
|
|
20
|
+
* @param {boolean} options.disableActionlint - Skip actionlint
|
|
21
|
+
* @param {boolean} options.disableMarkdownlint - Skip markdownlint
|
|
22
|
+
* @param {boolean} options.disableEslintSecurity - Skip ESLint security
|
|
23
|
+
* @param {boolean} options.allowLatestGitleaks - Allow latest gitleaks version
|
|
24
|
+
*/
|
|
25
|
+
async function handleValidationCommands(options) {
|
|
26
|
+
const {
|
|
27
|
+
isConfigSecurityMode,
|
|
28
|
+
isDocsValidationMode,
|
|
29
|
+
isComprehensiveMode,
|
|
30
|
+
isValidationMode,
|
|
31
|
+
disableNpmAudit,
|
|
32
|
+
disableGitleaks,
|
|
33
|
+
disableActionlint,
|
|
34
|
+
disableMarkdownlint,
|
|
35
|
+
disableEslintSecurity,
|
|
36
|
+
allowLatestGitleaks,
|
|
37
|
+
} = options
|
|
38
|
+
|
|
39
|
+
const validationOptions = {
|
|
40
|
+
disableNpmAudit,
|
|
41
|
+
disableGitleaks,
|
|
42
|
+
disableActionlint,
|
|
43
|
+
disableMarkdownlint,
|
|
44
|
+
disableEslintSecurity,
|
|
45
|
+
allowLatestGitleaks,
|
|
46
|
+
}
|
|
47
|
+
const validator = new ValidationRunner(validationOptions)
|
|
48
|
+
|
|
49
|
+
if (isConfigSecurityMode) {
|
|
50
|
+
try {
|
|
51
|
+
await validator.runConfigSecurity()
|
|
52
|
+
process.exit(0)
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error(
|
|
55
|
+
`\n❌ Configuration security validation failed:\n${error.message}`
|
|
56
|
+
)
|
|
57
|
+
process.exit(1)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (isDocsValidationMode) {
|
|
62
|
+
try {
|
|
63
|
+
await validator.runDocumentationValidation()
|
|
64
|
+
process.exit(0)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`\n❌ Documentation validation failed:\n${error.message}`)
|
|
67
|
+
process.exit(1)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (isComprehensiveMode || isValidationMode) {
|
|
72
|
+
try {
|
|
73
|
+
// Use parallel validation for 3-5x speedup (runs checks concurrently)
|
|
74
|
+
await validator.runComprehensiveCheckParallel()
|
|
75
|
+
process.exit(0)
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(`\n❌ Comprehensive validation failed:\n${error.message}`)
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
handleValidationCommands,
|
|
85
|
+
}
|
package/lib/config-validator.js
CHANGED
|
@@ -11,6 +11,33 @@ const addFormats = /** @type {(ajv: any) => void} */ (
|
|
|
11
11
|
addFormatsImport.default || addFormatsImport
|
|
12
12
|
)
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Format a single AJV validation error into a human-readable message
|
|
16
|
+
*/
|
|
17
|
+
function formatValidationError(error) {
|
|
18
|
+
const errorPath = error.instancePath || '(root)'
|
|
19
|
+
const message = error.message || 'validation failed'
|
|
20
|
+
|
|
21
|
+
const errorFormatters = {
|
|
22
|
+
required: () =>
|
|
23
|
+
`Missing required field: ${error.params?.missingProperty || 'unknown'}`,
|
|
24
|
+
enum: () =>
|
|
25
|
+
`Invalid value at ${errorPath}: must be one of ${error.params?.allowedValues?.join(', ') || 'unknown values'}`,
|
|
26
|
+
type: () =>
|
|
27
|
+
`Invalid type at ${errorPath}: expected ${error.params?.type || 'unknown'}`,
|
|
28
|
+
pattern: () => `Invalid format at ${errorPath}: ${message}`,
|
|
29
|
+
minimum: () =>
|
|
30
|
+
`Invalid value at ${errorPath}: must be >= ${error.params?.limit ?? 'unknown'}`,
|
|
31
|
+
additionalProperties: () =>
|
|
32
|
+
`Unknown property at ${errorPath}: ${error.params?.additionalProperty || 'unknown'}`,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const formatter = errorFormatters[error.keyword]
|
|
36
|
+
return formatter
|
|
37
|
+
? formatter()
|
|
38
|
+
: `Validation error at ${errorPath}: ${message}`
|
|
39
|
+
}
|
|
40
|
+
|
|
14
41
|
function validateQualityConfig(configPath) {
|
|
15
42
|
const result = {
|
|
16
43
|
valid: false,
|
|
@@ -61,51 +88,7 @@ function validateQualityConfig(configPath) {
|
|
|
61
88
|
|
|
62
89
|
if (!valid) {
|
|
63
90
|
if (validate.errors) {
|
|
64
|
-
validate.errors.
|
|
65
|
-
const errorPath = error.instancePath || '(root)'
|
|
66
|
-
const message = error.message || 'validation failed'
|
|
67
|
-
|
|
68
|
-
if (error.keyword === 'required') {
|
|
69
|
-
result.errors.push(
|
|
70
|
-
'Missing required field: ' +
|
|
71
|
-
(error.params?.missingProperty || 'unknown')
|
|
72
|
-
)
|
|
73
|
-
} else if (error.keyword === 'enum') {
|
|
74
|
-
result.errors.push(
|
|
75
|
-
'Invalid value at ' +
|
|
76
|
-
errorPath +
|
|
77
|
-
': must be one of ' +
|
|
78
|
-
(error.params?.allowedValues?.join(', ') || 'unknown values')
|
|
79
|
-
)
|
|
80
|
-
} else if (error.keyword === 'type') {
|
|
81
|
-
result.errors.push(
|
|
82
|
-
'Invalid type at ' +
|
|
83
|
-
errorPath +
|
|
84
|
-
': expected ' +
|
|
85
|
-
(error.params?.type || 'unknown')
|
|
86
|
-
)
|
|
87
|
-
} else if (error.keyword === 'pattern') {
|
|
88
|
-
result.errors.push('Invalid format at ' + errorPath + ': ' + message)
|
|
89
|
-
} else if (error.keyword === 'minimum') {
|
|
90
|
-
result.errors.push(
|
|
91
|
-
'Invalid value at ' +
|
|
92
|
-
errorPath +
|
|
93
|
-
': must be >= ' +
|
|
94
|
-
(error.params?.limit ?? 'unknown')
|
|
95
|
-
)
|
|
96
|
-
} else if (error.keyword === 'additionalProperties') {
|
|
97
|
-
result.errors.push(
|
|
98
|
-
'Unknown property at ' +
|
|
99
|
-
errorPath +
|
|
100
|
-
': ' +
|
|
101
|
-
(error.params?.additionalProperty || 'unknown')
|
|
102
|
-
)
|
|
103
|
-
} else {
|
|
104
|
-
result.errors.push(
|
|
105
|
-
'Validation error at ' + errorPath + ': ' + message
|
|
106
|
-
)
|
|
107
|
-
}
|
|
108
|
-
})
|
|
91
|
+
result.errors = validate.errors.map(formatValidationError)
|
|
109
92
|
}
|
|
110
93
|
} else {
|
|
111
94
|
result.valid = true
|
package/lib/error-reporter.js
CHANGED
|
@@ -190,7 +190,7 @@ function loadErrorReports() {
|
|
|
190
190
|
const data = fs.readFileSync(ERROR_REPORTS_FILE, 'utf8')
|
|
191
191
|
return JSON.parse(data)
|
|
192
192
|
}
|
|
193
|
-
} catch {
|
|
193
|
+
} catch (_error) {
|
|
194
194
|
// If corrupted, start fresh
|
|
195
195
|
console.warn('⚠️ Error reports data corrupted, starting fresh')
|
|
196
196
|
}
|
|
@@ -253,13 +253,25 @@ class ErrorReporter {
|
|
|
253
253
|
const category = categorizeError(error)
|
|
254
254
|
const reportId = generateReportId()
|
|
255
255
|
|
|
256
|
+
// DR20 fix: Limit stack trace exposure in production mode
|
|
257
|
+
const isProduction = process.env.NODE_ENV === 'production'
|
|
258
|
+
const fullStack = sanitizeStackTrace(error?.stack || '')
|
|
259
|
+
|
|
260
|
+
// In production: only include first 3 lines of stack (error + top 2 frames)
|
|
261
|
+
// In dev/test: include full sanitized stack for debugging
|
|
262
|
+
const stackToInclude =
|
|
263
|
+
isProduction && fullStack
|
|
264
|
+
? fullStack.split('\n').slice(0, 3).join('\n')
|
|
265
|
+
: fullStack
|
|
266
|
+
|
|
256
267
|
const report = {
|
|
257
268
|
id: reportId,
|
|
258
269
|
timestamp: new Date().toISOString(),
|
|
259
270
|
category,
|
|
260
271
|
errorType: error?.constructor?.name || 'Error',
|
|
261
272
|
message: sanitizeMessage(error?.message || 'Unknown error'),
|
|
262
|
-
sanitizedStack:
|
|
273
|
+
sanitizedStack: stackToInclude,
|
|
274
|
+
stackTruncated: isProduction && fullStack.split('\n').length > 3,
|
|
263
275
|
operation: this.operation,
|
|
264
276
|
context: {
|
|
265
277
|
nodeVersion: process.version,
|