create-qa-architect 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/.editorconfig +12 -0
  2. package/.github/CLAUDE_MD_AUTOMATION.md +248 -0
  3. package/.github/PROGRESSIVE_QUALITY_IMPLEMENTATION.md +408 -0
  4. package/.github/PROGRESSIVE_QUALITY_PROPOSAL.md +443 -0
  5. package/.github/RELEASE_CHECKLIST.md +100 -0
  6. package/.github/dependabot.yml +50 -0
  7. package/.github/git-sync.sh +48 -0
  8. package/.github/workflows/claude-md-validation.yml +82 -0
  9. package/.github/workflows/nightly-gitleaks-verification.yml +176 -0
  10. package/.github/workflows/pnpm-ci.yml.example +53 -0
  11. package/.github/workflows/python-ci.yml.example +69 -0
  12. package/.github/workflows/quality-legacy.yml.backup +165 -0
  13. package/.github/workflows/quality-progressive.yml.example +291 -0
  14. package/.github/workflows/quality.yml +436 -0
  15. package/.github/workflows/release.yml +53 -0
  16. package/.nvmrc +1 -0
  17. package/.prettierignore +14 -0
  18. package/.prettierrc +9 -0
  19. package/.stylelintrc.json +5 -0
  20. package/README.md +212 -0
  21. package/config/.lighthouserc.js +45 -0
  22. package/config/.pre-commit-config.yaml +66 -0
  23. package/config/constants.js +128 -0
  24. package/config/defaults.js +124 -0
  25. package/config/pyproject.toml +124 -0
  26. package/config/quality-config.schema.json +97 -0
  27. package/config/quality-python.yml +89 -0
  28. package/config/requirements-dev.txt +15 -0
  29. package/create-saas-monetization.js +1465 -0
  30. package/eslint.config.cjs +117 -0
  31. package/eslint.config.ts.cjs +99 -0
  32. package/legal/README.md +106 -0
  33. package/legal/copyright.md +76 -0
  34. package/legal/disclaimer.md +146 -0
  35. package/legal/privacy-policy.html +324 -0
  36. package/legal/privacy-policy.md +196 -0
  37. package/legal/terms-of-service.md +224 -0
  38. package/lib/billing-dashboard.html +645 -0
  39. package/lib/config-validator.js +163 -0
  40. package/lib/dependency-monitoring-basic.js +185 -0
  41. package/lib/dependency-monitoring-premium.js +1490 -0
  42. package/lib/error-reporter.js +444 -0
  43. package/lib/interactive/prompt.js +128 -0
  44. package/lib/interactive/questions.js +146 -0
  45. package/lib/license-validator.js +403 -0
  46. package/lib/licensing.js +989 -0
  47. package/lib/package-utils.js +187 -0
  48. package/lib/project-maturity.js +516 -0
  49. package/lib/security-enhancements.js +340 -0
  50. package/lib/setup-enhancements.js +317 -0
  51. package/lib/smart-strategy-generator.js +344 -0
  52. package/lib/telemetry.js +323 -0
  53. package/lib/template-loader.js +252 -0
  54. package/lib/typescript-config-generator.js +210 -0
  55. package/lib/ui-helpers.js +74 -0
  56. package/lib/validation/base-validator.js +174 -0
  57. package/lib/validation/cache-manager.js +158 -0
  58. package/lib/validation/config-security.js +741 -0
  59. package/lib/validation/documentation.js +326 -0
  60. package/lib/validation/index.js +186 -0
  61. package/lib/validation/validation-factory.js +153 -0
  62. package/lib/validation/workflow-validation.js +172 -0
  63. package/lib/yaml-utils.js +120 -0
  64. package/marketing/beta-user-email-campaign.md +372 -0
  65. package/marketing/landing-page.html +721 -0
  66. package/package.json +165 -0
  67. package/setup.js +2076 -0
@@ -0,0 +1,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 }