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
package/setup.js
ADDED
|
@@ -0,0 +1,2076 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const { execSync } = require('child_process')
|
|
6
|
+
const {
|
|
7
|
+
mergeScripts,
|
|
8
|
+
mergeDevDependencies,
|
|
9
|
+
mergeLintStaged,
|
|
10
|
+
} = require('./lib/package-utils')
|
|
11
|
+
const { showProgress } = require('./lib/ui-helpers')
|
|
12
|
+
const {
|
|
13
|
+
NODE_VERSION,
|
|
14
|
+
SCAN_LIMITS,
|
|
15
|
+
EXCLUDE_DIRECTORIES,
|
|
16
|
+
} = require('./config/constants')
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Check Node version and lazily load @npmcli/package-json
|
|
20
|
+
* This prevents import errors on older Node versions for basic commands like --help
|
|
21
|
+
*/
|
|
22
|
+
function checkNodeVersionAndLoadPackageJson() {
|
|
23
|
+
const nodeVersion = process.version
|
|
24
|
+
const majorVersion = parseInt(nodeVersion.split('.')[0].slice(1))
|
|
25
|
+
|
|
26
|
+
if (majorVersion < NODE_VERSION.MIN_MAJOR) {
|
|
27
|
+
console.error(
|
|
28
|
+
`â Node.js ${nodeVersion} is not supported. This tool requires Node.js ${NODE_VERSION.MIN_MAJOR} or higher.`
|
|
29
|
+
)
|
|
30
|
+
console.log('Please upgrade Node.js and try again.')
|
|
31
|
+
console.log('Visit https://nodejs.org/ to download the latest version.')
|
|
32
|
+
process.exit(1)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
return require('@npmcli/package-json')
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(`â Failed to load package.json utilities: ${error.message}`)
|
|
39
|
+
console.log('This tool requires Node.js 20+ with modern module support.')
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
STYLELINT_EXTENSIONS,
|
|
46
|
+
getDefaultDevDependencies,
|
|
47
|
+
getDefaultLintStaged,
|
|
48
|
+
getDefaultScripts,
|
|
49
|
+
} = require('./config/defaults')
|
|
50
|
+
|
|
51
|
+
// Enhanced validation capabilities
|
|
52
|
+
const { ValidationRunner } = require('./lib/validation')
|
|
53
|
+
const { validateQualityConfig } = require('./lib/config-validator')
|
|
54
|
+
|
|
55
|
+
// Interactive mode capabilities
|
|
56
|
+
const { InteractivePrompt } = require('./lib/interactive/prompt')
|
|
57
|
+
const { runInteractiveFlow } = require('./lib/interactive/questions')
|
|
58
|
+
|
|
59
|
+
// Basic dependency monitoring (Free Tier)
|
|
60
|
+
const {
|
|
61
|
+
hasNpmProject,
|
|
62
|
+
generateBasicDependabotConfig,
|
|
63
|
+
writeBasicDependabotConfig,
|
|
64
|
+
} = require('./lib/dependency-monitoring-basic')
|
|
65
|
+
|
|
66
|
+
// Premium dependency monitoring (Pro/Team/Enterprise Tiers)
|
|
67
|
+
const {
|
|
68
|
+
generatePremiumDependabotConfig,
|
|
69
|
+
writePremiumDependabotConfig,
|
|
70
|
+
} = require('./lib/dependency-monitoring-premium')
|
|
71
|
+
|
|
72
|
+
// Custom template loading
|
|
73
|
+
const { TemplateLoader } = require('./lib/template-loader')
|
|
74
|
+
|
|
75
|
+
// Licensing system
|
|
76
|
+
const {
|
|
77
|
+
getLicenseInfo,
|
|
78
|
+
hasFeature,
|
|
79
|
+
showUpgradeMessage,
|
|
80
|
+
showLicenseStatus,
|
|
81
|
+
checkUsageCaps,
|
|
82
|
+
incrementUsage,
|
|
83
|
+
} = require('./lib/licensing')
|
|
84
|
+
|
|
85
|
+
// Smart Test Strategy Generator (Pro/Team/Enterprise feature)
|
|
86
|
+
const {
|
|
87
|
+
detectProjectType,
|
|
88
|
+
generateSmartStrategy,
|
|
89
|
+
writeSmartStrategy,
|
|
90
|
+
generateSmartPrePushHook,
|
|
91
|
+
getTestTierScripts,
|
|
92
|
+
} = require('./lib/smart-strategy-generator')
|
|
93
|
+
|
|
94
|
+
// Telemetry (opt-in usage tracking)
|
|
95
|
+
const { TelemetrySession, showTelemetryStatus } = require('./lib/telemetry')
|
|
96
|
+
|
|
97
|
+
// Error reporting (opt-in crash analytics)
|
|
98
|
+
const {
|
|
99
|
+
ErrorReporter,
|
|
100
|
+
showErrorReportingStatus,
|
|
101
|
+
} = require('./lib/error-reporter')
|
|
102
|
+
|
|
103
|
+
// Critical setup enhancements (fixes production quality gaps)
|
|
104
|
+
const {
|
|
105
|
+
applyProductionQualityFixes,
|
|
106
|
+
// generateEnhancedPreCommitHook,
|
|
107
|
+
validateProjectSetup,
|
|
108
|
+
} = require('./lib/setup-enhancements')
|
|
109
|
+
|
|
110
|
+
const STYLELINT_EXTENSION_SET = new Set(STYLELINT_EXTENSIONS)
|
|
111
|
+
const STYLELINT_DEFAULT_TARGET = `**/*.{${STYLELINT_EXTENSIONS.join(',')}}`
|
|
112
|
+
const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
|
|
113
|
+
const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
|
|
114
|
+
const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Safely reads directory contents without throwing on permission errors
|
|
118
|
+
*
|
|
119
|
+
* Wraps fs.readdirSync with error handling to prevent crashes when
|
|
120
|
+
* encountering permission denied errors or non-existent directories.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} dir - Directory path to read
|
|
123
|
+
* @returns {fs.Dirent[]} Array of directory entries, empty array on error
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const entries = safeReadDir('./src')
|
|
127
|
+
* // Returns: [Dirent { name: 'index.js', ... }, ...]
|
|
128
|
+
* // On error: []
|
|
129
|
+
*/
|
|
130
|
+
const safeReadDir = dir => {
|
|
131
|
+
try {
|
|
132
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
133
|
+
} catch {
|
|
134
|
+
return []
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Checks if a filename has a Stylelint-supported extension
|
|
140
|
+
*
|
|
141
|
+
* Validates whether a file should be linted by Stylelint based on
|
|
142
|
+
* its extension. Supports: css, scss, sass, less, pcss
|
|
143
|
+
*
|
|
144
|
+
* @param {string} fileName - Name of the file to check (e.g., 'styles.css')
|
|
145
|
+
* @returns {boolean} True if file has a supported CSS extension
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* isStylelintFile('styles.css') // true
|
|
149
|
+
* isStylelintFile('index.js') // false
|
|
150
|
+
* isStylelintFile('theme.scss') // true
|
|
151
|
+
*/
|
|
152
|
+
const isStylelintFile = fileName => {
|
|
153
|
+
const ext = path.extname(fileName).slice(1).toLowerCase()
|
|
154
|
+
return STYLELINT_EXTENSION_SET.has(ext)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const directoryContainsStylelintFiles = (dir, depth = 0) => {
|
|
158
|
+
if (depth > MAX_STYLELINT_SCAN_DEPTH) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const entries = safeReadDir(dir)
|
|
163
|
+
for (const entry of entries) {
|
|
164
|
+
if (entry.isSymbolicLink()) {
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const entryPath = path.join(dir, entry.name)
|
|
169
|
+
|
|
170
|
+
if (entry.isFile() && isStylelintFile(entry.name)) {
|
|
171
|
+
return true
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (entry.isDirectory()) {
|
|
175
|
+
if (STYLELINT_SCAN_EXCLUDES.has(entry.name)) {
|
|
176
|
+
continue
|
|
177
|
+
}
|
|
178
|
+
if (directoryContainsStylelintFiles(entryPath, depth + 1)) {
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return false
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Intelligently discovers Stylelint target directories in a project
|
|
189
|
+
*
|
|
190
|
+
* Scans the root directory to find which subdirectories contain CSS/SCSS files
|
|
191
|
+
* and generates optimized glob patterns for Stylelint. Avoids scanning excluded
|
|
192
|
+
* directories like node_modules and skips symbolic links for safety.
|
|
193
|
+
*
|
|
194
|
+
* Algorithm:
|
|
195
|
+
* 1. Scan root directory for CSS files and relevant subdirectories
|
|
196
|
+
* 2. Skip excluded dirs (node_modules, .git, etc.) and symlinks
|
|
197
|
+
* 3. Recursively check subdirs up to MAX_STYLELINT_SCAN_DEPTH
|
|
198
|
+
* 4. Generate specific globs for dirs with CSS files
|
|
199
|
+
* 5. Fall back to default glob if no CSS files found
|
|
200
|
+
*
|
|
201
|
+
* @param {string} rootDir - Root directory to scan
|
|
202
|
+
* @returns {string[]} Array of glob patterns for Stylelint targets
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* findStylelintTargets('/project')
|
|
206
|
+
* // Project has CSS in root and src/:
|
|
207
|
+
* // ['**\/*.{css,scss,sass,less,pcss}', 'src/**\/*.{css,scss,sass,less,pcss}']
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* findStylelintTargets('/empty-project')
|
|
211
|
+
* // No CSS files found:
|
|
212
|
+
* // ['**\/*.{css,scss,sass,less,pcss}'] (default fallback)
|
|
213
|
+
*/
|
|
214
|
+
const findStylelintTargets = rootDir => {
|
|
215
|
+
const entries = safeReadDir(rootDir)
|
|
216
|
+
const targets = new Set()
|
|
217
|
+
let hasRootCss = false
|
|
218
|
+
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
if (entry.isSymbolicLink()) {
|
|
221
|
+
continue
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const entryPath = path.join(rootDir, entry.name)
|
|
225
|
+
|
|
226
|
+
if (entry.isFile()) {
|
|
227
|
+
if (isStylelintFile(entry.name)) {
|
|
228
|
+
hasRootCss = true
|
|
229
|
+
}
|
|
230
|
+
continue
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!entry.isDirectory()) {
|
|
234
|
+
continue
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (STYLELINT_SCAN_EXCLUDES.has(entry.name)) {
|
|
238
|
+
continue
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (directoryContainsStylelintFiles(entryPath)) {
|
|
242
|
+
targets.add(entry.name)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const resolvedTargets = []
|
|
247
|
+
|
|
248
|
+
if (hasRootCss) {
|
|
249
|
+
resolvedTargets.push(STYLELINT_EXTENSION_GLOB)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Array.from(targets)
|
|
253
|
+
.sort()
|
|
254
|
+
.forEach(dir => {
|
|
255
|
+
resolvedTargets.push(`${dir}/**/${STYLELINT_EXTENSION_GLOB}`)
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (!resolvedTargets.length) {
|
|
259
|
+
return [STYLELINT_DEFAULT_TARGET]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return resolvedTargets
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const patternIncludesStylelintExtension = pattern => {
|
|
266
|
+
const lower = pattern.toLowerCase()
|
|
267
|
+
return STYLELINT_EXTENSIONS.some(ext => lower.includes(`.${ext}`))
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Input validation and sanitization functions from WFHroulette patterns
|
|
271
|
+
const validateAndSanitizeInput = input => {
|
|
272
|
+
if (typeof input !== 'string') {
|
|
273
|
+
throw new Error('Input must be a string')
|
|
274
|
+
}
|
|
275
|
+
// Normalize and trim input
|
|
276
|
+
const normalized = input.trim()
|
|
277
|
+
if (normalized.length === 0) {
|
|
278
|
+
return null
|
|
279
|
+
}
|
|
280
|
+
// Basic sanitization - remove potentially dangerous characters
|
|
281
|
+
const sanitized = normalized.replace(/[<>'"&]/g, '')
|
|
282
|
+
return sanitized
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Parse CLI arguments and return configuration object
|
|
287
|
+
* @param {string[]} rawArgs - Raw command line arguments
|
|
288
|
+
* @returns {Object} Parsed configuration
|
|
289
|
+
*/
|
|
290
|
+
function parseArguments(rawArgs) {
|
|
291
|
+
const sanitizedArgs = rawArgs
|
|
292
|
+
.map(arg => validateAndSanitizeInput(arg))
|
|
293
|
+
.filter(Boolean)
|
|
294
|
+
|
|
295
|
+
// Interactive mode detection - to be handled at execution time
|
|
296
|
+
const isInteractiveRequested = sanitizedArgs.includes('--interactive')
|
|
297
|
+
|
|
298
|
+
const isUpdateMode = sanitizedArgs.includes('--update')
|
|
299
|
+
const isValidationMode = sanitizedArgs.includes('--validate')
|
|
300
|
+
const isConfigSecurityMode = sanitizedArgs.includes('--security-config')
|
|
301
|
+
const isDocsValidationMode = sanitizedArgs.includes('--validate-docs')
|
|
302
|
+
const isComprehensiveMode = sanitizedArgs.includes('--comprehensive')
|
|
303
|
+
const isDependencyMonitoringMode =
|
|
304
|
+
sanitizedArgs.includes('--deps') ||
|
|
305
|
+
sanitizedArgs.includes('--dependency-monitoring')
|
|
306
|
+
const isLicenseStatusMode = sanitizedArgs.includes('--license-status')
|
|
307
|
+
const isTelemetryStatusMode = sanitizedArgs.includes('--telemetry-status')
|
|
308
|
+
const isErrorReportingStatusMode = sanitizedArgs.includes(
|
|
309
|
+
'--error-reporting-status'
|
|
310
|
+
)
|
|
311
|
+
const isCheckMaturityMode = sanitizedArgs.includes('--check-maturity')
|
|
312
|
+
const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
|
|
313
|
+
const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
|
|
314
|
+
const isDryRun = sanitizedArgs.includes('--dry-run')
|
|
315
|
+
|
|
316
|
+
// Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
|
|
317
|
+
// Normalize path to prevent traversal attacks and make absolute
|
|
318
|
+
const templateFlagIndex = sanitizedArgs.findIndex(arg => arg === '--template')
|
|
319
|
+
const customTemplatePath =
|
|
320
|
+
templateFlagIndex !== -1 && rawArgs[templateFlagIndex + 1]
|
|
321
|
+
? path.resolve(rawArgs[templateFlagIndex + 1])
|
|
322
|
+
: null
|
|
323
|
+
|
|
324
|
+
// Granular tool disable options
|
|
325
|
+
const disableNpmAudit = sanitizedArgs.includes('--no-npm-audit')
|
|
326
|
+
const disableGitleaks = sanitizedArgs.includes('--no-gitleaks')
|
|
327
|
+
const disableActionlint = sanitizedArgs.includes('--no-actionlint')
|
|
328
|
+
const disableMarkdownlint = sanitizedArgs.includes('--no-markdownlint')
|
|
329
|
+
const disableEslintSecurity = sanitizedArgs.includes('--no-eslint-security')
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
sanitizedArgs,
|
|
333
|
+
isInteractiveRequested,
|
|
334
|
+
isUpdateMode,
|
|
335
|
+
isValidationMode,
|
|
336
|
+
isConfigSecurityMode,
|
|
337
|
+
isDocsValidationMode,
|
|
338
|
+
isComprehensiveMode,
|
|
339
|
+
isDependencyMonitoringMode,
|
|
340
|
+
isLicenseStatusMode,
|
|
341
|
+
isTelemetryStatusMode,
|
|
342
|
+
isErrorReportingStatusMode,
|
|
343
|
+
isCheckMaturityMode,
|
|
344
|
+
isValidateConfigMode,
|
|
345
|
+
isActivateLicenseMode,
|
|
346
|
+
isDryRun,
|
|
347
|
+
customTemplatePath,
|
|
348
|
+
disableNpmAudit,
|
|
349
|
+
disableGitleaks,
|
|
350
|
+
disableActionlint,
|
|
351
|
+
disableMarkdownlint,
|
|
352
|
+
disableEslintSecurity,
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Main entry point - wraps everything in async context for interactive mode
|
|
358
|
+
*/
|
|
359
|
+
;(async function main() {
|
|
360
|
+
// Initial argument parsing
|
|
361
|
+
const args = process.argv.slice(2)
|
|
362
|
+
let parsedConfig = parseArguments(args)
|
|
363
|
+
|
|
364
|
+
// Destructure for backward compatibility
|
|
365
|
+
let {
|
|
366
|
+
sanitizedArgs,
|
|
367
|
+
isInteractiveRequested,
|
|
368
|
+
isUpdateMode,
|
|
369
|
+
isValidationMode,
|
|
370
|
+
isConfigSecurityMode,
|
|
371
|
+
isDocsValidationMode,
|
|
372
|
+
isComprehensiveMode,
|
|
373
|
+
isDependencyMonitoringMode,
|
|
374
|
+
isLicenseStatusMode,
|
|
375
|
+
isTelemetryStatusMode,
|
|
376
|
+
isErrorReportingStatusMode,
|
|
377
|
+
isCheckMaturityMode,
|
|
378
|
+
isValidateConfigMode,
|
|
379
|
+
isActivateLicenseMode,
|
|
380
|
+
isDryRun,
|
|
381
|
+
customTemplatePath,
|
|
382
|
+
disableNpmAudit,
|
|
383
|
+
disableGitleaks,
|
|
384
|
+
disableActionlint,
|
|
385
|
+
disableMarkdownlint,
|
|
386
|
+
disableEslintSecurity,
|
|
387
|
+
} = parsedConfig
|
|
388
|
+
|
|
389
|
+
// Initialize telemetry session (opt-in only, fails silently)
|
|
390
|
+
const telemetry = new TelemetrySession()
|
|
391
|
+
|
|
392
|
+
// Handle interactive mode FIRST (before any routing)
|
|
393
|
+
// This must happen before help/dry-run/routing to ensure interactive selections drive behavior
|
|
394
|
+
if (isInteractiveRequested) {
|
|
395
|
+
const prompt = new InteractivePrompt()
|
|
396
|
+
|
|
397
|
+
// Check TTY availability
|
|
398
|
+
if (!prompt.isTTY()) {
|
|
399
|
+
console.error(
|
|
400
|
+
'â Interactive mode requires a TTY environment (interactive terminal).'
|
|
401
|
+
)
|
|
402
|
+
console.error(
|
|
403
|
+
' For non-interactive use, please specify flags directly.'
|
|
404
|
+
)
|
|
405
|
+
console.error(' Run with --help to see available options.\n')
|
|
406
|
+
process.exit(1)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Run interactive flow
|
|
410
|
+
let interactiveFlags
|
|
411
|
+
try {
|
|
412
|
+
interactiveFlags = await runInteractiveFlow(prompt)
|
|
413
|
+
console.log(
|
|
414
|
+
`\nđ Running setup with options: ${interactiveFlags.join(' ')}\n`
|
|
415
|
+
)
|
|
416
|
+
} catch (error) {
|
|
417
|
+
if (error.message.includes('cancelled')) {
|
|
418
|
+
console.log('\nâ Interactive mode cancelled\n')
|
|
419
|
+
process.exit(0)
|
|
420
|
+
}
|
|
421
|
+
console.error(`â Interactive mode error: ${error.message}\n`)
|
|
422
|
+
process.exit(1)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Merge interactive flags with original command-line args
|
|
426
|
+
// Remove --interactive flag, keep all other original flags (like --template)
|
|
427
|
+
const originalFlags = args.filter(arg => arg !== '--interactive')
|
|
428
|
+
const mergedFlags = [...originalFlags, ...interactiveFlags]
|
|
429
|
+
|
|
430
|
+
// Re-parse with merged flags
|
|
431
|
+
parsedConfig = parseArguments(mergedFlags)
|
|
432
|
+
|
|
433
|
+
// Update all configuration variables
|
|
434
|
+
;({
|
|
435
|
+
sanitizedArgs,
|
|
436
|
+
isInteractiveRequested, // Will be false after re-parse since we filtered it out
|
|
437
|
+
isUpdateMode,
|
|
438
|
+
isValidationMode,
|
|
439
|
+
isConfigSecurityMode,
|
|
440
|
+
isDocsValidationMode,
|
|
441
|
+
isComprehensiveMode,
|
|
442
|
+
isDependencyMonitoringMode,
|
|
443
|
+
isLicenseStatusMode,
|
|
444
|
+
isTelemetryStatusMode,
|
|
445
|
+
isErrorReportingStatusMode,
|
|
446
|
+
isCheckMaturityMode,
|
|
447
|
+
isDryRun,
|
|
448
|
+
customTemplatePath,
|
|
449
|
+
disableNpmAudit,
|
|
450
|
+
disableGitleaks,
|
|
451
|
+
disableActionlint,
|
|
452
|
+
disableMarkdownlint,
|
|
453
|
+
disableEslintSecurity,
|
|
454
|
+
} = parsedConfig)
|
|
455
|
+
|
|
456
|
+
console.log('đ Configuration after interactive selections applied\n')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Show telemetry status if requested
|
|
460
|
+
if (isTelemetryStatusMode) {
|
|
461
|
+
showTelemetryStatus()
|
|
462
|
+
process.exit(0)
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Show error reporting status if requested
|
|
466
|
+
if (isErrorReportingStatusMode) {
|
|
467
|
+
showErrorReportingStatus()
|
|
468
|
+
process.exit(0)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Show help if requested
|
|
472
|
+
if (sanitizedArgs.includes('--help') || sanitizedArgs.includes('-h')) {
|
|
473
|
+
console.log(`
|
|
474
|
+
đ Create Quality Automation Setup
|
|
475
|
+
|
|
476
|
+
Usage: npx create-qa-architect@latest [options]
|
|
477
|
+
|
|
478
|
+
SETUP OPTIONS:
|
|
479
|
+
(no args) Run complete quality automation setup
|
|
480
|
+
--interactive Interactive mode with guided configuration prompts
|
|
481
|
+
--update Update existing configuration
|
|
482
|
+
--deps Add basic dependency monitoring (Free Tier)
|
|
483
|
+
--dependency-monitoring Same as --deps
|
|
484
|
+
--template <path> Use custom templates from specified directory
|
|
485
|
+
--dry-run Preview changes without modifying files
|
|
486
|
+
|
|
487
|
+
VALIDATION OPTIONS:
|
|
488
|
+
--validate Run comprehensive validation (same as --comprehensive)
|
|
489
|
+
--comprehensive Run all validation checks
|
|
490
|
+
--security-config Run configuration security checks only
|
|
491
|
+
--validate-docs Run documentation validation only
|
|
492
|
+
--validate-config Validate .qualityrc.json configuration file
|
|
493
|
+
--check-maturity Detect and display project maturity level
|
|
494
|
+
|
|
495
|
+
LICENSE, TELEMETRY & ERROR REPORTING:
|
|
496
|
+
--license-status Show current license tier and available features
|
|
497
|
+
--activate-license Activate Pro/Team/Enterprise license key from Stripe purchase
|
|
498
|
+
--telemetry-status Show telemetry status and opt-in instructions
|
|
499
|
+
--error-reporting-status Show error reporting status and privacy information
|
|
500
|
+
|
|
501
|
+
GRANULAR TOOL CONTROL:
|
|
502
|
+
--no-npm-audit Disable npm audit dependency vulnerability checks
|
|
503
|
+
--no-gitleaks Disable gitleaks secret scanning
|
|
504
|
+
--allow-latest-gitleaks Allow unpinned latest gitleaks (NOT RECOMMENDED - supply chain risk)
|
|
505
|
+
--no-actionlint Disable actionlint GitHub Actions workflow validation
|
|
506
|
+
--no-markdownlint Disable markdownlint markdown formatting checks
|
|
507
|
+
--no-eslint-security Disable ESLint security rule checking
|
|
508
|
+
|
|
509
|
+
EXAMPLES:
|
|
510
|
+
npx create-qa-architect@latest
|
|
511
|
+
â Set up quality automation with all tools
|
|
512
|
+
|
|
513
|
+
npx create-qa-architect@latest --deps
|
|
514
|
+
â Add basic dependency monitoring (Dependabot config + weekly updates + GitHub Actions)
|
|
515
|
+
|
|
516
|
+
npx create-qa-architect@latest --license-status
|
|
517
|
+
â Show current license tier and upgrade options
|
|
518
|
+
|
|
519
|
+
npx create-qa-architect@latest --activate-license
|
|
520
|
+
â Activate Pro/Team/Enterprise license after Stripe purchase
|
|
521
|
+
|
|
522
|
+
npx create-qa-architect@latest --telemetry-status
|
|
523
|
+
â Show telemetry status and privacy information
|
|
524
|
+
|
|
525
|
+
npx create-qa-architect@latest --error-reporting-status
|
|
526
|
+
â Show error reporting status and crash analytics information
|
|
527
|
+
|
|
528
|
+
npx create-qa-architect@latest --check-maturity
|
|
529
|
+
â Detect project maturity level (minimal, bootstrap, development, production-ready)
|
|
530
|
+
|
|
531
|
+
npx create-qa-architect@latest --validate-config
|
|
532
|
+
â Validate .qualityrc.json configuration file against JSON Schema
|
|
533
|
+
|
|
534
|
+
npx create-qa-architect@latest --comprehensive --no-gitleaks
|
|
535
|
+
â Run validation but skip gitleaks secret scanning
|
|
536
|
+
|
|
537
|
+
npx create-qa-architect@latest --security-config --allow-latest-gitleaks
|
|
538
|
+
â Run security checks with unpinned gitleaks (NOT RECOMMENDED - supply chain risk)
|
|
539
|
+
|
|
540
|
+
npx create-qa-architect@latest --security-config --no-npm-audit
|
|
541
|
+
â Run security checks but skip npm audit
|
|
542
|
+
|
|
543
|
+
npx create-qa-architect@latest --dry-run
|
|
544
|
+
â Preview what files and configurations would be created/modified
|
|
545
|
+
|
|
546
|
+
PRIVACY & TELEMETRY:
|
|
547
|
+
Telemetry and error reporting are OPT-IN only (disabled by default). To enable:
|
|
548
|
+
export QAA_TELEMETRY=true # Usage tracking (local only)
|
|
549
|
+
export QAA_ERROR_REPORTING=true # Crash analytics (local only)
|
|
550
|
+
All data stays local (~/.create-qa-architect/)
|
|
551
|
+
No personal information collected. Run --telemetry-status or
|
|
552
|
+
--error-reporting-status for details.
|
|
553
|
+
|
|
554
|
+
HELP:
|
|
555
|
+
--help, -h Show this help message
|
|
556
|
+
`)
|
|
557
|
+
process.exit(0)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
console.log(
|
|
561
|
+
`đ ${isDryRun ? '[DRY RUN] Previewing' : isUpdateMode ? 'Updating' : isDependencyMonitoringMode ? 'Adding dependency monitoring to' : 'Setting up'} Quality Automation...\n`
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
// Handle dry-run mode - preview what would be changed
|
|
565
|
+
if (isDryRun) {
|
|
566
|
+
console.log('đ DRY RUN MODE - No files will be modified\n')
|
|
567
|
+
console.log('The following files would be created/modified:\n')
|
|
568
|
+
console.log('Configuration Files:')
|
|
569
|
+
console.log(' ⢠.prettierrc - Prettier formatting configuration')
|
|
570
|
+
console.log(' ⢠.prettierignore - Files to exclude from formatting')
|
|
571
|
+
console.log(' ⢠eslint.config.cjs - ESLint linting configuration')
|
|
572
|
+
console.log(' ⢠.stylelintrc.json - Stylelint CSS linting configuration')
|
|
573
|
+
console.log(
|
|
574
|
+
' ⢠.editorconfig - Editor configuration for consistent formatting'
|
|
575
|
+
)
|
|
576
|
+
console.log(' ⢠.nvmrc - Node version specification')
|
|
577
|
+
console.log(' ⢠.npmrc - npm configuration with engine-strict')
|
|
578
|
+
console.log('')
|
|
579
|
+
console.log('Git Hooks (Husky):')
|
|
580
|
+
console.log(' ⢠.husky/pre-commit - Pre-commit hook for lint-staged')
|
|
581
|
+
console.log(
|
|
582
|
+
' ⢠.husky/pre-push - Pre-push validation (lint, format, tests)'
|
|
583
|
+
)
|
|
584
|
+
console.log('')
|
|
585
|
+
console.log('GitHub Actions:')
|
|
586
|
+
console.log(' ⢠.github/workflows/quality.yml - Quality checks workflow')
|
|
587
|
+
console.log('')
|
|
588
|
+
console.log('Package.json Modifications:')
|
|
589
|
+
console.log(
|
|
590
|
+
' ⢠Add devDependencies: eslint, prettier, stylelint, husky, lint-staged'
|
|
591
|
+
)
|
|
592
|
+
console.log(' ⢠Add scripts: format, lint, prepare')
|
|
593
|
+
console.log(' ⢠Add lint-staged configuration')
|
|
594
|
+
console.log(' ⢠Add engines requirement (Node >=20)')
|
|
595
|
+
console.log('')
|
|
596
|
+
console.log('â
Dry run complete - no files were modified')
|
|
597
|
+
console.log('')
|
|
598
|
+
console.log('To apply these changes, run without --dry-run flag:')
|
|
599
|
+
console.log(' npx create-qa-architect@latest')
|
|
600
|
+
process.exit(0)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Handle validation-only commands
|
|
604
|
+
async function handleValidationCommands() {
|
|
605
|
+
const validationOptions = {
|
|
606
|
+
disableNpmAudit,
|
|
607
|
+
disableGitleaks,
|
|
608
|
+
disableActionlint,
|
|
609
|
+
disableMarkdownlint,
|
|
610
|
+
disableEslintSecurity,
|
|
611
|
+
}
|
|
612
|
+
const validator = new ValidationRunner(validationOptions)
|
|
613
|
+
|
|
614
|
+
if (isConfigSecurityMode) {
|
|
615
|
+
try {
|
|
616
|
+
await validator.runConfigSecurity()
|
|
617
|
+
process.exit(0)
|
|
618
|
+
} catch (error) {
|
|
619
|
+
console.error(
|
|
620
|
+
`\nâ Configuration security validation failed:\n${error.message}`
|
|
621
|
+
)
|
|
622
|
+
process.exit(1)
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
if (isDocsValidationMode) {
|
|
627
|
+
try {
|
|
628
|
+
await validator.runDocumentationValidation()
|
|
629
|
+
process.exit(0)
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.error(`\nâ Documentation validation failed:\n${error.message}`)
|
|
632
|
+
process.exit(1)
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (isComprehensiveMode || isValidationMode) {
|
|
637
|
+
try {
|
|
638
|
+
// Use parallel validation for 3-5x speedup (runs checks concurrently)
|
|
639
|
+
await validator.runComprehensiveCheckParallel()
|
|
640
|
+
process.exit(0)
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.error(`\nâ Comprehensive validation failed:\n${error.message}`)
|
|
643
|
+
process.exit(1)
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Detect Python project
|
|
649
|
+
function detectPythonProject(projectPath) {
|
|
650
|
+
const pythonFiles = [
|
|
651
|
+
'pyproject.toml',
|
|
652
|
+
'requirements.txt',
|
|
653
|
+
'setup.py',
|
|
654
|
+
'Pipfile',
|
|
655
|
+
]
|
|
656
|
+
return pythonFiles.some(file => fs.existsSync(path.join(projectPath, file)))
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Detect Rust project
|
|
660
|
+
function detectRustProject(projectPath) {
|
|
661
|
+
return fs.existsSync(path.join(projectPath, 'Cargo.toml'))
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Detect Ruby project
|
|
665
|
+
function detectRubyProject(projectPath) {
|
|
666
|
+
return fs.existsSync(path.join(projectPath, 'Gemfile'))
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Handle dependency monitoring (Free/Pro/Team/Enterprise)
|
|
670
|
+
async function handleDependencyMonitoring() {
|
|
671
|
+
const projectPath = process.cwd()
|
|
672
|
+
const license = getLicenseInfo()
|
|
673
|
+
|
|
674
|
+
// Detect all supported ecosystems (npm, Python, Ruby, Rust, etc.)
|
|
675
|
+
const hasNpm = hasNpmProject(projectPath)
|
|
676
|
+
const hasPython = detectPythonProject(projectPath)
|
|
677
|
+
const hasRust = detectRustProject(projectPath)
|
|
678
|
+
const hasRuby = detectRubyProject(projectPath)
|
|
679
|
+
|
|
680
|
+
if (!hasNpm && !hasPython && !hasRust && !hasRuby) {
|
|
681
|
+
console.error(
|
|
682
|
+
'â No supported dependency file found (package.json, pyproject.toml, requirements.txt, Gemfile, Cargo.toml).'
|
|
683
|
+
)
|
|
684
|
+
console.log("đĄ Make sure you're in a directory with dependency files.")
|
|
685
|
+
process.exit(1)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
if (hasNpm) console.log('đŚ Detected: npm project')
|
|
689
|
+
if (hasPython) console.log('đ Detected: Python project')
|
|
690
|
+
if (hasRust) console.log('đŚ Detected: Rust project')
|
|
691
|
+
if (hasRuby) console.log('đ Detected: Ruby project')
|
|
692
|
+
console.log(`đ License tier: ${license.tier.toUpperCase()}`)
|
|
693
|
+
|
|
694
|
+
// Enforce Free tier caps for dependency monitoring (counted as dependency PRs)
|
|
695
|
+
if (license.tier === 'FREE') {
|
|
696
|
+
const capCheck = checkUsageCaps('dependency-pr')
|
|
697
|
+
if (!capCheck.allowed) {
|
|
698
|
+
console.error(`â ${capCheck.reason}`)
|
|
699
|
+
console.error(
|
|
700
|
+
' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/cqa'
|
|
701
|
+
)
|
|
702
|
+
process.exit(1)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const increment = incrementUsage('dependency-pr')
|
|
706
|
+
const usage = increment.usage || capCheck.usage
|
|
707
|
+
const caps = capCheck.caps
|
|
708
|
+
if (usage && caps && caps.maxDependencyPRsPerMonth !== undefined) {
|
|
709
|
+
console.log(
|
|
710
|
+
`đ§Ž Usage: ${usage.dependencyPRs}/${caps.maxDependencyPRsPerMonth} dependency monitoring runs used this month`
|
|
711
|
+
)
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
const dependabotPath = path.join(projectPath, '.github', 'dependabot.yml')
|
|
716
|
+
|
|
717
|
+
// Use premium or basic config based on license tier
|
|
718
|
+
// Free beta ended with v4.1.1 - Premium features now require Pro/Team/Enterprise tier
|
|
719
|
+
// Pro/Team/Enterprise: Framework-aware dependency monitoring with grouping
|
|
720
|
+
// Free: Basic npm-only dependency monitoring
|
|
721
|
+
const shouldUsePremium =
|
|
722
|
+
license.tier === 'PRO' ||
|
|
723
|
+
license.tier === 'TEAM' ||
|
|
724
|
+
license.tier === 'ENTERPRISE'
|
|
725
|
+
|
|
726
|
+
// Free tier only supports npm projects. Fail fast with a clear message.
|
|
727
|
+
if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
|
|
728
|
+
console.error(
|
|
729
|
+
'â Dependency monitoring for this project requires a Pro, Team, or Enterprise license.'
|
|
730
|
+
)
|
|
731
|
+
console.error(
|
|
732
|
+
' Free tier supports npm projects only. Detected non-npm ecosystems.'
|
|
733
|
+
)
|
|
734
|
+
console.error(
|
|
735
|
+
' Options: add npm/package.json, or upgrade and re-run: npx create-qa-architect@latest --deps after activation.'
|
|
736
|
+
)
|
|
737
|
+
process.exit(1)
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (shouldUsePremium) {
|
|
741
|
+
console.log(
|
|
742
|
+
'\nđ Setting up framework-aware dependency monitoring (Premium)...\n'
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
const configData = generatePremiumDependabotConfig({
|
|
746
|
+
projectPath,
|
|
747
|
+
schedule: 'weekly',
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
if (configData) {
|
|
751
|
+
const { ecosystems } = configData
|
|
752
|
+
const ecosystemNames = Object.keys(ecosystems)
|
|
753
|
+
|
|
754
|
+
if (ecosystemNames.length > 0) {
|
|
755
|
+
console.log('đ Detected ecosystems:')
|
|
756
|
+
|
|
757
|
+
// Find primary ecosystem and total package count
|
|
758
|
+
let primaryEcosystem = null
|
|
759
|
+
ecosystemNames.forEach(ecoName => {
|
|
760
|
+
const eco = ecosystems[ecoName]
|
|
761
|
+
const frameworks = Object.keys(eco.detected || {})
|
|
762
|
+
const totalPackages = frameworks.reduce((sum, fw) => {
|
|
763
|
+
return sum + (eco.detected[fw]?.count || 0)
|
|
764
|
+
}, 0)
|
|
765
|
+
|
|
766
|
+
console.log(` ⢠${ecoName}: ${totalPackages} packages`)
|
|
767
|
+
|
|
768
|
+
if (eco.primary) {
|
|
769
|
+
primaryEcosystem = ecoName
|
|
770
|
+
}
|
|
771
|
+
})
|
|
772
|
+
|
|
773
|
+
if (primaryEcosystem) {
|
|
774
|
+
console.log(`\nđŻ Primary ecosystem: ${primaryEcosystem}`)
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
writePremiumDependabotConfig(configData, dependabotPath)
|
|
779
|
+
console.log(
|
|
780
|
+
'\nâ
Created .github/dependabot.yml with framework grouping'
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
console.log('\nđ Premium dependency monitoring setup complete!')
|
|
784
|
+
console.log('\nđ What was added (Pro Tier):')
|
|
785
|
+
console.log(' ⢠Framework-aware dependency grouping')
|
|
786
|
+
console.log(
|
|
787
|
+
` ⢠${Object.keys(configData.config.updates[0].groups || {}).length} dependency groups created`
|
|
788
|
+
)
|
|
789
|
+
console.log(' ⢠Intelligent update batching (reduces PRs by 60%+)')
|
|
790
|
+
console.log(' ⢠GitHub Actions dependency monitoring')
|
|
791
|
+
}
|
|
792
|
+
} else {
|
|
793
|
+
console.log(
|
|
794
|
+
'\nđ Setting up basic dependency monitoring (Free Tier)...\n'
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
const dependabotConfig = generateBasicDependabotConfig({
|
|
798
|
+
projectPath,
|
|
799
|
+
schedule: 'weekly',
|
|
800
|
+
})
|
|
801
|
+
|
|
802
|
+
if (dependabotConfig) {
|
|
803
|
+
writeBasicDependabotConfig(dependabotConfig, dependabotPath)
|
|
804
|
+
console.log('â
Created .github/dependabot.yml')
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
console.log('\nđ Basic dependency monitoring setup complete!')
|
|
808
|
+
console.log('\nđ What was added (Free Tier):')
|
|
809
|
+
console.log(' ⢠Basic Dependabot configuration for npm packages')
|
|
810
|
+
console.log(' ⢠Weekly dependency updates on Monday 9am')
|
|
811
|
+
console.log(' ⢠GitHub Actions dependency monitoring')
|
|
812
|
+
|
|
813
|
+
// Show upgrade message for premium features
|
|
814
|
+
console.log('\nđ Premium features now available:')
|
|
815
|
+
console.log(
|
|
816
|
+
' â
Framework-aware package grouping (React, Vue, Angular)'
|
|
817
|
+
)
|
|
818
|
+
console.log(' ⢠Coming soon: Multi-language support (Python, Rust, Go)')
|
|
819
|
+
console.log(' ⢠Planned: Advanced security audit workflows')
|
|
820
|
+
console.log(' ⢠Planned: Custom update schedules and notifications')
|
|
821
|
+
|
|
822
|
+
showUpgradeMessage('Framework-Aware Dependency Grouping')
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
console.log('\nđĄ Next steps:')
|
|
826
|
+
console.log(' ⢠Review and commit .github/dependabot.yml')
|
|
827
|
+
console.log(' ⢠Enable Dependabot alerts in GitHub repository settings')
|
|
828
|
+
console.log(
|
|
829
|
+
' ⢠Dependabot will start monitoring weekly for dependency updates'
|
|
830
|
+
)
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Handle license status command
|
|
834
|
+
if (isLicenseStatusMode) {
|
|
835
|
+
showLicenseStatus()
|
|
836
|
+
process.exit(0)
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// Handle license activation command
|
|
840
|
+
if (isActivateLicenseMode) {
|
|
841
|
+
const { promptLicenseActivation } = require('./lib/licensing')
|
|
842
|
+
|
|
843
|
+
console.log('đ Create Quality Automation - License Activation')
|
|
844
|
+
console.log('ââââââââââââââââââââââââââââââââââââââââââââââââ')
|
|
845
|
+
|
|
846
|
+
try {
|
|
847
|
+
const result = await promptLicenseActivation()
|
|
848
|
+
|
|
849
|
+
if (result.success) {
|
|
850
|
+
console.log('\nđ Success! Premium features are now available.')
|
|
851
|
+
console.log('\nNext steps:')
|
|
852
|
+
console.log('⢠Run: npx create-qa-architect@latest --deps')
|
|
853
|
+
console.log('⢠Enable framework-aware dependency grouping')
|
|
854
|
+
console.log('⢠Enjoy 60%+ reduction in dependency PRs!')
|
|
855
|
+
} else {
|
|
856
|
+
console.log('\nâ License activation failed.')
|
|
857
|
+
console.log('⢠Check your license key format (QAA-XXXX-XXXX-XXXX-XXXX)')
|
|
858
|
+
console.log('⢠Verify your email address')
|
|
859
|
+
console.log('⢠Contact support: hello@aibuilderlab.com')
|
|
860
|
+
}
|
|
861
|
+
} catch (error) {
|
|
862
|
+
console.error('\nâ License activation error:', error.message)
|
|
863
|
+
console.log('Contact support for assistance: hello@aibuilderlab.com')
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
process.exit(0)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Handle check maturity command
|
|
870
|
+
if (isCheckMaturityMode) {
|
|
871
|
+
const { ProjectMaturityDetector } = require('./lib/project-maturity')
|
|
872
|
+
const detector = new ProjectMaturityDetector({
|
|
873
|
+
projectPath: process.cwd(),
|
|
874
|
+
verbose: true,
|
|
875
|
+
})
|
|
876
|
+
detector.printReport()
|
|
877
|
+
process.exit(0)
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Handle validate config command
|
|
881
|
+
if (isValidateConfigMode) {
|
|
882
|
+
const { validateAndReport } = require('./lib/config-validator')
|
|
883
|
+
const configPath = path.join(process.cwd(), '.qualityrc.json')
|
|
884
|
+
const isValid = validateAndReport(configPath)
|
|
885
|
+
process.exit(isValid ? 0 : 1)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// Handle dependency monitoring command
|
|
889
|
+
if (isDependencyMonitoringMode) {
|
|
890
|
+
return (async () => {
|
|
891
|
+
try {
|
|
892
|
+
await handleDependencyMonitoring()
|
|
893
|
+
process.exit(0)
|
|
894
|
+
} catch (error) {
|
|
895
|
+
console.error('Dependency monitoring setup error:', error.message)
|
|
896
|
+
process.exit(1)
|
|
897
|
+
}
|
|
898
|
+
})()
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Run validation commands if requested
|
|
902
|
+
if (
|
|
903
|
+
isValidationMode ||
|
|
904
|
+
isConfigSecurityMode ||
|
|
905
|
+
isDocsValidationMode ||
|
|
906
|
+
isComprehensiveMode
|
|
907
|
+
) {
|
|
908
|
+
// Handle validation commands and exit
|
|
909
|
+
return (async () => {
|
|
910
|
+
try {
|
|
911
|
+
await handleValidationCommands()
|
|
912
|
+
} catch (error) {
|
|
913
|
+
console.error('Validation error:', error.message)
|
|
914
|
+
process.exit(1)
|
|
915
|
+
}
|
|
916
|
+
})()
|
|
917
|
+
} else {
|
|
918
|
+
// Normal setup flow
|
|
919
|
+
async function runMainSetup() {
|
|
920
|
+
// Record telemetry start event (opt-in only, fails silently)
|
|
921
|
+
telemetry.recordStart({
|
|
922
|
+
mode: isDryRun ? 'dry-run' : isUpdateMode ? 'update' : 'setup',
|
|
923
|
+
hasCustomTemplate: !!customTemplatePath,
|
|
924
|
+
isInteractive: false, // Already handled at this point
|
|
925
|
+
})
|
|
926
|
+
|
|
927
|
+
// Check if we're in a git repository
|
|
928
|
+
const gitSpinner = showProgress('Checking git repository...')
|
|
929
|
+
try {
|
|
930
|
+
execSync('git status', { stdio: 'ignore' })
|
|
931
|
+
gitSpinner.succeed('Git repository verified')
|
|
932
|
+
} catch {
|
|
933
|
+
gitSpinner.fail('Not a git repository')
|
|
934
|
+
console.error('â This must be run in a git repository')
|
|
935
|
+
console.log('Run "git init" first, then try again.')
|
|
936
|
+
process.exit(1)
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Enforce FREE tier repo limit (1 private repo)
|
|
940
|
+
// Must happen before any file modifications
|
|
941
|
+
const license = getLicenseInfo()
|
|
942
|
+
if (license.tier === 'FREE') {
|
|
943
|
+
// Generate unique repo ID from git remote or directory name
|
|
944
|
+
let repoId
|
|
945
|
+
try {
|
|
946
|
+
const remoteUrl = execSync('git remote get-url origin', {
|
|
947
|
+
encoding: 'utf8',
|
|
948
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
949
|
+
}).trim()
|
|
950
|
+
repoId = remoteUrl
|
|
951
|
+
} catch {
|
|
952
|
+
// No remote - use absolute path as fallback
|
|
953
|
+
repoId = process.cwd()
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const repoCheck = checkUsageCaps('repo')
|
|
957
|
+
const currentRepos = repoCheck.usage?.repos || []
|
|
958
|
+
|
|
959
|
+
// Only enforce if this is a NEW repo (not already tracked)
|
|
960
|
+
if (!currentRepos.includes(repoId)) {
|
|
961
|
+
if (!repoCheck.allowed) {
|
|
962
|
+
console.error(`\nâ ${repoCheck.reason}`)
|
|
963
|
+
console.error(
|
|
964
|
+
' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/cqa'
|
|
965
|
+
)
|
|
966
|
+
process.exit(1)
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
// Register this repo
|
|
970
|
+
incrementUsage('repo', 1, repoId)
|
|
971
|
+
console.log(
|
|
972
|
+
`â
Registered repo (FREE tier: ${currentRepos.length + 1}/1 repos used)`
|
|
973
|
+
)
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// Validate custom template path BEFORE any mutations
|
|
978
|
+
if (customTemplatePath) {
|
|
979
|
+
if (!fs.existsSync(customTemplatePath)) {
|
|
980
|
+
console.error(
|
|
981
|
+
`â Custom template path does not exist: ${customTemplatePath}`
|
|
982
|
+
)
|
|
983
|
+
console.error(
|
|
984
|
+
'\nWhen using --template, the path must exist and be a valid directory.'
|
|
985
|
+
)
|
|
986
|
+
console.error('Please check the path and try again.\n')
|
|
987
|
+
process.exit(1)
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
const stats = fs.statSync(customTemplatePath)
|
|
991
|
+
if (!stats.isDirectory()) {
|
|
992
|
+
console.error(
|
|
993
|
+
`â Custom template path is not a directory: ${customTemplatePath}`
|
|
994
|
+
)
|
|
995
|
+
console.error(
|
|
996
|
+
'\nThe --template path must be a directory containing template files.'
|
|
997
|
+
)
|
|
998
|
+
console.error('Please provide a valid directory path.\n')
|
|
999
|
+
process.exit(1)
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
console.log(`â
Custom template path validated: ${customTemplatePath}`)
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// Check if package.json exists with validation
|
|
1006
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json')
|
|
1007
|
+
let packageJson = {}
|
|
1008
|
+
|
|
1009
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1010
|
+
try {
|
|
1011
|
+
const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8')
|
|
1012
|
+
// Validate JSON content before parsing
|
|
1013
|
+
if (packageJsonContent.trim().length === 0) {
|
|
1014
|
+
console.error('â package.json is empty')
|
|
1015
|
+
console.log(
|
|
1016
|
+
'Please add valid JSON content to package.json and try again.'
|
|
1017
|
+
)
|
|
1018
|
+
process.exit(1)
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
packageJson = JSON.parse(packageJsonContent)
|
|
1022
|
+
|
|
1023
|
+
// Validate package.json structure
|
|
1024
|
+
if (typeof packageJson !== 'object' || packageJson === null) {
|
|
1025
|
+
console.error('â package.json must contain a valid JSON object')
|
|
1026
|
+
console.log('Please fix the package.json structure and try again.')
|
|
1027
|
+
process.exit(1)
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// Sanitize package name if present
|
|
1031
|
+
if (packageJson.name && typeof packageJson.name === 'string') {
|
|
1032
|
+
packageJson.name =
|
|
1033
|
+
validateAndSanitizeInput(packageJson.name) || 'my-project'
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
console.log('â
Found existing package.json')
|
|
1037
|
+
} catch (error) {
|
|
1038
|
+
console.error(`â Error parsing package.json: ${error.message}`)
|
|
1039
|
+
console.log(
|
|
1040
|
+
'Please fix the JSON syntax in package.json and try again.'
|
|
1041
|
+
)
|
|
1042
|
+
console.log(
|
|
1043
|
+
'Common issues: trailing commas, missing quotes, unclosed brackets'
|
|
1044
|
+
)
|
|
1045
|
+
process.exit(1)
|
|
1046
|
+
}
|
|
1047
|
+
} else {
|
|
1048
|
+
console.log('đŚ Creating new package.json')
|
|
1049
|
+
const projectName =
|
|
1050
|
+
validateAndSanitizeInput(path.basename(process.cwd())) || 'my-project'
|
|
1051
|
+
packageJson = {
|
|
1052
|
+
name: projectName,
|
|
1053
|
+
version: '1.0.0',
|
|
1054
|
+
description: '',
|
|
1055
|
+
main: 'index.js',
|
|
1056
|
+
scripts: {},
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const hasTypeScriptDependency = Boolean(
|
|
1061
|
+
(packageJson.devDependencies &&
|
|
1062
|
+
packageJson.devDependencies.typescript) ||
|
|
1063
|
+
(packageJson.dependencies && packageJson.dependencies.typescript)
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
const tsconfigCandidates = ['tsconfig.json', 'tsconfig.base.json']
|
|
1067
|
+
const hasTypeScriptConfig = tsconfigCandidates.some(file =>
|
|
1068
|
+
fs.existsSync(path.join(process.cwd(), file))
|
|
1069
|
+
)
|
|
1070
|
+
|
|
1071
|
+
const usesTypeScript = Boolean(
|
|
1072
|
+
hasTypeScriptDependency || hasTypeScriptConfig
|
|
1073
|
+
)
|
|
1074
|
+
if (usesTypeScript) {
|
|
1075
|
+
console.log(
|
|
1076
|
+
'đ Detected TypeScript configuration; enabling TypeScript lint defaults'
|
|
1077
|
+
)
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// Python detection (including in workspace packages for monorepos)
|
|
1081
|
+
const pythonCandidates = [
|
|
1082
|
+
'pyproject.toml',
|
|
1083
|
+
'setup.py',
|
|
1084
|
+
'requirements.txt',
|
|
1085
|
+
'poetry.lock',
|
|
1086
|
+
]
|
|
1087
|
+
const hasPythonConfig = pythonCandidates.some(file =>
|
|
1088
|
+
fs.existsSync(path.join(process.cwd(), file))
|
|
1089
|
+
)
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* Recursively check for Python files in directory and subdirectories
|
|
1093
|
+
* Limited to 2 levels deep to avoid performance issues in large monorepos
|
|
1094
|
+
*/
|
|
1095
|
+
function hasPythonFilesRecursive(dir, depth = 0, maxDepth = 2) {
|
|
1096
|
+
if (depth > maxDepth) return false
|
|
1097
|
+
|
|
1098
|
+
try {
|
|
1099
|
+
const entries = safeReadDir(dir)
|
|
1100
|
+
|
|
1101
|
+
// Count .py files in current directory (excluding __pycache__)
|
|
1102
|
+
const pyFiles = entries.filter(
|
|
1103
|
+
dirent =>
|
|
1104
|
+
dirent.isFile() &&
|
|
1105
|
+
dirent.name.endsWith('.py') &&
|
|
1106
|
+
dirent.name !== '__pycache__'
|
|
1107
|
+
)
|
|
1108
|
+
|
|
1109
|
+
// Strong indicators: multiple .py files OR main/app/run patterns
|
|
1110
|
+
const hasMultiplePyFiles = pyFiles.length >= 2
|
|
1111
|
+
const hasMainPattern = pyFiles.some(
|
|
1112
|
+
f =>
|
|
1113
|
+
f.name === 'main.py' ||
|
|
1114
|
+
f.name === 'app.py' ||
|
|
1115
|
+
f.name === 'run.py' ||
|
|
1116
|
+
f.name === '__main__.py'
|
|
1117
|
+
)
|
|
1118
|
+
|
|
1119
|
+
// Require stronger evidence than a single random .py file
|
|
1120
|
+
if (hasMultiplePyFiles || hasMainPattern) return true
|
|
1121
|
+
|
|
1122
|
+
// Check subdirectories (skip node_modules, .git, etc.)
|
|
1123
|
+
const skipDirs = ['node_modules', '.git', 'dist', 'build', 'coverage']
|
|
1124
|
+
for (const dirent of entries) {
|
|
1125
|
+
if (dirent.isDirectory() && !skipDirs.includes(dirent.name)) {
|
|
1126
|
+
const subDir = path.join(dir, dirent.name)
|
|
1127
|
+
if (hasPythonFilesRecursive(subDir, depth + 1, maxDepth)) {
|
|
1128
|
+
return true
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
return false
|
|
1134
|
+
} catch {
|
|
1135
|
+
return false
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const hasPythonFiles = hasPythonFilesRecursive(process.cwd())
|
|
1140
|
+
|
|
1141
|
+
const usesPython = Boolean(hasPythonConfig || hasPythonFiles)
|
|
1142
|
+
if (usesPython) {
|
|
1143
|
+
console.log(
|
|
1144
|
+
'đ Detected Python project; enabling Python quality automation'
|
|
1145
|
+
)
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
const stylelintTargets = findStylelintTargets(process.cwd())
|
|
1149
|
+
const usingDefaultStylelintTarget =
|
|
1150
|
+
stylelintTargets.length === 1 &&
|
|
1151
|
+
stylelintTargets[0] === STYLELINT_DEFAULT_TARGET
|
|
1152
|
+
if (!usingDefaultStylelintTarget) {
|
|
1153
|
+
console.log(
|
|
1154
|
+
`đ Detected stylelint targets: ${stylelintTargets.join(', ')}`
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
// Add quality automation scripts (conservative: do not overwrite existing)
|
|
1159
|
+
console.log('đ Adding quality automation scripts...')
|
|
1160
|
+
const defaultScripts = getDefaultScripts({
|
|
1161
|
+
typescript: usesTypeScript,
|
|
1162
|
+
stylelintTargets,
|
|
1163
|
+
})
|
|
1164
|
+
|
|
1165
|
+
// Import enhanced scripts to fix production quality gaps
|
|
1166
|
+
const {
|
|
1167
|
+
getEnhancedTypeScriptScripts,
|
|
1168
|
+
} = require('./lib/typescript-config-generator')
|
|
1169
|
+
const enhancedScripts = getEnhancedTypeScriptScripts()
|
|
1170
|
+
|
|
1171
|
+
// Merge both default and enhanced scripts
|
|
1172
|
+
packageJson.scripts = mergeScripts(packageJson.scripts || {}, {
|
|
1173
|
+
...defaultScripts,
|
|
1174
|
+
...enhancedScripts,
|
|
1175
|
+
})
|
|
1176
|
+
|
|
1177
|
+
// Add devDependencies
|
|
1178
|
+
console.log('đŚ Adding devDependencies...')
|
|
1179
|
+
const defaultDevDependencies = getDefaultDevDependencies({
|
|
1180
|
+
typescript: usesTypeScript,
|
|
1181
|
+
})
|
|
1182
|
+
packageJson.devDependencies = mergeDevDependencies(
|
|
1183
|
+
packageJson.devDependencies || {},
|
|
1184
|
+
defaultDevDependencies
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
// Add lint-staged configuration
|
|
1188
|
+
console.log('âď¸ Adding lint-staged configuration...')
|
|
1189
|
+
const defaultLintStaged = getDefaultLintStaged({
|
|
1190
|
+
typescript: usesTypeScript,
|
|
1191
|
+
stylelintTargets,
|
|
1192
|
+
python: usesPython,
|
|
1193
|
+
})
|
|
1194
|
+
|
|
1195
|
+
// Import enhanced lint-staged to fix production quality gaps
|
|
1196
|
+
const {
|
|
1197
|
+
getEnhancedLintStaged,
|
|
1198
|
+
} = require('./lib/typescript-config-generator')
|
|
1199
|
+
const enhancedLintStaged = getEnhancedLintStaged(
|
|
1200
|
+
usesPython,
|
|
1201
|
+
usesTypeScript
|
|
1202
|
+
)
|
|
1203
|
+
|
|
1204
|
+
// Merge enhanced configuration with defaults
|
|
1205
|
+
const finalLintStaged = { ...defaultLintStaged, ...enhancedLintStaged }
|
|
1206
|
+
|
|
1207
|
+
const hasExistingCssPatterns = Object.keys(
|
|
1208
|
+
packageJson['lint-staged'] || {}
|
|
1209
|
+
).some(patternIncludesStylelintExtension)
|
|
1210
|
+
|
|
1211
|
+
if (hasExistingCssPatterns) {
|
|
1212
|
+
console.log(
|
|
1213
|
+
'âšď¸ Detected existing lint-staged CSS globs; preserving current CSS targets'
|
|
1214
|
+
)
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
packageJson['lint-staged'] = mergeLintStaged(
|
|
1218
|
+
packageJson['lint-staged'] || {},
|
|
1219
|
+
finalLintStaged,
|
|
1220
|
+
{ stylelintTargets },
|
|
1221
|
+
patternIncludesStylelintExtension
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
// Write updated package.json using @npmcli/package-json
|
|
1225
|
+
try {
|
|
1226
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
1227
|
+
let pkgJson
|
|
1228
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1229
|
+
// Load existing package.json
|
|
1230
|
+
pkgJson = await PackageJson.load(process.cwd())
|
|
1231
|
+
// Update with our changes
|
|
1232
|
+
Object.assign(pkgJson.content, packageJson)
|
|
1233
|
+
} else {
|
|
1234
|
+
// Create new package.json
|
|
1235
|
+
pkgJson = await PackageJson.create(process.cwd())
|
|
1236
|
+
Object.assign(pkgJson.content, packageJson)
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
await pkgJson.save()
|
|
1240
|
+
console.log('â
Updated package.json')
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
console.error(`â Error writing package.json: ${error.message}`)
|
|
1243
|
+
process.exit(1)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
// Ensure Node toolchain pinning in target project
|
|
1247
|
+
const nvmrcPath = path.join(process.cwd(), '.nvmrc')
|
|
1248
|
+
if (!fs.existsSync(nvmrcPath)) {
|
|
1249
|
+
fs.writeFileSync(nvmrcPath, '20\n')
|
|
1250
|
+
console.log('â
Added .nvmrc (Node 20)')
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
const npmrcPath = path.join(process.cwd(), '.npmrc')
|
|
1254
|
+
if (!fs.existsSync(npmrcPath)) {
|
|
1255
|
+
fs.writeFileSync(npmrcPath, 'engine-strict = true\n')
|
|
1256
|
+
console.log('â
Added .npmrc (engine-strict)')
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
// Generate .qualityrc.json with detected maturity level
|
|
1260
|
+
const qualityrcPath = path.join(process.cwd(), '.qualityrc.json')
|
|
1261
|
+
if (!fs.existsSync(qualityrcPath)) {
|
|
1262
|
+
const { ProjectMaturityDetector } = require('./lib/project-maturity')
|
|
1263
|
+
const detector = new ProjectMaturityDetector({
|
|
1264
|
+
projectPath: process.cwd(),
|
|
1265
|
+
})
|
|
1266
|
+
const detectedMaturity = detector.detect()
|
|
1267
|
+
const stats = detector.analyzeProject()
|
|
1268
|
+
|
|
1269
|
+
const qualityConfig = {
|
|
1270
|
+
version: '1.0.0',
|
|
1271
|
+
maturity: 'auto',
|
|
1272
|
+
detected: {
|
|
1273
|
+
level: detectedMaturity,
|
|
1274
|
+
sourceFiles: stats.totalSourceFiles,
|
|
1275
|
+
testFiles: stats.testFiles,
|
|
1276
|
+
hasDocumentation: stats.hasDocumentation,
|
|
1277
|
+
hasDependencies: stats.hasDependencies,
|
|
1278
|
+
detectedAt: new Date().toISOString(),
|
|
1279
|
+
},
|
|
1280
|
+
checks: {
|
|
1281
|
+
prettier: { enabled: true, required: true },
|
|
1282
|
+
eslint: { enabled: 'auto', required: false },
|
|
1283
|
+
stylelint: { enabled: 'auto', required: false },
|
|
1284
|
+
tests: { enabled: 'auto', required: false },
|
|
1285
|
+
coverage: { enabled: false, required: false, threshold: 80 },
|
|
1286
|
+
'security-audit': { enabled: 'auto', required: false },
|
|
1287
|
+
documentation: { enabled: false, required: false },
|
|
1288
|
+
lighthouse: { enabled: false, required: false },
|
|
1289
|
+
},
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
fs.writeFileSync(
|
|
1293
|
+
qualityrcPath,
|
|
1294
|
+
JSON.stringify(qualityConfig, null, 2) + '\n'
|
|
1295
|
+
)
|
|
1296
|
+
console.log(`â
Added .qualityrc.json (detected: ${detectedMaturity})`)
|
|
1297
|
+
|
|
1298
|
+
// Validate the generated config
|
|
1299
|
+
const validationResult = validateQualityConfig(qualityrcPath)
|
|
1300
|
+
if (!validationResult.valid) {
|
|
1301
|
+
console.warn(
|
|
1302
|
+
'â ď¸ Warning: Generated config has validation issues (this should not happen):'
|
|
1303
|
+
)
|
|
1304
|
+
validationResult.errors.forEach(error => {
|
|
1305
|
+
console.warn(` - ${error}`)
|
|
1306
|
+
})
|
|
1307
|
+
}
|
|
1308
|
+
} else {
|
|
1309
|
+
// Config exists, validate it
|
|
1310
|
+
// Temporarily disabled - debugging
|
|
1311
|
+
// const validationResult = validateQualityConfig(qualityrcPath)
|
|
1312
|
+
// if (!validationResult.valid) {
|
|
1313
|
+
// console.warn(
|
|
1314
|
+
// 'â ď¸ Warning: Existing .qualityrc.json has validation issues:'
|
|
1315
|
+
// )
|
|
1316
|
+
// validationResult.errors.forEach(error => {
|
|
1317
|
+
// console.warn(` - ${error}`)
|
|
1318
|
+
// })
|
|
1319
|
+
// console.warn(
|
|
1320
|
+
// ' Setup will continue, but you may want to fix these issues.\n'
|
|
1321
|
+
// )
|
|
1322
|
+
// }
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// Load and merge templates (custom + defaults)
|
|
1326
|
+
// Enable strict mode when custom template path is explicitly provided
|
|
1327
|
+
const templateSpinner = showProgress('Loading templates...')
|
|
1328
|
+
const templateLoader = new TemplateLoader({
|
|
1329
|
+
verbose: true,
|
|
1330
|
+
strict: !!customTemplatePath,
|
|
1331
|
+
})
|
|
1332
|
+
|
|
1333
|
+
let templates
|
|
1334
|
+
try {
|
|
1335
|
+
templates = await templateLoader.mergeTemplates(
|
|
1336
|
+
customTemplatePath,
|
|
1337
|
+
__dirname
|
|
1338
|
+
)
|
|
1339
|
+
if (customTemplatePath) {
|
|
1340
|
+
templateSpinner.succeed('Custom templates loaded successfully')
|
|
1341
|
+
} else {
|
|
1342
|
+
templateSpinner.succeed('Default templates loaded')
|
|
1343
|
+
}
|
|
1344
|
+
} catch (error) {
|
|
1345
|
+
templateSpinner.fail('Template loading failed')
|
|
1346
|
+
console.error(`â Template loading failed: ${error.message}`)
|
|
1347
|
+
console.error(
|
|
1348
|
+
'\nWhen using --template, the path must exist and be a valid directory.'
|
|
1349
|
+
)
|
|
1350
|
+
console.error('Please check the path and try again.\n')
|
|
1351
|
+
process.exit(1)
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Create .github/workflows directory if it doesn't exist
|
|
1355
|
+
const configSpinner = showProgress('Copying configuration files...')
|
|
1356
|
+
const workflowDir = path.join(process.cwd(), '.github', 'workflows')
|
|
1357
|
+
if (!fs.existsSync(workflowDir)) {
|
|
1358
|
+
fs.mkdirSync(workflowDir, { recursive: true })
|
|
1359
|
+
console.log('đ Created .github/workflows directory')
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
// Copy workflow file if it doesn't exist
|
|
1363
|
+
const workflowFile = path.join(workflowDir, 'quality.yml')
|
|
1364
|
+
if (!fs.existsSync(workflowFile)) {
|
|
1365
|
+
const templateWorkflow =
|
|
1366
|
+
templateLoader.getTemplate(
|
|
1367
|
+
templates,
|
|
1368
|
+
path.join('.github', 'workflows', 'quality.yml')
|
|
1369
|
+
) ||
|
|
1370
|
+
fs.readFileSync(
|
|
1371
|
+
path.join(__dirname, '.github/workflows/quality.yml'),
|
|
1372
|
+
'utf8'
|
|
1373
|
+
)
|
|
1374
|
+
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
1375
|
+
console.log('â
Added GitHub Actions workflow')
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Copy Prettier config if it doesn't exist
|
|
1379
|
+
const prettierrcPath = path.join(process.cwd(), '.prettierrc')
|
|
1380
|
+
if (!fs.existsSync(prettierrcPath)) {
|
|
1381
|
+
const templatePrettierrc =
|
|
1382
|
+
templateLoader.getTemplate(templates, '.prettierrc') ||
|
|
1383
|
+
fs.readFileSync(path.join(__dirname, '.prettierrc'), 'utf8')
|
|
1384
|
+
fs.writeFileSync(prettierrcPath, templatePrettierrc)
|
|
1385
|
+
console.log('â
Added Prettier configuration')
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Copy ESLint config if it doesn't exist
|
|
1389
|
+
const eslintConfigPath = path.join(process.cwd(), 'eslint.config.cjs')
|
|
1390
|
+
const eslintTemplateFile = usesTypeScript
|
|
1391
|
+
? 'eslint.config.ts.cjs'
|
|
1392
|
+
: 'eslint.config.cjs'
|
|
1393
|
+
const templateEslint =
|
|
1394
|
+
templateLoader.getTemplate(templates, eslintTemplateFile) ||
|
|
1395
|
+
fs.readFileSync(path.join(__dirname, eslintTemplateFile), 'utf8')
|
|
1396
|
+
|
|
1397
|
+
if (!fs.existsSync(eslintConfigPath)) {
|
|
1398
|
+
fs.writeFileSync(eslintConfigPath, templateEslint)
|
|
1399
|
+
console.log(
|
|
1400
|
+
`â
Added ESLint configuration${usesTypeScript ? ' (TypeScript-aware)' : ''}`
|
|
1401
|
+
)
|
|
1402
|
+
} else if (usesTypeScript) {
|
|
1403
|
+
const existingConfig = fs.readFileSync(eslintConfigPath, 'utf8')
|
|
1404
|
+
if (!existingConfig.includes('@typescript-eslint')) {
|
|
1405
|
+
fs.writeFileSync(eslintConfigPath, templateEslint)
|
|
1406
|
+
console.log('âťď¸ Updated ESLint configuration with TypeScript support')
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
const legacyEslintrcPath = path.join(process.cwd(), '.eslintrc.json')
|
|
1411
|
+
if (fs.existsSync(legacyEslintrcPath)) {
|
|
1412
|
+
console.log(
|
|
1413
|
+
'âšď¸ Detected legacy .eslintrc.json; ESLint 9 prefers eslint.config.cjs. Consider removing the legacy file after verifying the new config.'
|
|
1414
|
+
)
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
// Copy Stylelint config if it doesn't exist
|
|
1418
|
+
const stylelintrcPath = path.join(process.cwd(), '.stylelintrc.json')
|
|
1419
|
+
if (!fs.existsSync(stylelintrcPath)) {
|
|
1420
|
+
const templateStylelint =
|
|
1421
|
+
templateLoader.getTemplate(templates, '.stylelintrc.json') ||
|
|
1422
|
+
fs.readFileSync(path.join(__dirname, '.stylelintrc.json'), 'utf8')
|
|
1423
|
+
fs.writeFileSync(stylelintrcPath, templateStylelint)
|
|
1424
|
+
console.log('â
Added Stylelint configuration')
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
// Copy .prettierignore if it doesn't exist
|
|
1428
|
+
const prettierignorePath = path.join(process.cwd(), '.prettierignore')
|
|
1429
|
+
if (!fs.existsSync(prettierignorePath)) {
|
|
1430
|
+
const templatePrettierignore =
|
|
1431
|
+
templateLoader.getTemplate(templates, '.prettierignore') ||
|
|
1432
|
+
fs.readFileSync(path.join(__dirname, '.prettierignore'), 'utf8')
|
|
1433
|
+
fs.writeFileSync(prettierignorePath, templatePrettierignore)
|
|
1434
|
+
console.log('â
Added Prettier ignore file')
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// Copy Lighthouse CI config if it doesn't exist
|
|
1438
|
+
const lighthousercPath = path.join(process.cwd(), '.lighthouserc.js')
|
|
1439
|
+
if (!fs.existsSync(lighthousercPath)) {
|
|
1440
|
+
const templateLighthouserc =
|
|
1441
|
+
templateLoader.getTemplate(
|
|
1442
|
+
templates,
|
|
1443
|
+
path.join('config', '.lighthouserc.js')
|
|
1444
|
+
) ||
|
|
1445
|
+
fs.readFileSync(
|
|
1446
|
+
path.join(__dirname, 'config', '.lighthouserc.js'),
|
|
1447
|
+
'utf8'
|
|
1448
|
+
)
|
|
1449
|
+
fs.writeFileSync(lighthousercPath, templateLighthouserc)
|
|
1450
|
+
console.log('â
Added Lighthouse CI configuration')
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Copy ESLint ignore if it doesn't exist
|
|
1454
|
+
const eslintignorePath = path.join(process.cwd(), '.eslintignore')
|
|
1455
|
+
const eslintignoreTemplatePath = path.join(__dirname, '.eslintignore')
|
|
1456
|
+
if (
|
|
1457
|
+
!fs.existsSync(eslintignorePath) &&
|
|
1458
|
+
(templateLoader.hasTemplate(templates, '.eslintignore') ||
|
|
1459
|
+
fs.existsSync(eslintignoreTemplatePath))
|
|
1460
|
+
) {
|
|
1461
|
+
const templateEslintIgnore =
|
|
1462
|
+
templateLoader.getTemplate(templates, '.eslintignore') ||
|
|
1463
|
+
fs.readFileSync(eslintignoreTemplatePath, 'utf8')
|
|
1464
|
+
fs.writeFileSync(eslintignorePath, templateEslintIgnore)
|
|
1465
|
+
console.log('â
Added ESLint ignore file')
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// Copy .editorconfig if it doesn't exist
|
|
1469
|
+
const editorconfigPath = path.join(process.cwd(), '.editorconfig')
|
|
1470
|
+
if (!fs.existsSync(editorconfigPath)) {
|
|
1471
|
+
const templateEditorconfig =
|
|
1472
|
+
templateLoader.getTemplate(templates, '.editorconfig') ||
|
|
1473
|
+
fs.readFileSync(path.join(__dirname, '.editorconfig'), 'utf8')
|
|
1474
|
+
fs.writeFileSync(editorconfigPath, templateEditorconfig)
|
|
1475
|
+
console.log('â
Added .editorconfig')
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
configSpinner.succeed('Configuration files copied')
|
|
1479
|
+
|
|
1480
|
+
// Ensure .gitignore exists with essential entries
|
|
1481
|
+
const gitignorePath = path.join(process.cwd(), '.gitignore')
|
|
1482
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
1483
|
+
const essentialGitignore = `# Dependencies
|
|
1484
|
+
node_modules/
|
|
1485
|
+
.pnpm-store/
|
|
1486
|
+
|
|
1487
|
+
# Environment variables
|
|
1488
|
+
.env*
|
|
1489
|
+
|
|
1490
|
+
# Logs
|
|
1491
|
+
*.log
|
|
1492
|
+
npm-debug.log*
|
|
1493
|
+
yarn-debug.log*
|
|
1494
|
+
yarn-error.log*
|
|
1495
|
+
pnpm-debug.log*
|
|
1496
|
+
|
|
1497
|
+
# Build outputs
|
|
1498
|
+
dist/
|
|
1499
|
+
build/
|
|
1500
|
+
.next/
|
|
1501
|
+
.nuxt/
|
|
1502
|
+
.output/
|
|
1503
|
+
.vercel/
|
|
1504
|
+
|
|
1505
|
+
# OS
|
|
1506
|
+
.DS_Store
|
|
1507
|
+
Thumbs.db
|
|
1508
|
+
|
|
1509
|
+
# IDE
|
|
1510
|
+
.vscode/
|
|
1511
|
+
.idea/
|
|
1512
|
+
*.swp
|
|
1513
|
+
*.swo
|
|
1514
|
+
|
|
1515
|
+
# Coverage
|
|
1516
|
+
coverage/
|
|
1517
|
+
.nyc_output/
|
|
1518
|
+
|
|
1519
|
+
# Cache
|
|
1520
|
+
.cache/
|
|
1521
|
+
.parcel-cache/
|
|
1522
|
+
.turbo/
|
|
1523
|
+
`
|
|
1524
|
+
fs.writeFileSync(gitignorePath, essentialGitignore)
|
|
1525
|
+
console.log('â
Added .gitignore with essential entries')
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// Ensure Husky pre-commit hook runs lint-staged
|
|
1529
|
+
const huskySpinner = showProgress('Setting up Husky git hooks...')
|
|
1530
|
+
try {
|
|
1531
|
+
const huskyDir = path.join(process.cwd(), '.husky')
|
|
1532
|
+
if (!fs.existsSync(huskyDir)) {
|
|
1533
|
+
fs.mkdirSync(huskyDir, { recursive: true })
|
|
1534
|
+
}
|
|
1535
|
+
const preCommitPath = path.join(huskyDir, 'pre-commit')
|
|
1536
|
+
if (!fs.existsSync(preCommitPath)) {
|
|
1537
|
+
const hook =
|
|
1538
|
+
'#!/bin/sh\n. "$(dirname "$0")/_/husky.sh"\n\n# Run lint-staged on staged files\nnpx --no -- lint-staged\n'
|
|
1539
|
+
fs.writeFileSync(preCommitPath, hook)
|
|
1540
|
+
fs.chmodSync(preCommitPath, 0o755)
|
|
1541
|
+
console.log('â
Added Husky pre-commit hook (lint-staged)')
|
|
1542
|
+
}
|
|
1543
|
+
} catch (e) {
|
|
1544
|
+
huskySpinner.warn('Could not create Husky pre-commit hook')
|
|
1545
|
+
console.warn('â ď¸ Could not create Husky pre-commit hook:', e.message)
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
// Ensure Husky pre-push hook runs validation checks
|
|
1549
|
+
try {
|
|
1550
|
+
const huskyDir = path.join(process.cwd(), '.husky')
|
|
1551
|
+
if (!fs.existsSync(huskyDir)) {
|
|
1552
|
+
fs.mkdirSync(huskyDir, { recursive: true })
|
|
1553
|
+
}
|
|
1554
|
+
const prePushPath = path.join(huskyDir, 'pre-push')
|
|
1555
|
+
if (!fs.existsSync(prePushPath)) {
|
|
1556
|
+
const hook = `#!/bin/sh
|
|
1557
|
+
. "$(dirname "$0")/_/husky.sh"
|
|
1558
|
+
|
|
1559
|
+
echo "đ Running pre-push validation..."
|
|
1560
|
+
|
|
1561
|
+
# Enforce Free tier pre-push cap (50/month)
|
|
1562
|
+
node - <<'EOF'
|
|
1563
|
+
const fs = require('fs')
|
|
1564
|
+
const path = require('path')
|
|
1565
|
+
const os = require('os')
|
|
1566
|
+
|
|
1567
|
+
const licenseDir =
|
|
1568
|
+
process.env.QAA_LICENSE_DIR || path.join(os.homedir(), '.create-qa-architect')
|
|
1569
|
+
const licenseFile = path.join(licenseDir, 'license.json')
|
|
1570
|
+
const usageFile = path.join(licenseDir, 'usage.json')
|
|
1571
|
+
const now = new Date()
|
|
1572
|
+
const currentMonth = now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0')
|
|
1573
|
+
|
|
1574
|
+
let usage = {
|
|
1575
|
+
month: currentMonth,
|
|
1576
|
+
prePushRuns: 0,
|
|
1577
|
+
dependencyPRs: 0,
|
|
1578
|
+
repos: [],
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
let tier = 'FREE'
|
|
1582
|
+
try {
|
|
1583
|
+
const data = JSON.parse(fs.readFileSync(licenseFile, 'utf8'))
|
|
1584
|
+
tier = (data && data.tier) || 'FREE'
|
|
1585
|
+
} catch (_error) {
|
|
1586
|
+
tier = 'FREE'
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
if (tier !== 'FREE') {
|
|
1590
|
+
process.exit(0)
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
try {
|
|
1594
|
+
const data = JSON.parse(fs.readFileSync(usageFile, 'utf8'))
|
|
1595
|
+
if (data.month === currentMonth) {
|
|
1596
|
+
usage = { ...usage, ...data }
|
|
1597
|
+
}
|
|
1598
|
+
} catch (_error) {
|
|
1599
|
+
// First run or corrupt file â start fresh
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
const CAP = 50
|
|
1603
|
+
if (usage.prePushRuns >= CAP) {
|
|
1604
|
+
console.error('â Free tier limit reached: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs this month')
|
|
1605
|
+
console.error(' Upgrade to Pro, Team, or Enterprise: https://vibebuildlab.com/cqa')
|
|
1606
|
+
process.exit(1)
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
usage.prePushRuns += 1
|
|
1610
|
+
fs.mkdirSync(licenseDir, { recursive: true })
|
|
1611
|
+
fs.writeFileSync(usageFile, JSON.stringify(usage, null, 2))
|
|
1612
|
+
console.log('đ§Ž Usage: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs used this month')
|
|
1613
|
+
EOF
|
|
1614
|
+
|
|
1615
|
+
# Validate command patterns (fast - catches deprecated patterns)
|
|
1616
|
+
if node -e "const pkg=require('./package.json');process.exit(pkg.scripts['test:patterns']?0:1)" 2>/dev/null; then
|
|
1617
|
+
echo "đ Validating command patterns..."
|
|
1618
|
+
npm run test:patterns || {
|
|
1619
|
+
echo "â Pattern validation failed! Deprecated patterns detected."
|
|
1620
|
+
exit 1
|
|
1621
|
+
}
|
|
1622
|
+
fi
|
|
1623
|
+
|
|
1624
|
+
# Run lint (catches errors before CI)
|
|
1625
|
+
echo "đ Linting..."
|
|
1626
|
+
npm run lint || {
|
|
1627
|
+
echo "â Lint failed! Fix errors before pushing."
|
|
1628
|
+
exit 1
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
# Run format check (ensures code style consistency)
|
|
1632
|
+
echo "⨠Checking formatting..."
|
|
1633
|
+
npm run format:check || {
|
|
1634
|
+
echo "â Format check failed! Run 'npm run format' to fix."
|
|
1635
|
+
exit 1
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
# Test command execution (CRITICAL - prevents command generation bugs)
|
|
1639
|
+
if node -e "const pkg=require('./package.json');process.exit(pkg.scripts['test:commands']?0:1)" 2>/dev/null; then
|
|
1640
|
+
echo "đ§Ş Testing command execution..."
|
|
1641
|
+
npm run test:commands || {
|
|
1642
|
+
echo "â Command execution tests failed! Generated commands are broken."
|
|
1643
|
+
exit 1
|
|
1644
|
+
}
|
|
1645
|
+
fi
|
|
1646
|
+
|
|
1647
|
+
# Run tests if they exist
|
|
1648
|
+
if node -e "const pkg=require('./package.json');process.exit(pkg.scripts.test?0:1)" 2>/dev/null; then
|
|
1649
|
+
echo "đ§Ş Running unit tests..."
|
|
1650
|
+
npm test || {
|
|
1651
|
+
echo "â Tests failed! Fix failing tests before pushing."
|
|
1652
|
+
exit 1
|
|
1653
|
+
}
|
|
1654
|
+
fi
|
|
1655
|
+
|
|
1656
|
+
echo "â
Pre-push validation passed!"
|
|
1657
|
+
`
|
|
1658
|
+
fs.writeFileSync(prePushPath, hook)
|
|
1659
|
+
fs.chmodSync(prePushPath, 0o755)
|
|
1660
|
+
console.log('â
Added Husky pre-push hook (validation)')
|
|
1661
|
+
}
|
|
1662
|
+
huskySpinner.succeed('Husky git hooks configured')
|
|
1663
|
+
} catch (e) {
|
|
1664
|
+
huskySpinner.warn('Could not create Husky pre-push hook')
|
|
1665
|
+
console.warn('â ď¸ Could not create Husky pre-push hook:', e.message)
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Ensure engines/volta pins in target package.json (enforce minimums)
|
|
1669
|
+
try {
|
|
1670
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1671
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
1672
|
+
const pkgJson = await PackageJson.load(process.cwd())
|
|
1673
|
+
|
|
1674
|
+
// Preserve existing engines but enforce Node >=20 minimum
|
|
1675
|
+
const existingEngines = pkgJson.content.engines || {}
|
|
1676
|
+
pkgJson.content.engines = {
|
|
1677
|
+
...existingEngines,
|
|
1678
|
+
node: '>=20', // Always enforce our minimum
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
// Preserve existing volta but set our pinned versions
|
|
1682
|
+
const existingVolta = pkgJson.content.volta || {}
|
|
1683
|
+
pkgJson.content.volta = {
|
|
1684
|
+
...existingVolta,
|
|
1685
|
+
node: '20.11.1',
|
|
1686
|
+
npm: '10.2.4',
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
await pkgJson.save()
|
|
1690
|
+
console.log(
|
|
1691
|
+
'â
Ensured engines and Volta pins in package.json (Node >=20 enforced)'
|
|
1692
|
+
)
|
|
1693
|
+
}
|
|
1694
|
+
} catch (e) {
|
|
1695
|
+
console.warn(
|
|
1696
|
+
'â ď¸ Could not update engines/volta in package.json:',
|
|
1697
|
+
e.message
|
|
1698
|
+
)
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// Python quality automation setup
|
|
1702
|
+
if (usesPython) {
|
|
1703
|
+
console.log('\nđ Setting up Python quality automation...')
|
|
1704
|
+
|
|
1705
|
+
const pythonSpinner = showProgress(
|
|
1706
|
+
'Configuring Python quality tools...'
|
|
1707
|
+
)
|
|
1708
|
+
|
|
1709
|
+
// Copy pyproject.toml if it doesn't exist
|
|
1710
|
+
const pyprojectPath = path.join(process.cwd(), 'pyproject.toml')
|
|
1711
|
+
if (!fs.existsSync(pyprojectPath)) {
|
|
1712
|
+
const templatePyproject =
|
|
1713
|
+
templateLoader.getTemplate(
|
|
1714
|
+
templates,
|
|
1715
|
+
path.join('config', 'pyproject.toml')
|
|
1716
|
+
) ||
|
|
1717
|
+
fs.readFileSync(
|
|
1718
|
+
path.join(__dirname, 'config/pyproject.toml'),
|
|
1719
|
+
'utf8'
|
|
1720
|
+
)
|
|
1721
|
+
fs.writeFileSync(pyprojectPath, templatePyproject)
|
|
1722
|
+
console.log(
|
|
1723
|
+
'â
Added pyproject.toml with Black, Ruff, isort, mypy config'
|
|
1724
|
+
)
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// Copy pre-commit config
|
|
1728
|
+
const preCommitPath = path.join(
|
|
1729
|
+
process.cwd(),
|
|
1730
|
+
'.pre-commit-config.yaml'
|
|
1731
|
+
)
|
|
1732
|
+
if (!fs.existsSync(preCommitPath)) {
|
|
1733
|
+
const templatePreCommit =
|
|
1734
|
+
templateLoader.getTemplate(
|
|
1735
|
+
templates,
|
|
1736
|
+
path.join('config', '.pre-commit-config.yaml')
|
|
1737
|
+
) ||
|
|
1738
|
+
fs.readFileSync(
|
|
1739
|
+
path.join(__dirname, 'config/.pre-commit-config.yaml'),
|
|
1740
|
+
'utf8'
|
|
1741
|
+
)
|
|
1742
|
+
fs.writeFileSync(preCommitPath, templatePreCommit)
|
|
1743
|
+
console.log('â
Added .pre-commit-config.yaml')
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
// Copy requirements-dev.txt
|
|
1747
|
+
const requirementsDevPath = path.join(
|
|
1748
|
+
process.cwd(),
|
|
1749
|
+
'requirements-dev.txt'
|
|
1750
|
+
)
|
|
1751
|
+
if (!fs.existsSync(requirementsDevPath)) {
|
|
1752
|
+
const templateRequirements =
|
|
1753
|
+
templateLoader.getTemplate(
|
|
1754
|
+
templates,
|
|
1755
|
+
path.join('config', 'requirements-dev.txt')
|
|
1756
|
+
) ||
|
|
1757
|
+
fs.readFileSync(
|
|
1758
|
+
path.join(__dirname, 'config/requirements-dev.txt'),
|
|
1759
|
+
'utf8'
|
|
1760
|
+
)
|
|
1761
|
+
fs.writeFileSync(requirementsDevPath, templateRequirements)
|
|
1762
|
+
console.log('â
Added requirements-dev.txt')
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
// Copy Python workflow
|
|
1766
|
+
const pythonWorkflowFile = path.join(workflowDir, 'quality-python.yml')
|
|
1767
|
+
if (!fs.existsSync(pythonWorkflowFile)) {
|
|
1768
|
+
const templatePythonWorkflow =
|
|
1769
|
+
templateLoader.getTemplate(
|
|
1770
|
+
templates,
|
|
1771
|
+
path.join('config', 'quality-python.yml')
|
|
1772
|
+
) ||
|
|
1773
|
+
fs.readFileSync(
|
|
1774
|
+
path.join(__dirname, 'config/quality-python.yml'),
|
|
1775
|
+
'utf8'
|
|
1776
|
+
)
|
|
1777
|
+
fs.writeFileSync(pythonWorkflowFile, templatePythonWorkflow)
|
|
1778
|
+
console.log('â
Added Python GitHub Actions workflow')
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// Create tests directory if it doesn't exist
|
|
1782
|
+
const testsDir = path.join(process.cwd(), 'tests')
|
|
1783
|
+
if (!fs.existsSync(testsDir)) {
|
|
1784
|
+
fs.mkdirSync(testsDir)
|
|
1785
|
+
fs.writeFileSync(path.join(testsDir, '__init__.py'), '')
|
|
1786
|
+
console.log('â
Created tests directory')
|
|
1787
|
+
}
|
|
1788
|
+
|
|
1789
|
+
// Add Python helper scripts to package.json if it exists and is a JS/TS project too
|
|
1790
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
1791
|
+
try {
|
|
1792
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
1793
|
+
const pkgJson = await PackageJson.load(process.cwd())
|
|
1794
|
+
|
|
1795
|
+
const pythonScripts = {
|
|
1796
|
+
'python:format': 'black .',
|
|
1797
|
+
'python:format:check': 'black --check .',
|
|
1798
|
+
'python:lint': 'ruff check .',
|
|
1799
|
+
'python:lint:fix': 'ruff check --fix .',
|
|
1800
|
+
'python:type-check': 'mypy .',
|
|
1801
|
+
'python:quality':
|
|
1802
|
+
'black --check . && ruff check . && isort --check-only . && mypy .',
|
|
1803
|
+
'python:test': 'pytest',
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
if (!pkgJson.content.scripts) {
|
|
1807
|
+
pkgJson.content.scripts = {}
|
|
1808
|
+
}
|
|
1809
|
+
// Use mergeScripts to preserve existing scripts
|
|
1810
|
+
pkgJson.content.scripts = mergeScripts(
|
|
1811
|
+
pkgJson.content.scripts,
|
|
1812
|
+
pythonScripts
|
|
1813
|
+
)
|
|
1814
|
+
await pkgJson.save()
|
|
1815
|
+
console.log('â
Added Python helper scripts to package.json')
|
|
1816
|
+
} catch (e) {
|
|
1817
|
+
console.warn(
|
|
1818
|
+
'â ď¸ Could not add Python scripts to package.json:',
|
|
1819
|
+
e.message
|
|
1820
|
+
)
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
pythonSpinner.succeed('Python quality tools configured')
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// Smart Test Strategy (Pro/Team/Enterprise feature)
|
|
1828
|
+
const smartStrategyEnabled = hasFeature('smartTestStrategy')
|
|
1829
|
+
if (smartStrategyEnabled) {
|
|
1830
|
+
const smartSpinner = showProgress('Setting up Smart Test Strategy...')
|
|
1831
|
+
|
|
1832
|
+
try {
|
|
1833
|
+
// Detect project type and generate customized strategy
|
|
1834
|
+
const projectType = detectProjectType(process.cwd())
|
|
1835
|
+
const { script, projectTypeName } = generateSmartStrategy({
|
|
1836
|
+
projectPath: process.cwd(),
|
|
1837
|
+
projectName: packageJson.name || path.basename(process.cwd()),
|
|
1838
|
+
projectType,
|
|
1839
|
+
})
|
|
1840
|
+
|
|
1841
|
+
// Write smart strategy script
|
|
1842
|
+
writeSmartStrategy(process.cwd(), script)
|
|
1843
|
+
console.log(`â
Added Smart Test Strategy (${projectTypeName})`)
|
|
1844
|
+
|
|
1845
|
+
// Update pre-push hook to use smart strategy
|
|
1846
|
+
const huskyDir = path.join(process.cwd(), '.husky')
|
|
1847
|
+
const prePushPath = path.join(huskyDir, 'pre-push')
|
|
1848
|
+
const smartPrePush = generateSmartPrePushHook()
|
|
1849
|
+
fs.writeFileSync(prePushPath, smartPrePush)
|
|
1850
|
+
fs.chmodSync(prePushPath, 0o755)
|
|
1851
|
+
console.log('â
Updated pre-push hook to use smart strategy')
|
|
1852
|
+
|
|
1853
|
+
// Add test tier scripts to package.json
|
|
1854
|
+
const testTierScripts = getTestTierScripts(projectType)
|
|
1855
|
+
const PackageJson = checkNodeVersionAndLoadPackageJson()
|
|
1856
|
+
const pkgJson = await PackageJson.load(process.cwd())
|
|
1857
|
+
pkgJson.content.scripts = mergeScripts(
|
|
1858
|
+
pkgJson.content.scripts || {},
|
|
1859
|
+
testTierScripts
|
|
1860
|
+
)
|
|
1861
|
+
await pkgJson.save()
|
|
1862
|
+
console.log(
|
|
1863
|
+
'â
Added test tier scripts (test:fast, test:medium, test:comprehensive)'
|
|
1864
|
+
)
|
|
1865
|
+
|
|
1866
|
+
smartSpinner.succeed('Smart Test Strategy configured')
|
|
1867
|
+
|
|
1868
|
+
console.log('\nđ Smart Test Strategy Benefits:')
|
|
1869
|
+
console.log(' ⢠70% faster pre-push validation on average')
|
|
1870
|
+
console.log(' ⢠Risk-based test selection')
|
|
1871
|
+
console.log(' ⢠Adapts to branch, time of day, and change size')
|
|
1872
|
+
console.log(
|
|
1873
|
+
' ⢠Override with SKIP_SMART=1, FORCE_COMPREHENSIVE=1, or FORCE_MINIMAL=1'
|
|
1874
|
+
)
|
|
1875
|
+
} catch (error) {
|
|
1876
|
+
smartSpinner.warn('Could not set up Smart Test Strategy')
|
|
1877
|
+
console.warn('â ď¸ Smart Test Strategy setup error:', error.message)
|
|
1878
|
+
}
|
|
1879
|
+
} else {
|
|
1880
|
+
// Show upgrade message for Free tier users
|
|
1881
|
+
console.log('\nđĄ Smart Test Strategy is available with Pro tier:')
|
|
1882
|
+
console.log(' ⢠70% faster pre-push validation')
|
|
1883
|
+
console.log(' ⢠Intelligent risk-based test selection')
|
|
1884
|
+
console.log(' ⢠Saves 10-20 hours/month per developer')
|
|
1885
|
+
showUpgradeMessage('Smart Test Strategy')
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// Generate placeholder test file with helpful documentation
|
|
1889
|
+
const testsDir = path.join(process.cwd(), 'tests')
|
|
1890
|
+
const testExtension = usesTypeScript ? 'ts' : 'js'
|
|
1891
|
+
const placeholderTestPath = path.join(
|
|
1892
|
+
testsDir,
|
|
1893
|
+
`placeholder.test.${testExtension}`
|
|
1894
|
+
)
|
|
1895
|
+
|
|
1896
|
+
if (!fs.existsSync(testsDir)) {
|
|
1897
|
+
fs.mkdirSync(testsDir, { recursive: true })
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
if (!fs.existsSync(placeholderTestPath)) {
|
|
1901
|
+
const placeholderContent = `import { describe, it, expect } from 'vitest'
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* PLACEHOLDER TEST FILE
|
|
1905
|
+
*
|
|
1906
|
+
* This file ensures your test suite passes even when you're just getting started.
|
|
1907
|
+
* Replace these placeholders with real tests as you build your application.
|
|
1908
|
+
*
|
|
1909
|
+
* Progressive Testing Strategy:
|
|
1910
|
+
* 1. Start: Use describe.skip() placeholders (tests pass but are marked as skipped)
|
|
1911
|
+
* 2. Planning: Convert to it.todo() when you know what to test
|
|
1912
|
+
* 3. Implementation: Write actual test implementations
|
|
1913
|
+
* 4. Tighten: Remove --passWithNoTests flag once you have real tests
|
|
1914
|
+
*
|
|
1915
|
+
* To tighten enforcement, update package.json:
|
|
1916
|
+
* - Change: "test": "vitest run --passWithNoTests"
|
|
1917
|
+
* - To: "test": "vitest run" (fails if no tests exist)
|
|
1918
|
+
*/
|
|
1919
|
+
|
|
1920
|
+
describe.skip('Example test suite (placeholder)', () => {
|
|
1921
|
+
/**
|
|
1922
|
+
* These tests are skipped by default to prevent false positives.
|
|
1923
|
+
* Remove .skip and implement these tests when you're ready.
|
|
1924
|
+
*/
|
|
1925
|
+
|
|
1926
|
+
it.todo('should test core functionality')
|
|
1927
|
+
|
|
1928
|
+
it.todo('should handle edge cases')
|
|
1929
|
+
|
|
1930
|
+
it.todo('should validate error conditions')
|
|
1931
|
+
})
|
|
1932
|
+
|
|
1933
|
+
// Example of a passing test (demonstrates test framework is working)
|
|
1934
|
+
describe('Test framework validation', () => {
|
|
1935
|
+
it('should confirm Vitest is properly configured', () => {
|
|
1936
|
+
expect(true).toBe(true)
|
|
1937
|
+
})
|
|
1938
|
+
})
|
|
1939
|
+
|
|
1940
|
+
/**
|
|
1941
|
+
* Next Steps:
|
|
1942
|
+
* 1. Create feature-specific test files (e.g., user.test.${testExtension}, api.test.${testExtension})
|
|
1943
|
+
* 2. Move these it.todo() placeholders to appropriate test files
|
|
1944
|
+
* 3. Implement actual test logic
|
|
1945
|
+
* 4. Delete this placeholder.test.${testExtension} file when you have real tests
|
|
1946
|
+
*
|
|
1947
|
+
* Resources:
|
|
1948
|
+
* - Vitest Docs: https://vitest.dev/guide/
|
|
1949
|
+
* - Testing Best Practices: https://github.com/goldbergyoni/javascript-testing-best-practices
|
|
1950
|
+
*/
|
|
1951
|
+
`
|
|
1952
|
+
fs.writeFileSync(placeholderTestPath, placeholderContent)
|
|
1953
|
+
console.log(
|
|
1954
|
+
`â
Added placeholder test file (tests/placeholder.test.${testExtension})`
|
|
1955
|
+
)
|
|
1956
|
+
console.log(
|
|
1957
|
+
' đĄ Replace with real tests as you build your application'
|
|
1958
|
+
)
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// Apply critical production quality fixes
|
|
1962
|
+
console.log('\nđ§ Applying production quality enhancements...')
|
|
1963
|
+
const qualityEnhancements = applyProductionQualityFixes('.', {
|
|
1964
|
+
hasTypeScript: usesTypeScript,
|
|
1965
|
+
hasPython: usesPython,
|
|
1966
|
+
skipTypeScriptTests: false,
|
|
1967
|
+
})
|
|
1968
|
+
|
|
1969
|
+
// Display applied fixes
|
|
1970
|
+
qualityEnhancements.fixes.forEach(fix => console.log(fix))
|
|
1971
|
+
|
|
1972
|
+
// Validate setup for common gaps
|
|
1973
|
+
const { warnings, errors } = validateProjectSetup('.')
|
|
1974
|
+
|
|
1975
|
+
if (errors.length > 0) {
|
|
1976
|
+
console.log('\nđ¨ CRITICAL ISSUES DETECTED:')
|
|
1977
|
+
errors.forEach(error => console.log(error))
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
if (warnings.length > 0) {
|
|
1981
|
+
console.log('\nâ ď¸ Setup Warnings:')
|
|
1982
|
+
warnings.forEach(warning => console.log(warning))
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
console.log('\nđ Quality automation setup complete!')
|
|
1986
|
+
|
|
1987
|
+
// Record telemetry completion event (opt-in only, fails silently)
|
|
1988
|
+
telemetry.recordComplete({
|
|
1989
|
+
usesPython,
|
|
1990
|
+
usesTypeScript,
|
|
1991
|
+
hasStylelintFiles: stylelintTargets.length > 0,
|
|
1992
|
+
mode: isDryRun ? 'dry-run' : isUpdateMode ? 'update' : 'setup',
|
|
1993
|
+
})
|
|
1994
|
+
|
|
1995
|
+
// Dynamic next steps based on detected languages
|
|
1996
|
+
console.log('\nđ Next steps:')
|
|
1997
|
+
|
|
1998
|
+
if (usesPython && fs.existsSync(packageJsonPath)) {
|
|
1999
|
+
console.log('JavaScript/TypeScript setup:')
|
|
2000
|
+
console.log('1. Run: npm install')
|
|
2001
|
+
console.log('2. Run: npm run prepare')
|
|
2002
|
+
console.log('\nPython setup:')
|
|
2003
|
+
console.log('3. Run: python3 -m pip install -r requirements-dev.txt')
|
|
2004
|
+
console.log('4. Run: pre-commit install')
|
|
2005
|
+
console.log('\n5. Commit your changes to activate both workflows')
|
|
2006
|
+
} else if (usesPython) {
|
|
2007
|
+
console.log('Python setup:')
|
|
2008
|
+
console.log('1. Run: python3 -m pip install -r requirements-dev.txt')
|
|
2009
|
+
console.log('2. Run: pre-commit install')
|
|
2010
|
+
console.log('3. Commit your changes to activate the workflow')
|
|
2011
|
+
} else {
|
|
2012
|
+
console.log('1. Run: npm install')
|
|
2013
|
+
console.log('2. Run: npm run prepare')
|
|
2014
|
+
console.log('3. Commit your changes to activate the workflow')
|
|
2015
|
+
}
|
|
2016
|
+
console.log('\n⨠Your project now has:')
|
|
2017
|
+
console.log(' ⢠Prettier code formatting')
|
|
2018
|
+
console.log(' ⢠Pre-commit hooks via Husky (lint-staged)')
|
|
2019
|
+
console.log(' ⢠Pre-push validation (lint, format, tests)')
|
|
2020
|
+
console.log(' ⢠GitHub Actions quality checks')
|
|
2021
|
+
console.log(' ⢠Lint-staged for efficient processing')
|
|
2022
|
+
} // End of runMainSetup function
|
|
2023
|
+
|
|
2024
|
+
// Run main setup (interactive handling already done at top if requested)
|
|
2025
|
+
await runMainSetup()
|
|
2026
|
+
} // End of normal setup flow
|
|
2027
|
+
|
|
2028
|
+
// Close the main async function and handle errors
|
|
2029
|
+
})().catch(error => {
|
|
2030
|
+
try {
|
|
2031
|
+
// Always show stack trace for debugging
|
|
2032
|
+
if (error?.stack) {
|
|
2033
|
+
console.error('\nđ Error stack trace:')
|
|
2034
|
+
console.error(error.stack)
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
// Record telemetry failure event (opt-in only, fails silently)
|
|
2038
|
+
const telemetry = new TelemetrySession()
|
|
2039
|
+
telemetry.recordFailure(error, {
|
|
2040
|
+
errorLocation: error?.stack ? error.stack.split('\n')[1] : 'unknown',
|
|
2041
|
+
})
|
|
2042
|
+
|
|
2043
|
+
// Capture and report error (opt-in only, fails silently)
|
|
2044
|
+
const errorReporter = new ErrorReporter('setup')
|
|
2045
|
+
const reportId = errorReporter.captureError(error, {
|
|
2046
|
+
operation: 'setup',
|
|
2047
|
+
errorLocation: error?.stack ? error.stack.split('\n')[1] : 'unknown',
|
|
2048
|
+
})
|
|
2049
|
+
|
|
2050
|
+
// Show friendly error message with category
|
|
2051
|
+
errorReporter.promptErrorReport(error)
|
|
2052
|
+
|
|
2053
|
+
// If report was captured, show location
|
|
2054
|
+
if (reportId) {
|
|
2055
|
+
console.log(`\nđ Error report saved: ${reportId}`)
|
|
2056
|
+
console.log(`View at: ~/.create-qa-architect/error-reports.json`)
|
|
2057
|
+
}
|
|
2058
|
+
} catch (reportingError) {
|
|
2059
|
+
// Error in error reporting - fallback to basic error display
|
|
2060
|
+
console.error('\nâ Setup failed with error:')
|
|
2061
|
+
console.error(error?.message || error || 'Unknown error')
|
|
2062
|
+
if (error?.stack) {
|
|
2063
|
+
console.error('\nStack trace:')
|
|
2064
|
+
console.error(error.stack)
|
|
2065
|
+
}
|
|
2066
|
+
// Show error reporting failure for debugging
|
|
2067
|
+
if (process.env.DEBUG) {
|
|
2068
|
+
console.error('\nâ ď¸ Error reporting also failed:')
|
|
2069
|
+
console.error(
|
|
2070
|
+
reportingError?.stack || reportingError?.message || reportingError
|
|
2071
|
+
)
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
process.exit(1)
|
|
2076
|
+
})
|