create-qa-architect 5.0.0

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