create-qa-architect 5.0.6 → 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/.github/workflows/dependabot-auto-merge.yml +32 -0
- package/LICENSE +3 -3
- package/README.md +54 -15
- package/docs/ADOPTION-SUMMARY.md +41 -0
- package/docs/ARCHITECTURE-REVIEW.md +67 -0
- package/docs/ARCHITECTURE.md +29 -41
- package/docs/CODE-REVIEW.md +100 -0
- package/docs/PREFLIGHT_REPORT.md +32 -40
- package/docs/REQUIREMENTS.md +148 -0
- package/docs/SECURITY-AUDIT.md +68 -0
- package/docs/TESTING.md +3 -4
- package/docs/test-trace-matrix.md +28 -0
- package/lib/billing-dashboard.html +6 -12
- 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 +343 -111
- 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 +82 -15
- package/lib/validation/workflow-validation.js +49 -23
- package/package.json +8 -10
- package/scripts/check-test-coverage.sh +46 -0
- package/setup.js +356 -285
- package/templates/QUALITY_TROUBLESHOOTING.md +32 -33
- package/templates/scripts/smart-test-strategy.sh +1 -1
- package/create-saas-monetization.js +0 -1513
|
@@ -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(
|
|
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
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
|
|
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(
|
|
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':
|