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
package/setup.js
CHANGED
|
@@ -1,7 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* DR26 fix: Architectural Refactoring Plan for setup.js (2201 lines)
|
|
5
|
+
*
|
|
6
|
+
* PRIORITY: This file should be split into focused modules to improve
|
|
7
|
+
* maintainability, testability, and reduce cognitive load.
|
|
8
|
+
*
|
|
9
|
+
* PROPOSED MODULE STRUCTURE (target: < 500 lines per module):
|
|
10
|
+
*
|
|
11
|
+
* 1. lib/commands/validate.js (~300 lines)
|
|
12
|
+
* - Validation command logic
|
|
13
|
+
* - Config validation workflows
|
|
14
|
+
* - Prelaunch validation integration
|
|
15
|
+
*
|
|
16
|
+
* 2. lib/commands/deps.js (~200 lines)
|
|
17
|
+
* - Dependency monitoring setup
|
|
18
|
+
* - Dependabot configuration
|
|
19
|
+
* - GitHub API integration calls
|
|
20
|
+
*
|
|
21
|
+
* 3. lib/commands/activate.js (~150 lines)
|
|
22
|
+
* - License activation flow
|
|
23
|
+
* - Interactive license prompts
|
|
24
|
+
* - License validation integration
|
|
25
|
+
*
|
|
26
|
+
* 4. lib/commands/setup-main.js (~800 lines)
|
|
27
|
+
* - Main setup orchestration
|
|
28
|
+
* - Template generation
|
|
29
|
+
* - File writing operations
|
|
30
|
+
* - Husky/lint-staged configuration
|
|
31
|
+
*
|
|
32
|
+
* 5. lib/setup-config.js (~200 lines)
|
|
33
|
+
* - Configuration merging logic
|
|
34
|
+
* - Package.json updates
|
|
35
|
+
* - Workflow injection helpers
|
|
36
|
+
*
|
|
37
|
+
* 6. setup.js (core CLI, ~200 lines)
|
|
38
|
+
* - Argument parsing
|
|
39
|
+
* - Command routing
|
|
40
|
+
* - Help/version display
|
|
41
|
+
* - Exit code handling
|
|
42
|
+
*
|
|
43
|
+
* MIGRATION CHECKLIST:
|
|
44
|
+
* - [ ] Extract validation command to lib/commands/validate.js
|
|
45
|
+
* - [ ] Extract deps command to lib/commands/deps.js
|
|
46
|
+
* - [ ] Extract license activation to lib/commands/activate.js
|
|
47
|
+
* - [ ] Extract main setup flow to lib/commands/setup-main.js
|
|
48
|
+
* - [ ] Extract config helpers to lib/setup-config.js
|
|
49
|
+
* - [ ] Update all tests to use new module structure
|
|
50
|
+
* - [ ] Verify all CLI commands still work (--help, --deps, --validate, etc.)
|
|
51
|
+
* - [ ] Update integration tests
|
|
52
|
+
* - [ ] Run full test suite and ensure 100% pass rate
|
|
53
|
+
*
|
|
54
|
+
* BENEFITS:
|
|
55
|
+
* - Easier to test individual commands
|
|
56
|
+
* - Reduced cognitive load when modifying specific features
|
|
57
|
+
* - Better code organization and discoverability
|
|
58
|
+
* - Easier to add new commands in the future
|
|
59
|
+
* - Faster development cycles (smaller files to navigate)
|
|
60
|
+
*
|
|
61
|
+
* BLOCKED BY: None (can be done incrementally)
|
|
62
|
+
* ESTIMATED EFFORT: 2-3 days with full test coverage
|
|
63
|
+
* RISK: Medium (need to ensure no regressions in CLI behavior)
|
|
64
|
+
*/
|
|
65
|
+
|
|
3
66
|
const fs = require('fs')
|
|
4
67
|
const path = require('path')
|
|
68
|
+
const crypto = require('crypto')
|
|
5
69
|
const { execSync } = require('child_process')
|
|
6
70
|
const {
|
|
7
71
|
mergeScripts,
|
|
@@ -49,29 +113,23 @@ const {
|
|
|
49
113
|
} = require('./config/defaults')
|
|
50
114
|
|
|
51
115
|
// Enhanced validation capabilities
|
|
52
|
-
const { ValidationRunner } = require('./lib/validation')
|
|
53
116
|
const { validateQualityConfig } = require('./lib/config-validator')
|
|
54
117
|
|
|
55
118
|
// Interactive mode capabilities
|
|
56
119
|
const { InteractivePrompt } = require('./lib/interactive/prompt')
|
|
57
120
|
const { runInteractiveFlow } = require('./lib/interactive/questions')
|
|
58
121
|
|
|
59
|
-
//
|
|
60
|
-
const {
|
|
61
|
-
hasNpmProject,
|
|
62
|
-
generateBasicDependabotConfig,
|
|
63
|
-
writeBasicDependabotConfig,
|
|
64
|
-
} = require('./lib/dependency-monitoring-basic')
|
|
65
|
-
|
|
66
|
-
// Premium dependency monitoring (Pro/Team/Enterprise Tiers)
|
|
67
|
-
const {
|
|
68
|
-
generatePremiumDependabotConfig,
|
|
69
|
-
writePremiumDependabotConfig,
|
|
70
|
-
} = require('./lib/dependency-monitoring-premium')
|
|
122
|
+
// Note: Dependency monitoring imports moved to ./lib/commands/deps.js
|
|
71
123
|
|
|
72
124
|
// Custom template loading
|
|
73
125
|
const { TemplateLoader } = require('./lib/template-loader')
|
|
74
126
|
|
|
127
|
+
// Command handlers (extracted for maintainability)
|
|
128
|
+
const {
|
|
129
|
+
handleValidationCommands,
|
|
130
|
+
handleDependencyMonitoring,
|
|
131
|
+
} = require('./lib/commands')
|
|
132
|
+
|
|
75
133
|
// Licensing system
|
|
76
134
|
const {
|
|
77
135
|
getLicenseInfo,
|
|
@@ -91,6 +149,26 @@ const {
|
|
|
91
149
|
getTestTierScripts,
|
|
92
150
|
} = require('./lib/smart-strategy-generator')
|
|
93
151
|
|
|
152
|
+
// Quality Tools Generator (Lighthouse, size-limit, axe-core, commitlint, coverage)
|
|
153
|
+
const {
|
|
154
|
+
writeLighthouseConfig,
|
|
155
|
+
writeSizeLimitConfig,
|
|
156
|
+
writeCommitlintConfig,
|
|
157
|
+
writeCommitMsgHook,
|
|
158
|
+
writeAxeTestSetup,
|
|
159
|
+
getQualityToolsDependencies,
|
|
160
|
+
getQualityToolsScripts,
|
|
161
|
+
} = require('./lib/quality-tools-generator')
|
|
162
|
+
|
|
163
|
+
// Pre-Launch Validation (SEO, links, a11y, docs, env)
|
|
164
|
+
const {
|
|
165
|
+
writeValidationScripts,
|
|
166
|
+
writeEnvValidator,
|
|
167
|
+
writePa11yConfig,
|
|
168
|
+
getPrelaunchScripts,
|
|
169
|
+
getPrelaunchDependencies,
|
|
170
|
+
} = require('./lib/prelaunch-validator')
|
|
171
|
+
|
|
94
172
|
// Telemetry (opt-in usage tracking)
|
|
95
173
|
const { TelemetrySession, showTelemetryStatus } = require('./lib/telemetry')
|
|
96
174
|
|
|
@@ -103,7 +181,6 @@ const {
|
|
|
103
181
|
// Critical setup enhancements (fixes production quality gaps)
|
|
104
182
|
const {
|
|
105
183
|
applyProductionQualityFixes,
|
|
106
|
-
// generateEnhancedPreCommitHook,
|
|
107
184
|
validateProjectSetup,
|
|
108
185
|
} = require('./lib/setup-enhancements')
|
|
109
186
|
|
|
@@ -113,6 +190,31 @@ const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
|
|
|
113
190
|
const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
|
|
114
191
|
const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
|
|
115
192
|
|
|
193
|
+
function normalizeRepoIdentifier(remoteUrl) {
|
|
194
|
+
if (!remoteUrl || typeof remoteUrl !== 'string') return null
|
|
195
|
+
|
|
196
|
+
const scpMatch = remoteUrl.match(/^[^@]+@([^:]+):(.+?)(\.git)?$/)
|
|
197
|
+
if (scpMatch) {
|
|
198
|
+
const host = scpMatch[1]
|
|
199
|
+
const repoPath = scpMatch[2].replace(/^\/+/, '').replace(/\.git$/, '')
|
|
200
|
+
return `${host}/${repoPath}`
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
try {
|
|
204
|
+
const parsed = new URL(remoteUrl)
|
|
205
|
+
const host = parsed.hostname
|
|
206
|
+
const repoPath = parsed.pathname.replace(/^\/+/, '').replace(/\.git$/, '')
|
|
207
|
+
if (!host || !repoPath) return null
|
|
208
|
+
return `${host}/${repoPath}`
|
|
209
|
+
} catch (_error) {
|
|
210
|
+
return null
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function hashRepoIdentifier(value) {
|
|
215
|
+
return crypto.createHash('sha256').update(value).digest('hex')
|
|
216
|
+
}
|
|
217
|
+
|
|
116
218
|
function injectCollaborationSteps(workflowContent, options = {}) {
|
|
117
219
|
const { enableSlackAlerts = false, enablePrComments = false } = options
|
|
118
220
|
let updated = workflowContent
|
|
@@ -151,7 +253,44 @@ function injectCollaborationSteps(workflowContent, options = {}) {
|
|
|
151
253
|
const safeReadDir = dir => {
|
|
152
254
|
try {
|
|
153
255
|
return fs.readdirSync(dir, { withFileTypes: true })
|
|
154
|
-
} catch {
|
|
256
|
+
} catch (error) {
|
|
257
|
+
// ENOENT is expected (dir doesn't exist) - return empty silently
|
|
258
|
+
if (error?.code === 'ENOENT') {
|
|
259
|
+
if (process.env.DEBUG) {
|
|
260
|
+
console.warn(
|
|
261
|
+
` Debug: safeReadDir(${dir}) returned empty (directory not found)`
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
return []
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// All other errors are unexpected and should be logged with context
|
|
268
|
+
console.error(`❌ Failed to read directory: ${dir}`)
|
|
269
|
+
console.error(` Error: ${error.message} (${error.code || 'unknown'})`)
|
|
270
|
+
console.error(` This may indicate a permission or filesystem issue`)
|
|
271
|
+
|
|
272
|
+
// Report to error tracking in production
|
|
273
|
+
if (process.env.NODE_ENV === 'production') {
|
|
274
|
+
try {
|
|
275
|
+
const errorReporter = new ErrorReporter()
|
|
276
|
+
errorReporter.captureException(error, {
|
|
277
|
+
context: 'safeReadDir',
|
|
278
|
+
directory: dir,
|
|
279
|
+
errorCode: error.code,
|
|
280
|
+
})
|
|
281
|
+
} catch (_error) {
|
|
282
|
+
// Don't fail if error reporting fails
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Re-throw for critical errors instead of silently returning []
|
|
287
|
+
if (['EACCES', 'EIO', 'ELOOP', 'EMFILE'].includes(error.code)) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Cannot read directory ${dir}: ${error.message}. ` +
|
|
290
|
+
`This may indicate a serious filesystem or permission issue.`
|
|
291
|
+
)
|
|
292
|
+
}
|
|
293
|
+
|
|
155
294
|
return []
|
|
156
295
|
}
|
|
157
296
|
}
|
|
@@ -332,7 +471,14 @@ function parseArguments(rawArgs) {
|
|
|
332
471
|
const isCheckMaturityMode = sanitizedArgs.includes('--check-maturity')
|
|
333
472
|
const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
|
|
334
473
|
const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
|
|
474
|
+
const isAnalyzeCiMode = sanitizedArgs.includes('--analyze-ci')
|
|
475
|
+
const isPrelaunchMode = sanitizedArgs.includes('--prelaunch')
|
|
335
476
|
const isDryRun = sanitizedArgs.includes('--dry-run')
|
|
477
|
+
const isWorkflowMinimal = sanitizedArgs.includes('--workflow-minimal')
|
|
478
|
+
const isWorkflowStandard = sanitizedArgs.includes('--workflow-standard')
|
|
479
|
+
const isWorkflowComprehensive = sanitizedArgs.includes(
|
|
480
|
+
'--workflow-comprehensive'
|
|
481
|
+
)
|
|
336
482
|
const ciProviderIndex = sanitizedArgs.findIndex(arg => arg === '--ci')
|
|
337
483
|
const ciProvider =
|
|
338
484
|
ciProviderIndex !== -1 && sanitizedArgs[ciProviderIndex + 1]
|
|
@@ -344,11 +490,39 @@ function parseArguments(rawArgs) {
|
|
|
344
490
|
// Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
|
|
345
491
|
// Normalize path to prevent traversal attacks and make absolute
|
|
346
492
|
const templateFlagIndex = sanitizedArgs.findIndex(arg => arg === '--template')
|
|
347
|
-
|
|
493
|
+
let customTemplatePath =
|
|
348
494
|
templateFlagIndex !== -1 && rawArgs[templateFlagIndex + 1]
|
|
349
495
|
? path.resolve(rawArgs[templateFlagIndex + 1])
|
|
350
496
|
: null
|
|
351
497
|
|
|
498
|
+
// Validate custom template path early to prevent path traversal attacks
|
|
499
|
+
if (customTemplatePath) {
|
|
500
|
+
const inputPath = rawArgs[templateFlagIndex + 1]
|
|
501
|
+
// Check for suspicious patterns (path traversal attempts)
|
|
502
|
+
if (inputPath.includes('..') || inputPath.includes('~')) {
|
|
503
|
+
console.error(
|
|
504
|
+
`❌ Invalid template path: "${inputPath}". Path traversal patterns not allowed.`
|
|
505
|
+
)
|
|
506
|
+
console.error(' Use absolute paths only (e.g., /Users/you/templates)')
|
|
507
|
+
process.exit(1)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Verify the resolved path exists and is a directory
|
|
511
|
+
try {
|
|
512
|
+
const stats = fs.statSync(customTemplatePath)
|
|
513
|
+
if (!stats.isDirectory()) {
|
|
514
|
+
console.error(
|
|
515
|
+
`❌ Template path is not a directory: ${customTemplatePath}`
|
|
516
|
+
)
|
|
517
|
+
process.exit(1)
|
|
518
|
+
}
|
|
519
|
+
} catch (error) {
|
|
520
|
+
console.error(`❌ Template path does not exist: ${customTemplatePath}`)
|
|
521
|
+
console.error(` Error: ${error.message}`)
|
|
522
|
+
process.exit(1)
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
352
526
|
// Granular tool disable options
|
|
353
527
|
const disableNpmAudit = sanitizedArgs.includes('--no-npm-audit')
|
|
354
528
|
const disableGitleaks = sanitizedArgs.includes('--no-gitleaks')
|
|
@@ -372,6 +546,8 @@ function parseArguments(rawArgs) {
|
|
|
372
546
|
isCheckMaturityMode,
|
|
373
547
|
isValidateConfigMode,
|
|
374
548
|
isActivateLicenseMode,
|
|
549
|
+
isAnalyzeCiMode,
|
|
550
|
+
isPrelaunchMode,
|
|
375
551
|
isDryRun,
|
|
376
552
|
ciProvider,
|
|
377
553
|
enableSlackAlerts,
|
|
@@ -383,6 +559,9 @@ function parseArguments(rawArgs) {
|
|
|
383
559
|
disableMarkdownlint,
|
|
384
560
|
disableEslintSecurity,
|
|
385
561
|
allowLatestGitleaks,
|
|
562
|
+
isWorkflowMinimal,
|
|
563
|
+
isWorkflowStandard,
|
|
564
|
+
isWorkflowComprehensive,
|
|
386
565
|
}
|
|
387
566
|
}
|
|
388
567
|
|
|
@@ -410,6 +589,8 @@ function parseArguments(rawArgs) {
|
|
|
410
589
|
isCheckMaturityMode,
|
|
411
590
|
isValidateConfigMode,
|
|
412
591
|
isActivateLicenseMode,
|
|
592
|
+
isPrelaunchMode,
|
|
593
|
+
isAnalyzeCiMode,
|
|
413
594
|
isDryRun,
|
|
414
595
|
ciProvider,
|
|
415
596
|
enableSlackAlerts,
|
|
@@ -421,6 +602,9 @@ function parseArguments(rawArgs) {
|
|
|
421
602
|
disableMarkdownlint,
|
|
422
603
|
disableEslintSecurity,
|
|
423
604
|
allowLatestGitleaks,
|
|
605
|
+
isWorkflowMinimal,
|
|
606
|
+
isWorkflowStandard,
|
|
607
|
+
isWorkflowComprehensive,
|
|
424
608
|
} = parsedConfig
|
|
425
609
|
|
|
426
610
|
// Initialize telemetry session (opt-in only, fails silently)
|
|
@@ -483,6 +667,8 @@ function parseArguments(rawArgs) {
|
|
|
483
667
|
isCheckMaturityMode,
|
|
484
668
|
isValidateConfigMode,
|
|
485
669
|
isActivateLicenseMode,
|
|
670
|
+
isPrelaunchMode,
|
|
671
|
+
isAnalyzeCiMode,
|
|
486
672
|
isDryRun,
|
|
487
673
|
ciProvider,
|
|
488
674
|
enableSlackAlerts,
|
|
@@ -494,6 +680,9 @@ function parseArguments(rawArgs) {
|
|
|
494
680
|
disableMarkdownlint,
|
|
495
681
|
disableEslintSecurity,
|
|
496
682
|
allowLatestGitleaks,
|
|
683
|
+
isWorkflowMinimal,
|
|
684
|
+
isWorkflowStandard,
|
|
685
|
+
isWorkflowComprehensive,
|
|
497
686
|
} = parsedConfig)
|
|
498
687
|
|
|
499
688
|
console.log('📋 Configuration after interactive selections applied\n')
|
|
@@ -528,6 +717,15 @@ SETUP OPTIONS:
|
|
|
528
717
|
--template <path> Use custom templates from specified directory
|
|
529
718
|
--dry-run Preview changes without modifying files
|
|
530
719
|
|
|
720
|
+
WORKFLOW TIERS (GitHub Actions optimization):
|
|
721
|
+
--workflow-minimal Minimal CI (default) - Single Node version, weekly security
|
|
722
|
+
~5-10 min/commit, ~$0-5/mo for typical projects
|
|
723
|
+
--workflow-standard Standard CI - Matrix testing on main, weekly security
|
|
724
|
+
~15-20 min/commit, ~$5-20/mo for typical projects
|
|
725
|
+
--workflow-comprehensive Comprehensive CI - Matrix on every push, security inline
|
|
726
|
+
~50-100 min/commit, ~$100-350/mo for typical projects
|
|
727
|
+
--analyze-ci Analyze GitHub Actions usage and get optimization tips (Pro)
|
|
728
|
+
|
|
531
729
|
VALIDATION OPTIONS:
|
|
532
730
|
--validate Run comprehensive validation (same as --comprehensive)
|
|
533
731
|
--comprehensive Run all validation checks
|
|
@@ -535,6 +733,7 @@ VALIDATION OPTIONS:
|
|
|
535
733
|
--validate-docs Run documentation validation only
|
|
536
734
|
--validate-config Validate .qualityrc.json configuration file
|
|
537
735
|
--check-maturity Detect and display project maturity level
|
|
736
|
+
--prelaunch Add pre-launch validation suite (SEO, links, a11y, docs)
|
|
538
737
|
|
|
539
738
|
LICENSE, TELEMETRY & ERROR REPORTING:
|
|
540
739
|
--license-status Show current license tier and available features
|
|
@@ -576,6 +775,9 @@ EXAMPLES:
|
|
|
576
775
|
npx create-qa-architect@latest --check-maturity
|
|
577
776
|
→ Detect project maturity level (minimal, bootstrap, development, production-ready)
|
|
578
777
|
|
|
778
|
+
npx create-qa-architect@latest --prelaunch
|
|
779
|
+
→ Add pre-launch validation: SEO (sitemap, robots, meta), links, a11y, docs
|
|
780
|
+
|
|
579
781
|
npx create-qa-architect@latest --validate-config
|
|
580
782
|
→ Validate .qualityrc.json configuration file against JSON Schema
|
|
581
783
|
|
|
@@ -591,6 +793,18 @@ EXAMPLES:
|
|
|
591
793
|
npx create-qa-architect@latest --dry-run
|
|
592
794
|
→ Preview what files and configurations would be created/modified
|
|
593
795
|
|
|
796
|
+
npx create-qa-architect@latest --workflow-minimal
|
|
797
|
+
→ Set up with minimal CI (default) - fastest, cheapest, ideal for solo devs
|
|
798
|
+
|
|
799
|
+
npx create-qa-architect@latest --workflow-standard
|
|
800
|
+
→ Set up with standard CI - balanced quality/cost for small teams
|
|
801
|
+
|
|
802
|
+
npx create-qa-architect@latest --update --workflow-minimal
|
|
803
|
+
→ Convert existing comprehensive workflow to minimal (reduce CI costs)
|
|
804
|
+
|
|
805
|
+
npx create-qa-architect@latest --analyze-ci
|
|
806
|
+
→ Analyze your GitHub Actions usage and get cost optimization recommendations (Pro)
|
|
807
|
+
|
|
594
808
|
PRIVACY & TELEMETRY:
|
|
595
809
|
Telemetry and error reporting are OPT-IN only (disabled by default). To enable:
|
|
596
810
|
export QAA_TELEMETRY=true # Usage tracking (local only)
|
|
@@ -648,258 +862,8 @@ HELP:
|
|
|
648
862
|
process.exit(0)
|
|
649
863
|
}
|
|
650
864
|
|
|
651
|
-
//
|
|
652
|
-
|
|
653
|
-
const validationOptions = {
|
|
654
|
-
disableNpmAudit,
|
|
655
|
-
disableGitleaks,
|
|
656
|
-
disableActionlint,
|
|
657
|
-
disableMarkdownlint,
|
|
658
|
-
disableEslintSecurity,
|
|
659
|
-
allowLatestGitleaks,
|
|
660
|
-
}
|
|
661
|
-
const validator = new ValidationRunner(validationOptions)
|
|
662
|
-
|
|
663
|
-
if (isConfigSecurityMode) {
|
|
664
|
-
try {
|
|
665
|
-
await validator.runConfigSecurity()
|
|
666
|
-
process.exit(0)
|
|
667
|
-
} catch (error) {
|
|
668
|
-
console.error(
|
|
669
|
-
`\n❌ Configuration security validation failed:\n${error.message}`
|
|
670
|
-
)
|
|
671
|
-
process.exit(1)
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
if (isDocsValidationMode) {
|
|
676
|
-
try {
|
|
677
|
-
await validator.runDocumentationValidation()
|
|
678
|
-
process.exit(0)
|
|
679
|
-
} catch (error) {
|
|
680
|
-
console.error(`\n❌ Documentation validation failed:\n${error.message}`)
|
|
681
|
-
process.exit(1)
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (isComprehensiveMode || isValidationMode) {
|
|
686
|
-
try {
|
|
687
|
-
// Use parallel validation for 3-5x speedup (runs checks concurrently)
|
|
688
|
-
await validator.runComprehensiveCheckParallel()
|
|
689
|
-
process.exit(0)
|
|
690
|
-
} catch (error) {
|
|
691
|
-
console.error(`\n❌ Comprehensive validation failed:\n${error.message}`)
|
|
692
|
-
process.exit(1)
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// Detect Python project
|
|
698
|
-
function detectPythonProject(projectPath) {
|
|
699
|
-
const pythonFiles = [
|
|
700
|
-
'pyproject.toml',
|
|
701
|
-
'requirements.txt',
|
|
702
|
-
'setup.py',
|
|
703
|
-
'Pipfile',
|
|
704
|
-
]
|
|
705
|
-
return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
// Detect Rust project
|
|
709
|
-
function detectRustProject(projectPath) {
|
|
710
|
-
return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Detect Ruby project
|
|
714
|
-
function detectRubyProject(projectPath) {
|
|
715
|
-
return fs.existsSync(path.join(projectPath, 'Gemfile'))
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
// Handle dependency monitoring (Free/Pro/Team/Enterprise)
|
|
719
|
-
async function handleDependencyMonitoring() {
|
|
720
|
-
const projectPath = process.cwd()
|
|
721
|
-
const license = getLicenseInfo()
|
|
722
|
-
|
|
723
|
-
// Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
|
|
724
|
-
const hasNpm = hasNpmProject(projectPath)
|
|
725
|
-
const hasPython = detectPythonProject(projectPath)
|
|
726
|
-
const hasRust = detectRustProject(projectPath)
|
|
727
|
-
const hasRuby = detectRubyProject(projectPath)
|
|
728
|
-
|
|
729
|
-
if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
|
|
730
|
-
console.error(
|
|
731
|
-
'❌ No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
|
|
732
|
-
)
|
|
733
|
-
console.log("💡 Make sure you're in a directory with dependency files.")
|
|
734
|
-
process.exit(1)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (hasNpm) console.log('📦 Detected: npm project')
|
|
738
|
-
if (hasPython) console.log('🐍 Detected: Python project')
|
|
739
|
-
if (hasRust) console.log('🦀 Detected: Rust project')
|
|
740
|
-
if (hasRuby) console.log('💎 Detected: Ruby project')
|
|
741
|
-
console.log(`📋 License tier: ${license.tier.toUpperCase()}`)
|
|
742
|
-
|
|
743
|
-
// Enforce Free tier caps for dependency monitoring (counted as dependency PRs)
|
|
744
|
-
if (license.tier === 'FREE') {
|
|
745
|
-
const capCheck = checkUsageCaps('dependency-pr')
|
|
746
|
-
if (!capCheck.allowed) {
|
|
747
|
-
console.error(`❌ ${capCheck.reason}`)
|
|
748
|
-
console.error(
|
|
749
|
-
' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qa-architect'
|
|
750
|
-
)
|
|
751
|
-
process.exit(1)
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
const increment = incrementUsage('dependency-pr')
|
|
755
|
-
const usage = increment.usage || capCheck.usage
|
|
756
|
-
const caps = capCheck.caps
|
|
757
|
-
if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
|
|
758
|
-
console.log(
|
|
759
|
-
`🧮 Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
|
|
760
|
-
)
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
|
|
765
|
-
|
|
766
|
-
// Use premium or basic config based on license tier
|
|
767
|
-
// Free beta ended with v4.1.1 - Premium features now require Pro/Team/Enterprise tier
|
|
768
|
-
// Pro/Team/Enterprise: Framework-aware dependency monitoring with grouping
|
|
769
|
-
// Free: Basic npm-only dependency monitoring
|
|
770
|
-
const shouldUsePremium =
|
|
771
|
-
license.tier === 'PRO' ||
|
|
772
|
-
license.tier === 'TEAM' ||
|
|
773
|
-
license.tier === 'ENTERPRISE'
|
|
774
|
-
|
|
775
|
-
// Free tier only supports npm projects. Fail fast with a clear message.
|
|
776
|
-
if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
|
|
777
|
-
console.error(
|
|
778
|
-
'❌ Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
|
|
779
|
-
)
|
|
780
|
-
console.error(
|
|
781
|
-
' Free tier supports npm projects only. Detected non-npm ecosystems.'
|
|
782
|
-
)
|
|
783
|
-
console.error(
|
|
784
|
-
' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
|
|
785
|
-
)
|
|
786
|
-
process.exit(1)
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
if (shouldUsePremium) {
|
|
790
|
-
console.log(
|
|
791
|
-
'\n🚀 Setting up framework-aware dependency monitoring (Premium)...\n'
|
|
792
|
-
)
|
|
793
|
-
|
|
794
|
-
const configData = generatePremiumDependabotConfig({
|
|
795
|
-
projectPath,
|
|
796
|
-
schedule: 'weekly',
|
|
797
|
-
})
|
|
798
|
-
|
|
799
|
-
if (configData) {
|
|
800
|
-
const { ecosystems } = configData
|
|
801
|
-
const ecosystemNames = Object.keys(ecosystems)
|
|
802
|
-
|
|
803
|
-
if (ecosystemNames.length > 0) {
|
|
804
|
-
console.log('🔍 Detected ecosystems:')
|
|
805
|
-
|
|
806
|
-
// Find primary ecosystem and total package count
|
|
807
|
-
let primaryEcosystem = null
|
|
808
|
-
ecosystemNames.forEach(ecoName => {
|
|
809
|
-
const eco = ecosystems[ecoName]
|
|
810
|
-
const frameworks = Object.keys(eco.detected || {})
|
|
811
|
-
const totalPackages = frameworks.reduce((sum, fw) => {
|
|
812
|
-
return sum + (eco.detected[fw]?.count || 0)
|
|
813
|
-
}, 0)
|
|
814
|
-
|
|
815
|
-
console.log(` • ${ecoName}: ${totalPackages} packages`)
|
|
816
|
-
|
|
817
|
-
if (eco.primary) {
|
|
818
|
-
primaryEcosystem = ecoName
|
|
819
|
-
}
|
|
820
|
-
})
|
|
821
|
-
|
|
822
|
-
if (primaryEcosystem) {
|
|
823
|
-
console.log(`\n🎯 Primary ecosystem: ${primaryEcosystem}`)
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
writePremiumDependabotConfig(configData, dependabotPath)
|
|
828
|
-
console.log(
|
|
829
|
-
'\n✅ Created .github/dependabot.yml with framework grouping'
|
|
830
|
-
)
|
|
831
|
-
|
|
832
|
-
console.log('\n🎉 Premium dependency monitoring setup complete!')
|
|
833
|
-
console.log('\n📋 What was added (Pro Tier):')
|
|
834
|
-
console.log(' • Framework-aware dependency grouping')
|
|
835
|
-
console.log(
|
|
836
|
-
` • ${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
|
|
837
|
-
)
|
|
838
|
-
console.log(' • Intelligent update batching (reduces PRs by 60%+)')
|
|
839
|
-
console.log(' • GitHub Actions dependency monitoring')
|
|
840
|
-
}
|
|
841
|
-
} else {
|
|
842
|
-
console.log(
|
|
843
|
-
'\n🔍 Setting up basic dependency monitoring (Free Tier)...\n'
|
|
844
|
-
)
|
|
845
|
-
|
|
846
|
-
const dependabotConfig = generateBasicDependabotConfig({
|
|
847
|
-
projectPath,
|
|
848
|
-
schedule: 'weekly',
|
|
849
|
-
})
|
|
850
|
-
|
|
851
|
-
if (dependabotConfig) {
|
|
852
|
-
writeBasicDependabotConfig(dependabotConfig, dependabotPath)
|
|
853
|
-
console.log('✅ Created .github/dependabot.yml')
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
console.log('\n🎉 Basic dependency monitoring setup complete!')
|
|
857
|
-
console.log('\n📋 What was added (Free Tier):')
|
|
858
|
-
console.log(' • Basic Dependabot configuration for npm packages')
|
|
859
|
-
console.log(' • Weekly dependency updates on Monday 9am')
|
|
860
|
-
console.log(' • GitHub Actions dependency monitoring')
|
|
861
|
-
|
|
862
|
-
// Show upgrade message for premium features
|
|
863
|
-
console.log('\n🔒 Premium features now available:')
|
|
864
|
-
console.log(
|
|
865
|
-
' ✅ Framework-aware package grouping (React, Vue, Angular)'
|
|
866
|
-
)
|
|
867
|
-
console.log(' • Coming soon: Multi-language support (Python, Rust, Go)')
|
|
868
|
-
console.log(' • Planned: Advanced security audit workflows')
|
|
869
|
-
console.log(' • Planned: Custom update schedules and notifications')
|
|
870
|
-
|
|
871
|
-
showUpgradeMessage('Framework-Aware Dependency Grouping')
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
// Auto-enable Dependabot on GitHub if token available
|
|
875
|
-
console.log('\n🔧 Attempting to enable Dependabot on GitHub...')
|
|
876
|
-
try {
|
|
877
|
-
const { setupDependabot } = require('./lib/github-api')
|
|
878
|
-
const result = await setupDependabot(projectPath, { verbose: true })
|
|
879
|
-
|
|
880
|
-
if (result.success) {
|
|
881
|
-
console.log('✅ Dependabot alerts and security updates enabled!')
|
|
882
|
-
} else if (result.errors.length > 0) {
|
|
883
|
-
console.log('⚠️ Could not auto-enable Dependabot:')
|
|
884
|
-
result.errors.forEach(err => console.log(` • ${err}`))
|
|
885
|
-
console.log('\n💡 Manual steps needed:')
|
|
886
|
-
console.log(' • Go to GitHub repo → Settings → Code security')
|
|
887
|
-
console.log(
|
|
888
|
-
' • Enable "Dependabot alerts" and "Dependabot security updates"'
|
|
889
|
-
)
|
|
890
|
-
}
|
|
891
|
-
} catch (error) {
|
|
892
|
-
console.log('⚠️ Could not auto-enable Dependabot:', error.message)
|
|
893
|
-
console.log('\n💡 Manual steps:')
|
|
894
|
-
console.log(' • Enable Dependabot in GitHub repo settings')
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
console.log('\n💡 Next steps:')
|
|
898
|
-
console.log(' • Review and commit .github/dependabot.yml')
|
|
899
|
-
console.log(
|
|
900
|
-
' • Dependabot will start monitoring weekly for dependency updates'
|
|
901
|
-
)
|
|
902
|
-
}
|
|
865
|
+
// Note: handleValidationCommands, handleDependencyMonitoring, detectPythonProject,
|
|
866
|
+
// detectRustProject, detectRubyProject are now imported from ./lib/commands
|
|
903
867
|
|
|
904
868
|
// Handle license status command
|
|
905
869
|
if (isLicenseStatusMode) {
|
|
@@ -948,6 +912,20 @@ HELP:
|
|
|
948
912
|
process.exit(0)
|
|
949
913
|
}
|
|
950
914
|
|
|
915
|
+
// Handle CI cost analysis command
|
|
916
|
+
if (isAnalyzeCiMode) {
|
|
917
|
+
return (async () => {
|
|
918
|
+
try {
|
|
919
|
+
const { handleAnalyzeCi } = require('./lib/commands/analyze-ci')
|
|
920
|
+
await handleAnalyzeCi()
|
|
921
|
+
process.exit(0)
|
|
922
|
+
} catch (error) {
|
|
923
|
+
console.error('CI cost analysis error:', error.message)
|
|
924
|
+
process.exit(1)
|
|
925
|
+
}
|
|
926
|
+
})()
|
|
927
|
+
}
|
|
928
|
+
|
|
951
929
|
// Handle validate config command
|
|
952
930
|
if (isValidateConfigMode) {
|
|
953
931
|
const { validateAndReport } = require('./lib/config-validator')
|
|
@@ -969,6 +947,83 @@ HELP:
|
|
|
969
947
|
})()
|
|
970
948
|
}
|
|
971
949
|
|
|
950
|
+
// Handle pre-launch validation setup command
|
|
951
|
+
if (isPrelaunchMode) {
|
|
952
|
+
return (async () => {
|
|
953
|
+
try {
|
|
954
|
+
const projectPath = process.cwd()
|
|
955
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
956
|
+
const pkgJson = await PackageJson.load(projectPath)
|
|
957
|
+
const license = getLicenseInfo()
|
|
958
|
+
const isPro =
|
|
959
|
+
license.tier === 'PRO' ||
|
|
960
|
+
license.tier === 'TEAM' ||
|
|
961
|
+
license.tier === 'ENTERPRISE'
|
|
962
|
+
|
|
963
|
+
console.log('\n📋 Setting up pre-launch validation suite...\n')
|
|
964
|
+
console.log(` License tier: ${license.tier.toUpperCase()}`)
|
|
965
|
+
|
|
966
|
+
// Check feature availability
|
|
967
|
+
if (!hasFeature('prelaunchValidation')) {
|
|
968
|
+
console.error('❌ Pre-launch validation requires a valid license.')
|
|
969
|
+
showUpgradeMessage('Pre-launch validation')
|
|
970
|
+
process.exit(1)
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Write validation scripts
|
|
974
|
+
const scriptsWritten = writeValidationScripts(projectPath)
|
|
975
|
+
console.log(` ✅ Created ${scriptsWritten.length} validation scripts`)
|
|
976
|
+
|
|
977
|
+
// Write pa11y config
|
|
978
|
+
writePa11yConfig(projectPath)
|
|
979
|
+
console.log(' ✅ Created .pa11yci config')
|
|
980
|
+
|
|
981
|
+
// Write env validator for Pro+
|
|
982
|
+
if (isPro && hasFeature('envValidation')) {
|
|
983
|
+
writeEnvValidator(projectPath)
|
|
984
|
+
console.log(' ✅ Created env vars validator (Pro)')
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// Add scripts to package.json
|
|
988
|
+
const prelaunchScripts = getPrelaunchScripts(isPro)
|
|
989
|
+
const prelaunchDeps = getPrelaunchDependencies(isPro)
|
|
990
|
+
|
|
991
|
+
// Merge scripts
|
|
992
|
+
const existingScripts = pkgJson.content.scripts || {}
|
|
993
|
+
pkgJson.update({
|
|
994
|
+
scripts: { ...existingScripts, ...prelaunchScripts },
|
|
995
|
+
})
|
|
996
|
+
|
|
997
|
+
// Merge devDependencies
|
|
998
|
+
const existingDevDeps = pkgJson.content.devDependencies || {}
|
|
999
|
+
pkgJson.update({
|
|
1000
|
+
devDependencies: { ...existingDevDeps, ...prelaunchDeps },
|
|
1001
|
+
})
|
|
1002
|
+
|
|
1003
|
+
await pkgJson.save()
|
|
1004
|
+
|
|
1005
|
+
console.log('\n✅ Pre-launch validation setup complete!\n')
|
|
1006
|
+
console.log('Available scripts:')
|
|
1007
|
+
console.log(' npm run validate:sitemap - Check sitemap.xml')
|
|
1008
|
+
console.log(' npm run validate:robots - Check robots.txt')
|
|
1009
|
+
console.log(' npm run validate:meta - Check meta tags')
|
|
1010
|
+
console.log(' npm run validate:links - Check for broken links')
|
|
1011
|
+
console.log(' npm run validate:a11y - Run accessibility audit')
|
|
1012
|
+
console.log(' npm run validate:docs - Check documentation')
|
|
1013
|
+
if (isPro) {
|
|
1014
|
+
console.log(' npm run validate:env - Audit env vars (Pro)')
|
|
1015
|
+
}
|
|
1016
|
+
console.log(' npm run validate:prelaunch - Run all checks')
|
|
1017
|
+
console.log('\n💡 Run: npm install && npm run validate:prelaunch')
|
|
1018
|
+
|
|
1019
|
+
process.exit(0)
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error('Pre-launch validation setup error:', error.message)
|
|
1022
|
+
process.exit(1)
|
|
1023
|
+
}
|
|
1024
|
+
})()
|
|
1025
|
+
}
|
|
1026
|
+
|
|
972
1027
|
// Run validation commands if requested
|
|
973
1028
|
if (
|
|
974
1029
|
isValidationMode ||
|
|
@@ -979,13 +1034,392 @@ HELP:
|
|
|
979
1034
|
// Handle validation commands and exit
|
|
980
1035
|
return (async () => {
|
|
981
1036
|
try {
|
|
982
|
-
await handleValidationCommands(
|
|
1037
|
+
await handleValidationCommands({
|
|
1038
|
+
isConfigSecurityMode,
|
|
1039
|
+
isDocsValidationMode,
|
|
1040
|
+
isComprehensiveMode,
|
|
1041
|
+
isValidationMode,
|
|
1042
|
+
disableNpmAudit,
|
|
1043
|
+
disableGitleaks,
|
|
1044
|
+
disableActionlint,
|
|
1045
|
+
disableMarkdownlint,
|
|
1046
|
+
disableEslintSecurity,
|
|
1047
|
+
allowLatestGitleaks,
|
|
1048
|
+
})
|
|
983
1049
|
} catch (error) {
|
|
984
1050
|
console.error('Validation error:', error.message)
|
|
985
1051
|
process.exit(1)
|
|
986
1052
|
}
|
|
987
1053
|
})()
|
|
988
1054
|
} else {
|
|
1055
|
+
/**
|
|
1056
|
+
* Setup quality tools based on license tier
|
|
1057
|
+
* - Lighthouse CI (Free: basic, Pro: with thresholds)
|
|
1058
|
+
* - Bundle size limits (Pro only)
|
|
1059
|
+
* - axe-core accessibility (Free)
|
|
1060
|
+
* - Conventional commits (Free)
|
|
1061
|
+
* - Coverage thresholds (Pro only)
|
|
1062
|
+
*/
|
|
1063
|
+
async function setupQualityTools(_usesTypeScript, _packageJson) {
|
|
1064
|
+
const qualitySpinner = showProgress('Setting up quality tools...')
|
|
1065
|
+
|
|
1066
|
+
try {
|
|
1067
|
+
const projectPath = process.cwd()
|
|
1068
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
1069
|
+
const pkgJson = await PackageJson.load(projectPath)
|
|
1070
|
+
const addedTools = []
|
|
1071
|
+
|
|
1072
|
+
// Determine which features are available
|
|
1073
|
+
const hasLighthouse = hasFeature('lighthouseCI')
|
|
1074
|
+
const hasLighthouseThresholds = hasFeature('lighthouseThresholds')
|
|
1075
|
+
const hasBundleSizeLimits = hasFeature('bundleSizeLimits')
|
|
1076
|
+
const hasAxeAccessibility = hasFeature('axeAccessibility')
|
|
1077
|
+
const hasConventionalCommits = hasFeature('conventionalCommits')
|
|
1078
|
+
const hasCoverageThresholds = hasFeature('coverageThresholds')
|
|
1079
|
+
|
|
1080
|
+
// 1. Lighthouse CI - available to all, thresholds for Pro+
|
|
1081
|
+
if (hasLighthouse) {
|
|
1082
|
+
try {
|
|
1083
|
+
const lighthousePath = path.join(projectPath, 'lighthouserc.js')
|
|
1084
|
+
if (!fs.existsSync(lighthousePath)) {
|
|
1085
|
+
writeLighthouseConfig(projectPath, {
|
|
1086
|
+
hasThresholds: hasLighthouseThresholds,
|
|
1087
|
+
})
|
|
1088
|
+
addedTools.push(
|
|
1089
|
+
hasLighthouseThresholds
|
|
1090
|
+
? 'Lighthouse CI (with thresholds)'
|
|
1091
|
+
: 'Lighthouse CI (basic)'
|
|
1092
|
+
)
|
|
1093
|
+
}
|
|
1094
|
+
} catch (error) {
|
|
1095
|
+
console.warn('⚠️ Failed to configure Lighthouse CI:', error.message)
|
|
1096
|
+
if (process.env.DEBUG) {
|
|
1097
|
+
console.error(' Stack:', error.stack)
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
// 2. Bundle size limits - Pro only
|
|
1103
|
+
if (hasBundleSizeLimits) {
|
|
1104
|
+
try {
|
|
1105
|
+
if (!pkgJson.content['size-limit']) {
|
|
1106
|
+
writeSizeLimitConfig(projectPath)
|
|
1107
|
+
addedTools.push('Bundle size limits (size-limit)')
|
|
1108
|
+
}
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
console.warn(
|
|
1111
|
+
'⚠️ Failed to configure bundle size limits:',
|
|
1112
|
+
error.message
|
|
1113
|
+
)
|
|
1114
|
+
if (process.env.DEBUG) {
|
|
1115
|
+
console.error(' Stack:', error.stack)
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// 3. axe-core accessibility testing - available to all
|
|
1121
|
+
if (hasAxeAccessibility) {
|
|
1122
|
+
try {
|
|
1123
|
+
const axeTestPath = path.join(
|
|
1124
|
+
projectPath,
|
|
1125
|
+
'tests',
|
|
1126
|
+
'accessibility.test.js'
|
|
1127
|
+
)
|
|
1128
|
+
if (!fs.existsSync(axeTestPath)) {
|
|
1129
|
+
writeAxeTestSetup(projectPath)
|
|
1130
|
+
addedTools.push('axe-core accessibility tests')
|
|
1131
|
+
}
|
|
1132
|
+
} catch (error) {
|
|
1133
|
+
console.warn(
|
|
1134
|
+
'⚠️ Failed to configure axe-core tests:',
|
|
1135
|
+
error.message
|
|
1136
|
+
)
|
|
1137
|
+
if (process.env.DEBUG) {
|
|
1138
|
+
console.error(' Stack:', error.stack)
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// 4. Conventional commits (commitlint) - available to all
|
|
1144
|
+
if (hasConventionalCommits) {
|
|
1145
|
+
try {
|
|
1146
|
+
const commitlintPath = path.join(
|
|
1147
|
+
projectPath,
|
|
1148
|
+
'commitlint.config.js'
|
|
1149
|
+
)
|
|
1150
|
+
if (!fs.existsSync(commitlintPath)) {
|
|
1151
|
+
writeCommitlintConfig(projectPath)
|
|
1152
|
+
writeCommitMsgHook(projectPath)
|
|
1153
|
+
addedTools.push('Conventional commits (commitlint)')
|
|
1154
|
+
}
|
|
1155
|
+
} catch (error) {
|
|
1156
|
+
console.warn('⚠️ Failed to configure commitlint:', error.message)
|
|
1157
|
+
if (process.env.DEBUG) {
|
|
1158
|
+
console.error(' Stack:', error.stack)
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// 5. Coverage thresholds - Pro only (info message handled elsewhere)
|
|
1164
|
+
if (hasCoverageThresholds) {
|
|
1165
|
+
addedTools.push('Coverage thresholds (70% lines, 70% functions)')
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Add dependencies for enabled features
|
|
1169
|
+
const deps = getQualityToolsDependencies({
|
|
1170
|
+
lighthouse: hasLighthouse,
|
|
1171
|
+
sizeLimit: hasBundleSizeLimits,
|
|
1172
|
+
commitlint: hasConventionalCommits,
|
|
1173
|
+
axeCore: hasAxeAccessibility,
|
|
1174
|
+
})
|
|
1175
|
+
|
|
1176
|
+
// Add scripts for enabled features
|
|
1177
|
+
const scripts = getQualityToolsScripts({
|
|
1178
|
+
lighthouse: hasLighthouse,
|
|
1179
|
+
sizeLimit: hasBundleSizeLimits,
|
|
1180
|
+
axeCore: hasAxeAccessibility,
|
|
1181
|
+
coverage: hasCoverageThresholds,
|
|
1182
|
+
})
|
|
1183
|
+
|
|
1184
|
+
// Merge dependencies and scripts
|
|
1185
|
+
pkgJson.content.devDependencies = mergeDevDependencies(
|
|
1186
|
+
pkgJson.content.devDependencies || {},
|
|
1187
|
+
deps
|
|
1188
|
+
)
|
|
1189
|
+
pkgJson.content.scripts = mergeScripts(
|
|
1190
|
+
pkgJson.content.scripts || {},
|
|
1191
|
+
scripts
|
|
1192
|
+
)
|
|
1193
|
+
await pkgJson.save()
|
|
1194
|
+
|
|
1195
|
+
if (addedTools.length > 0) {
|
|
1196
|
+
qualitySpinner.succeed(
|
|
1197
|
+
`Quality tools configured: ${addedTools.length} tools`
|
|
1198
|
+
)
|
|
1199
|
+
addedTools.forEach(tool => console.log(` ✅ ${tool}`))
|
|
1200
|
+
|
|
1201
|
+
// Show Pro upsell for missing features
|
|
1202
|
+
if (!hasBundleSizeLimits || !hasCoverageThresholds) {
|
|
1203
|
+
console.log('\n💎 Upgrade to Pro for additional quality tools:')
|
|
1204
|
+
if (!hasBundleSizeLimits) {
|
|
1205
|
+
console.log(' • Bundle size limits (size-limit)')
|
|
1206
|
+
}
|
|
1207
|
+
if (!hasCoverageThresholds) {
|
|
1208
|
+
console.log(' • Coverage threshold enforcement')
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
} else {
|
|
1212
|
+
qualitySpinner.succeed('Quality tools already configured')
|
|
1213
|
+
}
|
|
1214
|
+
} catch (error) {
|
|
1215
|
+
qualitySpinner.fail('Quality tools setup failed')
|
|
1216
|
+
console.error(
|
|
1217
|
+
`❌ Unexpected error during quality tools setup: ${error.message}`
|
|
1218
|
+
)
|
|
1219
|
+
if (process.env.DEBUG) {
|
|
1220
|
+
console.error(' Stack:', error.stack)
|
|
1221
|
+
}
|
|
1222
|
+
console.error(
|
|
1223
|
+
' Please report this issue at https://github.com/your-repo/issues'
|
|
1224
|
+
)
|
|
1225
|
+
throw error // Re-throw to prevent silent continuation
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
/**
|
|
1230
|
+
* Detect existing workflow mode from quality.yml
|
|
1231
|
+
* @param {string} projectPath - Path to the project
|
|
1232
|
+
* @returns {'minimal'|'standard'|'comprehensive'|null} Detected mode
|
|
1233
|
+
*/
|
|
1234
|
+
function detectExistingWorkflowMode(projectPath) {
|
|
1235
|
+
const workflowPath = path.join(
|
|
1236
|
+
projectPath,
|
|
1237
|
+
'.github',
|
|
1238
|
+
'workflows',
|
|
1239
|
+
'quality.yml'
|
|
1240
|
+
)
|
|
1241
|
+
|
|
1242
|
+
if (!fs.existsSync(workflowPath)) {
|
|
1243
|
+
return null
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
try {
|
|
1247
|
+
const content = fs.readFileSync(workflowPath, 'utf8')
|
|
1248
|
+
|
|
1249
|
+
// Check for version markers (new format)
|
|
1250
|
+
if (content.includes('# WORKFLOW_MODE: minimal')) {
|
|
1251
|
+
return 'minimal'
|
|
1252
|
+
}
|
|
1253
|
+
if (content.includes('# WORKFLOW_MODE: standard')) {
|
|
1254
|
+
return 'standard'
|
|
1255
|
+
}
|
|
1256
|
+
if (content.includes('# WORKFLOW_MODE: comprehensive')) {
|
|
1257
|
+
return 'comprehensive'
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// Legacy detection (no version marker)
|
|
1261
|
+
// Comprehensive: has security job + matrix testing on every push
|
|
1262
|
+
// Standard: has matrix testing but security is scheduled
|
|
1263
|
+
// Minimal: no matrix, single node version
|
|
1264
|
+
const hasSecurityJob = /jobs:\s*\n\s*security:/m.test(content)
|
|
1265
|
+
const hasMatrixInTests = /tests:[\s\S]*?strategy:[\s\S]*?matrix:/m.test(
|
|
1266
|
+
content
|
|
1267
|
+
)
|
|
1268
|
+
const hasScheduledSecurity =
|
|
1269
|
+
/on:\s*\n\s*schedule:[\s\S]*?- cron:/m.test(content)
|
|
1270
|
+
|
|
1271
|
+
if (hasSecurityJob && hasMatrixInTests && !hasScheduledSecurity) {
|
|
1272
|
+
return 'comprehensive'
|
|
1273
|
+
}
|
|
1274
|
+
if (hasMatrixInTests && hasScheduledSecurity) {
|
|
1275
|
+
return 'standard'
|
|
1276
|
+
}
|
|
1277
|
+
if (!hasMatrixInTests) {
|
|
1278
|
+
return 'minimal'
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Default to comprehensive for unknown patterns
|
|
1282
|
+
return 'comprehensive'
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
console.warn(
|
|
1285
|
+
`⚠️ Could not detect existing workflow mode: ${error.message}`
|
|
1286
|
+
)
|
|
1287
|
+
return null
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Inject workflow mode-specific configuration into quality.yml
|
|
1293
|
+
* @param {string} workflowContent - Template content
|
|
1294
|
+
* @param {'minimal'|'standard'|'comprehensive'} mode - Selected mode
|
|
1295
|
+
* @returns {string} Modified workflow content
|
|
1296
|
+
*/
|
|
1297
|
+
function injectWorkflowMode(workflowContent, mode) {
|
|
1298
|
+
let updated = workflowContent
|
|
1299
|
+
|
|
1300
|
+
// Add version marker at the top (after name section)
|
|
1301
|
+
const versionMarker = `# WORKFLOW_MODE: ${mode}`
|
|
1302
|
+
if (!updated.includes('# WORKFLOW_MODE:')) {
|
|
1303
|
+
// Insert after the comment block and before jobs
|
|
1304
|
+
updated = updated.replace(/(\n\njobs:)/, `\n${versionMarker}\n$1`)
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// Replace PATH_FILTERS_PLACEHOLDER
|
|
1308
|
+
if (updated.includes('# PATH_FILTERS_PLACEHOLDER')) {
|
|
1309
|
+
if (mode === 'minimal' || mode === 'standard') {
|
|
1310
|
+
const pathsIgnore = `paths-ignore:
|
|
1311
|
+
- '**.md'
|
|
1312
|
+
- 'docs/**'
|
|
1313
|
+
- 'LICENSE'
|
|
1314
|
+
- '.gitignore'
|
|
1315
|
+
- '.editorconfig'`
|
|
1316
|
+
updated = updated.replace('# PATH_FILTERS_PLACEHOLDER', pathsIgnore)
|
|
1317
|
+
} else {
|
|
1318
|
+
// comprehensive: Remove placeholder
|
|
1319
|
+
updated = updated.replace(/\s*# PATH_FILTERS_PLACEHOLDER\n?/, '')
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Replace SECURITY_SCHEDULE_PLACEHOLDER
|
|
1324
|
+
if (updated.includes('# SECURITY_SCHEDULE_PLACEHOLDER')) {
|
|
1325
|
+
if (mode === 'minimal' || mode === 'standard') {
|
|
1326
|
+
// Add weekly schedule for security scans
|
|
1327
|
+
const scheduleConfig = `schedule:
|
|
1328
|
+
- cron: '0 0 * * 0' # Weekly on Sunday (security scans)
|
|
1329
|
+
workflow_dispatch: # Manual trigger`
|
|
1330
|
+
updated = updated.replace(
|
|
1331
|
+
'# SECURITY_SCHEDULE_PLACEHOLDER',
|
|
1332
|
+
scheduleConfig
|
|
1333
|
+
)
|
|
1334
|
+
} else {
|
|
1335
|
+
// comprehensive: Remove placeholder
|
|
1336
|
+
updated = updated.replace(/\s*# SECURITY_SCHEDULE_PLACEHOLDER\n?/, '')
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Replace SECURITY_CONDITION_PLACEHOLDER
|
|
1341
|
+
if (updated.includes('# SECURITY_CONDITION_PLACEHOLDER')) {
|
|
1342
|
+
if (mode === 'minimal' || mode === 'standard') {
|
|
1343
|
+
// Only run security on schedule events (not on every push)
|
|
1344
|
+
// Replace both the placeholder comment AND the existing if line
|
|
1345
|
+
updated = updated.replace(
|
|
1346
|
+
/# SECURITY_CONDITION_PLACEHOLDER\n\s*if: needs\.detect-maturity\.outputs\.has-deps == 'true'/,
|
|
1347
|
+
`if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && needs.detect-maturity.outputs.has-deps == 'true'`
|
|
1348
|
+
)
|
|
1349
|
+
} else {
|
|
1350
|
+
// comprehensive: Remove placeholder, keep the existing if condition
|
|
1351
|
+
updated = updated.replace(
|
|
1352
|
+
/\s*# SECURITY_CONDITION_PLACEHOLDER\n?/,
|
|
1353
|
+
''
|
|
1354
|
+
)
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Replace MATRIX_PLACEHOLDER
|
|
1359
|
+
if (updated.includes('# MATRIX_PLACEHOLDER')) {
|
|
1360
|
+
if (mode === 'minimal') {
|
|
1361
|
+
// Single Node version, no matrix
|
|
1362
|
+
// Replace both the placeholder comment AND the existing strategy block
|
|
1363
|
+
updated = updated.replace(
|
|
1364
|
+
/# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
|
|
1365
|
+
`strategy:
|
|
1366
|
+
fail-fast: false
|
|
1367
|
+
matrix:
|
|
1368
|
+
node-version: [22]`
|
|
1369
|
+
)
|
|
1370
|
+
} else if (mode === 'standard') {
|
|
1371
|
+
// Matrix testing only on main branch
|
|
1372
|
+
// 1. Modify the job-level if condition to add branch check
|
|
1373
|
+
updated = updated.replace(
|
|
1374
|
+
/if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0\n\s*# TESTS_CONDITION_PLACEHOLDER/,
|
|
1375
|
+
`if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0
|
|
1376
|
+
# TESTS_CONDITION_PLACEHOLDER`
|
|
1377
|
+
)
|
|
1378
|
+
// 2. Replace matrix placeholder and existing strategy block
|
|
1379
|
+
updated = updated.replace(
|
|
1380
|
+
/# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
|
|
1381
|
+
`strategy:
|
|
1382
|
+
fail-fast: false
|
|
1383
|
+
matrix:
|
|
1384
|
+
node-version: [20, 22]`
|
|
1385
|
+
)
|
|
1386
|
+
} else {
|
|
1387
|
+
// comprehensive: Matrix on every push
|
|
1388
|
+
// Replace placeholder, merge with existing strategy
|
|
1389
|
+
updated = updated.replace(
|
|
1390
|
+
/# MATRIX_PLACEHOLDER\n\s*strategy:\n\s*fail-fast: false/,
|
|
1391
|
+
`strategy:
|
|
1392
|
+
fail-fast: false
|
|
1393
|
+
matrix:
|
|
1394
|
+
node-version: [20, 22]`
|
|
1395
|
+
)
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// Replace TESTS_CONDITION_PLACEHOLDER
|
|
1400
|
+
if (updated.includes('# TESTS_CONDITION_PLACEHOLDER')) {
|
|
1401
|
+
let testsCondition = ''
|
|
1402
|
+
|
|
1403
|
+
if (mode === 'minimal') {
|
|
1404
|
+
// No matrix, always run
|
|
1405
|
+
testsCondition = '# Runs on every push (single Node version)'
|
|
1406
|
+
} else if (mode === 'standard') {
|
|
1407
|
+
// Matrix only on main
|
|
1408
|
+
testsCondition = '# Matrix testing only on main branch'
|
|
1409
|
+
} else {
|
|
1410
|
+
// comprehensive: Matrix on every push
|
|
1411
|
+
testsCondition = '# Matrix testing on every push'
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
updated = updated.replace(
|
|
1415
|
+
'# TESTS_CONDITION_PLACEHOLDER',
|
|
1416
|
+
testsCondition
|
|
1417
|
+
)
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
return updated
|
|
1421
|
+
}
|
|
1422
|
+
|
|
989
1423
|
// Normal setup flow
|
|
990
1424
|
async function runMainSetup() {
|
|
991
1425
|
// Record telemetry start event (opt-in only, fails silently)
|
|
@@ -1000,7 +1434,7 @@ HELP:
|
|
|
1000
1434
|
try {
|
|
1001
1435
|
execSync('git status', { stdio: 'ignore' })
|
|
1002
1436
|
gitSpinner.succeed('Git repository verified')
|
|
1003
|
-
} catch {
|
|
1437
|
+
} catch (_error) {
|
|
1004
1438
|
gitSpinner.fail('Not a git repository')
|
|
1005
1439
|
console.error('❌ This must be run in a git repository')
|
|
1006
1440
|
console.log('Run "git init" first, then try again.')
|
|
@@ -1010,6 +1444,8 @@ HELP:
|
|
|
1010
1444
|
// Enforce FREE tier repo limit (1 private repo)
|
|
1011
1445
|
// Must happen before any file modifications
|
|
1012
1446
|
const license = getLicenseInfo()
|
|
1447
|
+
let pendingRepoRegistration = null
|
|
1448
|
+
let pendingRepoUsageSnapshot = null
|
|
1013
1449
|
if (license.tier === 'FREE') {
|
|
1014
1450
|
// Generate unique repo ID from git remote or directory name
|
|
1015
1451
|
let repoId
|
|
@@ -1018,10 +1454,11 @@ HELP:
|
|
|
1018
1454
|
encoding: 'utf8',
|
|
1019
1455
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
1020
1456
|
}).trim()
|
|
1021
|
-
|
|
1022
|
-
|
|
1457
|
+
const normalized = normalizeRepoIdentifier(remoteUrl)
|
|
1458
|
+
repoId = hashRepoIdentifier(normalized || remoteUrl)
|
|
1459
|
+
} catch (_error) {
|
|
1023
1460
|
// No remote - use absolute path as fallback
|
|
1024
|
-
repoId = process.cwd()
|
|
1461
|
+
repoId = hashRepoIdentifier(process.cwd())
|
|
1025
1462
|
}
|
|
1026
1463
|
|
|
1027
1464
|
const repoCheck = checkUsageCaps('repo')
|
|
@@ -1037,11 +1474,8 @@ HELP:
|
|
|
1037
1474
|
process.exit(1)
|
|
1038
1475
|
}
|
|
1039
1476
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
console.log(
|
|
1043
|
-
`✅ Registered repo (FREE tier: ${(repoCheck.usage?.repoCount || 0) + 1}/1 repos used)`
|
|
1044
|
-
)
|
|
1477
|
+
pendingRepoRegistration = repoId
|
|
1478
|
+
pendingRepoUsageSnapshot = repoCheck.usage
|
|
1045
1479
|
}
|
|
1046
1480
|
}
|
|
1047
1481
|
|
|
@@ -1133,7 +1567,7 @@ HELP:
|
|
|
1133
1567
|
const hasTypeScriptDependency = Boolean(
|
|
1134
1568
|
(packageJson.devDependencies &&
|
|
1135
1569
|
packageJson.devDependencies.typescript) ||
|
|
1136
|
-
|
|
1570
|
+
(packageJson.dependencies && packageJson.dependencies.typescript)
|
|
1137
1571
|
)
|
|
1138
1572
|
|
|
1139
1573
|
const tsconfigCandidates = ['tsconfig.json', 'tsconfig.base.json']
|
|
@@ -1204,7 +1638,17 @@ HELP:
|
|
|
1204
1638
|
}
|
|
1205
1639
|
|
|
1206
1640
|
return false
|
|
1207
|
-
} catch {
|
|
1641
|
+
} catch (error) {
|
|
1642
|
+
// Silent failure fix: Log unexpected errors in debug mode
|
|
1643
|
+
if (
|
|
1644
|
+
process.env.DEBUG &&
|
|
1645
|
+
error.code !== 'ENOENT' &&
|
|
1646
|
+
error.code !== 'EACCES'
|
|
1647
|
+
) {
|
|
1648
|
+
console.warn(
|
|
1649
|
+
`⚠️ Could not scan ${dir} for Python files: ${error.message}`
|
|
1650
|
+
)
|
|
1651
|
+
}
|
|
1208
1652
|
return false
|
|
1209
1653
|
}
|
|
1210
1654
|
}
|
|
@@ -1218,6 +1662,19 @@ HELP:
|
|
|
1218
1662
|
)
|
|
1219
1663
|
}
|
|
1220
1664
|
|
|
1665
|
+
// Shell project detection
|
|
1666
|
+
const { ProjectMaturityDetector } = require('./lib/project-maturity')
|
|
1667
|
+
const maturityDetector = new ProjectMaturityDetector({
|
|
1668
|
+
projectPath: process.cwd(),
|
|
1669
|
+
})
|
|
1670
|
+
const projectStats = maturityDetector.analyzeProject()
|
|
1671
|
+
const usesShell = projectStats.isShellProject
|
|
1672
|
+
if (usesShell) {
|
|
1673
|
+
console.log(
|
|
1674
|
+
'🐚 Detected shell script project; enabling shell quality automation'
|
|
1675
|
+
)
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1221
1678
|
const stylelintTargets = findStylelintTargets(process.cwd())
|
|
1222
1679
|
const usingDefaultStylelintTarget =
|
|
1223
1680
|
stylelintTargets.length === 1 &&
|
|
@@ -1370,29 +1827,44 @@ HELP:
|
|
|
1370
1827
|
|
|
1371
1828
|
// Validate the generated config
|
|
1372
1829
|
const validationResult = validateQualityConfig(qualityrcPath)
|
|
1830
|
+
if (!validationResult.valid) {
|
|
1831
|
+
console.error(
|
|
1832
|
+
'\n❌ CRITICAL: Generated .qualityrc.json failed validation'
|
|
1833
|
+
)
|
|
1834
|
+
console.error(
|
|
1835
|
+
' This should never happen. Please report this bug.\n'
|
|
1836
|
+
)
|
|
1837
|
+
|
|
1838
|
+
console.error('Validation errors:')
|
|
1839
|
+
validationResult.errors.forEach((error, index) => {
|
|
1840
|
+
console.error(` ${index + 1}. ${error}`)
|
|
1841
|
+
})
|
|
1842
|
+
|
|
1843
|
+
console.error(`\n🐛 Report issue with this info:`)
|
|
1844
|
+
console.error(` • File: ${qualityrcPath}`)
|
|
1845
|
+
console.error(` • Detected maturity: ${detectedMaturity}`)
|
|
1846
|
+
console.error(` • Error count: ${validationResult.errors.length}`)
|
|
1847
|
+
console.error(
|
|
1848
|
+
` • https://github.com/vibebuildlab/qa-architect/issues/new\n`
|
|
1849
|
+
)
|
|
1850
|
+
|
|
1851
|
+
// Don't continue - this is a bug in the tool itself
|
|
1852
|
+
throw new Error('Invalid quality config generated - cannot continue')
|
|
1853
|
+
}
|
|
1854
|
+
} else {
|
|
1855
|
+
// TD8 fix: Re-enabled validation (was disabled for debugging)
|
|
1856
|
+
const validationResult = validateQualityConfig(qualityrcPath)
|
|
1373
1857
|
if (!validationResult.valid) {
|
|
1374
1858
|
console.warn(
|
|
1375
|
-
'⚠️ Warning:
|
|
1859
|
+
'⚠️ Warning: Existing .qualityrc.json has validation issues:'
|
|
1376
1860
|
)
|
|
1377
1861
|
validationResult.errors.forEach(error => {
|
|
1378
1862
|
console.warn(` - ${error}`)
|
|
1379
1863
|
})
|
|
1864
|
+
console.warn(
|
|
1865
|
+
' Setup will continue, but you may want to fix these issues.\n'
|
|
1866
|
+
)
|
|
1380
1867
|
}
|
|
1381
|
-
} else {
|
|
1382
|
-
// Config exists, validate it
|
|
1383
|
-
// Temporarily disabled - debugging
|
|
1384
|
-
// const validationResult = validateQualityConfig(qualityrcPath)
|
|
1385
|
-
// if (!validationResult.valid) {
|
|
1386
|
-
// console.warn(
|
|
1387
|
-
// '⚠️ Warning: Existing .qualityrc.json has validation issues:'
|
|
1388
|
-
// )
|
|
1389
|
-
// validationResult.errors.forEach(error => {
|
|
1390
|
-
// console.warn(` - ${error}`)
|
|
1391
|
-
// })
|
|
1392
|
-
// console.warn(
|
|
1393
|
-
// ' Setup will continue, but you may want to fix these issues.\n'
|
|
1394
|
-
// )
|
|
1395
|
-
// }
|
|
1396
1868
|
}
|
|
1397
1869
|
|
|
1398
1870
|
// Load and merge templates (custom + defaults)
|
|
@@ -1471,6 +1943,23 @@ HELP:
|
|
|
1471
1943
|
}
|
|
1472
1944
|
|
|
1473
1945
|
const workflowFile = path.join(githubWorkflowDir, 'quality.yml')
|
|
1946
|
+
|
|
1947
|
+
// Determine workflow mode
|
|
1948
|
+
let workflowMode = 'minimal' // Default to minimal
|
|
1949
|
+
if (isWorkflowMinimal) {
|
|
1950
|
+
workflowMode = 'minimal'
|
|
1951
|
+
} else if (isWorkflowStandard) {
|
|
1952
|
+
workflowMode = 'standard'
|
|
1953
|
+
} else if (isWorkflowComprehensive) {
|
|
1954
|
+
workflowMode = 'comprehensive'
|
|
1955
|
+
} else if (fs.existsSync(workflowFile)) {
|
|
1956
|
+
// Detect existing mode when updating
|
|
1957
|
+
const existingMode = detectExistingWorkflowMode(process.cwd())
|
|
1958
|
+
if (existingMode) {
|
|
1959
|
+
workflowMode = existingMode
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1474
1963
|
if (!fs.existsSync(workflowFile)) {
|
|
1475
1964
|
let templateWorkflow =
|
|
1476
1965
|
templateLoader.getTemplate(
|
|
@@ -1482,13 +1971,59 @@ HELP:
|
|
|
1482
1971
|
'utf8'
|
|
1483
1972
|
)
|
|
1484
1973
|
|
|
1974
|
+
// Inject workflow mode configuration
|
|
1975
|
+
templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
|
|
1976
|
+
|
|
1977
|
+
// Inject collaboration steps
|
|
1485
1978
|
templateWorkflow = injectCollaborationSteps(templateWorkflow, {
|
|
1486
1979
|
enableSlackAlerts,
|
|
1487
1980
|
enablePrComments,
|
|
1488
1981
|
})
|
|
1489
1982
|
|
|
1490
1983
|
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
1491
|
-
console.log(
|
|
1984
|
+
console.log(`✅ Added GitHub Actions workflow (${workflowMode} mode)`)
|
|
1985
|
+
} else if (isUpdateMode) {
|
|
1986
|
+
// Update existing workflow with new mode if explicitly specified
|
|
1987
|
+
if (
|
|
1988
|
+
isWorkflowMinimal ||
|
|
1989
|
+
isWorkflowStandard ||
|
|
1990
|
+
isWorkflowComprehensive
|
|
1991
|
+
) {
|
|
1992
|
+
// Load fresh template and re-inject
|
|
1993
|
+
let templateWorkflow =
|
|
1994
|
+
templateLoader.getTemplate(
|
|
1995
|
+
templates,
|
|
1996
|
+
path.join('.github', 'workflows', 'quality.yml')
|
|
1997
|
+
) ||
|
|
1998
|
+
fs.readFileSync(
|
|
1999
|
+
path.join(__dirname, '.github/workflows/quality.yml'),
|
|
2000
|
+
'utf8'
|
|
2001
|
+
)
|
|
2002
|
+
|
|
2003
|
+
// Inject workflow mode configuration
|
|
2004
|
+
templateWorkflow = injectWorkflowMode(
|
|
2005
|
+
templateWorkflow,
|
|
2006
|
+
workflowMode
|
|
2007
|
+
)
|
|
2008
|
+
|
|
2009
|
+
// Inject collaboration steps (preserve from existing if present)
|
|
2010
|
+
const existingWorkflow = fs.readFileSync(workflowFile, 'utf8')
|
|
2011
|
+
const hasSlackAlerts =
|
|
2012
|
+
existingWorkflow.includes('SLACK_WEBHOOK_URL')
|
|
2013
|
+
const hasPrComments = existingWorkflow.includes(
|
|
2014
|
+
'PR_COMMENT_PLACEHOLDER'
|
|
2015
|
+
)
|
|
2016
|
+
|
|
2017
|
+
templateWorkflow = injectCollaborationSteps(templateWorkflow, {
|
|
2018
|
+
enableSlackAlerts: hasSlackAlerts,
|
|
2019
|
+
enablePrComments: hasPrComments,
|
|
2020
|
+
})
|
|
2021
|
+
|
|
2022
|
+
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
2023
|
+
console.log(
|
|
2024
|
+
`♻️ Updated GitHub Actions workflow to ${workflowMode} mode`
|
|
2025
|
+
)
|
|
2026
|
+
}
|
|
1492
2027
|
}
|
|
1493
2028
|
}
|
|
1494
2029
|
|
|
@@ -1699,7 +2234,7 @@ let tier = 'FREE'
|
|
|
1699
2234
|
try {
|
|
1700
2235
|
const data = JSON.parse(fs.readFileSync(licenseFile, 'utf8'))
|
|
1701
2236
|
tier = (data && data.tier) || 'FREE'
|
|
1702
|
-
} catch {
|
|
2237
|
+
} catch (error) {
|
|
1703
2238
|
tier = 'FREE'
|
|
1704
2239
|
}
|
|
1705
2240
|
|
|
@@ -1712,7 +2247,7 @@ try {
|
|
|
1712
2247
|
if (data.month === currentMonth) {
|
|
1713
2248
|
usage = { ...usage, ...data }
|
|
1714
2249
|
}
|
|
1715
|
-
} catch {
|
|
2250
|
+
} catch (error) {
|
|
1716
2251
|
// First run or corrupt file – start fresh
|
|
1717
2252
|
}
|
|
1718
2253
|
|
|
@@ -1900,6 +2435,86 @@ echo "✅ Pre-push validation passed!"
|
|
|
1900
2435
|
}
|
|
1901
2436
|
}
|
|
1902
2437
|
|
|
2438
|
+
pythonSpinner.succeed('Python quality tools configured')
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// Shell project setup
|
|
2442
|
+
if (usesShell) {
|
|
2443
|
+
// Copy Shell CI workflow (GitHub Actions only)
|
|
2444
|
+
if (ciProvider === 'github') {
|
|
2445
|
+
const shellCiWorkflowFile = path.join(
|
|
2446
|
+
githubWorkflowDir,
|
|
2447
|
+
'shell-ci.yml'
|
|
2448
|
+
)
|
|
2449
|
+
if (!fs.existsSync(shellCiWorkflowFile)) {
|
|
2450
|
+
const templateShellCiWorkflow =
|
|
2451
|
+
templateLoader.getTemplate(
|
|
2452
|
+
templates,
|
|
2453
|
+
path.join('config', 'shell-ci.yml')
|
|
2454
|
+
) ||
|
|
2455
|
+
fs.readFileSync(
|
|
2456
|
+
path.join(__dirname, 'config/shell-ci.yml'),
|
|
2457
|
+
'utf8'
|
|
2458
|
+
)
|
|
2459
|
+
fs.writeFileSync(shellCiWorkflowFile, templateShellCiWorkflow)
|
|
2460
|
+
console.log('✅ Added Shell CI GitHub Actions workflow')
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// Copy Shell Quality workflow
|
|
2464
|
+
const shellQualityWorkflowFile = path.join(
|
|
2465
|
+
githubWorkflowDir,
|
|
2466
|
+
'shell-quality.yml'
|
|
2467
|
+
)
|
|
2468
|
+
if (!fs.existsSync(shellQualityWorkflowFile)) {
|
|
2469
|
+
const templateShellQualityWorkflow =
|
|
2470
|
+
templateLoader.getTemplate(
|
|
2471
|
+
templates,
|
|
2472
|
+
path.join('config', 'shell-quality.yml')
|
|
2473
|
+
) ||
|
|
2474
|
+
fs.readFileSync(
|
|
2475
|
+
path.join(__dirname, 'config/shell-quality.yml'),
|
|
2476
|
+
'utf8'
|
|
2477
|
+
)
|
|
2478
|
+
fs.writeFileSync(
|
|
2479
|
+
shellQualityWorkflowFile,
|
|
2480
|
+
templateShellQualityWorkflow
|
|
2481
|
+
)
|
|
2482
|
+
console.log('✅ Added Shell Quality GitHub Actions workflow')
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
// Create a basic README if it doesn't exist
|
|
2487
|
+
const readmePath = path.join(process.cwd(), 'README.md')
|
|
2488
|
+
if (!fs.existsSync(readmePath)) {
|
|
2489
|
+
const projectName = path.basename(process.cwd())
|
|
2490
|
+
const basicReadme = `# ${projectName}
|
|
2491
|
+
|
|
2492
|
+
Shell script collection for ${projectName}.
|
|
2493
|
+
|
|
2494
|
+
## Usage
|
|
2495
|
+
|
|
2496
|
+
\`\`\`bash
|
|
2497
|
+
# Make scripts executable
|
|
2498
|
+
chmod +x *.sh
|
|
2499
|
+
|
|
2500
|
+
# Run a script
|
|
2501
|
+
./script-name.sh
|
|
2502
|
+
\`\`\`
|
|
2503
|
+
|
|
2504
|
+
## Development
|
|
2505
|
+
|
|
2506
|
+
Quality checks are automated via GitHub Actions:
|
|
2507
|
+
- ShellCheck linting
|
|
2508
|
+
- Syntax validation
|
|
2509
|
+
- Permission checks
|
|
2510
|
+
- Best practices analysis
|
|
2511
|
+
`
|
|
2512
|
+
fs.writeFileSync(readmePath, basicReadme)
|
|
2513
|
+
console.log('✅ Created basic README.md')
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
|
|
2517
|
+
if (usesPython) {
|
|
1903
2518
|
// Create tests directory if it doesn't exist
|
|
1904
2519
|
const testsDir = path.join(process.cwd(), 'tests')
|
|
1905
2520
|
if (!fs.existsSync(testsDir)) {
|
|
@@ -1942,8 +2557,6 @@ echo "✅ Pre-push validation passed!"
|
|
|
1942
2557
|
)
|
|
1943
2558
|
}
|
|
1944
2559
|
}
|
|
1945
|
-
|
|
1946
|
-
pythonSpinner.succeed('Python quality tools configured')
|
|
1947
2560
|
}
|
|
1948
2561
|
|
|
1949
2562
|
// Smart Test Strategy (Pro/Team/Enterprise feature)
|
|
@@ -2007,6 +2620,9 @@ echo "✅ Pre-push validation passed!"
|
|
|
2007
2620
|
showUpgradeMessage('Smart Test Strategy')
|
|
2008
2621
|
}
|
|
2009
2622
|
|
|
2623
|
+
// Quality Tools Integration
|
|
2624
|
+
await setupQualityTools(usesTypeScript, packageJson)
|
|
2625
|
+
|
|
2010
2626
|
// Generate placeholder test file with helpful documentation
|
|
2011
2627
|
const testsDir = path.join(process.cwd(), 'tests')
|
|
2012
2628
|
const testExtension = usesTypeScript ? 'ts' : 'js'
|
|
@@ -2106,6 +2722,12 @@ describe('Test framework validation', () => {
|
|
|
2106
2722
|
|
|
2107
2723
|
console.log('\n🎉 Quality automation setup complete!')
|
|
2108
2724
|
|
|
2725
|
+
if (pendingRepoRegistration) {
|
|
2726
|
+
incrementUsage('repo', 1, pendingRepoRegistration)
|
|
2727
|
+
const repoCount = (pendingRepoUsageSnapshot?.repoCount || 0) + 1
|
|
2728
|
+
console.log(`✅ Registered repo (FREE tier: ${repoCount}/1 repos used)`)
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2109
2731
|
// Record telemetry completion event (opt-in only, fails silently)
|
|
2110
2732
|
telemetry.recordComplete({
|
|
2111
2733
|
usesPython,
|