create-qa-architect 5.0.7 → 5.4.3

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 (45) hide show
  1. package/.github/workflows/auto-release.yml +49 -0
  2. package/.github/workflows/quality.yml +11 -11
  3. package/.github/workflows/shell-ci.yml.example +82 -0
  4. package/.github/workflows/shell-quality.yml.example +148 -0
  5. package/README.md +165 -12
  6. package/config/shell-ci.yml +82 -0
  7. package/config/shell-quality.yml +148 -0
  8. package/docs/ADOPTION-SUMMARY.md +41 -0
  9. package/docs/ARCHITECTURE-REVIEW.md +67 -0
  10. package/docs/ARCHITECTURE.md +29 -45
  11. package/docs/CI-COST-ANALYSIS.md +323 -0
  12. package/docs/CODE-REVIEW.md +100 -0
  13. package/docs/REQUIREMENTS.md +148 -0
  14. package/docs/SECURITY-AUDIT.md +68 -0
  15. package/docs/test-trace-matrix.md +28 -0
  16. package/eslint.config.cjs +2 -0
  17. package/lib/commands/analyze-ci.js +616 -0
  18. package/lib/commands/deps.js +293 -0
  19. package/lib/commands/index.js +29 -0
  20. package/lib/commands/validate.js +85 -0
  21. package/lib/config-validator.js +28 -45
  22. package/lib/error-reporter.js +14 -2
  23. package/lib/github-api.js +138 -13
  24. package/lib/license-signing.js +125 -0
  25. package/lib/license-validator.js +359 -71
  26. package/lib/licensing.js +434 -106
  27. package/lib/package-utils.js +9 -9
  28. package/lib/prelaunch-validator.js +828 -0
  29. package/lib/project-maturity.js +58 -6
  30. package/lib/quality-tools-generator.js +495 -0
  31. package/lib/result-types.js +112 -0
  32. package/lib/security-enhancements.js +1 -1
  33. package/lib/smart-strategy-generator.js +46 -10
  34. package/lib/telemetry.js +1 -1
  35. package/lib/template-loader.js +52 -19
  36. package/lib/ui-helpers.js +1 -1
  37. package/lib/validation/cache-manager.js +36 -6
  38. package/lib/validation/config-security.js +100 -33
  39. package/lib/validation/index.js +68 -97
  40. package/lib/validation/workflow-validation.js +28 -7
  41. package/package.json +4 -6
  42. package/scripts/check-test-coverage.sh +46 -0
  43. package/scripts/validate-claude-md.js +80 -0
  44. package/setup.js +923 -301
  45. package/create-saas-monetization.js +0 -1513
@@ -157,6 +157,9 @@ class ProjectMaturityDetector {
157
157
  hasTests: this.hasTests(),
158
158
  hasDependencies: this.hasDependencies(),
159
159
  hasCssFiles: this.hasCssFiles(),
160
+ hasShellScripts: this.hasShellScripts(),
161
+ shellScriptCount: this.countShellScripts(),
162
+ isShellProject: this.isShellProject(),
160
163
  packageJsonExists: this.packageJsonExists(),
161
164
  }
162
165
  }
@@ -266,7 +269,7 @@ class ProjectMaturityDetector {
266
269
  const deps = packageJson.dependencies || {}
267
270
  const devDeps = packageJson.devDependencies || {}
268
271
  return Object.keys(deps).length > 0 || Object.keys(devDeps).length > 0
269
- } catch {
272
+ } catch (_error) {
270
273
  return false
271
274
  }
272
275
  }
@@ -287,6 +290,36 @@ class ProjectMaturityDetector {
287
290
  )
288
291
  }
289
292
 
293
+ /**
294
+ * Check if project has shell scripts
295
+ * @returns {boolean} True if shell scripts exist
296
+ */
297
+ hasShellScripts() {
298
+ return this.countShellScripts() > 0
299
+ }
300
+
301
+ /**
302
+ * Count shell script files
303
+ * @returns {number} Number of shell script files
304
+ */
305
+ countShellScripts() {
306
+ const extensions = ['.sh', '.bash']
307
+
308
+ return this.countFilesRecursive(this.projectPath, {
309
+ extensions,
310
+ excludeDirs: EXCLUDE_DIRECTORIES.PROJECT_MATURITY,
311
+ maxDepth: 4,
312
+ })
313
+ }
314
+
315
+ /**
316
+ * Check if this is primarily a shell script project
317
+ * @returns {boolean} True if shell project (has .sh files, no package.json)
318
+ */
319
+ isShellProject() {
320
+ return this.hasShellScripts() && !this.packageJsonExists()
321
+ }
322
+
290
323
  /**
291
324
  * Check if package.json exists
292
325
  * @returns {boolean} True if package.json exists
@@ -405,9 +438,17 @@ class ProjectMaturityDetector {
405
438
  }
406
439
  }
407
440
  } catch (error) {
408
- // Silently ignore permission errors
409
- if (this.verbose) {
410
- console.warn(`Warning: Could not read directory ${dir}:`, error.message)
441
+ // Always log permission errors (affects maturity detection accuracy)
442
+ if (error.code === 'EACCES') {
443
+ console.warn(`⚠️ Permission denied: ${dir} (excluded from analysis)`)
444
+ } else if (error.code !== 'ENOENT') {
445
+ console.warn(
446
+ `⚠️ Could not read ${dir}: ${error.message} (${error.code})`
447
+ )
448
+ }
449
+
450
+ if (this.verbose && error.stack) {
451
+ console.warn(` Stack: ${error.stack}`)
411
452
  }
412
453
  }
413
454
 
@@ -435,7 +476,7 @@ class ProjectMaturityDetector {
435
476
 
436
477
  /**
437
478
  * Generate GitHub Actions outputs for maturity detection
438
- * @returns {{maturity: string, sourceCount: number, testCount: number, hasDeps: boolean, hasDocs: boolean, hasCss: boolean, requiredChecks: string, optionalChecks: string, disabledChecks: string}} GitHub Actions output format
479
+ * @returns {{maturity: string, sourceCount: number, testCount: number, hasDeps: boolean, hasDocs: boolean, hasCss: boolean, hasShell: boolean, shellCount: number, isShellProject: boolean, requiredChecks: string, optionalChecks: string, disabledChecks: string}} GitHub Actions output format
439
480
  */
440
481
  generateGitHubActionsOutput() {
441
482
  const maturity = this.detect()
@@ -449,6 +490,9 @@ class ProjectMaturityDetector {
449
490
  hasDeps: stats.hasDependencies,
450
491
  hasDocs: stats.hasDocumentation,
451
492
  hasCss: stats.hasCssFiles,
493
+ hasShell: stats.hasShellScripts,
494
+ shellCount: stats.shellScriptCount,
495
+ isShellProject: stats.isShellProject,
452
496
  requiredChecks: checks.required.join(','),
453
497
  optionalChecks: checks.optional.join(','),
454
498
  disabledChecks: checks.disabled.join(','),
@@ -472,7 +516,12 @@ class ProjectMaturityDetector {
472
516
  console.log(` • Test files: ${stats.testFiles}`)
473
517
  console.log(` • Documentation: ${stats.hasDocumentation ? 'Yes' : 'No'}`)
474
518
  console.log(` • Dependencies: ${stats.hasDependencies ? 'Yes' : 'No'}`)
475
- console.log(` • CSS files: ${stats.hasCssFiles ? 'Yes' : 'No'}\n`)
519
+ console.log(` • CSS files: ${stats.hasCssFiles ? 'Yes' : 'No'}`)
520
+ console.log(` • Shell scripts: ${stats.shellScriptCount}`)
521
+ if (stats.isShellProject) {
522
+ console.log(` • Project type: Shell script project`)
523
+ }
524
+ console.log()
476
525
 
477
526
  console.log('Quality Checks:')
478
527
  console.log(` ✅ Required: ${level.checks.required.join(', ')}`)
@@ -504,6 +553,9 @@ if (require.main === module) {
504
553
  console.log(`has-deps=${output.hasDeps}`)
505
554
  console.log(`has-docs=${output.hasDocs}`)
506
555
  console.log(`has-css=${output.hasCss}`)
556
+ console.log(`has-shell=${output.hasShell}`)
557
+ console.log(`shell-count=${output.shellCount}`)
558
+ console.log(`is-shell-project=${output.isShellProject}`)
507
559
  console.log(`required-checks=${output.requiredChecks}`)
508
560
  console.log(`optional-checks=${output.optionalChecks}`)
509
561
  console.log(`disabled-checks=${output.disabledChecks}`)
@@ -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
+ }