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.
@@ -0,0 +1,495 @@
1
+ /**
2
+ * Quality Tools Generator
3
+ * Generates configuration for advanced quality tools:
4
+ * - Lighthouse CI (performance, accessibility, SEO, best practices)
5
+ * - Bundle size limits (size-limit)
6
+ * - axe-core accessibility testing
7
+ * - Conventional commits (commitlint)
8
+ * - Coverage thresholds
9
+ */
10
+
11
+ const fs = require('fs')
12
+ const path = require('path')
13
+
14
+ /**
15
+ * Generate Lighthouse CI configuration
16
+ * @param {Object} options - Configuration options
17
+ * @param {string} options.projectPath - Path to project
18
+ * @param {boolean} options.hasThresholds - Whether to include score thresholds (Pro)
19
+ * @param {string} options.collectUrl - URL to audit (defaults to http://localhost:3000)
20
+ * @returns {string} lighthouserc.js content
21
+ */
22
+ function generateLighthouseConfig(options = {}) {
23
+ const {
24
+ hasThresholds = false,
25
+ collectUrl = 'http://localhost:3000',
26
+ staticDistDir = null,
27
+ } = options
28
+
29
+ const collectConfig = staticDistDir
30
+ ? `staticDistDir: '${staticDistDir}',`
31
+ : `url: ['${collectUrl}'],
32
+ startServerCommand: 'npm run start',
33
+ startServerReadyPattern: 'ready|listening|started',
34
+ startServerReadyTimeout: 30000,`
35
+
36
+ const assertConfig = hasThresholds
37
+ ? `
38
+ assert: {
39
+ preset: 'lighthouse:recommended',
40
+ assertions: {
41
+ // Performance
42
+ 'first-contentful-paint': ['warn', { maxNumericValue: 2000 }],
43
+ 'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
44
+ 'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
45
+ 'total-blocking-time': ['warn', { maxNumericValue: 300 }],
46
+
47
+ // Categories (0-1 scale)
48
+ 'categories:performance': ['warn', { minScore: 0.8 }],
49
+ 'categories:accessibility': ['error', { minScore: 0.9 }],
50
+ 'categories:best-practices': ['warn', { minScore: 0.9 }],
51
+ 'categories:seo': ['warn', { minScore: 0.9 }],
52
+
53
+ // Allow some common warnings
54
+ 'unsized-images': 'off',
55
+ 'uses-responsive-images': 'off',
56
+ },
57
+ },`
58
+ : `
59
+ assert: {
60
+ // Basic assertions for Free tier - just warnings, no failures
61
+ assertions: {
62
+ 'categories:accessibility': ['warn', { minScore: 0.7 }],
63
+ 'categories:best-practices': ['warn', { minScore: 0.7 }],
64
+ },
65
+ },`
66
+
67
+ return `module.exports = {
68
+ ci: {
69
+ collect: {
70
+ ${collectConfig}
71
+ numberOfRuns: 3,
72
+ },
73
+ upload: {
74
+ target: 'temporary-public-storage',
75
+ },${assertConfig}
76
+ },
77
+ }
78
+ `
79
+ }
80
+
81
+ /**
82
+ * Generate size-limit configuration for bundle size limits
83
+ * @param {Object} options - Configuration options
84
+ * @param {string} options.projectPath - Path to project
85
+ * @returns {Array} size-limit config array
86
+ */
87
+ function generateSizeLimitConfig(options = {}) {
88
+ const { projectPath = process.cwd() } = options
89
+
90
+ // Detect build output paths
91
+ const possibleDists = ['dist', 'build', '.next', 'out', 'public']
92
+ let distDir = 'dist'
93
+
94
+ for (const dir of possibleDists) {
95
+ if (fs.existsSync(path.join(projectPath, dir))) {
96
+ distDir = dir
97
+ break
98
+ }
99
+ }
100
+
101
+ // Detect if it's a Next.js app
102
+ const isNextJS =
103
+ fs.existsSync(path.join(projectPath, 'next.config.js')) ||
104
+ fs.existsSync(path.join(projectPath, 'next.config.mjs'))
105
+
106
+ if (isNextJS) {
107
+ return [
108
+ {
109
+ path: '.next/static/**/*.js',
110
+ limit: '300 kB',
111
+ webpack: false,
112
+ },
113
+ ]
114
+ }
115
+
116
+ return [
117
+ {
118
+ path: `${distDir}/**/*.js`,
119
+ limit: '250 kB',
120
+ },
121
+ {
122
+ path: `${distDir}/**/*.css`,
123
+ limit: '50 kB',
124
+ },
125
+ ]
126
+ }
127
+
128
+ /**
129
+ * Generate commitlint configuration for conventional commits
130
+ * @returns {string} commitlint.config.js content
131
+ */
132
+ function generateCommitlintConfig() {
133
+ return `module.exports = {
134
+ extends: ['@commitlint/config-conventional'],
135
+ rules: {
136
+ // Type must be one of the conventional types
137
+ 'type-enum': [
138
+ 2,
139
+ 'always',
140
+ [
141
+ 'feat', // New feature
142
+ 'fix', // Bug fix
143
+ 'docs', // Documentation only
144
+ 'style', // Formatting, no code change
145
+ 'refactor', // Code change that neither fixes a bug nor adds a feature
146
+ 'perf', // Performance improvement
147
+ 'test', // Adding tests
148
+ 'build', // Build system or external dependencies
149
+ 'ci', // CI configuration
150
+ 'chore', // Maintenance tasks
151
+ 'revert', // Revert a previous commit
152
+ ],
153
+ ],
154
+ // Subject line max length
155
+ 'subject-max-length': [2, 'always', 100],
156
+ // Subject must not be empty
157
+ 'subject-empty': [2, 'never'],
158
+ // Type must not be empty
159
+ 'type-empty': [2, 'never'],
160
+ },
161
+ }
162
+ `
163
+ }
164
+
165
+ /**
166
+ * Generate commit-msg hook content for commitlint
167
+ * @returns {string} commit-msg hook content
168
+ */
169
+ function generateCommitMsgHook() {
170
+ return `#!/bin/sh
171
+ npx --no -- commitlint --edit "$1"
172
+ `
173
+ }
174
+
175
+ /**
176
+ * Generate vitest coverage thresholds configuration
177
+ * @param {Object} options - Configuration options
178
+ * @param {number} options.lines - Line coverage threshold (default: 70)
179
+ * @param {number} options.functions - Function coverage threshold (default: 70)
180
+ * @param {number} options.branches - Branch coverage threshold (default: 60)
181
+ * @param {number} options.statements - Statement coverage threshold (default: 70)
182
+ * @returns {Object} Coverage threshold config object
183
+ */
184
+ function generateCoverageThresholds(options = {}) {
185
+ const { lines = 70, functions = 70, branches = 60, statements = 70 } = options
186
+
187
+ return {
188
+ lines,
189
+ functions,
190
+ branches,
191
+ statements,
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Generate vitest.config.ts/js with coverage thresholds
197
+ * @param {Object} options - Configuration options
198
+ * @returns {string} vitest.config content
199
+ */
200
+ function generateVitestConfigWithCoverage(options = {}) {
201
+ const { typescript = false, thresholds = {} } = options
202
+ const coverageThresholds = generateCoverageThresholds(thresholds)
203
+
204
+ const importStatement = typescript
205
+ ? "import { defineConfig } from 'vitest/config'"
206
+ : "const { defineConfig } = require('vitest/config')"
207
+ const exportStatement = typescript ? 'export default' : 'module.exports ='
208
+
209
+ return `${importStatement}
210
+
211
+ ${exportStatement} defineConfig({
212
+ test: {
213
+ globals: true,
214
+ environment: 'node',
215
+ include: ['tests/**/*.test.{js,ts}', 'src/**/*.test.{js,ts}'],
216
+ exclude: ['node_modules', 'dist', 'build'],
217
+ coverage: {
218
+ provider: 'v8',
219
+ reporter: ['text', 'json', 'html', 'lcov'],
220
+ exclude: [
221
+ 'node_modules/**',
222
+ 'tests/**',
223
+ '**/*.test.{js,ts}',
224
+ '**/*.config.{js,ts,mjs}',
225
+ 'coverage/**',
226
+ 'dist/**',
227
+ 'build/**',
228
+ ],
229
+ thresholds: {
230
+ lines: ${coverageThresholds.lines},
231
+ functions: ${coverageThresholds.functions},
232
+ branches: ${coverageThresholds.branches},
233
+ statements: ${coverageThresholds.statements},
234
+ },
235
+ },
236
+ },
237
+ })
238
+ `
239
+ }
240
+
241
+ /**
242
+ * Generate axe-core accessibility test setup
243
+ * @returns {string} Accessibility test file content
244
+ */
245
+ function generateAxeTestSetup() {
246
+ return `/**
247
+ * Accessibility Testing with axe-core
248
+ *
249
+ * This file sets up automated accessibility testing using axe-core.
250
+ * It can be run as part of your test suite or CI pipeline.
251
+ *
252
+ * Usage:
253
+ * npm run test:a11y
254
+ *
255
+ * For manual testing, use the browser extension:
256
+ * https://www.deque.com/axe/browser-extensions/
257
+ */
258
+
259
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest'
260
+
261
+ // For component testing, use @axe-core/react or @axe-core/playwright
262
+ // This example uses puppeteer for full page testing
263
+
264
+ let browser
265
+
266
+ describe('Accessibility Tests', () => {
267
+ beforeAll(async () => {
268
+ // If using puppeteer for E2E accessibility testing:
269
+ // const puppeteer = require('puppeteer')
270
+ // browser = await puppeteer.launch()
271
+ // const page = await browser.newPage()
272
+
273
+ // For now, this is a placeholder that passes
274
+ // Replace with actual implementation based on your framework
275
+ })
276
+
277
+ afterAll(async () => {
278
+ if (browser) {
279
+ await browser.close()
280
+ }
281
+ })
282
+
283
+ it.skip('should have no critical accessibility violations on homepage', async () => {
284
+ // Example with puppeteer + @axe-core/puppeteer:
285
+ // const { AxePuppeteer } = require('@axe-core/puppeteer')
286
+ // await page.goto('http://localhost:3000')
287
+ // const results = await new AxePuppeteer(page).analyze()
288
+ // expect(results.violations.filter(v => v.impact === 'critical')).toHaveLength(0)
289
+ expect(true).toBe(true)
290
+ })
291
+
292
+ it.skip('should have proper heading hierarchy', async () => {
293
+ // Implement heading hierarchy check
294
+ expect(true).toBe(true)
295
+ })
296
+
297
+ it.skip('should have sufficient color contrast', async () => {
298
+ // Implement color contrast check
299
+ expect(true).toBe(true)
300
+ })
301
+
302
+ it.skip('should have accessible form labels', async () => {
303
+ // Implement form label check
304
+ expect(true).toBe(true)
305
+ })
306
+ })
307
+
308
+ /**
309
+ * Helper function to run axe-core on a page
310
+ * @param {Object} page - Puppeteer/Playwright page object
311
+ * @returns {Promise<Object>} axe results
312
+ */
313
+ export async function runAxeOnPage(page) {
314
+ // Inject axe-core into the page
315
+ await page.addScriptTag({
316
+ path: require.resolve('axe-core'),
317
+ })
318
+
319
+ // Run axe
320
+ const results = await page.evaluate(async () => {
321
+ // eslint-disable-next-line no-undef
322
+ return await axe.run()
323
+ })
324
+
325
+ return results
326
+ }
327
+
328
+ /**
329
+ * Filter axe violations by impact level
330
+ * @param {Array} violations - axe violations array
331
+ * @param {Array} levels - Impact levels to include ['critical', 'serious', 'moderate', 'minor']
332
+ * @returns {Array} Filtered violations
333
+ */
334
+ export function filterByImpact(violations, levels = ['critical', 'serious']) {
335
+ return violations.filter(v => levels.includes(v.impact))
336
+ }
337
+ `
338
+ }
339
+
340
+ /**
341
+ * Write Lighthouse CI config to project
342
+ * @param {string} projectPath - Path to project
343
+ * @param {Object} options - Options for config generation
344
+ */
345
+ function writeLighthouseConfig(projectPath, options = {}) {
346
+ const configPath = path.join(projectPath, 'lighthouserc.js')
347
+ const config = generateLighthouseConfig(options)
348
+ fs.writeFileSync(configPath, config)
349
+ return configPath
350
+ }
351
+
352
+ /**
353
+ * Write size-limit config to package.json
354
+ * @param {string} projectPath - Path to project
355
+ * @param {Object} options - Options for config generation
356
+ */
357
+ function writeSizeLimitConfig(projectPath, options = {}) {
358
+ const packageJsonPath = path.join(projectPath, 'package.json')
359
+ if (!fs.existsSync(packageJsonPath)) {
360
+ throw new Error('package.json not found')
361
+ }
362
+
363
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
364
+ packageJson['size-limit'] = generateSizeLimitConfig({
365
+ projectPath,
366
+ ...options,
367
+ })
368
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n')
369
+ return packageJsonPath
370
+ }
371
+
372
+ /**
373
+ * Write commitlint config to project
374
+ * @param {string} projectPath - Path to project
375
+ */
376
+ function writeCommitlintConfig(projectPath) {
377
+ const configPath = path.join(projectPath, 'commitlint.config.js')
378
+ const config = generateCommitlintConfig()
379
+ fs.writeFileSync(configPath, config)
380
+ return configPath
381
+ }
382
+
383
+ /**
384
+ * Write commit-msg hook for commitlint
385
+ * @param {string} projectPath - Path to project
386
+ */
387
+ function writeCommitMsgHook(projectPath) {
388
+ const huskyDir = path.join(projectPath, '.husky')
389
+ if (!fs.existsSync(huskyDir)) {
390
+ fs.mkdirSync(huskyDir, { recursive: true })
391
+ }
392
+
393
+ const hookPath = path.join(huskyDir, 'commit-msg')
394
+ const hook = generateCommitMsgHook()
395
+ fs.writeFileSync(hookPath, hook)
396
+ fs.chmodSync(hookPath, 0o755)
397
+ return hookPath
398
+ }
399
+
400
+ /**
401
+ * Write axe test setup to project
402
+ * @param {string} projectPath - Path to project
403
+ * @param {Object} options - Options for test generation
404
+ */
405
+ function writeAxeTestSetup(projectPath, options = {}) {
406
+ const testsDir = path.join(projectPath, 'tests')
407
+ if (!fs.existsSync(testsDir)) {
408
+ fs.mkdirSync(testsDir, { recursive: true })
409
+ }
410
+
411
+ const testPath = path.join(testsDir, 'accessibility.test.js')
412
+ const content = generateAxeTestSetup(options)
413
+ fs.writeFileSync(testPath, content)
414
+ return testPath
415
+ }
416
+
417
+ /**
418
+ * Get dev dependencies for quality tools
419
+ * @param {Object} features - Which features to include
420
+ * @returns {Object} Dev dependencies object
421
+ */
422
+ function getQualityToolsDependencies(features = {}) {
423
+ const deps = {}
424
+
425
+ if (features.lighthouse) {
426
+ deps['@lhci/cli'] = '^0.14.0'
427
+ }
428
+
429
+ if (features.sizeLimit) {
430
+ deps['size-limit'] = '^11.0.0'
431
+ deps['@size-limit/file'] = '^11.0.0'
432
+ }
433
+
434
+ if (features.commitlint) {
435
+ deps['@commitlint/cli'] = '^19.0.0'
436
+ deps['@commitlint/config-conventional'] = '^19.0.0'
437
+ }
438
+
439
+ if (features.axeCore) {
440
+ deps['axe-core'] = '^4.10.0'
441
+ }
442
+
443
+ return deps
444
+ }
445
+
446
+ /**
447
+ * Get npm scripts for quality tools
448
+ * @param {Object} features - Which features to include
449
+ * @returns {Object} Scripts object
450
+ */
451
+ function getQualityToolsScripts(features = {}) {
452
+ const scripts = {}
453
+
454
+ if (features.lighthouse) {
455
+ scripts['lighthouse:ci'] = 'lhci autorun'
456
+ scripts['lighthouse:upload'] = 'lhci upload'
457
+ }
458
+
459
+ if (features.sizeLimit) {
460
+ scripts['size'] = 'size-limit'
461
+ scripts['size:why'] = 'size-limit --why'
462
+ }
463
+
464
+ if (features.axeCore) {
465
+ scripts['test:a11y'] = 'vitest run tests/accessibility.test.js'
466
+ }
467
+
468
+ if (features.coverage) {
469
+ scripts['test:coverage'] = 'vitest run --coverage'
470
+ scripts['test:coverage:check'] =
471
+ 'vitest run --coverage --coverage.thresholds.lines=70 --coverage.thresholds.functions=70'
472
+ }
473
+
474
+ return scripts
475
+ }
476
+
477
+ module.exports = {
478
+ // Config generators
479
+ generateLighthouseConfig,
480
+ generateSizeLimitConfig,
481
+ generateCommitlintConfig,
482
+ generateCommitMsgHook,
483
+ generateCoverageThresholds,
484
+ generateVitestConfigWithCoverage,
485
+ generateAxeTestSetup,
486
+ // Writers
487
+ writeLighthouseConfig,
488
+ writeSizeLimitConfig,
489
+ writeCommitlintConfig,
490
+ writeCommitMsgHook,
491
+ writeAxeTestSetup,
492
+ // Helpers
493
+ getQualityToolsDependencies,
494
+ getQualityToolsScripts,
495
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * DR25 fix: Standard result type helpers for consistent API across modules
3
+ *
4
+ * This module provides standard result builders to ensure consistency across
5
+ * all modules in the codebase. Use these instead of ad-hoc result objects.
6
+ *
7
+ * STANDARD PATTERNS:
8
+ *
9
+ * 1. Success/failure operations (file I/O, validation, etc.):
10
+ * { success: true, data?: any }
11
+ * { success: false, error: string, details?: any }
12
+ *
13
+ * 2. Validation results:
14
+ * { valid: true, data?: any }
15
+ * { valid: false, error: string, details?: any }
16
+ *
17
+ * 3. Query results:
18
+ * - Found: return data directly or { found: true, data }
19
+ * - Not found: return null or { found: false }
20
+ *
21
+ * MIGRATION STATUS:
22
+ * - lib/licensing.js: Uses mixed patterns (being migrated)
23
+ * - lib/license-validator.js: Uses valid/error pattern
24
+ * - webhook-handler.js: Uses success/error pattern
25
+ * - lib/error-reporter.js: Uses success/error pattern
26
+ * - lib/template-loader.js: Uses success/errors array pattern
27
+ *
28
+ * @module result-types
29
+ */
30
+
31
+ 'use strict'
32
+
33
+ /**
34
+ * Create a successful operation result
35
+ * @param {any} data - Optional data to include
36
+ * @returns {{ success: true, data?: any }}
37
+ */
38
+ function success(data = null) {
39
+ const result = { success: true }
40
+ if (data !== null && data !== undefined) {
41
+ result.data = data
42
+ }
43
+ return result
44
+ }
45
+
46
+ /**
47
+ * Create a failed operation result
48
+ * @param {string} error - Error message
49
+ * @param {any} details - Optional error details
50
+ * @returns {{ success: false, error: string, details?: any }}
51
+ */
52
+ function failure(error, details = null) {
53
+ const result = { success: false, error }
54
+ if (details !== null && details !== undefined) {
55
+ result.details = details
56
+ }
57
+ return result
58
+ }
59
+
60
+ /**
61
+ * Create a successful validation result
62
+ * @param {any} data - Optional validated data
63
+ * @returns {{ valid: true, data?: any }}
64
+ */
65
+ function valid(data = null) {
66
+ const result = { valid: true }
67
+ if (data !== null && data !== undefined) {
68
+ result.data = data
69
+ }
70
+ return result
71
+ }
72
+
73
+ /**
74
+ * Create a failed validation result
75
+ * @param {string} error - Validation error message
76
+ * @param {any} details - Optional error details
77
+ * @returns {{ valid: false, error: string, details?: any }}
78
+ */
79
+ function invalid(error, details = null) {
80
+ const result = { valid: false, error }
81
+ if (details !== null && details !== undefined) {
82
+ result.details = details
83
+ }
84
+ return result
85
+ }
86
+
87
+ /**
88
+ * Check if a result indicates success (works with both patterns)
89
+ * @param {object} result - Result object to check
90
+ * @returns {boolean} True if successful or valid
91
+ */
92
+ function isSuccess(result) {
93
+ return result && (result.success === true || result.valid === true)
94
+ }
95
+
96
+ /**
97
+ * Check if a result indicates failure (works with both patterns)
98
+ * @param {object} result - Result object to check
99
+ * @returns {boolean} True if failed or invalid
100
+ */
101
+ function isFailure(result) {
102
+ return result && (result.success === false || result.valid === false)
103
+ }
104
+
105
+ module.exports = {
106
+ success,
107
+ failure,
108
+ valid,
109
+ invalid,
110
+ isSuccess,
111
+ isFailure,
112
+ }
@@ -10,7 +10,7 @@ const path = require('path')
10
10
  * Generate enhanced security configuration
11
11
  * Makes security scanning the default, not optional
12
12
  */
13
- function generateSecurityFirstConfig(_projectPath = '.') {
13
+ function generateSecurityFirstConfig() {
14
14
  const securityConfig = {
15
15
  // Secret scanning configuration
16
16
  gitleaks: {
@@ -154,17 +154,38 @@ const PROJECT_CONFIGS = {
154
154
 
155
155
  /**
156
156
  * Read package.json from project path
157
+ * DR22 fix: Differentiate between missing file vs. read/parse errors
157
158
  */
158
159
  function readPackageJson(projectPath) {
160
+ const pkgPath = path.join(projectPath, 'package.json')
161
+
162
+ // File doesn't exist - expected for non-JS projects
163
+ if (!fs.existsSync(pkgPath)) {
164
+ return null
165
+ }
166
+
159
167
  try {
160
- const pkgPath = path.join(projectPath, 'package.json')
161
- if (fs.existsSync(pkgPath)) {
162
- return JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
168
+ const content = fs.readFileSync(pkgPath, 'utf8')
169
+ return JSON.parse(content)
170
+ } catch (error) {
171
+ // DR22 fix: Provide specific error messages for different failure types
172
+ if (error instanceof SyntaxError) {
173
+ console.error(`❌ package.json has invalid JSON syntax: ${error.message}`)
174
+ console.error(` File: ${pkgPath}`)
175
+ console.error(' Please fix JSON syntax errors before continuing')
176
+ } else if (error.code === 'EACCES') {
177
+ console.error(`❌ Permission denied reading package.json: ${pkgPath}`)
178
+ console.error(' Check file permissions and try again')
179
+ } else {
180
+ console.error(
181
+ `❌ Unexpected error reading package.json: ${error.message}`
182
+ )
183
+ if (process.env.DEBUG) {
184
+ console.error(error.stack)
185
+ }
163
186
  }
164
- } catch {
165
- // Ignore errors
187
+ return null
166
188
  }
167
- return null
168
189
  }
169
190
 
170
191
  /**
@@ -321,9 +342,7 @@ fi
321
342
  /**
322
343
  * Add test tier scripts to package.json
323
344
  */
324
- function getTestTierScripts(_projectType) {
325
- // const config = PROJECT_CONFIGS[projectType] || PROJECT_CONFIGS.default
326
-
345
+ function getTestTierScripts() {
327
346
  return {
328
347
  'test:fast': 'vitest run --reporter=basic --coverage=false',
329
348
  'test:medium':