create-qa-architect 5.0.7 → 5.3.1
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/README.md +46 -1
- package/docs/ADOPTION-SUMMARY.md +41 -0
- package/docs/ARCHITECTURE-REVIEW.md +67 -0
- package/docs/ARCHITECTURE.md +29 -45
- 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/lib/commands/deps.js +245 -0
- package/lib/commands/index.js +25 -0
- package/lib/commands/validate.js +85 -0
- package/lib/error-reporter.js +13 -1
- package/lib/github-api.js +108 -13
- package/lib/license-signing.js +110 -0
- package/lib/license-validator.js +359 -71
- package/lib/licensing.js +333 -99
- package/lib/prelaunch-validator.js +828 -0
- 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 +28 -9
- package/lib/template-loader.js +52 -19
- package/lib/validation/cache-manager.js +36 -6
- package/lib/validation/config-security.js +78 -15
- package/lib/validation/workflow-validation.js +28 -7
- package/package.json +2 -4
- package/scripts/check-test-coverage.sh +46 -0
- package/setup.js +350 -284
- package/create-saas-monetization.js +0 -1513
package/lib/licensing.js
CHANGED
|
@@ -7,6 +7,15 @@ const fs = require('fs')
|
|
|
7
7
|
const path = require('path')
|
|
8
8
|
const os = require('os')
|
|
9
9
|
const crypto = require('crypto')
|
|
10
|
+
const {
|
|
11
|
+
LICENSE_KEY_PATTERN,
|
|
12
|
+
buildLicensePayload,
|
|
13
|
+
hashEmail,
|
|
14
|
+
signPayload,
|
|
15
|
+
stableStringify,
|
|
16
|
+
verifyPayload,
|
|
17
|
+
loadKeyFromEnv,
|
|
18
|
+
} = require('./license-signing')
|
|
10
19
|
|
|
11
20
|
// License storage locations
|
|
12
21
|
// Support environment variable override for testing (like telemetry/error-reporter)
|
|
@@ -42,11 +51,34 @@ Object.defineProperty(exports, 'LICENSE_FILE', {
|
|
|
42
51
|
* - TEAM: Contact us (Organizations) - coming soon
|
|
43
52
|
* - ENTERPRISE: Contact us (Large Orgs) - coming soon
|
|
44
53
|
*/
|
|
45
|
-
|
|
54
|
+
// DR23 fix: Freeze object to prevent accidental or malicious mutation
|
|
55
|
+
const LICENSE_TIERS = Object.freeze({
|
|
46
56
|
FREE: 'FREE',
|
|
47
57
|
PRO: 'PRO',
|
|
48
58
|
TEAM: 'TEAM',
|
|
49
59
|
ENTERPRISE: 'ENTERPRISE',
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
function normalizeLicenseKey(key) {
|
|
63
|
+
if (typeof key !== 'string') return ''
|
|
64
|
+
return key.trim().toUpperCase()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* DR23 fix: Deep freeze helper to prevent mutation at any level
|
|
69
|
+
*/
|
|
70
|
+
function deepFreeze(obj) {
|
|
71
|
+
Object.freeze(obj)
|
|
72
|
+
Object.getOwnPropertyNames(obj).forEach(prop => {
|
|
73
|
+
if (
|
|
74
|
+
obj[prop] !== null &&
|
|
75
|
+
(typeof obj[prop] === 'object' || typeof obj[prop] === 'function') &&
|
|
76
|
+
!Object.isFrozen(obj[prop])
|
|
77
|
+
) {
|
|
78
|
+
deepFreeze(obj[prop])
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
return obj
|
|
50
82
|
}
|
|
51
83
|
|
|
52
84
|
/**
|
|
@@ -56,8 +88,10 @@ const LICENSE_TIERS = {
|
|
|
56
88
|
* PRO: Solo devs/small teams - unlimited, full features
|
|
57
89
|
* TEAM: Organizations - per-seat, shared policies
|
|
58
90
|
* ENTERPRISE: Large orgs - SSO, compliance, SLA
|
|
91
|
+
*
|
|
92
|
+
* DR23 fix: Deep frozen to prevent accidental or malicious mutation
|
|
59
93
|
*/
|
|
60
|
-
const FEATURES = {
|
|
94
|
+
const FEATURES = deepFreeze({
|
|
61
95
|
[LICENSE_TIERS.FREE]: {
|
|
62
96
|
// Caps (enforced in setup.js and CLI)
|
|
63
97
|
maxPrivateRepos: 1,
|
|
@@ -75,10 +109,26 @@ const FEATURES = {
|
|
|
75
109
|
advancedWorkflows: false,
|
|
76
110
|
notifications: false,
|
|
77
111
|
multiRepo: false,
|
|
112
|
+
// Quality tools
|
|
113
|
+
lighthouseCI: true, // ✅ Basic Lighthouse (no thresholds)
|
|
114
|
+
lighthouseThresholds: false, // ❌ PRO feature - custom thresholds
|
|
115
|
+
bundleSizeLimits: false, // ❌ PRO feature
|
|
116
|
+
axeAccessibility: true, // ✅ Basic a11y testing
|
|
117
|
+
conventionalCommits: true, // ✅ Commit message enforcement
|
|
118
|
+
coverageThresholds: false, // ❌ PRO feature
|
|
119
|
+
// Pre-launch validation
|
|
120
|
+
prelaunchValidation: true, // ✅ Basic prelaunch checks
|
|
121
|
+
seoValidation: true, // ✅ Sitemap, robots, meta tags
|
|
122
|
+
linkValidation: true, // ✅ Broken link detection
|
|
123
|
+
docsValidation: true, // ✅ Documentation completeness
|
|
124
|
+
envValidation: false, // ❌ PRO feature - env vars audit
|
|
78
125
|
roadmap: [
|
|
79
126
|
'✅ ESLint, Prettier, Stylelint configuration',
|
|
80
127
|
'✅ Basic Husky pre-commit hooks',
|
|
81
128
|
'✅ Basic npm dependency monitoring (10 PRs/month)',
|
|
129
|
+
'✅ Lighthouse CI (basic, no thresholds)',
|
|
130
|
+
'✅ axe-core accessibility testing',
|
|
131
|
+
'✅ Conventional commits (commitlint)',
|
|
82
132
|
'⚠️ Limited: 1 private repo, JS/TS only',
|
|
83
133
|
'❌ No security scanning (Gitleaks, ESLint security)',
|
|
84
134
|
'❌ No Smart Test Strategy',
|
|
@@ -102,6 +152,19 @@ const FEATURES = {
|
|
|
102
152
|
advancedWorkflows: false,
|
|
103
153
|
notifications: false,
|
|
104
154
|
multiRepo: false,
|
|
155
|
+
// Quality tools - all enabled
|
|
156
|
+
lighthouseCI: true, // ✅ Full Lighthouse CI
|
|
157
|
+
lighthouseThresholds: true, // ✅ Custom performance/a11y thresholds
|
|
158
|
+
bundleSizeLimits: true, // ✅ Bundle size enforcement
|
|
159
|
+
axeAccessibility: true, // ✅ Advanced a11y testing
|
|
160
|
+
conventionalCommits: true, // ✅ Commit message enforcement
|
|
161
|
+
coverageThresholds: true, // ✅ Coverage threshold enforcement
|
|
162
|
+
// Pre-launch validation - all enabled
|
|
163
|
+
prelaunchValidation: true, // ✅ Full prelaunch suite
|
|
164
|
+
seoValidation: true, // ✅ Sitemap, robots, meta tags
|
|
165
|
+
linkValidation: true, // ✅ Broken link detection
|
|
166
|
+
docsValidation: true, // ✅ Documentation completeness
|
|
167
|
+
envValidation: true, // ✅ Env vars audit
|
|
105
168
|
roadmap: [
|
|
106
169
|
'✅ Unlimited repos and runs',
|
|
107
170
|
'✅ Smart Test Strategy (70% faster pre-push validation)',
|
|
@@ -109,8 +172,11 @@ const FEATURES = {
|
|
|
109
172
|
'✅ TypeScript production protection',
|
|
110
173
|
'✅ Multi-language (Python, Rust, Ruby)',
|
|
111
174
|
'✅ Framework-aware dependency grouping',
|
|
175
|
+
'✅ Lighthouse CI with custom thresholds',
|
|
176
|
+
'✅ Bundle size limits (size-limit)',
|
|
177
|
+
'✅ Coverage threshold enforcement',
|
|
178
|
+
'✅ Pre-launch validation with env vars audit',
|
|
112
179
|
'✅ Email support (24-48h response)',
|
|
113
|
-
'🔄 Performance budgets - Coming Q1 2026',
|
|
114
180
|
],
|
|
115
181
|
},
|
|
116
182
|
[LICENSE_TIERS.TEAM]: {
|
|
@@ -139,6 +205,19 @@ const FEATURES = {
|
|
|
139
205
|
advancedWorkflows: true,
|
|
140
206
|
notifications: true,
|
|
141
207
|
multiRepo: true,
|
|
208
|
+
// Quality tools - all enabled (inherited from PRO)
|
|
209
|
+
lighthouseCI: true,
|
|
210
|
+
lighthouseThresholds: true,
|
|
211
|
+
bundleSizeLimits: true,
|
|
212
|
+
axeAccessibility: true,
|
|
213
|
+
conventionalCommits: true,
|
|
214
|
+
coverageThresholds: true,
|
|
215
|
+
// Pre-launch validation - all enabled (inherited from PRO)
|
|
216
|
+
prelaunchValidation: true,
|
|
217
|
+
seoValidation: true,
|
|
218
|
+
linkValidation: true,
|
|
219
|
+
docsValidation: true,
|
|
220
|
+
envValidation: true,
|
|
142
221
|
roadmap: [
|
|
143
222
|
'✅ All PRO features included',
|
|
144
223
|
'✅ Per-seat licensing (5-seat minimum)',
|
|
@@ -175,6 +254,19 @@ const FEATURES = {
|
|
|
175
254
|
advancedWorkflows: true,
|
|
176
255
|
notifications: true,
|
|
177
256
|
multiRepo: true,
|
|
257
|
+
// Quality tools - all enabled (inherited from TEAM)
|
|
258
|
+
lighthouseCI: true,
|
|
259
|
+
lighthouseThresholds: true,
|
|
260
|
+
bundleSizeLimits: true,
|
|
261
|
+
axeAccessibility: true,
|
|
262
|
+
conventionalCommits: true,
|
|
263
|
+
coverageThresholds: true,
|
|
264
|
+
// Pre-launch validation - all enabled (inherited from TEAM)
|
|
265
|
+
prelaunchValidation: true,
|
|
266
|
+
seoValidation: true,
|
|
267
|
+
linkValidation: true,
|
|
268
|
+
docsValidation: true,
|
|
269
|
+
envValidation: true,
|
|
178
270
|
// Enterprise-specific
|
|
179
271
|
ssoIntegration: true, // SSO/SAML
|
|
180
272
|
scimReady: true,
|
|
@@ -198,13 +290,19 @@ const FEATURES = {
|
|
|
198
290
|
'✅ Optional on-prem license server',
|
|
199
291
|
],
|
|
200
292
|
},
|
|
201
|
-
}
|
|
293
|
+
})
|
|
202
294
|
|
|
203
295
|
/**
|
|
204
296
|
* Check if developer/owner mode is enabled
|
|
205
297
|
* Allows the tool creator to use all features without a license
|
|
298
|
+
* Security: Production mode always enforces license checks
|
|
206
299
|
*/
|
|
207
300
|
function isDeveloperMode() {
|
|
301
|
+
// Security: Production mode never allows developer bypass
|
|
302
|
+
if (process.env.NODE_ENV === 'production') {
|
|
303
|
+
return false
|
|
304
|
+
}
|
|
305
|
+
|
|
208
306
|
// Check environment variable
|
|
209
307
|
if (process.env.QAA_DEVELOPER === 'true') {
|
|
210
308
|
return true
|
|
@@ -216,8 +314,19 @@ function isDeveloperMode() {
|
|
|
216
314
|
if (fs.existsSync(developerMarkerFile)) {
|
|
217
315
|
return true
|
|
218
316
|
}
|
|
219
|
-
} catch {
|
|
220
|
-
//
|
|
317
|
+
} catch (error) {
|
|
318
|
+
// DR10 fix: Halt on ELOOP in production (security issue)
|
|
319
|
+
if (error?.code === 'ELOOP') {
|
|
320
|
+
const message =
|
|
321
|
+
'Symlink loop detected in license directory - possible security issue'
|
|
322
|
+
if (process.env.NODE_ENV === 'production') {
|
|
323
|
+
throw new Error(message)
|
|
324
|
+
} else {
|
|
325
|
+
console.warn(`⚠️ ${message}`)
|
|
326
|
+
}
|
|
327
|
+
} else if (process.env.DEBUG && error?.code !== 'ENOENT') {
|
|
328
|
+
console.warn(`⚠️ Developer mode check failed: ${error.message}`)
|
|
329
|
+
}
|
|
221
330
|
}
|
|
222
331
|
|
|
223
332
|
return false
|
|
@@ -248,23 +357,24 @@ function getLicenseInfo() {
|
|
|
248
357
|
return { tier: LICENSE_TIERS.FREE, valid: true }
|
|
249
358
|
}
|
|
250
359
|
|
|
251
|
-
|
|
252
|
-
|
|
360
|
+
const licenseKey = normalizeLicenseKey(
|
|
361
|
+
localLicense.licenseKey || localLicense.key
|
|
362
|
+
)
|
|
363
|
+
if (!licenseKey || !localLicense.email) {
|
|
253
364
|
return {
|
|
254
365
|
tier: LICENSE_TIERS.FREE,
|
|
255
366
|
valid: true,
|
|
256
|
-
error:
|
|
257
|
-
'License signature verification failed - license may have been tampered with',
|
|
367
|
+
error: 'Invalid license format',
|
|
258
368
|
}
|
|
259
369
|
}
|
|
260
370
|
|
|
261
|
-
// Check
|
|
262
|
-
|
|
263
|
-
if (!licenseKey || !localLicense.email) {
|
|
371
|
+
// Check if license is valid
|
|
372
|
+
if (!localLicense.valid) {
|
|
264
373
|
return {
|
|
265
374
|
tier: LICENSE_TIERS.FREE,
|
|
266
375
|
valid: true,
|
|
267
|
-
error:
|
|
376
|
+
error:
|
|
377
|
+
'License signature verification failed - license may have been tampered with',
|
|
268
378
|
}
|
|
269
379
|
}
|
|
270
380
|
|
|
@@ -309,17 +419,16 @@ function getLicenseInfo() {
|
|
|
309
419
|
* Supports both legacy format and new Stripe-generated keys
|
|
310
420
|
*/
|
|
311
421
|
function validateLicenseKey(key, tier) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (stripeFormat.test(key)) {
|
|
422
|
+
const normalizedKey = normalizeLicenseKey(key)
|
|
423
|
+
// TD15 fix: Use shared constant for license key pattern
|
|
424
|
+
if (LICENSE_KEY_PATTERN.test(normalizedKey)) {
|
|
316
425
|
// Stripe-generated key - always valid if properly formatted
|
|
317
426
|
return true
|
|
318
427
|
}
|
|
319
428
|
|
|
320
429
|
// Legacy format validation for backward compatibility
|
|
321
430
|
const expectedPrefix = `QAA-${tier.toUpperCase()}-`
|
|
322
|
-
return
|
|
431
|
+
return normalizedKey.startsWith(expectedPrefix) && normalizedKey.length > 20
|
|
323
432
|
}
|
|
324
433
|
|
|
325
434
|
/**
|
|
@@ -327,19 +436,27 @@ function validateLicenseKey(key, tier) {
|
|
|
327
436
|
*/
|
|
328
437
|
function verifyLicenseSignature(payload, signature) {
|
|
329
438
|
try {
|
|
330
|
-
const
|
|
331
|
-
process.env.
|
|
332
|
-
|
|
333
|
-
.createHmac('sha256', secret)
|
|
334
|
-
.update(JSON.stringify(payload))
|
|
335
|
-
.digest('hex')
|
|
336
|
-
|
|
337
|
-
return crypto.timingSafeEqual(
|
|
338
|
-
Buffer.from(signature),
|
|
339
|
-
Buffer.from(expectedSignature)
|
|
439
|
+
const publicKey = loadKeyFromEnv(
|
|
440
|
+
process.env.QAA_LICENSE_PUBLIC_KEY,
|
|
441
|
+
process.env.QAA_LICENSE_PUBLIC_KEY_PATH
|
|
340
442
|
)
|
|
341
|
-
|
|
342
|
-
|
|
443
|
+
if (!publicKey) {
|
|
444
|
+
// TD12 fix: Log warning when public key is missing in non-dev mode
|
|
445
|
+
// Security: Production mode never allows developer bypass
|
|
446
|
+
const isDevMode =
|
|
447
|
+
process.env.NODE_ENV !== 'production' &&
|
|
448
|
+
process.env.QAA_DEVELOPER === 'true'
|
|
449
|
+
if (!isDevMode) {
|
|
450
|
+
console.warn(
|
|
451
|
+
'⚠️ License public key not configured - signature verification skipped'
|
|
452
|
+
)
|
|
453
|
+
}
|
|
454
|
+
return isDevMode
|
|
455
|
+
}
|
|
456
|
+
return verifyPayload(payload, signature, publicKey)
|
|
457
|
+
} catch (error) {
|
|
458
|
+
// TD12 fix: Log verification failures instead of silently returning false
|
|
459
|
+
console.warn(`⚠️ Signature verification failed: ${error.message}`)
|
|
343
460
|
return false
|
|
344
461
|
}
|
|
345
462
|
}
|
|
@@ -376,7 +493,6 @@ function getSupportedLanguages() {
|
|
|
376
493
|
*/
|
|
377
494
|
function showUpgradeMessage(feature) {
|
|
378
495
|
const license = getLicenseInfo()
|
|
379
|
-
const _tierFeatures = FEATURES[license.tier] || FEATURES[LICENSE_TIERS.FREE]
|
|
380
496
|
|
|
381
497
|
console.log(`\n🔒 ${feature} is a premium feature`)
|
|
382
498
|
console.log(`📊 Current license: ${license.tier.toUpperCase()}`)
|
|
@@ -431,19 +547,52 @@ function showUpgradeMessage(feature) {
|
|
|
431
547
|
*/
|
|
432
548
|
function saveLicense(tier, key, email, expires = null) {
|
|
433
549
|
try {
|
|
550
|
+
// DR24 fix: Validate tier is a valid LICENSE_TIERS value
|
|
551
|
+
const validTiers = Object.values(LICENSE_TIERS)
|
|
552
|
+
if (!validTiers.includes(tier)) {
|
|
553
|
+
return {
|
|
554
|
+
success: false,
|
|
555
|
+
error: `Invalid tier "${tier}". Must be one of: ${validTiers.join(', ')}`,
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
434
559
|
const licenseDir = getLicenseDir()
|
|
435
560
|
const licenseFile = getLicenseFile()
|
|
561
|
+
const normalizedKey = normalizeLicenseKey(key)
|
|
562
|
+
const privateKey = loadKeyFromEnv(
|
|
563
|
+
process.env.LICENSE_REGISTRY_PRIVATE_KEY,
|
|
564
|
+
process.env.LICENSE_REGISTRY_PRIVATE_KEY_PATH
|
|
565
|
+
)
|
|
436
566
|
|
|
437
567
|
if (!fs.existsSync(licenseDir)) {
|
|
438
568
|
fs.mkdirSync(licenseDir, { recursive: true })
|
|
439
569
|
}
|
|
440
570
|
|
|
571
|
+
if (!privateKey) {
|
|
572
|
+
return {
|
|
573
|
+
success: false,
|
|
574
|
+
error:
|
|
575
|
+
'LICENSE_REGISTRY_PRIVATE_KEY or LICENSE_REGISTRY_PRIVATE_KEY_PATH is required to save a signed license',
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const payload = buildLicensePayload({
|
|
580
|
+
licenseKey: normalizedKey,
|
|
581
|
+
tier,
|
|
582
|
+
isFounder: false,
|
|
583
|
+
emailHash: hashEmail(email),
|
|
584
|
+
issued: new Date().toISOString(),
|
|
585
|
+
})
|
|
586
|
+
const signature = signPayload(payload, privateKey)
|
|
587
|
+
|
|
441
588
|
const licenseData = {
|
|
442
589
|
tier,
|
|
443
|
-
|
|
590
|
+
licenseKey: normalizedKey,
|
|
444
591
|
email,
|
|
445
592
|
expires,
|
|
446
593
|
activated: new Date().toISOString(),
|
|
594
|
+
payload,
|
|
595
|
+
signature,
|
|
447
596
|
}
|
|
448
597
|
|
|
449
598
|
fs.writeFileSync(licenseFile, JSON.stringify(licenseData, null, 2))
|
|
@@ -458,8 +607,18 @@ function saveLicense(tier, key, email, expires = null) {
|
|
|
458
607
|
*/
|
|
459
608
|
function saveLicenseWithSignature(tier, key, email, validation) {
|
|
460
609
|
try {
|
|
610
|
+
// DR24 fix: Validate tier is a valid LICENSE_TIERS value
|
|
611
|
+
const validTiers = Object.values(LICENSE_TIERS)
|
|
612
|
+
if (!validTiers.includes(tier)) {
|
|
613
|
+
return {
|
|
614
|
+
success: false,
|
|
615
|
+
error: `Invalid tier "${tier}". Must be one of: ${validTiers.join(', ')}`,
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
461
619
|
const licenseDir = getLicenseDir()
|
|
462
620
|
const licenseFile = getLicenseFile()
|
|
621
|
+
const normalizedKey = normalizeLicenseKey(key)
|
|
463
622
|
|
|
464
623
|
if (!fs.existsSync(licenseDir)) {
|
|
465
624
|
fs.mkdirSync(licenseDir, { recursive: true })
|
|
@@ -467,7 +626,7 @@ function saveLicenseWithSignature(tier, key, email, validation) {
|
|
|
467
626
|
|
|
468
627
|
const licenseData = {
|
|
469
628
|
tier,
|
|
470
|
-
licenseKey:
|
|
629
|
+
licenseKey: normalizedKey, // ✅ Changed from 'key' to 'licenseKey' to match StripeIntegration
|
|
471
630
|
email,
|
|
472
631
|
expires: validation.expires,
|
|
473
632
|
activated: new Date().toISOString(),
|
|
@@ -535,11 +694,16 @@ async function addLegitimateKey(
|
|
|
535
694
|
purchaseEmail = null
|
|
536
695
|
) {
|
|
537
696
|
try {
|
|
697
|
+
const normalizedKey = normalizeLicenseKey(licenseKey)
|
|
538
698
|
// Use the same license directory as the CLI
|
|
539
699
|
const licenseDir =
|
|
540
700
|
process.env.QAA_LICENSE_DIR ||
|
|
541
701
|
path.join(os.homedir(), '.create-qa-architect')
|
|
542
702
|
const legitimateDBFile = path.join(licenseDir, 'legitimate-licenses.json')
|
|
703
|
+
const privateKey = loadKeyFromEnv(
|
|
704
|
+
process.env.LICENSE_REGISTRY_PRIVATE_KEY,
|
|
705
|
+
process.env.LICENSE_REGISTRY_PRIVATE_KEY_PATH
|
|
706
|
+
)
|
|
543
707
|
|
|
544
708
|
// Ensure directory exists
|
|
545
709
|
if (!fs.existsSync(licenseDir)) {
|
|
@@ -551,43 +715,72 @@ async function addLegitimateKey(
|
|
|
551
715
|
if (fs.existsSync(legitimateDBFile)) {
|
|
552
716
|
try {
|
|
553
717
|
database = JSON.parse(fs.readFileSync(legitimateDBFile, 'utf8'))
|
|
554
|
-
} catch {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
718
|
+
} catch (parseError) {
|
|
719
|
+
// Silent failure fix: Backup corrupt file before overwriting
|
|
720
|
+
const backupPath = `${legitimateDBFile}.corrupted.${Date.now()}`
|
|
721
|
+
try {
|
|
722
|
+
fs.copyFileSync(legitimateDBFile, backupPath)
|
|
723
|
+
console.error(
|
|
724
|
+
`Warning: Could not parse existing database. Backed up to ${backupPath}`
|
|
725
|
+
)
|
|
726
|
+
} catch {
|
|
727
|
+
console.error(
|
|
728
|
+
'Warning: Could not parse or backup existing database, creating new one'
|
|
729
|
+
)
|
|
730
|
+
}
|
|
731
|
+
console.error(`Parse error: ${parseError.message}`)
|
|
558
732
|
}
|
|
559
733
|
}
|
|
560
734
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
description: 'Legitimate license database - populated by admin/webhook',
|
|
735
|
+
if (!privateKey) {
|
|
736
|
+
return {
|
|
737
|
+
success: false,
|
|
738
|
+
error:
|
|
739
|
+
'LICENSE_REGISTRY_PRIVATE_KEY or LICENSE_REGISTRY_PRIVATE_KEY_PATH is required to add legitimate keys',
|
|
567
740
|
}
|
|
568
741
|
}
|
|
569
742
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
743
|
+
const issued = new Date().toISOString()
|
|
744
|
+
const emailHash = hashEmail(purchaseEmail)
|
|
745
|
+
const payload = buildLicensePayload({
|
|
746
|
+
licenseKey: normalizedKey,
|
|
573
747
|
tier,
|
|
574
748
|
isFounder,
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
749
|
+
emailHash,
|
|
750
|
+
issued,
|
|
751
|
+
})
|
|
752
|
+
const signature = signPayload(payload, privateKey)
|
|
753
|
+
|
|
754
|
+
const { _metadata, ...existingLicenses } = database
|
|
755
|
+
const licenses = {
|
|
756
|
+
...existingLicenses,
|
|
757
|
+
[normalizedKey]: {
|
|
758
|
+
tier,
|
|
759
|
+
isFounder,
|
|
760
|
+
issued,
|
|
761
|
+
emailHash,
|
|
762
|
+
signature,
|
|
763
|
+
keyId: process.env.LICENSE_REGISTRY_KEY_ID || 'default',
|
|
764
|
+
},
|
|
578
765
|
}
|
|
579
766
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
database._metadata.totalLicenses = Object.keys(database).length - 1 // Exclude metadata
|
|
583
|
-
|
|
584
|
-
// Calculate SHA256 checksum for integrity verification (MANDATORY)
|
|
585
|
-
const { _metadata, ...licensesOnly } = database
|
|
586
|
-
const sha256 = crypto
|
|
767
|
+
const registrySignature = signPayload(licenses, privateKey)
|
|
768
|
+
const hash = crypto
|
|
587
769
|
.createHash('sha256')
|
|
588
|
-
.update(
|
|
770
|
+
.update(stableStringify(licenses))
|
|
589
771
|
.digest('hex')
|
|
590
|
-
|
|
772
|
+
const metadata = {
|
|
773
|
+
version: '1.0',
|
|
774
|
+
created: _metadata?.created || new Date().toISOString(),
|
|
775
|
+
lastUpdate: new Date().toISOString(),
|
|
776
|
+
description: 'Legitimate license database - populated by admin/webhook',
|
|
777
|
+
algorithm: 'ed25519',
|
|
778
|
+
keyId: process.env.LICENSE_REGISTRY_KEY_ID || 'default',
|
|
779
|
+
registrySignature,
|
|
780
|
+
hash,
|
|
781
|
+
totalLicenses: Object.keys(licenses).length,
|
|
782
|
+
}
|
|
783
|
+
database = { _metadata: metadata, ...licenses }
|
|
591
784
|
|
|
592
785
|
// Save database
|
|
593
786
|
fs.writeFileSync(legitimateDBFile, JSON.stringify(database, null, 2))
|
|
@@ -608,60 +801,60 @@ async function addLegitimateKey(
|
|
|
608
801
|
|
|
609
802
|
/**
|
|
610
803
|
* Interactive license activation prompt
|
|
804
|
+
* DR27 fix: Converted from callback-based readline to async/await pattern
|
|
611
805
|
*/
|
|
612
806
|
async function promptLicenseActivation() {
|
|
613
|
-
const readline = require('readline')
|
|
807
|
+
const readline = require('readline/promises')
|
|
614
808
|
|
|
615
809
|
const rl = readline.createInterface({
|
|
616
810
|
input: process.stdin,
|
|
617
811
|
output: process.stdout,
|
|
618
812
|
})
|
|
619
813
|
|
|
620
|
-
|
|
814
|
+
try {
|
|
621
815
|
console.log('\n🔑 License Activation')
|
|
622
816
|
console.log(
|
|
623
817
|
'Enter your license key from the purchase confirmation email.\n'
|
|
624
818
|
)
|
|
625
819
|
|
|
626
|
-
rl.question(
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
rl.close()
|
|
630
|
-
resolve({ success: false })
|
|
631
|
-
return
|
|
632
|
-
}
|
|
820
|
+
const licenseKey = await rl.question(
|
|
821
|
+
'License key (QAA-XXXX-XXXX-XXXX-XXXX): '
|
|
822
|
+
)
|
|
633
823
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
return
|
|
640
|
-
}
|
|
824
|
+
if (!licenseKey.trim()) {
|
|
825
|
+
console.log('❌ License key required')
|
|
826
|
+
rl.close()
|
|
827
|
+
return { success: false }
|
|
828
|
+
}
|
|
641
829
|
|
|
642
|
-
|
|
830
|
+
const email = await rl.question('Email address: ')
|
|
643
831
|
|
|
644
|
-
|
|
832
|
+
if (!email.trim()) {
|
|
833
|
+
console.log('❌ Email address required')
|
|
834
|
+
rl.close()
|
|
835
|
+
return { success: false }
|
|
836
|
+
}
|
|
645
837
|
|
|
646
|
-
|
|
647
|
-
!result.success &&
|
|
648
|
-
result.error &&
|
|
649
|
-
result.error.includes('not found')
|
|
650
|
-
) {
|
|
651
|
-
console.log('\n📞 License activation assistance:')
|
|
652
|
-
console.log(
|
|
653
|
-
' If you purchased this license, please contact support at:'
|
|
654
|
-
)
|
|
655
|
-
console.log(' Email: support@vibebuildlab.com')
|
|
656
|
-
console.log(
|
|
657
|
-
' Include your license key and purchase email for verification.'
|
|
658
|
-
)
|
|
659
|
-
}
|
|
838
|
+
rl.close()
|
|
660
839
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
840
|
+
const result = await activateLicense(licenseKey.trim(), email.trim())
|
|
841
|
+
|
|
842
|
+
if (!result.success && result.error && result.error.includes('not found')) {
|
|
843
|
+
console.log('\n📞 License activation assistance:')
|
|
844
|
+
console.log(
|
|
845
|
+
' If you purchased this license, please contact support at:'
|
|
846
|
+
)
|
|
847
|
+
console.log(' Email: support@vibebuildlab.com')
|
|
848
|
+
console.log(
|
|
849
|
+
' Include your license key and purchase email for verification.'
|
|
850
|
+
)
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
return result
|
|
854
|
+
} catch (error) {
|
|
855
|
+
rl.close()
|
|
856
|
+
return { success: false, error: error.message }
|
|
857
|
+
}
|
|
665
858
|
}
|
|
666
859
|
|
|
667
860
|
/**
|
|
@@ -743,14 +936,53 @@ function loadUsage() {
|
|
|
743
936
|
|
|
744
937
|
return data
|
|
745
938
|
}
|
|
746
|
-
} catch {
|
|
747
|
-
//
|
|
939
|
+
} catch (error) {
|
|
940
|
+
// DR8 fix: Prevent quota bypass through file corruption
|
|
941
|
+
if (error instanceof SyntaxError) {
|
|
942
|
+
const usageFile = getUsageFile()
|
|
943
|
+
console.warn('⚠️ Usage tracking file corrupted')
|
|
944
|
+
console.warn(' Creating backup and resetting to current month start')
|
|
945
|
+
|
|
946
|
+
// Backup corrupted file for forensics
|
|
947
|
+
const backupPath = `${usageFile}.corrupted.${Date.now()}`
|
|
948
|
+
try {
|
|
949
|
+
fs.copyFileSync(usageFile, backupPath)
|
|
950
|
+
console.warn(` Backup saved: ${backupPath}`)
|
|
951
|
+
} catch (_backupError) {
|
|
952
|
+
// Ignore backup failures
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
// DR8 fix: For FREE tier, reset to max usage to prevent quota bypass
|
|
956
|
+
const license = getLicenseInfo()
|
|
957
|
+
if (license.tier === LICENSE_TIERS.FREE) {
|
|
958
|
+
console.warn(
|
|
959
|
+
' ⚠️ FREE tier: Starting with maximum usage for security'
|
|
960
|
+
)
|
|
961
|
+
const caps = FEATURES[LICENSE_TIERS.FREE]
|
|
962
|
+
return {
|
|
963
|
+
month: getCurrentMonth(),
|
|
964
|
+
prePushRuns: caps.maxPrePushRunsPerMonth,
|
|
965
|
+
dependencyPRs: caps.maxDependencyPRsPerMonth,
|
|
966
|
+
repos: [],
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
} else if (process.env.DEBUG && error?.code !== 'ENOENT') {
|
|
970
|
+
console.warn(`⚠️ Could not read usage file: ${error.message}`)
|
|
971
|
+
}
|
|
748
972
|
}
|
|
749
973
|
|
|
750
974
|
// Default usage data
|
|
975
|
+
return getDefaultUsage()
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
function getCurrentMonth() {
|
|
751
979
|
const now = new Date()
|
|
980
|
+
return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
function getDefaultUsage() {
|
|
752
984
|
return {
|
|
753
|
-
month:
|
|
985
|
+
month: getCurrentMonth(),
|
|
754
986
|
prePushRuns: 0,
|
|
755
987
|
dependencyPRs: 0,
|
|
756
988
|
repos: [],
|
|
@@ -768,7 +1000,9 @@ function saveUsage(usage) {
|
|
|
768
1000
|
}
|
|
769
1001
|
fs.writeFileSync(getUsageFile(), JSON.stringify(usage, null, 2))
|
|
770
1002
|
return true
|
|
771
|
-
} catch {
|
|
1003
|
+
} catch (error) {
|
|
1004
|
+
// TD12 fix: Log errors saving usage data instead of silently failing
|
|
1005
|
+
console.warn(`⚠️ Failed to save usage data: ${error.message}`)
|
|
772
1006
|
return false
|
|
773
1007
|
}
|
|
774
1008
|
}
|