create-qa-architect 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +12 -0
- package/.github/CLAUDE_MD_AUTOMATION.md +248 -0
- package/.github/PROGRESSIVE_QUALITY_IMPLEMENTATION.md +408 -0
- package/.github/PROGRESSIVE_QUALITY_PROPOSAL.md +443 -0
- package/.github/RELEASE_CHECKLIST.md +100 -0
- package/.github/dependabot.yml +50 -0
- package/.github/git-sync.sh +48 -0
- package/.github/workflows/claude-md-validation.yml +82 -0
- package/.github/workflows/nightly-gitleaks-verification.yml +176 -0
- package/.github/workflows/pnpm-ci.yml.example +53 -0
- package/.github/workflows/python-ci.yml.example +69 -0
- package/.github/workflows/quality-legacy.yml.backup +165 -0
- package/.github/workflows/quality-progressive.yml.example +291 -0
- package/.github/workflows/quality.yml +436 -0
- package/.github/workflows/release.yml +53 -0
- package/.nvmrc +1 -0
- package/.prettierignore +14 -0
- package/.prettierrc +9 -0
- package/.stylelintrc.json +5 -0
- package/README.md +212 -0
- package/config/.lighthouserc.js +45 -0
- package/config/.pre-commit-config.yaml +66 -0
- package/config/constants.js +128 -0
- package/config/defaults.js +124 -0
- package/config/pyproject.toml +124 -0
- package/config/quality-config.schema.json +97 -0
- package/config/quality-python.yml +89 -0
- package/config/requirements-dev.txt +15 -0
- package/create-saas-monetization.js +1465 -0
- package/eslint.config.cjs +117 -0
- package/eslint.config.ts.cjs +99 -0
- package/legal/README.md +106 -0
- package/legal/copyright.md +76 -0
- package/legal/disclaimer.md +146 -0
- package/legal/privacy-policy.html +324 -0
- package/legal/privacy-policy.md +196 -0
- package/legal/terms-of-service.md +224 -0
- package/lib/billing-dashboard.html +645 -0
- package/lib/config-validator.js +163 -0
- package/lib/dependency-monitoring-basic.js +185 -0
- package/lib/dependency-monitoring-premium.js +1490 -0
- package/lib/error-reporter.js +444 -0
- package/lib/interactive/prompt.js +128 -0
- package/lib/interactive/questions.js +146 -0
- package/lib/license-validator.js +403 -0
- package/lib/licensing.js +989 -0
- package/lib/package-utils.js +187 -0
- package/lib/project-maturity.js +516 -0
- package/lib/security-enhancements.js +340 -0
- package/lib/setup-enhancements.js +317 -0
- package/lib/smart-strategy-generator.js +344 -0
- package/lib/telemetry.js +323 -0
- package/lib/template-loader.js +252 -0
- package/lib/typescript-config-generator.js +210 -0
- package/lib/ui-helpers.js +74 -0
- package/lib/validation/base-validator.js +174 -0
- package/lib/validation/cache-manager.js +158 -0
- package/lib/validation/config-security.js +741 -0
- package/lib/validation/documentation.js +326 -0
- package/lib/validation/index.js +186 -0
- package/lib/validation/validation-factory.js +153 -0
- package/lib/validation/workflow-validation.js +172 -0
- package/lib/yaml-utils.js +120 -0
- package/marketing/beta-user-email-campaign.md +372 -0
- package/marketing/landing-page.html +721 -0
- package/package.json +165 -0
- package/setup.js +2076 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* License Validator (user-side)
|
|
3
|
+
*
|
|
4
|
+
* - No Stripe dependencies (secrets stay server-side)
|
|
5
|
+
* - Fetches a signed license registry from a configurable HTTPS endpoint
|
|
6
|
+
* - Caches locally for offline use with graceful fallback
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const os = require('os')
|
|
12
|
+
const crypto = require('crypto')
|
|
13
|
+
|
|
14
|
+
class LicenseValidator {
|
|
15
|
+
constructor() {
|
|
16
|
+
// Support environment variable override for testing (like telemetry/error-reporter)
|
|
17
|
+
this.licenseDir =
|
|
18
|
+
process.env.QAA_LICENSE_DIR ||
|
|
19
|
+
path.join(os.homedir(), '.create-qa-architect')
|
|
20
|
+
this.licenseFile = path.join(this.licenseDir, 'license.json')
|
|
21
|
+
this.legitimateDBFile = path.join(
|
|
22
|
+
this.licenseDir,
|
|
23
|
+
'legitimate-licenses.json'
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
// Allow enterprises to host their own registry
|
|
27
|
+
this.licenseDbUrl =
|
|
28
|
+
process.env.QAA_LICENSE_DB_URL ||
|
|
29
|
+
'https://license.aibuilderlab.com/qaa/legitimate-licenses.json'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
ensureLicenseDir() {
|
|
33
|
+
if (!fs.existsSync(this.licenseDir)) {
|
|
34
|
+
fs.mkdirSync(this.licenseDir, { recursive: true })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize license directory and database if needed
|
|
40
|
+
*/
|
|
41
|
+
initialize() {
|
|
42
|
+
try {
|
|
43
|
+
this.ensureLicenseDir()
|
|
44
|
+
|
|
45
|
+
// Initialize legitimate license database if it doesn't exist
|
|
46
|
+
if (!fs.existsSync(this.legitimateDBFile)) {
|
|
47
|
+
const initialDB = {
|
|
48
|
+
_metadata: {
|
|
49
|
+
version: '1.0',
|
|
50
|
+
created: new Date().toISOString(),
|
|
51
|
+
description:
|
|
52
|
+
'Legitimate license database - populated by webhook/admin',
|
|
53
|
+
},
|
|
54
|
+
}
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
this.legitimateDBFile,
|
|
57
|
+
JSON.stringify(initialDB, null, 2)
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return true
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.error('Failed to initialize license directory:', error.message)
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Load legitimate licenses from the cached database
|
|
70
|
+
*/
|
|
71
|
+
loadLegitimateDatabase() {
|
|
72
|
+
try {
|
|
73
|
+
if (fs.existsSync(this.legitimateDBFile)) {
|
|
74
|
+
const data = fs.readFileSync(this.legitimateDBFile, 'utf8')
|
|
75
|
+
const parsed = JSON.parse(data)
|
|
76
|
+
|
|
77
|
+
// Remove metadata for license lookup
|
|
78
|
+
const { _metadata, ...licenses } = parsed
|
|
79
|
+
return licenses
|
|
80
|
+
}
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error('Error loading legitimate license database:', error.message)
|
|
83
|
+
}
|
|
84
|
+
return {}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Compute SHA256 hash for integrity checks
|
|
89
|
+
*/
|
|
90
|
+
computeSha256(json) {
|
|
91
|
+
return crypto.createHash('sha256').update(json).digest('hex')
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Fetch latest legitimate licenses from server (if available)
|
|
96
|
+
*/
|
|
97
|
+
async fetchLegitimateDatabase() {
|
|
98
|
+
try {
|
|
99
|
+
this.ensureLicenseDir()
|
|
100
|
+
console.log(
|
|
101
|
+
`🔄 Fetching latest license database from ${this.licenseDbUrl} ...`
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
const controller = new AbortController()
|
|
105
|
+
const timeout = setTimeout(() => controller.abort(), 10000)
|
|
106
|
+
const response = await fetch(this.licenseDbUrl, {
|
|
107
|
+
signal: controller.signal,
|
|
108
|
+
headers: { 'User-Agent': 'create-qa-architect-cli' },
|
|
109
|
+
})
|
|
110
|
+
clearTimeout(timeout)
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error(`HTTP ${response.status}`)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const database = await response.json()
|
|
117
|
+
|
|
118
|
+
if (!database || typeof database !== 'object' || !database._metadata) {
|
|
119
|
+
throw new Error('invalid database format')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Mandatory integrity verification
|
|
123
|
+
if (!database._metadata.sha256) {
|
|
124
|
+
throw new Error('license database missing sha256 checksum')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const { _metadata, ...licenses } = database
|
|
128
|
+
const computed = this.computeSha256(JSON.stringify(licenses))
|
|
129
|
+
if (computed !== database._metadata.sha256) {
|
|
130
|
+
throw new Error('license database integrity check failed')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cache locally for offline use
|
|
134
|
+
fs.writeFileSync(this.legitimateDBFile, JSON.stringify(database, null, 2))
|
|
135
|
+
console.log('✅ License database updated and cached')
|
|
136
|
+
|
|
137
|
+
return licenses
|
|
138
|
+
} catch (error) {
|
|
139
|
+
const isTest = process.argv.join(' ').includes('test')
|
|
140
|
+
const prefix = isTest ? '📋 TEST SCENARIO:' : '⚠️'
|
|
141
|
+
console.log(`${prefix} Database fetch failed: ${error.message}`)
|
|
142
|
+
return this.loadLegitimateDatabase()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Validate license key (fetches latest database, then validates locally)
|
|
148
|
+
*/
|
|
149
|
+
async validateLicense(licenseKey, userEmail) {
|
|
150
|
+
try {
|
|
151
|
+
// Check if already activated locally
|
|
152
|
+
const localLicense = this.getLocalLicense()
|
|
153
|
+
if (
|
|
154
|
+
localLicense &&
|
|
155
|
+
localLicense.licenseKey === licenseKey &&
|
|
156
|
+
localLicense.valid
|
|
157
|
+
) {
|
|
158
|
+
return {
|
|
159
|
+
valid: true,
|
|
160
|
+
tier: localLicense.tier,
|
|
161
|
+
isFounder: localLicense.isFounder || false,
|
|
162
|
+
email: localLicense.email,
|
|
163
|
+
source: 'local_file',
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Fetch latest legitimate database from server
|
|
168
|
+
const legitimateDB = await this.fetchLegitimateDatabase()
|
|
169
|
+
|
|
170
|
+
// If database is empty (no licenses), fail with actionable message
|
|
171
|
+
const licenseInfo = legitimateDB[licenseKey]
|
|
172
|
+
const hasLicenses = Object.keys(legitimateDB || {}).length > 0
|
|
173
|
+
|
|
174
|
+
if (!hasLicenses) {
|
|
175
|
+
return {
|
|
176
|
+
valid: false,
|
|
177
|
+
error:
|
|
178
|
+
'License registry is empty. Please connect to the internet and retry, or ask support to populate your license.',
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!licenseInfo) {
|
|
183
|
+
return {
|
|
184
|
+
valid: false,
|
|
185
|
+
error:
|
|
186
|
+
'License key not found. Verify the key and email, or contact support if this was a purchase.',
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Verify email matches (if specified in database)
|
|
191
|
+
if (
|
|
192
|
+
licenseInfo.email &&
|
|
193
|
+
licenseInfo.email.toLowerCase() !== userEmail.toLowerCase()
|
|
194
|
+
) {
|
|
195
|
+
return {
|
|
196
|
+
valid: false,
|
|
197
|
+
error:
|
|
198
|
+
'Email address does not match the license registration. Please use the email associated with your purchase.',
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// License is valid
|
|
203
|
+
console.log(
|
|
204
|
+
`✅ License validated: ${licenseInfo.tier} ${licenseInfo.isFounder ? '(Founder)' : ''}`
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
valid: true,
|
|
209
|
+
tier: licenseInfo.tier,
|
|
210
|
+
isFounder: licenseInfo.isFounder || false,
|
|
211
|
+
customerId: licenseInfo.customerId,
|
|
212
|
+
email: userEmail,
|
|
213
|
+
source: 'legitimate_database',
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
console.error('License validation error:', error.message)
|
|
217
|
+
return {
|
|
218
|
+
valid: false,
|
|
219
|
+
error:
|
|
220
|
+
'License validation failed due to an internal error. Please try again or contact support.',
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Get local license file if it exists
|
|
227
|
+
*/
|
|
228
|
+
getLocalLicense() {
|
|
229
|
+
if (fs.existsSync(this.licenseFile)) {
|
|
230
|
+
// Let JSON parse errors propagate to caller for proper error handling
|
|
231
|
+
const license = JSON.parse(fs.readFileSync(this.licenseFile, 'utf8'))
|
|
232
|
+
|
|
233
|
+
// Check signature if present
|
|
234
|
+
if (license.payload && license.signature) {
|
|
235
|
+
const isValid = this.verifySignature(license.payload, license.signature)
|
|
236
|
+
return { ...license, valid: isValid }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Legacy format
|
|
240
|
+
return {
|
|
241
|
+
...license,
|
|
242
|
+
valid: true,
|
|
243
|
+
tier: license.tier,
|
|
244
|
+
licenseKey: license.licenseKey || license.key,
|
|
245
|
+
email: license.email,
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return null
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Save license locally after successful validation
|
|
253
|
+
*/
|
|
254
|
+
saveLicense(licenseData) {
|
|
255
|
+
try {
|
|
256
|
+
this.initialize()
|
|
257
|
+
|
|
258
|
+
const payload = {
|
|
259
|
+
customerId: licenseData.customerId,
|
|
260
|
+
tier: licenseData.tier,
|
|
261
|
+
isFounder: licenseData.isFounder,
|
|
262
|
+
email: licenseData.email,
|
|
263
|
+
issued: Date.now(),
|
|
264
|
+
version: '1.0',
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const signature = this.signPayload(payload)
|
|
268
|
+
|
|
269
|
+
const licenseRecord = {
|
|
270
|
+
licenseKey: licenseData.licenseKey,
|
|
271
|
+
tier: licenseData.tier,
|
|
272
|
+
isFounder: licenseData.isFounder,
|
|
273
|
+
email: licenseData.email,
|
|
274
|
+
activated: new Date().toISOString(),
|
|
275
|
+
payload,
|
|
276
|
+
signature,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fs.writeFileSync(this.licenseFile, JSON.stringify(licenseRecord, null, 2))
|
|
280
|
+
|
|
281
|
+
console.log('✅ License activated successfully!')
|
|
282
|
+
console.log(`📋 Tier: ${licenseData.tier}`)
|
|
283
|
+
console.log(`🎁 Founder: ${licenseData.isFounder ? 'Yes' : 'No'}`)
|
|
284
|
+
console.log(`📧 Email: ${licenseData.email}`)
|
|
285
|
+
|
|
286
|
+
return { success: true }
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Failed to save license:', error.message)
|
|
289
|
+
return { success: false, error: error.message }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Sign payload for validation
|
|
295
|
+
*/
|
|
296
|
+
signPayload(payload) {
|
|
297
|
+
const secret =
|
|
298
|
+
process.env.LICENSE_SIGNING_SECRET || 'cqa-dev-secret-change-in-prod'
|
|
299
|
+
return crypto
|
|
300
|
+
.createHmac('sha256', secret)
|
|
301
|
+
.update(JSON.stringify(payload))
|
|
302
|
+
.digest('hex')
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Verify signature
|
|
307
|
+
*/
|
|
308
|
+
verifySignature(payload, signature) {
|
|
309
|
+
const expectedSignature = this.signPayload(payload)
|
|
310
|
+
try {
|
|
311
|
+
return crypto.timingSafeEqual(
|
|
312
|
+
Buffer.from(signature),
|
|
313
|
+
Buffer.from(expectedSignature)
|
|
314
|
+
)
|
|
315
|
+
} catch {
|
|
316
|
+
return false
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Activate license (main user entry point)
|
|
322
|
+
*/
|
|
323
|
+
async activateLicense(licenseKey, userEmail) {
|
|
324
|
+
try {
|
|
325
|
+
// Validate license key format
|
|
326
|
+
if (
|
|
327
|
+
!licenseKey.match(
|
|
328
|
+
/^QAA-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/
|
|
329
|
+
)
|
|
330
|
+
) {
|
|
331
|
+
return {
|
|
332
|
+
success: false,
|
|
333
|
+
error:
|
|
334
|
+
'Invalid license key format. Expected format: QAA-XXXX-XXXX-XXXX-XXXX',
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Validate email format
|
|
339
|
+
if (!userEmail || !userEmail.includes('@')) {
|
|
340
|
+
return {
|
|
341
|
+
success: false,
|
|
342
|
+
error: 'Valid email address required for license activation',
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
console.log('🔍 Validating license key...')
|
|
347
|
+
|
|
348
|
+
// Validate against database
|
|
349
|
+
const validation = await this.validateLicense(licenseKey, userEmail)
|
|
350
|
+
|
|
351
|
+
if (!validation.valid) {
|
|
352
|
+
return {
|
|
353
|
+
success: false,
|
|
354
|
+
error: validation.error || 'License validation failed',
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Save locally
|
|
359
|
+
const saveResult = this.saveLicense({
|
|
360
|
+
licenseKey,
|
|
361
|
+
tier: validation.tier,
|
|
362
|
+
isFounder: validation.isFounder,
|
|
363
|
+
email: userEmail,
|
|
364
|
+
customerId: validation.customerId,
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
if (saveResult.success) {
|
|
368
|
+
return {
|
|
369
|
+
success: true,
|
|
370
|
+
tier: validation.tier,
|
|
371
|
+
isFounder: validation.isFounder,
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
return {
|
|
375
|
+
success: false,
|
|
376
|
+
error: 'License validation succeeded but failed to save locally',
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('License activation failed:', error.message)
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
error: `License activation failed: ${error.message}`,
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Remove license (for testing)
|
|
390
|
+
*/
|
|
391
|
+
removeLicense() {
|
|
392
|
+
try {
|
|
393
|
+
if (fs.existsSync(this.licenseFile)) {
|
|
394
|
+
fs.unlinkSync(this.licenseFile)
|
|
395
|
+
}
|
|
396
|
+
return { success: true }
|
|
397
|
+
} catch (error) {
|
|
398
|
+
return { success: false, error: error.message }
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
module.exports = { LicenseValidator }
|