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,252 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { EXCLUDE_DIRECTORIES } = require('../config/constants')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TemplateLoader - Load and merge custom template configurations
|
|
9
|
+
*
|
|
10
|
+
* Supports loading template files from a local directory to override default
|
|
11
|
+
* configurations. Enables organizations to maintain custom coding standards
|
|
12
|
+
* while still using the automated setup tooling.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const loader = new TemplateLoader()
|
|
16
|
+
* const templates = await loader.mergeTemplates('/path/to/custom', '/path/to/defaults')
|
|
17
|
+
*/
|
|
18
|
+
class TemplateLoader {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.verbose = options.verbose !== false
|
|
21
|
+
this.strict = options.strict === true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate that a template path is valid
|
|
26
|
+
* @param {string} templatePath - Path to template directory
|
|
27
|
+
* @returns {boolean} True if path is valid
|
|
28
|
+
*/
|
|
29
|
+
isValidTemplatePath(templatePath) {
|
|
30
|
+
if (!templatePath) {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const stats = fs.statSync(templatePath)
|
|
36
|
+
return stats.isDirectory()
|
|
37
|
+
} catch {
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Validate template directory structure
|
|
44
|
+
* Empty directories are valid (partial templates)
|
|
45
|
+
* @param {string} templateDir - Path to template directory
|
|
46
|
+
* @returns {Promise<boolean>} True if structure is valid
|
|
47
|
+
*/
|
|
48
|
+
async validateTemplateStructure(templateDir) {
|
|
49
|
+
try {
|
|
50
|
+
// Any directory is valid - even empty ones (partial templates are OK)
|
|
51
|
+
|
|
52
|
+
const stats = fs.statSync(templateDir)
|
|
53
|
+
return stats.isDirectory()
|
|
54
|
+
} catch {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Directories that should be skipped during template loading
|
|
61
|
+
* This prevents scanning node_modules and other unnecessary directories
|
|
62
|
+
*/
|
|
63
|
+
static SKIP_DIRECTORIES = new Set(EXCLUDE_DIRECTORIES.TEMPLATE_LOADING)
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Known template directories that should be scanned
|
|
67
|
+
* When loading from package directory, only these are scanned
|
|
68
|
+
*/
|
|
69
|
+
static TEMPLATE_DIRECTORIES = new Set(EXCLUDE_DIRECTORIES.TEMPLATE_WHITELIST)
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a directory should be skipped during scanning
|
|
73
|
+
* @param {string} dirName - Directory name
|
|
74
|
+
* @param {boolean} isPackageDir - Whether this is the package's own directory
|
|
75
|
+
* @returns {boolean} True if directory should be skipped
|
|
76
|
+
*/
|
|
77
|
+
shouldSkipDirectory(dirName, isPackageDir = false) {
|
|
78
|
+
// Always skip these directories
|
|
79
|
+
if (TemplateLoader.SKIP_DIRECTORIES.has(dirName)) {
|
|
80
|
+
return true
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// For package directory, only scan known template directories
|
|
84
|
+
if (isPackageDir && !TemplateLoader.TEMPLATE_DIRECTORIES.has(dirName)) {
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Recursively loads all template files from a directory tree
|
|
93
|
+
*
|
|
94
|
+
* Walks through a directory tree loading file contents while respecting
|
|
95
|
+
* security boundaries and skip rules. When loading from the package's own
|
|
96
|
+
* directory (isPackageDir=true), only scans allowed template directories
|
|
97
|
+
* to prevent loading arbitrary package files.
|
|
98
|
+
*
|
|
99
|
+
* Algorithm:
|
|
100
|
+
* 1. Read directory entries (files and subdirectories)
|
|
101
|
+
* 2. Skip excluded directories (node_modules, .git, etc.)
|
|
102
|
+
* 3. For files: load content and map by relative path
|
|
103
|
+
* 4. For subdirs: recursively load templates (depth-first)
|
|
104
|
+
* 5. Merge all templates into single flat map
|
|
105
|
+
* 6. Silently continue on errors (returns partial results)
|
|
106
|
+
*
|
|
107
|
+
* Security:
|
|
108
|
+
* - Package dir mode: Only loads from TEMPLATE_DIRECTORIES whitelist
|
|
109
|
+
* - Respects SKIP_DIRECTORIES blacklist (node_modules, .git, etc.)
|
|
110
|
+
* - Handles permission errors gracefully
|
|
111
|
+
*
|
|
112
|
+
* @param {string} dir - Directory to load from (absolute path)
|
|
113
|
+
* @param {string} [baseDir=dir] - Base directory for calculating relative paths
|
|
114
|
+
* @param {boolean} [isPackageDir=false] - True if scanning package's own directory
|
|
115
|
+
* (enables whitelist mode for security)
|
|
116
|
+
* @returns {Promise<Object<string, string>>} Map of relative file paths to contents
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* // Load custom templates from user directory
|
|
120
|
+
* const templates = await loader.loadTemplates('/user/templates', '/user/templates', false)
|
|
121
|
+
* // Returns: { 'config/.prettierrc': '...', '.github/workflows/ci.yml': '...' }
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* // Load from package directory (restricted mode)
|
|
125
|
+
* const templates = await loader.loadTemplates('/pkg/config', '/pkg', true)
|
|
126
|
+
* // Only loads from whitelisted dirs: config/, .github/, lib/
|
|
127
|
+
*/
|
|
128
|
+
async loadTemplates(dir, baseDir = dir, isPackageDir = false) {
|
|
129
|
+
const templates = {}
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
133
|
+
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
// Skip directories that should not be scanned
|
|
136
|
+
if (
|
|
137
|
+
entry.isDirectory() &&
|
|
138
|
+
this.shouldSkipDirectory(entry.name, isPackageDir)
|
|
139
|
+
) {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const fullPath = path.join(dir, entry.name)
|
|
144
|
+
const relativePath = path.relative(baseDir, fullPath)
|
|
145
|
+
|
|
146
|
+
if (entry.isDirectory()) {
|
|
147
|
+
// Recursively load from subdirectories
|
|
148
|
+
// After first level, we're no longer in package root
|
|
149
|
+
const subTemplates = await this.loadTemplates(
|
|
150
|
+
fullPath,
|
|
151
|
+
baseDir,
|
|
152
|
+
false
|
|
153
|
+
)
|
|
154
|
+
Object.assign(templates, subTemplates)
|
|
155
|
+
} else if (entry.isFile()) {
|
|
156
|
+
// Load file content asynchronously for better performance
|
|
157
|
+
const content = await fs.promises.readFile(fullPath, 'utf8')
|
|
158
|
+
templates[relativePath] = content
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (this.verbose) {
|
|
163
|
+
console.warn(
|
|
164
|
+
`⚠️ Warning: Could not load templates from ${dir}: ${error.message}`
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return templates
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Merge custom templates with defaults
|
|
174
|
+
* Custom templates override defaults, but defaults fill in gaps
|
|
175
|
+
* @param {string} customDir - Path to custom template directory
|
|
176
|
+
* @param {string} defaultsDir - Path to defaults directory (package __dirname)
|
|
177
|
+
* @returns {Promise<Object>} Merged template map
|
|
178
|
+
*/
|
|
179
|
+
async mergeTemplates(customDir, defaultsDir) {
|
|
180
|
+
const merged = {}
|
|
181
|
+
|
|
182
|
+
// Load defaults first (from package directory - restrict to known template dirs)
|
|
183
|
+
if (this.isValidTemplatePath(defaultsDir)) {
|
|
184
|
+
const defaults = await this.loadTemplates(defaultsDir, defaultsDir, true)
|
|
185
|
+
Object.assign(merged, defaults)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Load custom templates (overrides defaults - scan fully, it's user-controlled)
|
|
189
|
+
if (customDir) {
|
|
190
|
+
// In strict mode, custom template path MUST be valid if provided
|
|
191
|
+
if (!this.isValidTemplatePath(customDir)) {
|
|
192
|
+
const errorMsg = `Custom template directory not found or invalid: ${customDir}`
|
|
193
|
+
if (this.strict) {
|
|
194
|
+
throw new Error(errorMsg)
|
|
195
|
+
} else {
|
|
196
|
+
if (this.verbose) {
|
|
197
|
+
const isTest = process.argv.join(' ').includes('test')
|
|
198
|
+
const prefix = isTest ? '📋 TEST SCENARIO:' : '⚠️'
|
|
199
|
+
console.warn(`${prefix} ${errorMsg}`)
|
|
200
|
+
console.warn(' Falling back to default templates')
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
const isValid = await this.validateTemplateStructure(customDir)
|
|
205
|
+
|
|
206
|
+
if (!isValid) {
|
|
207
|
+
const errorMsg = 'Invalid template structure'
|
|
208
|
+
if (this.strict) {
|
|
209
|
+
throw new Error(errorMsg)
|
|
210
|
+
} else {
|
|
211
|
+
if (this.verbose) {
|
|
212
|
+
console.warn(`⚠️ ${errorMsg}, using defaults`)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
const custom = await this.loadTemplates(customDir, customDir, false)
|
|
217
|
+
Object.assign(merged, custom) // Custom templates override defaults
|
|
218
|
+
|
|
219
|
+
if (this.verbose && Object.keys(custom).length > 0) {
|
|
220
|
+
console.log(
|
|
221
|
+
`✅ Loaded ${Object.keys(custom).length} custom template file(s)`
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return merged
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the template file content or undefined if not found
|
|
233
|
+
* @param {Object} templates - Template map from loadTemplates/mergeTemplates
|
|
234
|
+
* @param {string} relativePath - Relative path to file
|
|
235
|
+
* @returns {string|undefined} File content or undefined
|
|
236
|
+
*/
|
|
237
|
+
getTemplate(templates, relativePath) {
|
|
238
|
+
return templates[relativePath]
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if a specific template file exists in the template map
|
|
243
|
+
* @param {Object} templates - Template map from loadTemplates/mergeTemplates
|
|
244
|
+
* @param {string} relativePath - Relative path to file
|
|
245
|
+
* @returns {boolean} True if template exists
|
|
246
|
+
*/
|
|
247
|
+
hasTemplate(templates, relativePath) {
|
|
248
|
+
return relativePath in templates
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
module.exports = { TemplateLoader }
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript Configuration Generator
|
|
3
|
+
* Fixes critical blind spot: tests excluded from TypeScript checking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs')
|
|
7
|
+
const path = require('path')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generate tests/tsconfig.json to ensure tests are TypeScript checked
|
|
11
|
+
* Critical fix for production TypeScript errors in test files
|
|
12
|
+
*/
|
|
13
|
+
function generateTestsTypeScriptConfig(projectPath = '.') {
|
|
14
|
+
const testsDir = path.join(projectPath, 'tests')
|
|
15
|
+
const testsTsConfigPath = path.join(testsDir, 'tsconfig.json')
|
|
16
|
+
|
|
17
|
+
// Create tests directory if it doesn't exist
|
|
18
|
+
if (!fs.existsSync(testsDir)) {
|
|
19
|
+
fs.mkdirSync(testsDir, { recursive: true })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Generate comprehensive tests TypeScript configuration
|
|
23
|
+
const testsTsConfig = {
|
|
24
|
+
extends: '../tsconfig.json',
|
|
25
|
+
compilerOptions: {
|
|
26
|
+
rootDir: '..',
|
|
27
|
+
noEmit: true,
|
|
28
|
+
types: ['vitest/globals', 'node', '@types/jest'],
|
|
29
|
+
},
|
|
30
|
+
include: ['../src/**/*', '../tests/**/*', '../*.ts', '../*.js'],
|
|
31
|
+
exclude: ['../node_modules', '../dist', '../build'],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Write tests TypeScript configuration
|
|
35
|
+
fs.writeFileSync(
|
|
36
|
+
testsTsConfigPath,
|
|
37
|
+
JSON.stringify(testsTsConfig, null, 2) + '\n'
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return testsTsConfigPath
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Add enhanced npm scripts for comprehensive TypeScript checking
|
|
45
|
+
* Includes both src and tests validation
|
|
46
|
+
*/
|
|
47
|
+
function getEnhancedTypeScriptScripts() {
|
|
48
|
+
return {
|
|
49
|
+
'type-check': 'tsc --noEmit',
|
|
50
|
+
'type-check:tests': 'tsc --noEmit --project tests/tsconfig.json',
|
|
51
|
+
'type-check:all': 'npm run type-check && npm run type-check:tests',
|
|
52
|
+
'quality:check': 'npm run type-check:all && npm run lint && npm run test',
|
|
53
|
+
'quality:ci': 'npm run quality:check && npm run security:audit',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generate enhanced lint-staged configuration
|
|
59
|
+
* Comprehensive quality checks instead of just CLAUDE.md
|
|
60
|
+
*/
|
|
61
|
+
function getEnhancedLintStaged(usesPython = false, hasTypeScript = false) {
|
|
62
|
+
const lintStaged = {
|
|
63
|
+
'package.json': ['prettier --write'],
|
|
64
|
+
'**/*.{json,md,yml,yaml}': ['prettier --write'],
|
|
65
|
+
'**/*.{js,jsx,mjs,cjs,html}': ['eslint --fix', 'prettier --write'],
|
|
66
|
+
'**/*.{css,scss,sass,less,pcss}': ['stylelint --fix', 'prettier --write'],
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add TypeScript checking to pre-commit if TypeScript detected
|
|
70
|
+
if (hasTypeScript) {
|
|
71
|
+
lintStaged['**/*.{ts,tsx}'] = [
|
|
72
|
+
'tsc --noEmit --skipLibCheck',
|
|
73
|
+
'eslint --fix',
|
|
74
|
+
'prettier --write',
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
// Add tests TypeScript checking
|
|
78
|
+
lintStaged['tests/**/*.{ts,tsx,js,jsx}'] = [
|
|
79
|
+
'tsc --noEmit --project tests/tsconfig.json',
|
|
80
|
+
'eslint --fix',
|
|
81
|
+
'prettier --write',
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add Python support if detected
|
|
86
|
+
if (usesPython) {
|
|
87
|
+
lintStaged['**/*.py'] = [
|
|
88
|
+
'black --check --diff',
|
|
89
|
+
'ruff check --fix',
|
|
90
|
+
'isort --check-only --diff',
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return lintStaged
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect project type for adaptive template selection
|
|
99
|
+
*/
|
|
100
|
+
function detectProjectType(projectPath = '.') {
|
|
101
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
102
|
+
|
|
103
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
104
|
+
return 'unknown'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
108
|
+
const deps = {
|
|
109
|
+
...packageJson.dependencies,
|
|
110
|
+
...packageJson.devDependencies,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// API Service Detection
|
|
114
|
+
if (deps.express || deps.fastify || deps.koa || deps['@nestjs/core']) {
|
|
115
|
+
return 'api-service'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Frontend App Detection
|
|
119
|
+
if (deps.react || deps.vue || deps.angular || deps.svelte) {
|
|
120
|
+
return 'frontend-app'
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Mobile App Detection
|
|
124
|
+
if (deps['react-native'] || deps['@ionic/react'] || deps['@capacitor/core']) {
|
|
125
|
+
return 'mobile-app'
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Library Detection
|
|
129
|
+
if (packageJson.main || packageJson.module || packageJson.exports) {
|
|
130
|
+
return 'library'
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// CLI Tool Detection
|
|
134
|
+
if (packageJson.bin) {
|
|
135
|
+
return 'cli-tool'
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return 'web-app'
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate project-specific quality configuration
|
|
143
|
+
*/
|
|
144
|
+
function getProjectQualityConfig(projectType) {
|
|
145
|
+
const configs = {
|
|
146
|
+
'api-service': {
|
|
147
|
+
qualityGates: {
|
|
148
|
+
typeCheck: { src: true, tests: true },
|
|
149
|
+
lint: { fix: false, failOnError: true },
|
|
150
|
+
test: { unit: true, integration: true },
|
|
151
|
+
security: { audit: true, secretScan: true },
|
|
152
|
+
},
|
|
153
|
+
testTypes: ['unit', 'integration', 'e2e'],
|
|
154
|
+
scripts: {
|
|
155
|
+
'test:integration': 'vitest run tests/integration/**/*.test.{js,ts}',
|
|
156
|
+
'test:e2e': 'vitest run tests/e2e/**/*.test.{js,ts}',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
'frontend-app': {
|
|
161
|
+
qualityGates: {
|
|
162
|
+
typeCheck: { src: true, tests: true },
|
|
163
|
+
lint: { fix: false, failOnError: true },
|
|
164
|
+
test: { unit: true, e2e: true },
|
|
165
|
+
accessibility: { check: true },
|
|
166
|
+
},
|
|
167
|
+
testTypes: ['unit', 'component', 'e2e'],
|
|
168
|
+
scripts: {
|
|
169
|
+
'test:component': 'vitest run tests/components/**/*.test.{js,ts,tsx}',
|
|
170
|
+
'test:e2e': 'playwright test',
|
|
171
|
+
'accessibility:check': 'axe-core tests/accessibility',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
'cli-tool': {
|
|
176
|
+
qualityGates: {
|
|
177
|
+
typeCheck: { src: true, tests: true },
|
|
178
|
+
lint: { fix: false, failOnError: true },
|
|
179
|
+
test: { unit: true, integration: true, commands: true },
|
|
180
|
+
security: { audit: true, secretScan: true },
|
|
181
|
+
},
|
|
182
|
+
testTypes: ['unit', 'integration', 'command'],
|
|
183
|
+
scripts: {
|
|
184
|
+
'test:commands': 'vitest run tests/commands/**/*.test.{js,ts}',
|
|
185
|
+
'test:integration': 'vitest run tests/integration/**/*.test.{js,ts}',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
configs[projectType] ||
|
|
192
|
+
configs['web-app'] || {
|
|
193
|
+
qualityGates: {
|
|
194
|
+
typeCheck: { src: true, tests: true },
|
|
195
|
+
lint: { fix: false, failOnError: true },
|
|
196
|
+
test: { unit: true },
|
|
197
|
+
},
|
|
198
|
+
testTypes: ['unit'],
|
|
199
|
+
scripts: {},
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
generateTestsTypeScriptConfig,
|
|
206
|
+
getEnhancedTypeScriptScripts,
|
|
207
|
+
getEnhancedLintStaged,
|
|
208
|
+
detectProjectType,
|
|
209
|
+
getProjectQualityConfig,
|
|
210
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UI Helpers for Progress Indicators and Accessibility
|
|
5
|
+
* Provides consistent messaging with ora spinners and fallback for CI/accessibility
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ACCESSIBILITY_MODE =
|
|
9
|
+
process.env.NO_EMOJI === 'true' || process.env.SCREEN_READER === 'true'
|
|
10
|
+
|
|
11
|
+
const icons = ACCESSIBILITY_MODE
|
|
12
|
+
? {
|
|
13
|
+
success: '[OK]',
|
|
14
|
+
error: '[ERROR]',
|
|
15
|
+
warning: '[WARN]',
|
|
16
|
+
info: '[INFO]',
|
|
17
|
+
working: '[...]',
|
|
18
|
+
}
|
|
19
|
+
: {
|
|
20
|
+
success: '✅',
|
|
21
|
+
error: '❌',
|
|
22
|
+
warning: '⚠️',
|
|
23
|
+
info: '💡',
|
|
24
|
+
working: '🔍',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Format a message with appropriate icon
|
|
29
|
+
* @param {string} type - Message type (success, error, warning, info, working)
|
|
30
|
+
* @param {string} message - Message text
|
|
31
|
+
* @returns {string} Formatted message
|
|
32
|
+
*/
|
|
33
|
+
function formatMessage(type, message) {
|
|
34
|
+
return `${icons[type]} ${message}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Show progress indicator with ora spinner or fallback
|
|
39
|
+
* @param {string} message - Progress message
|
|
40
|
+
* @returns {Object} Spinner-like object with succeed/fail/warn methods
|
|
41
|
+
*/
|
|
42
|
+
function showProgress(message) {
|
|
43
|
+
// In CI or non-TTY environments, use simple logging
|
|
44
|
+
if (process.env.CI || !process.stdout.isTTY) {
|
|
45
|
+
console.log(formatMessage('working', message))
|
|
46
|
+
return {
|
|
47
|
+
succeed: msg => console.log(formatMessage('success', msg)),
|
|
48
|
+
fail: msg => console.error(formatMessage('error', msg)),
|
|
49
|
+
warn: msg => console.warn(formatMessage('warning', msg)),
|
|
50
|
+
info: msg => console.log(formatMessage('info', msg)),
|
|
51
|
+
stop: () => {},
|
|
52
|
+
start: () => {},
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Try to use ora for interactive terminals
|
|
57
|
+
try {
|
|
58
|
+
const ora = require('ora')
|
|
59
|
+
return ora(message).start()
|
|
60
|
+
} catch {
|
|
61
|
+
// Fallback if ora not installed (graceful degradation)
|
|
62
|
+
console.log(formatMessage('working', message))
|
|
63
|
+
return {
|
|
64
|
+
succeed: msg => console.log(formatMessage('success', msg)),
|
|
65
|
+
fail: msg => console.error(formatMessage('error', msg)),
|
|
66
|
+
warn: msg => console.warn(formatMessage('warning', msg)),
|
|
67
|
+
info: msg => console.log(formatMessage('info', msg)),
|
|
68
|
+
stop: () => {},
|
|
69
|
+
start: () => {},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { formatMessage, showProgress, icons, ACCESSIBILITY_MODE }
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base Validator Class
|
|
5
|
+
* Provides common error handling, state management, and validation patterns
|
|
6
|
+
* for all validator implementations
|
|
7
|
+
*/
|
|
8
|
+
class BaseValidator {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.options = options
|
|
11
|
+
this.issues = []
|
|
12
|
+
this.warnings = []
|
|
13
|
+
this.validationComplete = false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if validation has been run
|
|
18
|
+
*/
|
|
19
|
+
hasRun() {
|
|
20
|
+
return this.validationComplete
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get all issues found
|
|
25
|
+
*/
|
|
26
|
+
getIssues() {
|
|
27
|
+
return this.issues
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get all warnings found
|
|
32
|
+
*/
|
|
33
|
+
getWarnings() {
|
|
34
|
+
return this.warnings
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Check if validation passed (no issues)
|
|
39
|
+
*/
|
|
40
|
+
passed() {
|
|
41
|
+
return this.hasRun() && this.issues.length === 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reset validation state
|
|
46
|
+
*/
|
|
47
|
+
reset() {
|
|
48
|
+
this.issues = []
|
|
49
|
+
this.warnings = []
|
|
50
|
+
this.validationComplete = false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Safe async operation wrapper with error handling
|
|
55
|
+
* @param {Function} operation - Async operation to execute
|
|
56
|
+
* @param {string} errorContext - Context for error messages
|
|
57
|
+
*/
|
|
58
|
+
async safeExecute(operation, errorContext) {
|
|
59
|
+
try {
|
|
60
|
+
await operation()
|
|
61
|
+
} catch (error) {
|
|
62
|
+
this.handleError(error, errorContext)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Centralized error handling
|
|
68
|
+
* @param {Error} error - The error that occurred
|
|
69
|
+
* @param {string} context - Context where the error occurred
|
|
70
|
+
*/
|
|
71
|
+
handleError(error, context) {
|
|
72
|
+
// Log detailed error for debugging
|
|
73
|
+
if (this.options.verbose) {
|
|
74
|
+
console.error(`Error in ${context}:`, error)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add user-friendly error message
|
|
78
|
+
const errorMessage = this.formatErrorMessage(error, context)
|
|
79
|
+
this.issues.push(errorMessage)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Format error message for user
|
|
84
|
+
* @param {Error} error - The error object
|
|
85
|
+
* @param {string} context - The context
|
|
86
|
+
* @returns {string} Formatted error message
|
|
87
|
+
*/
|
|
88
|
+
formatErrorMessage(error, context) {
|
|
89
|
+
// Handle common error types with actionable suggestions
|
|
90
|
+
if (error?.code === 'ENOENT') {
|
|
91
|
+
return (
|
|
92
|
+
`${context}: File or command not found - ${error.message}\n` +
|
|
93
|
+
` 💡 Suggestion: Check that the file exists and the path is correct. ` +
|
|
94
|
+
`If it's a command, ensure it's installed and in your PATH.`
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (error?.code === 'EACCES' || error?.code === 'EPERM') {
|
|
99
|
+
return (
|
|
100
|
+
`${context}: Permission denied - ${error.message}\n` +
|
|
101
|
+
` 💡 Suggestion: Try running with appropriate permissions (chmod +x for scripts) ` +
|
|
102
|
+
`or check file ownership. On Windows, run as Administrator if needed.`
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (error?.code === 'MODULE_NOT_FOUND') {
|
|
107
|
+
// Try to extract module name from error message
|
|
108
|
+
const moduleMatch = error.message.match(
|
|
109
|
+
/Cannot find module ['"]([^'"]+)['"]/
|
|
110
|
+
)
|
|
111
|
+
const moduleName = moduleMatch ? moduleMatch[1] : 'the required module'
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
`${context}: Required module not installed - ${error.message}\n` +
|
|
115
|
+
` 💡 Suggestion: Install the missing module with: npm install ${moduleName}`
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (error.name === 'SyntaxError') {
|
|
120
|
+
return (
|
|
121
|
+
`${context}: Syntax error - ${error.message}\n` +
|
|
122
|
+
` 💡 Suggestion: Check for typos, missing commas, or unmatched brackets. ` +
|
|
123
|
+
`If it's JSON, validate it with a JSON linter.`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Default error message (no specific suggestion)
|
|
128
|
+
return `${context}: ${error.message}`
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add an issue to the issues list
|
|
133
|
+
* @param {string} message - Issue message
|
|
134
|
+
*/
|
|
135
|
+
addIssue(message) {
|
|
136
|
+
this.issues.push(message)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Add a warning to the warnings list
|
|
141
|
+
* @param {string} message - Warning message
|
|
142
|
+
*/
|
|
143
|
+
addWarning(message) {
|
|
144
|
+
this.warnings.push(message)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Validate - must be implemented by subclasses
|
|
149
|
+
*/
|
|
150
|
+
async validate() {
|
|
151
|
+
throw new Error('validate() must be implemented by subclass')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Print validation results
|
|
156
|
+
*/
|
|
157
|
+
printResults() {
|
|
158
|
+
if (this.issues.length > 0) {
|
|
159
|
+
console.error(`❌ Found ${this.issues.length} issue(s):`)
|
|
160
|
+
this.issues.forEach(issue => console.error(` ${issue}`))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (this.warnings.length > 0) {
|
|
164
|
+
console.warn(`⚠️ Found ${this.warnings.length} warning(s):`)
|
|
165
|
+
this.warnings.forEach(warning => console.warn(` ${warning}`))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (this.passed()) {
|
|
169
|
+
console.log('✅ Validation passed')
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
module.exports = BaseValidator
|