create-qa-architect 5.0.1 → 5.0.6
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/RELEASE_CHECKLIST.md +2 -4
- package/.github/workflows/daily-deploy-check.yml +136 -0
- package/.github/workflows/nightly-gitleaks-verification.yml +1 -1
- package/.github/workflows/release.yml +12 -10
- package/.github/workflows/weekly-audit.yml +173 -0
- package/README.md +4 -4
- package/config/defaults.js +22 -1
- package/config/quality-config.schema.json +1 -1
- package/create-saas-monetization.js +65 -27
- package/docs/ARCHITECTURE.md +0 -1
- package/docs/DEPLOYMENT.md +1 -2
- package/docs/PREFLIGHT_REPORT.md +108 -0
- package/docs/TESTING.md +1 -2
- package/lib/config-validator.js +8 -2
- package/lib/dependency-monitoring-premium.js +21 -19
- package/lib/github-api.js +249 -0
- package/lib/interactive/questions.js +4 -0
- package/lib/license-validator.js +1 -1
- package/lib/licensing.js +9 -9
- package/lib/package-utils.js +9 -8
- package/lib/project-maturity.js +1 -1
- package/lib/template-loader.js +2 -0
- package/lib/ui-helpers.js +2 -1
- package/lib/validation/base-validator.js +5 -1
- package/lib/validation/cache-manager.js +1 -0
- package/lib/validation/config-security.js +5 -4
- package/lib/validation/validation-factory.js +1 -1
- package/lib/yaml-utils.js +15 -10
- package/package.json +12 -9
- package/scripts/check-docs.sh +63 -0
- package/scripts/smart-test-strategy.sh +98 -0
- package/scripts/test-e2e-package.sh +283 -0
- package/scripts/validate-command-patterns.js +112 -0
- package/setup.js +33 -9
- package/templates/scripts/smart-test-strategy.sh +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Preflight Review: QA Architect (create-qa-architect)
|
|
2
|
+
|
|
3
|
+
**Depth**: standard
|
|
4
|
+
**Date**: 2025-12-09
|
|
5
|
+
**Version**: 5.0.2
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Overall Status: ✅ PASS
|
|
10
|
+
|
|
11
|
+
All critical launch blockers pass. Minor issues documented below are acceptable for npm package release.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Critical Issues (P0) - Must Fix
|
|
16
|
+
|
|
17
|
+
| Issue | Category | Location | Fix |
|
|
18
|
+
| ----- | -------- | -------- | --- |
|
|
19
|
+
| None | - | - | - |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Important Issues (P1) - Should Fix
|
|
24
|
+
|
|
25
|
+
| Issue | Category | Location | Recommendation |
|
|
26
|
+
| ------------------------ | -------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
27
|
+
| Gitleaks false positives | Security | tests/\*.test.js | Test fixtures use fake API key patterns (QAA-XXXX format); not real secrets. Consider adding `.gitleaksignore` for test files. |
|
|
28
|
+
| npm version mismatch | Release | package.json | Local 5.0.2, npm shows 5.0.1. Publish pending or recently published. |
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## P0 Functional Checks
|
|
33
|
+
|
|
34
|
+
| Check | Status | Notes |
|
|
35
|
+
| ----------------- | ------ | ---------------------- |
|
|
36
|
+
| All tests passing | ✅ | Full test suite passes |
|
|
37
|
+
| npm audit | ✅ | 0 vulnerabilities |
|
|
38
|
+
| ESLint | ✅ | No errors |
|
|
39
|
+
| Build/validation | ✅ | Core validation passes |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## P0 Security Checks
|
|
44
|
+
|
|
45
|
+
| Check | Status | Notes |
|
|
46
|
+
| ------------------------- | ------ | -------------------------------------------------------------------- |
|
|
47
|
+
| npm audit (high/critical) | ✅ | 0 vulnerabilities found |
|
|
48
|
+
| Hardcoded secrets scan | ⚠️ | 4 findings - all in test files with fake keys (QAA-1234-XXXX format) |
|
|
49
|
+
| No production secrets | ✅ | No `.env` files, no real API keys |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Product Packaging
|
|
54
|
+
|
|
55
|
+
| Item | Status | Notes |
|
|
56
|
+
| ------------ | ------ | ----------------------- |
|
|
57
|
+
| CHANGELOG.md | ✅ | Present |
|
|
58
|
+
| LICENSE | ✅ | Present |
|
|
59
|
+
| README.md | ✅ | Present |
|
|
60
|
+
| .env.example | N/A | Not needed for CLI tool |
|
|
61
|
+
| Version tags | ✅ | v4.3.0 - v5.0.2 |
|
|
62
|
+
| Git status | ✅ | Clean working tree |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quality Violations
|
|
67
|
+
|
|
68
|
+
| Type | Count | Assessment |
|
|
69
|
+
| ----------------------- | ----- | ------------------------------------------------------------ |
|
|
70
|
+
| eslint-disable comments | 24 | All have security justification comments explaining why safe |
|
|
71
|
+
| any types | 0 | JavaScript project, N/A |
|
|
72
|
+
|
|
73
|
+
**Note**: The `eslint-disable` comments are all for security-related ESLint rules (detect-unsafe-regex, detect-non-literal-fs-filename, detect-object-injection) and each includes a detailed safety justification explaining why the pattern is safe in context.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Silent Killer Check (N/A for npm package)
|
|
78
|
+
|
|
79
|
+
| Integration | Status | Notes |
|
|
80
|
+
| --------------- | ------ | --------------------- |
|
|
81
|
+
| Stripe webhooks | N/A | CLI tool, no webhooks |
|
|
82
|
+
| OAuth redirects | N/A | CLI tool |
|
|
83
|
+
| API keys | N/A | CLI tool |
|
|
84
|
+
| CORS config | N/A | CLI tool |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Next Steps
|
|
89
|
+
|
|
90
|
+
1. **Optional**: Add `.gitleaksignore` to exclude test files with fake license keys
|
|
91
|
+
2. **Verify**: Confirm npm publish completed for 5.0.2 (may be propagating)
|
|
92
|
+
3. **Ready**: Proceed with launch/release announcement
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Recommendation
|
|
97
|
+
|
|
98
|
+
**✅ CLEARED FOR LAUNCH**
|
|
99
|
+
|
|
100
|
+
This is an npm CLI package, not a web application. All critical checks pass:
|
|
101
|
+
|
|
102
|
+
- Tests passing
|
|
103
|
+
- No security vulnerabilities
|
|
104
|
+
- No real secrets
|
|
105
|
+
- Clean git state
|
|
106
|
+
- Proper versioning and packaging
|
|
107
|
+
|
|
108
|
+
The gitleaks findings are false positives on intentional test fixtures using fake license key formats.
|
package/docs/TESTING.md
CHANGED
|
@@ -48,7 +48,7 @@ Use real packages from the ecosystem, not toy examples:
|
|
|
48
48
|
const TOP_PYTHON_PACKAGES = [
|
|
49
49
|
'django-cors-headers',
|
|
50
50
|
'scikit-learn',
|
|
51
|
-
'pytest-cov'
|
|
51
|
+
'pytest-cov',
|
|
52
52
|
]
|
|
53
53
|
```
|
|
54
54
|
|
|
@@ -59,4 +59,3 @@ Always run before release:
|
|
|
59
59
|
```bash
|
|
60
60
|
npm run prerelease # Runs docs:check + all tests
|
|
61
61
|
```
|
|
62
|
-
|
package/lib/config-validator.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const AjvImport = require('ajv')
|
|
4
|
+
const addFormatsImport = require('ajv-formats')
|
|
5
5
|
const fs = require('fs')
|
|
6
6
|
const path = require('path')
|
|
7
7
|
|
|
8
|
+
// Handle CJS/ESM interop for Ajv and ajv-formats in JS type-checking
|
|
9
|
+
const Ajv = /** @type {any} */ (AjvImport.default || AjvImport)
|
|
10
|
+
const addFormats = /** @type {(ajv: any) => void} */ (
|
|
11
|
+
addFormatsImport.default || addFormatsImport
|
|
12
|
+
)
|
|
13
|
+
|
|
8
14
|
function validateQualityConfig(configPath) {
|
|
9
15
|
const result = {
|
|
10
16
|
valid: false,
|
|
@@ -289,7 +289,7 @@ function matchesPattern(depName, pattern) {
|
|
|
289
289
|
if (!regex) {
|
|
290
290
|
// Cache miss - compile and store
|
|
291
291
|
const regexPattern = pattern.replace(/\*/g, '.*')
|
|
292
|
-
// eslint-disable-next-line security/detect-non-literal-regexp
|
|
292
|
+
// eslint-disable-next-line security/detect-non-literal-regexp -- Safe: pattern from internal config, only allows * wildcards replaced with .*, anchored with ^$
|
|
293
293
|
regex = new RegExp(`^${regexPattern}$`)
|
|
294
294
|
|
|
295
295
|
// Implement size-limited cache with FIFO eviction
|
|
@@ -348,7 +348,7 @@ function generateReactGroups(_frameworkInfo) {
|
|
|
348
348
|
/**
|
|
349
349
|
* Generate dependency groups for Vue ecosystem
|
|
350
350
|
*
|
|
351
|
-
* @param {Object}
|
|
351
|
+
* @param {Object} _frameworkInfo - Vue detection results
|
|
352
352
|
* @returns {Object} Dependabot groups configuration
|
|
353
353
|
*/
|
|
354
354
|
function generateVueGroups(_frameworkInfo) {
|
|
@@ -376,7 +376,7 @@ function generateVueGroups(_frameworkInfo) {
|
|
|
376
376
|
/**
|
|
377
377
|
* Generate dependency groups for Angular ecosystem
|
|
378
378
|
*
|
|
379
|
-
* @param {Object}
|
|
379
|
+
* @param {Object} _frameworkInfo - Angular detection results
|
|
380
380
|
* @returns {Object} Dependabot groups configuration
|
|
381
381
|
*/
|
|
382
382
|
function generateAngularGroups(_frameworkInfo) {
|
|
@@ -404,7 +404,7 @@ function generateAngularGroups(_frameworkInfo) {
|
|
|
404
404
|
/**
|
|
405
405
|
* Generate dependency groups for testing frameworks
|
|
406
406
|
*
|
|
407
|
-
* @param {Object}
|
|
407
|
+
* @param {Object} _frameworkInfo - Testing framework detection results
|
|
408
408
|
* @returns {Object} Dependabot groups configuration
|
|
409
409
|
*/
|
|
410
410
|
function generateTestingGroups(_frameworkInfo) {
|
|
@@ -428,7 +428,7 @@ function generateTestingGroups(_frameworkInfo) {
|
|
|
428
428
|
/**
|
|
429
429
|
* Generate dependency groups for build tools
|
|
430
430
|
*
|
|
431
|
-
* @param {Object}
|
|
431
|
+
* @param {Object} _frameworkInfo - Build tool detection results
|
|
432
432
|
* @returns {Object} Dependabot groups configuration
|
|
433
433
|
*/
|
|
434
434
|
function generateBuildToolGroups(_frameworkInfo) {
|
|
@@ -446,7 +446,7 @@ function generateBuildToolGroups(_frameworkInfo) {
|
|
|
446
446
|
/**
|
|
447
447
|
* Generate Storybook dependency groups
|
|
448
448
|
*
|
|
449
|
-
* @param {Object}
|
|
449
|
+
* @param {Object} _frameworkInfo - Storybook detection results
|
|
450
450
|
* @returns {Object} Dependabot groups configuration
|
|
451
451
|
*/
|
|
452
452
|
function generateStorybookGroups(_frameworkInfo) {
|
|
@@ -490,7 +490,7 @@ function hasPythonProject(projectPath) {
|
|
|
490
490
|
* attacks with maliciously large requirements files.
|
|
491
491
|
*
|
|
492
492
|
* @param {string} requirementsPath - Path to requirements.txt file
|
|
493
|
-
* @returns {
|
|
493
|
+
* @returns {Record<string, string>} Map of package names to version specifiers
|
|
494
494
|
* @throws {Error} If file exceeds MAX_REQUIREMENTS_FILE_SIZE
|
|
495
495
|
*
|
|
496
496
|
* @example
|
|
@@ -512,6 +512,7 @@ function parsePipRequirements(requirementsPath) {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
const content = fs.readFileSync(requirementsPath, 'utf8')
|
|
515
|
+
/** @type {Record<string, string>} */
|
|
515
516
|
const dependencies = {}
|
|
516
517
|
|
|
517
518
|
content.split('\n').forEach(line => {
|
|
@@ -529,7 +530,7 @@ function parsePipRequirements(requirementsPath) {
|
|
|
529
530
|
// Support dotted names (zope.interface), hyphens (pytest-cov), underscores (google_cloud)
|
|
530
531
|
// Also handle extras like fastapi[all] by capturing everything before the bracket
|
|
531
532
|
// Fixed: Replaced (.*) with ([^\s]*) to prevent catastrophic backtracking
|
|
532
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
533
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded character classes [\w.-], [\w,\s-], [^\s], anchored ^$, no nested quantifiers
|
|
533
534
|
const match = line.match(/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/)
|
|
534
535
|
if (match) {
|
|
535
536
|
const [, name, _extras, operator, version] = match
|
|
@@ -552,12 +553,13 @@ function parsePipRequirements(requirementsPath) {
|
|
|
552
553
|
*/
|
|
553
554
|
function parsePyprojectToml(pyprojectPath) {
|
|
554
555
|
const content = fs.readFileSync(pyprojectPath, 'utf8')
|
|
556
|
+
/** @type {Record<string, string>} */
|
|
555
557
|
const dependencies = {}
|
|
556
558
|
|
|
557
559
|
// Parse PEP 621 list-style dependencies: dependencies = ["package>=1.0.0", ...]
|
|
558
560
|
// Match main dependencies array: dependencies = [...]
|
|
559
561
|
// Allow optional whitespace/comments after ] to handle: ] # end of deps
|
|
560
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
562
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Safe: lazy quantifier *? prevents backtracking, anchored ^$, bounded alternation
|
|
561
563
|
const mainDepPattern = /^dependencies\s*=\s*\[([\s\S]*?)\]\s*(?:#.*)?$/m
|
|
562
564
|
const mainMatch = mainDepPattern.exec(content)
|
|
563
565
|
|
|
@@ -574,7 +576,7 @@ function parsePyprojectToml(pyprojectPath) {
|
|
|
574
576
|
// Support dotted names, hyphens, underscores, and extras
|
|
575
577
|
// Fixed: Replaced ($|.*) with ([^\s]*) to prevent catastrophic backtracking
|
|
576
578
|
const match = depString.match(
|
|
577
|
-
/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex
|
|
579
|
+
/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex -- Safe: bounded character classes, anchored ^$, no nested quantifiers
|
|
578
580
|
)
|
|
579
581
|
if (match) {
|
|
580
582
|
const [, name, _extras, operator, version] = match
|
|
@@ -606,7 +608,7 @@ function parsePyprojectToml(pyprojectPath) {
|
|
|
606
608
|
const depString = pkgMatch[1].trim()
|
|
607
609
|
|
|
608
610
|
const match = depString.match(
|
|
609
|
-
/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?(
|
|
611
|
+
/^([\w.-]+)(\[[\w,\s-]+\])?([><=!~]+)?([^\s]*)$/ // eslint-disable-line security/detect-unsafe-regex -- Safe: bounded character classes, anchored ^$, no nested quantifiers
|
|
610
612
|
)
|
|
611
613
|
if (match) {
|
|
612
614
|
const [, name, _extras, operator, version] = match
|
|
@@ -829,7 +831,7 @@ function parseCargoToml(cargoPath) {
|
|
|
829
831
|
if (!trimmed || trimmed.startsWith('#')) continue
|
|
830
832
|
|
|
831
833
|
// Match simple pattern: name = "version"
|
|
832
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
834
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded groups \w+, [^"']+, anchored ^, no nested quantifiers
|
|
833
835
|
const simpleMatch = trimmed.match(/^(\w+(?:-\w+)*)\s*=\s*["']([^"']+)["']/)
|
|
834
836
|
if (simpleMatch) {
|
|
835
837
|
const [, name, version] = simpleMatch
|
|
@@ -840,7 +842,7 @@ function parseCargoToml(cargoPath) {
|
|
|
840
842
|
|
|
841
843
|
// Match complex pattern: name = { version = "...", ... }
|
|
842
844
|
const complexMatch = trimmed.match(
|
|
843
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
845
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded negated class [^}]*, anchored ^, no nested quantifiers
|
|
844
846
|
/^(\w+(?:-\w+)*)\s*=\s*\{[^}]*version\s*=\s*["']([^"']+)["']/
|
|
845
847
|
)
|
|
846
848
|
if (complexMatch) {
|
|
@@ -970,7 +972,7 @@ function parseGemfile(gemfilePath) {
|
|
|
970
972
|
|
|
971
973
|
// Match: gem 'rails', '~> 7.0' or gem 'rails'
|
|
972
974
|
const gemMatch = trimmed.match(
|
|
973
|
-
// eslint-disable-next-line security/detect-unsafe-regex
|
|
975
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- Safe: bounded negated class [^'"]+, no nested quantifiers, processed line-by-line
|
|
974
976
|
/gem\s+['"]([^'"]+)['"]\s*(?:,\s*['"]([^'"]+)['"])?/
|
|
975
977
|
)
|
|
976
978
|
if (gemMatch) {
|
|
@@ -1270,11 +1272,11 @@ function generateBundlerGroups(bundlerFrameworks) {
|
|
|
1270
1272
|
/**
|
|
1271
1273
|
* Generate premium Dependabot configuration with multi-language framework-aware grouping
|
|
1272
1274
|
*
|
|
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
|
|
1275
|
+
* @param {Object} [options] - Configuration options
|
|
1276
|
+
* @param {string} [options.projectPath='.'] - Path to project directory
|
|
1277
|
+
* @param {string} [options.schedule='weekly'] - Update schedule (daily, weekly, monthly)
|
|
1278
|
+
* @param {string} [options.day='monday'] - Day of week for updates
|
|
1279
|
+
* @param {string} [options.time='09:00'] - Time for updates
|
|
1278
1280
|
* @returns {Object|null} Dependabot configuration object or null if not licensed
|
|
1279
1281
|
*/
|
|
1280
1282
|
function generatePremiumDependabotConfig(options = {}) {
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub API Integration for QA Architect
|
|
3
|
+
* Enables Dependabot alerts and security features via GitHub API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const https = require('https')
|
|
7
|
+
const { execSync } = require('child_process')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get GitHub token from environment or gh CLI
|
|
11
|
+
*/
|
|
12
|
+
function getGitHubToken() {
|
|
13
|
+
// Check environment variable first
|
|
14
|
+
if (process.env.GITHUB_TOKEN) {
|
|
15
|
+
return process.env.GITHUB_TOKEN
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Try to get from gh CLI
|
|
19
|
+
try {
|
|
20
|
+
const token = execSync('gh auth token', { encoding: 'utf8' }).trim()
|
|
21
|
+
if (token) return token
|
|
22
|
+
} catch {
|
|
23
|
+
// gh CLI not available or not authenticated
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Get repository info from git remote
|
|
31
|
+
*/
|
|
32
|
+
function getRepoInfo(projectPath = '.') {
|
|
33
|
+
try {
|
|
34
|
+
const remoteUrl = execSync('git remote get-url origin', {
|
|
35
|
+
cwd: projectPath,
|
|
36
|
+
encoding: 'utf8',
|
|
37
|
+
}).trim()
|
|
38
|
+
|
|
39
|
+
// Parse GitHub URL (https or ssh format)
|
|
40
|
+
const httpsMatch = remoteUrl.match(
|
|
41
|
+
/github\.com[/:]([^/]+)\/([^/.]+)(\.git)?$/
|
|
42
|
+
)
|
|
43
|
+
if (httpsMatch) {
|
|
44
|
+
return { owner: httpsMatch[1], repo: httpsMatch[2] }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return null
|
|
48
|
+
} catch {
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Make GitHub API request
|
|
55
|
+
*/
|
|
56
|
+
function githubRequest(method, path, token, data = null) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const options = {
|
|
59
|
+
hostname: 'api.github.com',
|
|
60
|
+
port: 443,
|
|
61
|
+
path: path,
|
|
62
|
+
method: method,
|
|
63
|
+
headers: {
|
|
64
|
+
Authorization: `Bearer ${token}`,
|
|
65
|
+
Accept: 'application/vnd.github+json',
|
|
66
|
+
'User-Agent': 'qa-architect',
|
|
67
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (data) {
|
|
72
|
+
options.headers['Content-Type'] = 'application/json'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const req = https.request(options, res => {
|
|
76
|
+
let body = ''
|
|
77
|
+
res.on('data', chunk => (body += chunk))
|
|
78
|
+
res.on('end', () => {
|
|
79
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
80
|
+
resolve({
|
|
81
|
+
status: res.statusCode,
|
|
82
|
+
data: body ? JSON.parse(body) : null,
|
|
83
|
+
})
|
|
84
|
+
} else if (res.statusCode === 204) {
|
|
85
|
+
resolve({ status: 204, data: null })
|
|
86
|
+
} else {
|
|
87
|
+
reject(
|
|
88
|
+
new Error(
|
|
89
|
+
`GitHub API error: ${res.statusCode} - ${body || res.statusMessage}`
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
req.on('error', reject)
|
|
97
|
+
|
|
98
|
+
if (data) {
|
|
99
|
+
req.write(JSON.stringify(data))
|
|
100
|
+
}
|
|
101
|
+
req.end()
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Check if Dependabot alerts are enabled
|
|
107
|
+
*/
|
|
108
|
+
async function checkDependabotStatus(owner, repo, token) {
|
|
109
|
+
try {
|
|
110
|
+
await githubRequest(
|
|
111
|
+
'GET',
|
|
112
|
+
`/repos/${owner}/${repo}/vulnerability-alerts`,
|
|
113
|
+
token
|
|
114
|
+
)
|
|
115
|
+
return true // 204 means enabled
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (error.message.includes('404')) {
|
|
118
|
+
return false // Not enabled
|
|
119
|
+
}
|
|
120
|
+
throw error
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Enable Dependabot alerts for a repository
|
|
126
|
+
*/
|
|
127
|
+
async function enableDependabotAlerts(owner, repo, token) {
|
|
128
|
+
try {
|
|
129
|
+
await githubRequest(
|
|
130
|
+
'PUT',
|
|
131
|
+
`/repos/${owner}/${repo}/vulnerability-alerts`,
|
|
132
|
+
token
|
|
133
|
+
)
|
|
134
|
+
return { success: true, message: 'Dependabot alerts enabled' }
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return { success: false, message: error.message }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Enable Dependabot security updates
|
|
142
|
+
*/
|
|
143
|
+
async function enableDependabotSecurityUpdates(owner, repo, token) {
|
|
144
|
+
try {
|
|
145
|
+
await githubRequest(
|
|
146
|
+
'PUT',
|
|
147
|
+
`/repos/${owner}/${repo}/automated-security-fixes`,
|
|
148
|
+
token
|
|
149
|
+
)
|
|
150
|
+
return { success: true, message: 'Dependabot security updates enabled' }
|
|
151
|
+
} catch (error) {
|
|
152
|
+
return { success: false, message: error.message }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Full setup: Enable all Dependabot features
|
|
158
|
+
*/
|
|
159
|
+
async function setupDependabot(projectPath = '.', options = {}) {
|
|
160
|
+
const { verbose = false } = options
|
|
161
|
+
const results = {
|
|
162
|
+
success: false,
|
|
163
|
+
repoInfo: null,
|
|
164
|
+
alerts: null,
|
|
165
|
+
securityUpdates: null,
|
|
166
|
+
errors: [],
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get token
|
|
170
|
+
const token = getGitHubToken()
|
|
171
|
+
if (!token) {
|
|
172
|
+
results.errors.push(
|
|
173
|
+
'No GitHub token found. Set GITHUB_TOKEN env var or run `gh auth login`'
|
|
174
|
+
)
|
|
175
|
+
return results
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Get repo info
|
|
179
|
+
const repoInfo = getRepoInfo(projectPath)
|
|
180
|
+
if (!repoInfo) {
|
|
181
|
+
results.errors.push('Could not determine GitHub repository from git remote')
|
|
182
|
+
return results
|
|
183
|
+
}
|
|
184
|
+
results.repoInfo = repoInfo
|
|
185
|
+
|
|
186
|
+
if (verbose) {
|
|
187
|
+
console.log(`📦 Repository: ${repoInfo.owner}/${repoInfo.repo}`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check current status
|
|
191
|
+
try {
|
|
192
|
+
const isEnabled = await checkDependabotStatus(
|
|
193
|
+
repoInfo.owner,
|
|
194
|
+
repoInfo.repo,
|
|
195
|
+
token
|
|
196
|
+
)
|
|
197
|
+
if (isEnabled) {
|
|
198
|
+
if (verbose) console.log('✅ Dependabot alerts already enabled')
|
|
199
|
+
results.alerts = { success: true, message: 'Already enabled' }
|
|
200
|
+
} else {
|
|
201
|
+
// Enable alerts
|
|
202
|
+
results.alerts = await enableDependabotAlerts(
|
|
203
|
+
repoInfo.owner,
|
|
204
|
+
repoInfo.repo,
|
|
205
|
+
token
|
|
206
|
+
)
|
|
207
|
+
if (verbose) {
|
|
208
|
+
console.log(
|
|
209
|
+
results.alerts.success
|
|
210
|
+
? '✅ Dependabot alerts enabled'
|
|
211
|
+
: `❌ Failed to enable alerts: ${results.alerts.message}`
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
results.errors.push(`Alerts check failed: ${error.message}`)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Enable security updates
|
|
220
|
+
try {
|
|
221
|
+
results.securityUpdates = await enableDependabotSecurityUpdates(
|
|
222
|
+
repoInfo.owner,
|
|
223
|
+
repoInfo.repo,
|
|
224
|
+
token
|
|
225
|
+
)
|
|
226
|
+
if (verbose) {
|
|
227
|
+
console.log(
|
|
228
|
+
results.securityUpdates.success
|
|
229
|
+
? '✅ Dependabot security updates enabled'
|
|
230
|
+
: `⚠️ Security updates: ${results.securityUpdates.message}`
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
} catch (error) {
|
|
234
|
+
results.errors.push(`Security updates failed: ${error.message}`)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
results.success = results.alerts?.success && results.errors.length === 0
|
|
238
|
+
|
|
239
|
+
return results
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
module.exports = {
|
|
243
|
+
getGitHubToken,
|
|
244
|
+
getRepoInfo,
|
|
245
|
+
checkDependabotStatus,
|
|
246
|
+
enableDependabotAlerts,
|
|
247
|
+
enableDependabotSecurityUpdates,
|
|
248
|
+
setupDependabot,
|
|
249
|
+
}
|
package/lib/license-validator.js
CHANGED
|
@@ -26,7 +26,7 @@ class LicenseValidator {
|
|
|
26
26
|
// Allow enterprises to host their own registry
|
|
27
27
|
this.licenseDbUrl =
|
|
28
28
|
process.env.QAA_LICENSE_DB_URL ||
|
|
29
|
-
'https://
|
|
29
|
+
'https://vibebuildlab.com/api/licenses/qa-architect.json'
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
ensureLicenseDir() {
|
package/lib/licensing.js
CHANGED
|
@@ -216,7 +216,7 @@ function isDeveloperMode() {
|
|
|
216
216
|
if (fs.existsSync(developerMarkerFile)) {
|
|
217
217
|
return true
|
|
218
218
|
}
|
|
219
|
-
} catch
|
|
219
|
+
} catch {
|
|
220
220
|
// Ignore errors checking for marker file
|
|
221
221
|
}
|
|
222
222
|
|
|
@@ -338,7 +338,7 @@ function verifyLicenseSignature(payload, signature) {
|
|
|
338
338
|
Buffer.from(signature),
|
|
339
339
|
Buffer.from(expectedSignature)
|
|
340
340
|
)
|
|
341
|
-
} catch
|
|
341
|
+
} catch {
|
|
342
342
|
// If signature verification fails, treat as invalid
|
|
343
343
|
return false
|
|
344
344
|
}
|
|
@@ -396,7 +396,7 @@ function showUpgradeMessage(feature) {
|
|
|
396
396
|
console.log('')
|
|
397
397
|
console.log(' 🎁 Start 14-day free trial - no credit card required')
|
|
398
398
|
console.log('')
|
|
399
|
-
console.log('🚀 Upgrade: https://vibebuildlab.com/
|
|
399
|
+
console.log('🚀 Upgrade: https://vibebuildlab.com/tools/qa-architect')
|
|
400
400
|
console.log(
|
|
401
401
|
'🔑 Activate: npx create-qa-architect@latest --activate-license'
|
|
402
402
|
)
|
|
@@ -414,7 +414,7 @@ function showUpgradeMessage(feature) {
|
|
|
414
414
|
console.log(' ✅ Slack/email alerts for failures')
|
|
415
415
|
console.log(' ✅ Priority support (business hours)')
|
|
416
416
|
console.log('')
|
|
417
|
-
console.log('👥 Upgrade: https://vibebuildlab.com/
|
|
417
|
+
console.log('👥 Upgrade: https://vibebuildlab.com/tools/qa-architect')
|
|
418
418
|
} else if (license.tier === LICENSE_TIERS.TEAM) {
|
|
419
419
|
console.log('\n🏢 Upgrade to ENTERPRISE - $249/month (annual) + onboarding')
|
|
420
420
|
console.log('')
|
|
@@ -553,7 +553,7 @@ async function addLegitimateKey(
|
|
|
553
553
|
if (fs.existsSync(legitimateDBFile)) {
|
|
554
554
|
try {
|
|
555
555
|
database = JSON.parse(fs.readFileSync(legitimateDBFile, 'utf8'))
|
|
556
|
-
} catch
|
|
556
|
+
} catch {
|
|
557
557
|
console.error(
|
|
558
558
|
'Warning: Could not parse existing database, creating new one'
|
|
559
559
|
)
|
|
@@ -654,7 +654,7 @@ async function promptLicenseActivation() {
|
|
|
654
654
|
console.log(
|
|
655
655
|
' If you purchased this license, please contact support at:'
|
|
656
656
|
)
|
|
657
|
-
console.log(' Email: support@
|
|
657
|
+
console.log(' Email: support@vibebuildlab.com')
|
|
658
658
|
console.log(
|
|
659
659
|
' Include your license key and purchase email for verification.'
|
|
660
660
|
)
|
|
@@ -745,7 +745,7 @@ function loadUsage() {
|
|
|
745
745
|
|
|
746
746
|
return data
|
|
747
747
|
}
|
|
748
|
-
} catch
|
|
748
|
+
} catch {
|
|
749
749
|
// Ignore errors reading usage file
|
|
750
750
|
}
|
|
751
751
|
|
|
@@ -770,7 +770,7 @@ function saveUsage(usage) {
|
|
|
770
770
|
}
|
|
771
771
|
fs.writeFileSync(getUsageFile(), JSON.stringify(usage, null, 2))
|
|
772
772
|
return true
|
|
773
|
-
} catch
|
|
773
|
+
} catch {
|
|
774
774
|
return false
|
|
775
775
|
}
|
|
776
776
|
}
|
|
@@ -958,7 +958,7 @@ function showLicenseStatus() {
|
|
|
958
958
|
// Show upgrade path
|
|
959
959
|
if (license.tier === LICENSE_TIERS.FREE) {
|
|
960
960
|
console.log('\n💡 Upgrade to PRO for unlimited access + security scanning')
|
|
961
|
-
console.log(' → https://vibebuildlab.com/
|
|
961
|
+
console.log(' → https://vibebuildlab.com/tools/qa-architect')
|
|
962
962
|
}
|
|
963
963
|
}
|
|
964
964
|
|
package/lib/package-utils.js
CHANGED
|
@@ -50,17 +50,17 @@ function mergeDevDependencies(initialDevDeps = {}, defaultDevDeps) {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Merge lint-staged configuration, preserving existing patterns
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {
|
|
55
|
-
* @param {
|
|
56
|
-
* @param {
|
|
57
|
-
* @returns {
|
|
53
|
+
* @param {Record<string, string|string[]>} [existing] - Existing lint-staged config
|
|
54
|
+
* @param {Record<string, string|string[]>} defaults - Default lint-staged config
|
|
55
|
+
* @param {{stylelintTargets?: string[]}} [options] - Merge options
|
|
56
|
+
* @param {(pattern: string) => boolean} [patternChecker] - Function to check if a pattern matches certain criteria
|
|
57
|
+
* @returns {Record<string, string|string[]>} Merged lint-staged config
|
|
58
58
|
*/
|
|
59
59
|
function mergeLintStaged(
|
|
60
|
+
defaults = {},
|
|
60
61
|
existing = {},
|
|
61
|
-
defaults,
|
|
62
62
|
options = {},
|
|
63
|
-
patternChecker =
|
|
63
|
+
patternChecker = _pattern => false
|
|
64
64
|
) {
|
|
65
65
|
const merged = { ...existing }
|
|
66
66
|
const stylelintTargets = options.stylelintTargets || []
|
|
@@ -88,7 +88,8 @@ function mergeLintStaged(
|
|
|
88
88
|
: [merged[pattern]]
|
|
89
89
|
|
|
90
90
|
const newCommands = [...existingCommands]
|
|
91
|
-
|
|
91
|
+
const commandList = Array.isArray(commands) ? commands : [commands]
|
|
92
|
+
commandList.forEach(command => {
|
|
92
93
|
if (!newCommands.includes(command)) {
|
|
93
94
|
newCommands.push(command)
|
|
94
95
|
}
|
package/lib/project-maturity.js
CHANGED
|
@@ -435,7 +435,7 @@ class ProjectMaturityDetector {
|
|
|
435
435
|
|
|
436
436
|
/**
|
|
437
437
|
* Generate GitHub Actions outputs for maturity detection
|
|
438
|
-
* @returns {string} GitHub Actions output format
|
|
438
|
+
* @returns {{maturity: string, sourceCount: number, testCount: number, hasDeps: boolean, hasDocs: boolean, hasCss: boolean, requiredChecks: string, optionalChecks: string, disabledChecks: string}} GitHub Actions output format
|
|
439
439
|
*/
|
|
440
440
|
generateGitHubActionsOutput() {
|
|
441
441
|
const maturity = this.detect()
|