create-qa-architect 5.0.0

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 (67) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/CLAUDE_MD_AUTOMATION.md +248 -0
  3. package/.github/PROGRESSIVE_QUALITY_IMPLEMENTATION.md +408 -0
  4. package/.github/PROGRESSIVE_QUALITY_PROPOSAL.md +443 -0
  5. package/.github/RELEASE_CHECKLIST.md +100 -0
  6. package/.github/dependabot.yml +50 -0
  7. package/.github/git-sync.sh +48 -0
  8. package/.github/workflows/claude-md-validation.yml +82 -0
  9. package/.github/workflows/nightly-gitleaks-verification.yml +176 -0
  10. package/.github/workflows/pnpm-ci.yml.example +53 -0
  11. package/.github/workflows/python-ci.yml.example +69 -0
  12. package/.github/workflows/quality-legacy.yml.backup +165 -0
  13. package/.github/workflows/quality-progressive.yml.example +291 -0
  14. package/.github/workflows/quality.yml +436 -0
  15. package/.github/workflows/release.yml +53 -0
  16. package/.nvmrc +1 -0
  17. package/.prettierignore +14 -0
  18. package/.prettierrc +9 -0
  19. package/.stylelintrc.json +5 -0
  20. package/README.md +212 -0
  21. package/config/.lighthouserc.js +45 -0
  22. package/config/.pre-commit-config.yaml +66 -0
  23. package/config/constants.js +128 -0
  24. package/config/defaults.js +124 -0
  25. package/config/pyproject.toml +124 -0
  26. package/config/quality-config.schema.json +97 -0
  27. package/config/quality-python.yml +89 -0
  28. package/config/requirements-dev.txt +15 -0
  29. package/create-saas-monetization.js +1465 -0
  30. package/eslint.config.cjs +117 -0
  31. package/eslint.config.ts.cjs +99 -0
  32. package/legal/README.md +106 -0
  33. package/legal/copyright.md +76 -0
  34. package/legal/disclaimer.md +146 -0
  35. package/legal/privacy-policy.html +324 -0
  36. package/legal/privacy-policy.md +196 -0
  37. package/legal/terms-of-service.md +224 -0
  38. package/lib/billing-dashboard.html +645 -0
  39. package/lib/config-validator.js +163 -0
  40. package/lib/dependency-monitoring-basic.js +185 -0
  41. package/lib/dependency-monitoring-premium.js +1490 -0
  42. package/lib/error-reporter.js +444 -0
  43. package/lib/interactive/prompt.js +128 -0
  44. package/lib/interactive/questions.js +146 -0
  45. package/lib/license-validator.js +403 -0
  46. package/lib/licensing.js +989 -0
  47. package/lib/package-utils.js +187 -0
  48. package/lib/project-maturity.js +516 -0
  49. package/lib/security-enhancements.js +340 -0
  50. package/lib/setup-enhancements.js +317 -0
  51. package/lib/smart-strategy-generator.js +344 -0
  52. package/lib/telemetry.js +323 -0
  53. package/lib/template-loader.js +252 -0
  54. package/lib/typescript-config-generator.js +210 -0
  55. package/lib/ui-helpers.js +74 -0
  56. package/lib/validation/base-validator.js +174 -0
  57. package/lib/validation/cache-manager.js +158 -0
  58. package/lib/validation/config-security.js +741 -0
  59. package/lib/validation/documentation.js +326 -0
  60. package/lib/validation/index.js +186 -0
  61. package/lib/validation/validation-factory.js +153 -0
  62. package/lib/validation/workflow-validation.js +172 -0
  63. package/lib/yaml-utils.js +120 -0
  64. package/marketing/beta-user-email-campaign.md +372 -0
  65. package/marketing/landing-page.html +721 -0
  66. package/package.json +165 -0
  67. package/setup.js +2076 -0
@@ -0,0 +1,1490 @@
1
+ /**
2
+ * Premium Dependency Monitoring Library (Pro/Team/Enterprise Tiers)
3
+ * Framework-aware dependency grouping with intelligent batching
4
+ *
5
+ * @module dependency-monitoring-premium
6
+ * @requires lib/licensing.js - License tier validation
7
+ * @requires lib/dependency-monitoring-basic.js - Fallback for free tier
8
+ */
9
+
10
+ const fs = require('fs')
11
+ const path = require('path')
12
+ const { getLicenseInfo } = require('./licensing')
13
+ const {
14
+ generateBasicDependabotConfig,
15
+ } = require('./dependency-monitoring-basic')
16
+ const yaml = require('js-yaml')
17
+ const { DEPENDENCY_MONITORING } = require('../config/constants')
18
+
19
+ /**
20
+ * npm Framework signature patterns for detection
21
+ * Maps framework names to dependency patterns that indicate their presence
22
+ */
23
+ const NPM_FRAMEWORK_SIGNATURES = {
24
+ react: {
25
+ core: ['react', 'react-dom'],
26
+ routing: ['react-router', 'react-router-dom', '@tanstack/react-router'],
27
+ state: ['zustand', 'jotai', 'redux', '@reduxjs/toolkit'],
28
+ query: ['@tanstack/react-query', 'swr'],
29
+ forms: ['react-hook-form', 'formik'],
30
+ ui: [
31
+ '@mui/material',
32
+ '@chakra-ui/react',
33
+ '@radix-ui/react-*',
34
+ '@headlessui/react',
35
+ ],
36
+ metaFrameworks: ['next', 'remix', 'gatsby'],
37
+ },
38
+ vue: {
39
+ core: ['vue'],
40
+ routing: ['vue-router'],
41
+ state: ['pinia', 'vuex'],
42
+ ecosystem: ['@vue/*', 'vueuse'],
43
+ ui: ['vuetify', 'element-plus', '@vueuse/core'],
44
+ metaFrameworks: ['nuxt'],
45
+ },
46
+ angular: {
47
+ core: ['@angular/core', '@angular/common', '@angular/platform-browser'],
48
+ routing: ['@angular/router'],
49
+ forms: ['@angular/forms'],
50
+ http: ['@angular/common/http'],
51
+ state: ['@ngrx/*', '@ngxs/*'],
52
+ ui: ['@angular/material', '@ng-bootstrap/ng-bootstrap'],
53
+ cli: ['@angular/cli', '@angular-devkit/*'],
54
+ },
55
+ svelte: {
56
+ core: ['svelte'],
57
+ metaFrameworks: ['@sveltejs/kit'],
58
+ },
59
+ testing: {
60
+ frameworks: [
61
+ 'jest',
62
+ 'vitest',
63
+ '@testing-library/*',
64
+ 'playwright',
65
+ '@playwright/test',
66
+ ],
67
+ },
68
+ build: {
69
+ tools: ['vite', 'webpack', 'turbo', 'nx', '@nx/*', 'esbuild', 'rollup'],
70
+ },
71
+ storybook: {
72
+ core: ['@storybook/*'],
73
+ },
74
+ }
75
+
76
+ /**
77
+ * Python Framework signature patterns for pip ecosystem
78
+ */
79
+ const PYTHON_FRAMEWORK_SIGNATURES = {
80
+ django: {
81
+ core: ['django'],
82
+ rest: ['djangorestframework', 'django-rest-framework'],
83
+ async: ['channels', 'django-channels'],
84
+ cms: ['wagtail', 'django-cms'],
85
+ },
86
+ flask: {
87
+ core: ['flask'],
88
+ extensions: ['flask-sqlalchemy', 'flask-restful', 'flask-cors'],
89
+ },
90
+ fastapi: {
91
+ core: ['fastapi'],
92
+ async: ['uvicorn', 'starlette'],
93
+ validation: ['pydantic'],
94
+ },
95
+ datascience: {
96
+ core: ['numpy', 'pandas', 'scipy'],
97
+ ml: ['scikit-learn', 'tensorflow', 'torch', 'pytorch'],
98
+ viz: ['matplotlib', 'seaborn', 'plotly'],
99
+ },
100
+ testing: {
101
+ frameworks: ['pytest', 'unittest2', 'nose2'],
102
+ helpers: ['pytest-*', 'coverage'],
103
+ },
104
+ web: {
105
+ servers: ['gunicorn', 'uwsgi'],
106
+ async: ['aiohttp', 'tornado'],
107
+ },
108
+ }
109
+
110
+ /**
111
+ * Rust Framework signature patterns for cargo ecosystem
112
+ */
113
+ const RUST_FRAMEWORK_SIGNATURES = {
114
+ actix: {
115
+ core: ['actix-web', 'actix-rt'],
116
+ middleware: ['actix-cors', 'actix-session'],
117
+ },
118
+ rocket: {
119
+ core: ['rocket'],
120
+ features: ['rocket_contrib'],
121
+ },
122
+ async: {
123
+ runtime: ['tokio', 'async-std'],
124
+ helpers: ['futures'],
125
+ },
126
+ serde: {
127
+ core: ['serde', 'serde_json'],
128
+ formats: ['serde_yaml', 'serde_derive'],
129
+ },
130
+ testing: {
131
+ frameworks: ['criterion', 'proptest'],
132
+ },
133
+ }
134
+
135
+ /**
136
+ * Ruby Framework signature patterns for bundler ecosystem
137
+ */
138
+ const RUBY_FRAMEWORK_SIGNATURES = {
139
+ rails: {
140
+ core: ['rails'],
141
+ database: ['activerecord', 'pg', 'mysql2'],
142
+ testing: ['rspec-rails', 'factory_bot_rails'],
143
+ frontend: ['webpacker', 'importmap-rails'],
144
+ },
145
+ sinatra: {
146
+ core: ['sinatra'],
147
+ extensions: ['sinatra-contrib'],
148
+ },
149
+ testing: {
150
+ frameworks: ['rspec', 'rspec-*', 'minitest'],
151
+ helpers: ['capybara', 'factory_bot', 'factory_bot_*'],
152
+ },
153
+ utilities: {
154
+ async: ['sidekiq', 'delayed_job'],
155
+ http: ['faraday', 'httparty'],
156
+ },
157
+ }
158
+
159
+ /**
160
+ * Detect frameworks and libraries present in a project
161
+ *
162
+ * @param {Object} packageJson - Parsed package.json content
163
+ * @returns {Object} Framework detection results
164
+ * @example
165
+ * {
166
+ * primary: 'react',
167
+ * detected: {
168
+ * react: { present: true, packages: ['react', 'react-dom'], version: '^18.0.0' },
169
+ * testing: { present: true, packages: ['jest', '@testing-library/react'] }
170
+ * }
171
+ * }
172
+ */
173
+ function detectFrameworks(packageJson) {
174
+ const allDependencies = {
175
+ ...(packageJson.dependencies || {}),
176
+ ...(packageJson.devDependencies || {}),
177
+ }
178
+
179
+ const detectionResults = {
180
+ primary: null,
181
+ detected: {},
182
+ }
183
+
184
+ // OPTIMIZED: Build reverse lookup map ONCE (instead of nested loops)
185
+ // This reduces complexity from O(F×C×P×D) to O(F×C×P + D×patterns_checked)
186
+ const patternMap = new Map() // pattern → { framework, category, pattern }
187
+
188
+ for (const [frameworkName, categories] of Object.entries(
189
+ NPM_FRAMEWORK_SIGNATURES
190
+ )) {
191
+ for (const [categoryName, categoryPackages] of Object.entries(categories)) {
192
+ for (const pattern of categoryPackages) {
193
+ if (!patternMap.has(pattern)) {
194
+ patternMap.set(pattern, [])
195
+ }
196
+ patternMap.get(pattern).push({
197
+ framework: frameworkName,
198
+ category: categoryName,
199
+ pattern: pattern,
200
+ })
201
+ }
202
+ }
203
+ }
204
+
205
+ // Track matched packages per framework
206
+ const frameworkMatches = {} // frameworkName → { packages: [], version: null }
207
+
208
+ // Single pass through dependencies (O(D) instead of O(F×C×P×D))
209
+ for (const [depName, depVersion] of Object.entries(allDependencies)) {
210
+ // Check each pattern (typically 50-100 patterns total)
211
+ for (const [pattern, frameworkInfos] of patternMap) {
212
+ if (matchesPattern(depName, pattern)) {
213
+ // Add to all frameworks that use this pattern
214
+ for (const { framework, category } of frameworkInfos) {
215
+ if (!frameworkMatches[framework]) {
216
+ frameworkMatches[framework] = { packages: [], version: null }
217
+ }
218
+
219
+ frameworkMatches[framework].packages.push(depName)
220
+
221
+ // Capture version from core package
222
+ if (!frameworkMatches[framework].version && category === 'core') {
223
+ frameworkMatches[framework].version = depVersion
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ // Build detection results from matched frameworks
231
+ for (const [frameworkName, matches] of Object.entries(frameworkMatches)) {
232
+ if (matches.packages.length > 0) {
233
+ detectionResults.detected[frameworkName] = {
234
+ present: true,
235
+ packages: matches.packages,
236
+ version: matches.version,
237
+ count: matches.packages.length,
238
+ }
239
+
240
+ // Set primary framework (first UI framework detected)
241
+ if (
242
+ !detectionResults.primary &&
243
+ ['react', 'vue', 'angular', 'svelte'].includes(frameworkName)
244
+ ) {
245
+ detectionResults.primary = frameworkName
246
+ }
247
+ }
248
+ }
249
+
250
+ return detectionResults
251
+ }
252
+
253
+ /**
254
+ * Regex pattern cache for performance optimization
255
+ *
256
+ * Caches compiled RegExp objects to avoid recompiling the same patterns
257
+ * repeatedly. Implements size-limited cache with FIFO eviction to prevent
258
+ * memory bloat in projects with many unique patterns.
259
+ *
260
+ * @type {Map<string, RegExp>}
261
+ */
262
+ const patternCache = new Map()
263
+ const MAX_PATTERN_CACHE_SIZE = DEPENDENCY_MONITORING.MAX_PATTERN_CACHE_SIZE
264
+
265
+ /**
266
+ * Check if a dependency name matches a pattern (supports wildcards)
267
+ *
268
+ * Uses an in-memory cache to avoid recompiling regex patterns on every
269
+ * invocation. For wildcard patterns (e.g., '@babel/*'), compiles the regex
270
+ * once and reuses it. For exact patterns, uses direct string comparison.
271
+ *
272
+ * Performance: O(1) for cached patterns, O(n) for first compilation
273
+ * Cache eviction: FIFO when size exceeds MAX_PATTERN_CACHE_SIZE
274
+ *
275
+ * @param {string} depName - Dependency name to check (e.g., '@babel/core')
276
+ * @param {string} pattern - Pattern to match (supports * wildcard, e.g., '@babel/*')
277
+ * @returns {boolean} True if depName matches pattern
278
+ *
279
+ * @example
280
+ * matchesPattern('@babel/core', '@babel/*') // true (wildcard)
281
+ * matchesPattern('react', 'react') // true (exact)
282
+ * matchesPattern('vue', 'react') // false
283
+ */
284
+ function matchesPattern(depName, pattern) {
285
+ if (pattern.includes('*')) {
286
+ // Check cache first
287
+ let regex = patternCache.get(pattern)
288
+
289
+ if (!regex) {
290
+ // Cache miss - compile and store
291
+ const regexPattern = pattern.replace(/\*/g, '.*')
292
+ // eslint-disable-next-line security/detect-non-literal-regexp
293
+ regex = new RegExp(`^${regexPattern}$`)
294
+
295
+ // Implement size-limited cache with FIFO eviction
296
+ if (patternCache.size >= MAX_PATTERN_CACHE_SIZE) {
297
+ // Remove oldest entry (first key in Map)
298
+ const firstKey = patternCache.keys().next().value
299
+ patternCache.delete(firstKey)
300
+ }
301
+
302
+ patternCache.set(pattern, regex)
303
+ }
304
+
305
+ return regex.test(depName)
306
+ }
307
+ return depName === pattern
308
+ }
309
+
310
+ /**
311
+ * Generate dependency groups for React ecosystem
312
+ *
313
+ * @param {Object} _frameworkInfo - React detection results (unused, reserved for future enhancements)
314
+ * @returns {Object} Dependabot groups configuration
315
+ */
316
+ function generateReactGroups(_frameworkInfo) {
317
+ const groups = {}
318
+
319
+ // React core group - highest priority, most critical updates
320
+ groups['react-core'] = {
321
+ patterns: ['react', 'react-dom', 'react-router*'],
322
+ 'update-types': ['minor', 'patch'],
323
+ 'dependency-type': 'production',
324
+ }
325
+
326
+ // React ecosystem - state management, data fetching
327
+ groups['react-ecosystem'] = {
328
+ patterns: ['@tanstack/*', 'zustand', 'jotai', 'swr', '@reduxjs/*'],
329
+ 'update-types': ['patch'],
330
+ 'dependency-type': 'production',
331
+ }
332
+
333
+ // React UI libraries
334
+ groups['react-ui'] = {
335
+ patterns: ['@mui/*', '@chakra-ui/*', '@radix-ui/*', '@headlessui/react'],
336
+ 'update-types': ['patch'],
337
+ }
338
+
339
+ // React forms
340
+ groups['react-forms'] = {
341
+ patterns: ['react-hook-form', 'formik'],
342
+ 'update-types': ['minor', 'patch'],
343
+ }
344
+
345
+ return groups
346
+ }
347
+
348
+ /**
349
+ * Generate dependency groups for Vue ecosystem
350
+ *
351
+ * @param {Object} frameworkInfo - Vue detection results
352
+ * @returns {Object} Dependabot groups configuration
353
+ */
354
+ function generateVueGroups(_frameworkInfo) {
355
+ const groups = {}
356
+
357
+ groups['vue-core'] = {
358
+ patterns: ['vue', 'vue-router', 'pinia'],
359
+ 'update-types': ['minor', 'patch'],
360
+ 'dependency-type': 'production',
361
+ }
362
+
363
+ groups['vue-ecosystem'] = {
364
+ patterns: ['@vue/*', '@vueuse/*', 'vueuse'],
365
+ 'update-types': ['patch'],
366
+ }
367
+
368
+ groups['vue-ui'] = {
369
+ patterns: ['vuetify', 'element-plus'],
370
+ 'update-types': ['patch'],
371
+ }
372
+
373
+ return groups
374
+ }
375
+
376
+ /**
377
+ * Generate dependency groups for Angular ecosystem
378
+ *
379
+ * @param {Object} frameworkInfo - Angular detection results
380
+ * @returns {Object} Dependabot groups configuration
381
+ */
382
+ function generateAngularGroups(_frameworkInfo) {
383
+ const groups = {}
384
+
385
+ groups['angular-core'] = {
386
+ patterns: ['@angular/core', '@angular/common', '@angular/platform-*'],
387
+ 'update-types': ['minor', 'patch'],
388
+ 'dependency-type': 'production',
389
+ }
390
+
391
+ groups['angular-ecosystem'] = {
392
+ patterns: ['@angular/*', '@ngrx/*', '@ngxs/*'],
393
+ 'update-types': ['patch'],
394
+ }
395
+
396
+ groups['angular-ui'] = {
397
+ patterns: ['@angular/material', '@ng-bootstrap/*'],
398
+ 'update-types': ['patch'],
399
+ }
400
+
401
+ return groups
402
+ }
403
+
404
+ /**
405
+ * Generate dependency groups for testing frameworks
406
+ *
407
+ * @param {Object} frameworkInfo - Testing framework detection results
408
+ * @returns {Object} Dependabot groups configuration
409
+ */
410
+ function generateTestingGroups(_frameworkInfo) {
411
+ const groups = {}
412
+
413
+ groups['testing-frameworks'] = {
414
+ patterns: [
415
+ 'jest',
416
+ 'vitest',
417
+ '@testing-library/*',
418
+ 'playwright',
419
+ '@playwright/*',
420
+ ],
421
+ 'update-types': ['minor', 'patch'],
422
+ 'dependency-type': 'development',
423
+ }
424
+
425
+ return groups
426
+ }
427
+
428
+ /**
429
+ * Generate dependency groups for build tools
430
+ *
431
+ * @param {Object} frameworkInfo - Build tool detection results
432
+ * @returns {Object} Dependabot groups configuration
433
+ */
434
+ function generateBuildToolGroups(_frameworkInfo) {
435
+ const groups = {}
436
+
437
+ groups['build-tools'] = {
438
+ patterns: ['vite', 'webpack', 'turbo', '@nx/*', 'esbuild', 'rollup'],
439
+ 'update-types': ['patch'],
440
+ 'dependency-type': 'development',
441
+ }
442
+
443
+ return groups
444
+ }
445
+
446
+ /**
447
+ * Generate Storybook dependency groups
448
+ *
449
+ * @param {Object} frameworkInfo - Storybook detection results
450
+ * @returns {Object} Dependabot groups configuration
451
+ */
452
+ function generateStorybookGroups(_frameworkInfo) {
453
+ const groups = {}
454
+
455
+ groups['storybook'] = {
456
+ patterns: ['@storybook/*'],
457
+ 'update-types': ['minor', 'patch'],
458
+ 'dependency-type': 'development',
459
+ }
460
+
461
+ return groups
462
+ }
463
+
464
+ /**
465
+ * ============================================================================
466
+ * PYTHON/PIP ECOSYSTEM SUPPORT
467
+ * ============================================================================
468
+ */
469
+
470
+ /**
471
+ * Check if project has Python dependencies
472
+ */
473
+ function hasPythonProject(projectPath) {
474
+ return (
475
+ fs.existsSync(path.join(projectPath, 'requirements.txt')) ||
476
+ fs.existsSync(path.join(projectPath, 'Pipfile')) ||
477
+ fs.existsSync(path.join(projectPath, 'pyproject.toml')) ||
478
+ fs.existsSync(path.join(projectPath, 'setup.py'))
479
+ )
480
+ }
481
+
482
+ /**
483
+ * Parse requirements.txt for Python dependencies
484
+ *
485
+ * Parses a Python requirements.txt file to extract dependency names and versions.
486
+ * Supports dotted package names (zope.interface), extras (fastapi[all]), and
487
+ * various version specifiers (==, >=, <, etc.).
488
+ *
489
+ * Security: Validates file size before reading to prevent memory exhaustion
490
+ * attacks with maliciously large requirements files.
491
+ *
492
+ * @param {string} requirementsPath - Path to requirements.txt file
493
+ * @returns {Object<string, string>} Map of package names to version specifiers
494
+ * @throws {Error} If file exceeds MAX_REQUIREMENTS_FILE_SIZE
495
+ *
496
+ * @example
497
+ * parsePipRequirements('./requirements.txt')
498
+ * // Returns: { 'flask': '==2.0.1', 'pytest': '>=7.0.0', 'requests': '*' }
499
+ */
500
+ function parsePipRequirements(requirementsPath) {
501
+ // Validate file size before reading to prevent memory issues
502
+ const stats = fs.statSync(requirementsPath)
503
+ const MAX_FILE_SIZE = DEPENDENCY_MONITORING.MAX_REQUIREMENTS_FILE_SIZE
504
+
505
+ if (stats.size > MAX_FILE_SIZE) {
506
+ const sizeMB = (stats.size / (1024 * 1024)).toFixed(2)
507
+ const limitMB = (MAX_FILE_SIZE / (1024 * 1024)).toFixed(0)
508
+ throw new Error(
509
+ `requirements.txt file too large (${sizeMB} MB). Maximum allowed size is ${limitMB} MB. ` +
510
+ `This prevents memory exhaustion. Please split into multiple files or remove unnecessary dependencies.`
511
+ )
512
+ }
513
+
514
+ const content = fs.readFileSync(requirementsPath, 'utf8')
515
+ const dependencies = {}
516
+
517
+ content.split('\n').forEach(line => {
518
+ // Remove inline comments (everything after #)
519
+ const commentIndex = line.indexOf('#')
520
+ if (commentIndex !== -1) {
521
+ line = line.substring(0, commentIndex)
522
+ }
523
+ line = line.trim()
524
+
525
+ // Skip empty lines
526
+ if (!line) return
527
+
528
+ // Parse: package==1.2.3 or package>=1.2.3
529
+ // Support dotted names (zope.interface), hyphens (pytest-cov), underscores (google_cloud)
530
+ // Also handle extras like fastapi[all] by capturing everything before the bracket
531
+ // Fixed: Replaced (.*) with ([^\s]*) to prevent catastrophic backtracking
532
+ // eslint-disable-next-line security/detect-unsafe-regex
533
+ const match = line.match(/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/)
534
+ if (match) {
535
+ const [, name, _extras, operator, version] = match
536
+ // Store package name without extras
537
+
538
+ dependencies[name] =
539
+ operator && version ? `${operator}${version.trim()}` : '*'
540
+ }
541
+ })
542
+
543
+ return dependencies
544
+ }
545
+
546
+ /**
547
+ * Parse pyproject.toml for Python dependencies (PEP 621 + legacy formats)
548
+ * Supports:
549
+ * - PEP 621: dependencies = ["package>=1.0.0", ...]
550
+ * - PEP 621: [project.optional-dependencies] dev = ["package>=1.0.0"]
551
+ * - Legacy: package = "^1.2.3" (Poetry, setuptools)
552
+ */
553
+ function parsePyprojectToml(pyprojectPath) {
554
+ const content = fs.readFileSync(pyprojectPath, 'utf8')
555
+ const dependencies = {}
556
+
557
+ // Parse PEP 621 list-style dependencies: dependencies = ["package>=1.0.0", ...]
558
+ // Match main dependencies array: dependencies = [...]
559
+ // Allow optional whitespace/comments after ] to handle: ] # end of deps
560
+ // eslint-disable-next-line security/detect-unsafe-regex
561
+ const mainDepPattern = /^dependencies\s*=\s*\[([\s\S]*?)\]\s*(?:#.*)?$/m
562
+ const mainMatch = mainDepPattern.exec(content)
563
+
564
+ if (mainMatch) {
565
+ const depList = mainMatch[1]
566
+ // Extract individual package lines: "package>=1.0.0"
567
+ const packagePattern = /["']([^"'\n]+)["']/g
568
+ let pkgMatch
569
+
570
+ while ((pkgMatch = packagePattern.exec(depList)) !== null) {
571
+ const depString = pkgMatch[1].trim()
572
+
573
+ // Parse: package>=1.0.0 or package[extra]>=1.0.0
574
+ // Support dotted names, hyphens, underscores, and extras
575
+ // Fixed: Replaced ($|.*) with ([^\s]*) to prevent catastrophic backtracking
576
+ const match = depString.match(
577
+ /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex
578
+ )
579
+ if (match) {
580
+ const [, name, _extras, operator, version] = match
581
+
582
+ dependencies[name] =
583
+ operator && version ? `${operator}${version.trim()}` : '*'
584
+ }
585
+ }
586
+ }
587
+
588
+ // Parse optional-dependencies sections: [project.optional-dependencies] dev = [...]
589
+ const optionalDepPattern =
590
+ /\[project\.optional-dependencies\]([\s\S]*?)(?=\n\[|$)/
591
+ const optionalMatch = optionalDepPattern.exec(content)
592
+
593
+ if (optionalMatch) {
594
+ const optionalSection = optionalMatch[1]
595
+ // Match each optional group: dev = [...], test-suite = [...], lint-tools = [...]
596
+ // Support hyphens, underscores, and dots in group names per PEP 621
597
+ const groupPattern = /([\w.-]+)\s*=\s*\[([\s\S]*?)\]/g
598
+ let groupMatch
599
+
600
+ while ((groupMatch = groupPattern.exec(optionalSection)) !== null) {
601
+ const depList = groupMatch[2]
602
+ const packagePattern = /["']([^"'\n]+)["']/g
603
+ let pkgMatch
604
+
605
+ while ((pkgMatch = packagePattern.exec(depList)) !== null) {
606
+ const depString = pkgMatch[1].trim()
607
+
608
+ const match = depString.match(
609
+ /^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?($|.*)$/ // eslint-disable-line security/detect-unsafe-regex
610
+ )
611
+ if (match) {
612
+ const [, name, _extras, operator, version] = match
613
+
614
+ dependencies[name] =
615
+ operator && version ? `${operator}${version.trim()}` : '*'
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ // Parse legacy key-value style: package = "^1.2.3"
622
+ // This handles Poetry and old setuptools formats
623
+ // ONLY parse within [tool.poetry.dependencies] and [project.dependencies] sections
624
+ const poetryDepSection = /\[tool\.poetry\.dependencies\]([\s\S]*?)(?=\n\[|$)/
625
+ const projectDepSection = /\[project\.dependencies\]([\s\S]*?)(?=\n\[|$)/
626
+
627
+ const sections = [poetryDepSection, projectDepSection]
628
+
629
+ for (const sectionPattern of sections) {
630
+ const sectionMatch = sectionPattern.exec(content)
631
+ if (sectionMatch) {
632
+ const sectionContent = sectionMatch[1]
633
+ const kvPattern = /([\w.-]+)\s*=\s*["']([^"']+)["']/g
634
+ let kvMatch
635
+
636
+ while ((kvMatch = kvPattern.exec(sectionContent)) !== null) {
637
+ const [, name, version] = kvMatch
638
+ // Skip Python version specifier
639
+ if (name === 'python') {
640
+ continue
641
+ }
642
+ // Only add if not already found in list-style dependencies
643
+
644
+ if (!dependencies[name]) {
645
+ dependencies[name] = version
646
+ }
647
+ }
648
+ }
649
+ }
650
+
651
+ return dependencies
652
+ }
653
+
654
+ /**
655
+ * Detect Python frameworks similar to npm framework detection
656
+ */
657
+ function detectPythonFrameworks(projectPath) {
658
+ let dependencies = {}
659
+
660
+ // Try requirements.txt first
661
+ const reqPath = path.join(projectPath, 'requirements.txt')
662
+ if (fs.existsSync(reqPath)) {
663
+ dependencies = { ...dependencies, ...parsePipRequirements(reqPath) }
664
+ }
665
+
666
+ // Try pyproject.toml
667
+ const pyprojectPath = path.join(projectPath, 'pyproject.toml')
668
+ if (fs.existsSync(pyprojectPath)) {
669
+ dependencies = { ...dependencies, ...parsePyprojectToml(pyprojectPath) }
670
+ }
671
+
672
+ const detectionResults = {
673
+ primary: null,
674
+ detected: {},
675
+ }
676
+
677
+ // Use same detection logic as npm frameworks
678
+ for (const [frameworkName, categories] of Object.entries(
679
+ PYTHON_FRAMEWORK_SIGNATURES
680
+ )) {
681
+ const matchedPackages = []
682
+
683
+ for (const categoryPackages of Object.values(categories)) {
684
+ for (const pattern of categoryPackages) {
685
+ for (const [depName] of Object.entries(dependencies)) {
686
+ if (matchesPattern(depName, pattern)) {
687
+ matchedPackages.push(depName)
688
+ }
689
+ }
690
+ }
691
+ }
692
+
693
+ if (matchedPackages.length > 0) {
694
+ detectionResults.detected[frameworkName] = {
695
+ present: true,
696
+ packages: matchedPackages,
697
+ count: matchedPackages.length,
698
+ }
699
+
700
+ // Set primary framework
701
+ if (
702
+ !detectionResults.primary &&
703
+ ['django', 'flask', 'fastapi'].includes(frameworkName)
704
+ ) {
705
+ detectionResults.primary = frameworkName
706
+ }
707
+ }
708
+ }
709
+
710
+ return detectionResults
711
+ }
712
+
713
+ /**
714
+ * Generate Django dependency groups
715
+ */
716
+ function generateDjangoGroups(_frameworkInfo) {
717
+ return {
718
+ 'django-core': {
719
+ patterns: ['django', 'djangorestframework'],
720
+ 'update-types': ['minor', 'patch'],
721
+ },
722
+ 'django-extensions': {
723
+ patterns: ['django-*'],
724
+ 'update-types': ['patch'],
725
+ },
726
+ }
727
+ }
728
+
729
+ /**
730
+ * Generate FastAPI dependency groups
731
+ */
732
+ function generateFastAPIGroups(_frameworkInfo) {
733
+ return {
734
+ 'fastapi-core': {
735
+ patterns: ['fastapi', 'uvicorn', 'starlette', 'pydantic'],
736
+ 'update-types': ['minor', 'patch'],
737
+ },
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Generate Flask dependency groups
743
+ */
744
+ function generateFlaskGroups(_frameworkInfo) {
745
+ return {
746
+ 'flask-core': {
747
+ patterns: ['flask', 'flask-*'],
748
+ 'update-types': ['minor', 'patch'],
749
+ },
750
+ }
751
+ }
752
+
753
+ /**
754
+ * Generate Data Science dependency groups
755
+ */
756
+ function generateDataScienceGroups(_frameworkInfo) {
757
+ return {
758
+ 'data-core': {
759
+ patterns: ['numpy', 'pandas', 'scipy'],
760
+ 'update-types': ['minor', 'patch'],
761
+ },
762
+ 'ml-frameworks': {
763
+ patterns: ['scikit-learn', 'tensorflow', 'torch', 'pytorch'],
764
+ 'update-types': ['patch'],
765
+ },
766
+ visualization: {
767
+ patterns: ['matplotlib', 'seaborn', 'plotly'],
768
+ 'update-types': ['patch'],
769
+ },
770
+ }
771
+ }
772
+
773
+ /**
774
+ * Generate Python testing groups
775
+ */
776
+ function generatePythonTestingGroups(_frameworkInfo) {
777
+ return {
778
+ 'testing-frameworks': {
779
+ patterns: ['pytest', 'pytest-*', 'coverage'],
780
+ 'update-types': ['minor', 'patch'],
781
+ },
782
+ }
783
+ }
784
+
785
+ /**
786
+ * ============================================================================
787
+ * RUST/CARGO ECOSYSTEM SUPPORT
788
+ * ============================================================================
789
+ */
790
+
791
+ /**
792
+ * Check if project has Rust dependencies
793
+ */
794
+ function hasRustProject(projectPath) {
795
+ return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
796
+ }
797
+
798
+ /**
799
+ * Parse Cargo.toml for Rust dependencies (simple regex-based)
800
+ */
801
+ function parseCargoToml(cargoPath) {
802
+ const content = fs.readFileSync(cargoPath, 'utf8')
803
+ const dependencies = {}
804
+
805
+ // Find [dependencies] section - extract until next section or end of file
806
+ const depsMatch = content.match(/\[dependencies\]([\s\S]*?)(?:\n\s*\[|$)/)
807
+ if (!depsMatch) return dependencies
808
+
809
+ const depsSection = depsMatch[1]
810
+
811
+ // Handle multi-line inline tables by joining continuation lines
812
+ let processedContent = depsSection
813
+ // Join lines that are part of inline tables (lines ending with incomplete braces)
814
+ processedContent = processedContent.replace(/\{[^}]*$/gm, match => {
815
+ // Find the closing brace
816
+ const startIdx = depsSection.indexOf(match)
817
+ const restContent = depsSection.slice(startIdx)
818
+ const closeBraceIdx = restContent.indexOf('}')
819
+ if (closeBraceIdx !== -1) {
820
+ return restContent.slice(0, closeBraceIdx + 1).replace(/\n/g, ' ')
821
+ }
822
+ return match
823
+ })
824
+
825
+ // Split by lines and process each line
826
+ const lines = processedContent.split('\n')
827
+ for (const line of lines) {
828
+ const trimmed = line.trim()
829
+ if (!trimmed || trimmed.startsWith('#')) continue
830
+
831
+ // Match simple pattern: name = "version"
832
+ // eslint-disable-next-line security/detect-unsafe-regex
833
+ const simpleMatch = trimmed.match(/^(\w+(?:-\w+)*)\s*=\s*["']([^"']+)["']/)
834
+ if (simpleMatch) {
835
+ const [, name, version] = simpleMatch
836
+
837
+ dependencies[name] = version
838
+ continue
839
+ }
840
+
841
+ // Match complex pattern: name = { version = "...", ... }
842
+ const complexMatch = trimmed.match(
843
+ // eslint-disable-next-line security/detect-unsafe-regex
844
+ /^(\w+(?:-\w+)*)\s*=\s*\{[^}]*version\s*=\s*["']([^"']+)["']/
845
+ )
846
+ if (complexMatch) {
847
+ const [, name, version] = complexMatch
848
+
849
+ dependencies[name] = version
850
+ }
851
+ }
852
+
853
+ return dependencies
854
+ }
855
+
856
+ /**
857
+ * Detect Rust frameworks
858
+ */
859
+ function detectRustFrameworks(projectPath) {
860
+ const cargoPath = path.join(projectPath, 'Cargo.toml')
861
+ if (!fs.existsSync(cargoPath)) {
862
+ return { primary: null, detected: {} }
863
+ }
864
+
865
+ const dependencies = parseCargoToml(cargoPath)
866
+ const detectionResults = {
867
+ primary: null,
868
+ detected: {},
869
+ }
870
+
871
+ for (const [frameworkName, categories] of Object.entries(
872
+ RUST_FRAMEWORK_SIGNATURES
873
+ )) {
874
+ const matchedPackages = []
875
+
876
+ for (const categoryPackages of Object.values(categories)) {
877
+ for (const pattern of categoryPackages) {
878
+ for (const [depName] of Object.entries(dependencies)) {
879
+ if (matchesPattern(depName, pattern)) {
880
+ matchedPackages.push(depName)
881
+ }
882
+ }
883
+ }
884
+ }
885
+
886
+ if (matchedPackages.length > 0) {
887
+ detectionResults.detected[frameworkName] = {
888
+ present: true,
889
+ packages: matchedPackages,
890
+ count: matchedPackages.length,
891
+ }
892
+
893
+ if (
894
+ !detectionResults.primary &&
895
+ ['actix', 'rocket'].includes(frameworkName)
896
+ ) {
897
+ detectionResults.primary = frameworkName
898
+ }
899
+ }
900
+ }
901
+
902
+ return detectionResults
903
+ }
904
+
905
+ /**
906
+ * Generate Actix dependency groups
907
+ */
908
+ function generateActixGroups(_frameworkInfo) {
909
+ return {
910
+ 'actix-core': {
911
+ patterns: ['actix-web', 'actix-rt'],
912
+ 'update-types': ['minor', 'patch'],
913
+ },
914
+ 'actix-ecosystem': {
915
+ patterns: ['actix-*'],
916
+ 'update-types': ['patch'],
917
+ },
918
+ }
919
+ }
920
+
921
+ /**
922
+ * Generate Async runtime dependency groups
923
+ */
924
+ function generateAsyncRuntimeGroups(_frameworkInfo) {
925
+ return {
926
+ 'async-runtime': {
927
+ patterns: ['tokio', 'async-std', 'futures'],
928
+ 'update-types': ['patch'],
929
+ },
930
+ }
931
+ }
932
+
933
+ /**
934
+ * Generate Serde dependency groups
935
+ */
936
+ function generateSerdeGroups(_frameworkInfo) {
937
+ return {
938
+ 'serde-ecosystem': {
939
+ patterns: ['serde', 'serde_json', 'serde_*'],
940
+ 'update-types': ['minor', 'patch'],
941
+ },
942
+ }
943
+ }
944
+
945
+ /**
946
+ * ============================================================================
947
+ * RUBY/BUNDLER ECOSYSTEM SUPPORT
948
+ * ============================================================================
949
+ */
950
+
951
+ /**
952
+ * Check if project has Ruby dependencies
953
+ */
954
+ function hasRubyProject(projectPath) {
955
+ return fs.existsSync(path.join(projectPath, 'Gemfile'))
956
+ }
957
+
958
+ /**
959
+ * Parse Gemfile for Ruby dependencies
960
+ */
961
+ function parseGemfile(gemfilePath) {
962
+ const content = fs.readFileSync(gemfilePath, 'utf8')
963
+ const dependencies = {}
964
+
965
+ // Process line by line to avoid newline issues
966
+ const lines = content.split('\n')
967
+ for (const line of lines) {
968
+ const trimmed = line.trim()
969
+ if (!trimmed || trimmed.startsWith('#')) continue
970
+
971
+ // Match: gem 'rails', '~> 7.0' or gem 'rails'
972
+ const gemMatch = trimmed.match(
973
+ // eslint-disable-next-line security/detect-unsafe-regex
974
+ /gem\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/
975
+ )
976
+ if (gemMatch) {
977
+ const [, name, version] = gemMatch
978
+
979
+ dependencies[name] = version || '*'
980
+ }
981
+ }
982
+
983
+ return dependencies
984
+ }
985
+
986
+ /**
987
+ * Detect Ruby frameworks
988
+ */
989
+ function detectRubyFrameworks(projectPath) {
990
+ const gemfilePath = path.join(projectPath, 'Gemfile')
991
+ if (!fs.existsSync(gemfilePath)) {
992
+ return { primary: null, detected: {} }
993
+ }
994
+
995
+ const dependencies = parseGemfile(gemfilePath)
996
+ const detectionResults = {
997
+ primary: null,
998
+ detected: {},
999
+ }
1000
+
1001
+ for (const [frameworkName, categories] of Object.entries(
1002
+ RUBY_FRAMEWORK_SIGNATURES
1003
+ )) {
1004
+ const matchedPackages = []
1005
+
1006
+ for (const categoryPackages of Object.values(categories)) {
1007
+ for (const pattern of categoryPackages) {
1008
+ for (const [depName] of Object.entries(dependencies)) {
1009
+ if (matchesPattern(depName, pattern)) {
1010
+ matchedPackages.push(depName)
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ if (matchedPackages.length > 0) {
1017
+ detectionResults.detected[frameworkName] = {
1018
+ present: true,
1019
+ packages: matchedPackages,
1020
+ count: matchedPackages.length,
1021
+ }
1022
+
1023
+ if (
1024
+ !detectionResults.primary &&
1025
+ ['rails', 'sinatra'].includes(frameworkName)
1026
+ ) {
1027
+ detectionResults.primary = frameworkName
1028
+ }
1029
+ }
1030
+ }
1031
+
1032
+ return detectionResults
1033
+ }
1034
+
1035
+ /**
1036
+ * Generate Rails dependency groups
1037
+ */
1038
+ function generateRailsGroups(_frameworkInfo) {
1039
+ return {
1040
+ 'rails-core': {
1041
+ patterns: ['rails', 'activerecord', 'actionpack'],
1042
+ 'update-types': ['minor', 'patch'],
1043
+ },
1044
+ 'rails-ecosystem': {
1045
+ patterns: ['rails-*', 'active*'],
1046
+ 'update-types': ['patch'],
1047
+ },
1048
+ }
1049
+ }
1050
+
1051
+ /**
1052
+ * Generate RSpec dependency groups
1053
+ */
1054
+ function generateRSpecGroups(_frameworkInfo) {
1055
+ return {
1056
+ 'testing-frameworks': {
1057
+ patterns: ['rspec', 'rspec-*', 'capybara', 'factory_bot'],
1058
+ 'update-types': ['minor', 'patch'],
1059
+ },
1060
+ }
1061
+ }
1062
+
1063
+ /**
1064
+ * Detect all ecosystems present in project
1065
+ */
1066
+ function detectAllEcosystems(projectPath) {
1067
+ const ecosystems = {}
1068
+
1069
+ // npm detection (existing)
1070
+ const packageJsonPath = path.join(projectPath, 'package.json')
1071
+ if (fs.existsSync(packageJsonPath)) {
1072
+ try {
1073
+ const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
1074
+
1075
+ // Validate JSON content before parsing
1076
+ if (!packageJsonContent || packageJsonContent.trim().length === 0) {
1077
+ console.error('❌ package.json is empty')
1078
+ console.log(
1079
+ 'Please add valid JSON content to package.json and try again.'
1080
+ )
1081
+ process.exit(1)
1082
+ }
1083
+
1084
+ const packageJson = JSON.parse(packageJsonContent)
1085
+
1086
+ // Validate package.json structure
1087
+ if (typeof packageJson !== 'object' || packageJson === null) {
1088
+ console.error('❌ package.json must contain a valid JSON object')
1089
+ console.log('Please fix the package.json structure and try again.')
1090
+ process.exit(1)
1091
+ }
1092
+
1093
+ ecosystems.npm = detectFrameworks(packageJson)
1094
+ } catch (error) {
1095
+ console.error(`❌ Error parsing package.json: ${error.message}`)
1096
+ console.log('\nPlease fix the JSON syntax in package.json and try again.')
1097
+ console.log(
1098
+ 'Common issues: trailing commas, missing quotes, unclosed brackets\n'
1099
+ )
1100
+ process.exit(1)
1101
+ }
1102
+ }
1103
+
1104
+ // Python detection
1105
+ if (hasPythonProject(projectPath)) {
1106
+ ecosystems.pip = detectPythonFrameworks(projectPath)
1107
+ }
1108
+
1109
+ // Rust detection
1110
+ if (hasRustProject(projectPath)) {
1111
+ ecosystems.cargo = detectRustFrameworks(projectPath)
1112
+ }
1113
+
1114
+ // Ruby detection
1115
+ if (hasRubyProject(projectPath)) {
1116
+ ecosystems.bundler = detectRubyFrameworks(projectPath)
1117
+ }
1118
+
1119
+ return ecosystems
1120
+ }
1121
+
1122
+ /**
1123
+ * Generate dependency groups for npm ecosystem
1124
+ */
1125
+ function generateNpmGroups(npmFrameworks) {
1126
+ let allGroups = {}
1127
+
1128
+ if (npmFrameworks.detected.react) {
1129
+ allGroups = {
1130
+ ...allGroups,
1131
+ ...generateReactGroups(npmFrameworks.detected.react),
1132
+ }
1133
+ }
1134
+
1135
+ if (npmFrameworks.detected.vue) {
1136
+ allGroups = {
1137
+ ...allGroups,
1138
+ ...generateVueGroups(npmFrameworks.detected.vue),
1139
+ }
1140
+ }
1141
+
1142
+ if (npmFrameworks.detected.angular) {
1143
+ allGroups = {
1144
+ ...allGroups,
1145
+ ...generateAngularGroups(npmFrameworks.detected.angular),
1146
+ }
1147
+ }
1148
+
1149
+ if (npmFrameworks.detected.testing) {
1150
+ allGroups = {
1151
+ ...allGroups,
1152
+ ...generateTestingGroups(npmFrameworks.detected.testing),
1153
+ }
1154
+ }
1155
+
1156
+ if (npmFrameworks.detected.build) {
1157
+ allGroups = {
1158
+ ...allGroups,
1159
+ ...generateBuildToolGroups(npmFrameworks.detected.build),
1160
+ }
1161
+ }
1162
+
1163
+ if (npmFrameworks.detected.storybook) {
1164
+ allGroups = {
1165
+ ...allGroups,
1166
+ ...generateStorybookGroups(npmFrameworks.detected.storybook),
1167
+ }
1168
+ }
1169
+
1170
+ return allGroups
1171
+ }
1172
+
1173
+ /**
1174
+ * Generate dependency groups for pip ecosystem
1175
+ */
1176
+ function generatePipGroups(pipFrameworks) {
1177
+ let allGroups = {}
1178
+
1179
+ if (pipFrameworks.detected.django) {
1180
+ allGroups = {
1181
+ ...allGroups,
1182
+ ...generateDjangoGroups(pipFrameworks.detected.django),
1183
+ }
1184
+ }
1185
+
1186
+ if (pipFrameworks.detected.flask) {
1187
+ allGroups = {
1188
+ ...allGroups,
1189
+ ...generateFlaskGroups(pipFrameworks.detected.flask),
1190
+ }
1191
+ }
1192
+
1193
+ if (pipFrameworks.detected.fastapi) {
1194
+ allGroups = {
1195
+ ...allGroups,
1196
+ ...generateFastAPIGroups(pipFrameworks.detected.fastapi),
1197
+ }
1198
+ }
1199
+
1200
+ if (pipFrameworks.detected.datascience) {
1201
+ allGroups = {
1202
+ ...allGroups,
1203
+ ...generateDataScienceGroups(pipFrameworks.detected.datascience),
1204
+ }
1205
+ }
1206
+
1207
+ if (pipFrameworks.detected.testing) {
1208
+ allGroups = {
1209
+ ...allGroups,
1210
+ ...generatePythonTestingGroups(pipFrameworks.detected.testing),
1211
+ }
1212
+ }
1213
+
1214
+ return allGroups
1215
+ }
1216
+
1217
+ /**
1218
+ * Generate dependency groups for cargo ecosystem
1219
+ */
1220
+ function generateCargoGroups(cargoFrameworks) {
1221
+ let allGroups = {}
1222
+
1223
+ if (cargoFrameworks.detected.actix) {
1224
+ allGroups = {
1225
+ ...allGroups,
1226
+ ...generateActixGroups(cargoFrameworks.detected.actix),
1227
+ }
1228
+ }
1229
+
1230
+ if (cargoFrameworks.detected.async) {
1231
+ allGroups = {
1232
+ ...allGroups,
1233
+ ...generateAsyncRuntimeGroups(cargoFrameworks.detected.async),
1234
+ }
1235
+ }
1236
+
1237
+ if (cargoFrameworks.detected.serde) {
1238
+ allGroups = {
1239
+ ...allGroups,
1240
+ ...generateSerdeGroups(cargoFrameworks.detected.serde),
1241
+ }
1242
+ }
1243
+
1244
+ return allGroups
1245
+ }
1246
+
1247
+ /**
1248
+ * Generate dependency groups for bundler ecosystem
1249
+ */
1250
+ function generateBundlerGroups(bundlerFrameworks) {
1251
+ let allGroups = {}
1252
+
1253
+ if (bundlerFrameworks.detected.rails) {
1254
+ allGroups = {
1255
+ ...allGroups,
1256
+ ...generateRailsGroups(bundlerFrameworks.detected.rails),
1257
+ }
1258
+ }
1259
+
1260
+ if (bundlerFrameworks.detected.testing) {
1261
+ allGroups = {
1262
+ ...allGroups,
1263
+ ...generateRSpecGroups(bundlerFrameworks.detected.testing),
1264
+ }
1265
+ }
1266
+
1267
+ return allGroups
1268
+ }
1269
+
1270
+ /**
1271
+ * Generate premium Dependabot configuration with multi-language framework-aware grouping
1272
+ *
1273
+ * @param {Object} options - Configuration options
1274
+ * @param {string} options.projectPath - Path to project directory
1275
+ * @param {string} options.schedule - Update schedule (daily, weekly, monthly)
1276
+ * @param {string} options.day - Day of week for updates
1277
+ * @param {string} options.time - Time for updates
1278
+ * @returns {Object|null} Dependabot configuration object or null if not licensed
1279
+ */
1280
+ function generatePremiumDependabotConfig(options = {}) {
1281
+ const license = getLicenseInfo()
1282
+
1283
+ // Premium features require Pro, Team, or Enterprise tier
1284
+ // FREE tier users get basic npm-only config
1285
+ const isPremiumTier =
1286
+ license.tier === 'PRO' ||
1287
+ license.tier === 'TEAM' ||
1288
+ license.tier === 'ENTERPRISE'
1289
+
1290
+ if (!isPremiumTier) {
1291
+ console.log(
1292
+ '💡 Multi-language monitoring requires Pro, Team, or Enterprise tier. Generating basic config...'
1293
+ )
1294
+ return generateBasicDependabotConfig(options)
1295
+ }
1296
+
1297
+ const {
1298
+ projectPath = '.',
1299
+ schedule = 'weekly',
1300
+ day = 'monday',
1301
+ time = '09:00',
1302
+ } = options
1303
+
1304
+ // Detect all ecosystems
1305
+ const ecosystems = detectAllEcosystems(projectPath)
1306
+
1307
+ // If no ecosystems detected, return null
1308
+ if (Object.keys(ecosystems).length === 0) {
1309
+ return null
1310
+ }
1311
+
1312
+ const updates = []
1313
+
1314
+ // npm ecosystem (if present)
1315
+ if (ecosystems.npm) {
1316
+ const npmGroups = generateNpmGroups(ecosystems.npm)
1317
+ updates.push({
1318
+ 'package-ecosystem': 'npm',
1319
+ directory: '/',
1320
+ schedule: { interval: schedule, day, time },
1321
+ 'open-pull-requests-limit': 10,
1322
+ labels: ['dependencies', 'npm'],
1323
+ 'commit-message': { prefix: 'deps(npm)', include: 'scope' },
1324
+ ...(Object.keys(npmGroups).length > 0 && { groups: npmGroups }),
1325
+ })
1326
+ }
1327
+
1328
+ // pip ecosystem (if present)
1329
+ if (ecosystems.pip) {
1330
+ const pipGroups = generatePipGroups(ecosystems.pip)
1331
+ updates.push({
1332
+ 'package-ecosystem': 'pip',
1333
+ directory: '/',
1334
+ schedule: { interval: schedule, day, time },
1335
+ 'open-pull-requests-limit': 10,
1336
+ labels: ['dependencies', 'python'],
1337
+ 'commit-message': { prefix: 'deps(python)' },
1338
+ ...(Object.keys(pipGroups).length > 0 && { groups: pipGroups }),
1339
+ })
1340
+ }
1341
+
1342
+ // cargo ecosystem (if present)
1343
+ if (ecosystems.cargo) {
1344
+ const cargoGroups = generateCargoGroups(ecosystems.cargo)
1345
+ updates.push({
1346
+ 'package-ecosystem': 'cargo',
1347
+ directory: '/',
1348
+ schedule: { interval: schedule, day, time },
1349
+ 'open-pull-requests-limit': 10,
1350
+ labels: ['dependencies', 'rust'],
1351
+ 'commit-message': { prefix: 'deps(rust)' },
1352
+ ...(Object.keys(cargoGroups).length > 0 && { groups: cargoGroups }),
1353
+ })
1354
+ }
1355
+
1356
+ // bundler ecosystem (if present)
1357
+ if (ecosystems.bundler) {
1358
+ const bundlerGroups = generateBundlerGroups(ecosystems.bundler)
1359
+ updates.push({
1360
+ 'package-ecosystem': 'bundler',
1361
+ directory: '/',
1362
+ schedule: { interval: schedule, day, time },
1363
+ 'open-pull-requests-limit': 10,
1364
+ labels: ['dependencies', 'ruby'],
1365
+ 'commit-message': { prefix: 'deps(ruby)' },
1366
+ ...(Object.keys(bundlerGroups).length > 0 && { groups: bundlerGroups }),
1367
+ })
1368
+ }
1369
+
1370
+ // GitHub Actions monitoring (always included)
1371
+ updates.push({
1372
+ 'package-ecosystem': 'github-actions',
1373
+ directory: '/',
1374
+ schedule: { interval: schedule, day, time },
1375
+ labels: ['dependencies', 'github-actions'],
1376
+ 'commit-message': { prefix: 'deps(actions)' },
1377
+ })
1378
+
1379
+ const config = {
1380
+ version: 2,
1381
+ updates,
1382
+ }
1383
+
1384
+ return { config, ecosystems }
1385
+ }
1386
+
1387
+ /**
1388
+ * Write premium Dependabot configuration to file (multi-language support)
1389
+ *
1390
+ * @param {Object} configData - Config and ecosystem detection results
1391
+ * @param {string} outputPath - Path to write config file
1392
+ */
1393
+ function writePremiumDependabotConfig(configData, outputPath) {
1394
+ const { config, ecosystems } = configData
1395
+
1396
+ // Build header with all detected ecosystems
1397
+ const _detectedLanguages = Object.keys(ecosystems).filter(e => e !== 'npm')
1398
+ const languageList = Object.keys(ecosystems).join(', ')
1399
+
1400
+ let frameworkSummary = ''
1401
+ Object.entries(ecosystems).forEach(([ecosystem, data]) => {
1402
+ const frameworks = Object.keys(data.detected || {}).join(', ')
1403
+ if (frameworks) {
1404
+ frameworkSummary += `# ${ecosystem}: ${frameworks}\n`
1405
+ }
1406
+ })
1407
+
1408
+ const yamlContent = `# Premium Dependabot configuration (Pro Tier)
1409
+ # Auto-generated by create-qa-architect
1410
+ # Multi-language framework-aware dependency grouping
1411
+ #
1412
+ # Detected ecosystems: ${languageList}
1413
+ ${frameworkSummary}#
1414
+ # This configuration groups dependencies by framework to reduce PR volume
1415
+ # and make dependency updates more manageable across all languages.
1416
+ #
1417
+ # Learn more: https://create-qa-architect.dev/docs/multi-language-grouping
1418
+
1419
+ ${yaml.dump(config, { indent: 2, lineWidth: 120, sortKeys: false })}`
1420
+
1421
+ const configDir = path.dirname(outputPath)
1422
+ if (!fs.existsSync(configDir)) {
1423
+ fs.mkdirSync(configDir, { recursive: true })
1424
+ }
1425
+
1426
+ fs.writeFileSync(outputPath, yamlContent)
1427
+
1428
+ // Post-write validation: verify the file can be parsed
1429
+ try {
1430
+ const writtenContent = fs.readFileSync(outputPath, 'utf8')
1431
+ yaml.load(writtenContent)
1432
+ } catch (error) {
1433
+ throw new Error(
1434
+ `Generated Premium Dependabot configuration is invalid YAML: ${error.message}`
1435
+ )
1436
+ }
1437
+ }
1438
+
1439
+ module.exports = {
1440
+ // npm ecosystem
1441
+ detectFrameworks,
1442
+ generateReactGroups,
1443
+ generateVueGroups,
1444
+ generateAngularGroups,
1445
+ generateTestingGroups,
1446
+ generateBuildToolGroups,
1447
+ generateStorybookGroups,
1448
+
1449
+ // Python ecosystem
1450
+ detectPythonFrameworks,
1451
+ generateDjangoGroups,
1452
+ generateFlaskGroups,
1453
+ generateFastAPIGroups,
1454
+ generateDataScienceGroups,
1455
+ generatePythonTestingGroups,
1456
+
1457
+ // Rust ecosystem
1458
+ detectRustFrameworks,
1459
+ generateActixGroups,
1460
+ generateAsyncRuntimeGroups,
1461
+ generateSerdeGroups,
1462
+
1463
+ // Ruby ecosystem
1464
+ detectRubyFrameworks,
1465
+ generateRailsGroups,
1466
+ generateRSpecGroups,
1467
+
1468
+ // Multi-language support
1469
+ detectAllEcosystems,
1470
+ generateNpmGroups,
1471
+ generatePipGroups,
1472
+ generateCargoGroups,
1473
+ generateBundlerGroups,
1474
+
1475
+ // Main config generation
1476
+ generatePremiumDependabotConfig,
1477
+ writePremiumDependabotConfig,
1478
+
1479
+ // Parsing functions (for testing)
1480
+ parsePyprojectToml,
1481
+ parsePipRequirements,
1482
+ parseCargoToml,
1483
+ parseGemfile,
1484
+
1485
+ // Framework signatures (for testing)
1486
+ NPM_FRAMEWORK_SIGNATURES,
1487
+ PYTHON_FRAMEWORK_SIGNATURES,
1488
+ RUST_FRAMEWORK_SIGNATURES,
1489
+ RUBY_FRAMEWORK_SIGNATURES,
1490
+ }