create-qa-architect 5.0.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/quality.yml +3 -0
- package/LICENSE +66 -0
- package/README.md +44 -30
- package/create-saas-monetization.js +10 -0
- package/docs/ARCHITECTURE.md +54 -0
- package/docs/DEPLOYMENT.md +63 -0
- package/docs/SLA_GATES.md +28 -0
- package/docs/TESTING.md +62 -0
- package/docs/security/SOC2_STARTER.md +29 -0
- package/lib/dependency-monitoring-basic.js +73 -26
- package/lib/licensing.js +5 -4
- package/lib/package-utils.js +215 -0
- package/lib/setup-enhancements.js +33 -0
- package/package.json +9 -7
- package/setup.js +131 -38
- package/templates/QUALITY_TROUBLESHOOTING.md +403 -0
- package/templates/ci/circleci-config.yml +35 -0
- package/templates/ci/gitlab-ci.yml +47 -0
- package/templates/integration-tests/api-service.test.js +244 -0
- package/templates/integration-tests/frontend-app.test.js +267 -0
- package/templates/scripts/smart-test-strategy.sh +109 -0
- package/templates/test-stubs/e2e.smoke.test.js +12 -0
- package/templates/test-stubs/unit.test.js +7 -0
- package/legal/README.md +0 -106
- package/legal/copyright.md +0 -76
- package/legal/disclaimer.md +0 -146
- package/legal/privacy-policy.html +0 -324
- package/legal/privacy-policy.md +0 -196
- package/legal/terms-of-service.md +0 -224
- package/marketing/beta-user-email-campaign.md +0 -372
- package/marketing/landing-page.html +0 -721
package/lib/package-utils.js
CHANGED
|
@@ -177,6 +177,219 @@ function getAuditCommand(packageManager) {
|
|
|
177
177
|
return commands[packageManager] || 'npm audit'
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Detect monorepo type and configuration
|
|
182
|
+
* @param {string} projectPath - Path to the project directory
|
|
183
|
+
* @returns {Object} Monorepo info: { type, isMonorepo, packages, tool }
|
|
184
|
+
*/
|
|
185
|
+
function detectMonorepoType(projectPath = process.cwd()) {
|
|
186
|
+
const fs = require('fs')
|
|
187
|
+
const path = require('path')
|
|
188
|
+
|
|
189
|
+
const result = {
|
|
190
|
+
isMonorepo: false,
|
|
191
|
+
type: null, // 'workspaces' | 'lerna' | 'nx' | 'turborepo' | 'rush'
|
|
192
|
+
tool: null, // Specific tool name
|
|
193
|
+
packages: [], // List of workspace package paths
|
|
194
|
+
packageManager: detectPackageManager(projectPath),
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for Nx (nx.json)
|
|
198
|
+
const nxJsonPath = path.join(projectPath, 'nx.json')
|
|
199
|
+
if (fs.existsSync(nxJsonPath)) {
|
|
200
|
+
result.isMonorepo = true
|
|
201
|
+
result.type = 'nx'
|
|
202
|
+
result.tool = 'nx'
|
|
203
|
+
try {
|
|
204
|
+
const nxJson = JSON.parse(fs.readFileSync(nxJsonPath, 'utf8'))
|
|
205
|
+
result.config = nxJson
|
|
206
|
+
} catch {
|
|
207
|
+
// Ignore parse errors
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check for Turborepo (turbo.json)
|
|
212
|
+
const turboJsonPath = path.join(projectPath, 'turbo.json')
|
|
213
|
+
if (fs.existsSync(turboJsonPath)) {
|
|
214
|
+
result.isMonorepo = true
|
|
215
|
+
result.type = 'turborepo'
|
|
216
|
+
result.tool = 'turborepo'
|
|
217
|
+
try {
|
|
218
|
+
const turboJson = JSON.parse(fs.readFileSync(turboJsonPath, 'utf8'))
|
|
219
|
+
result.config = turboJson
|
|
220
|
+
} catch {
|
|
221
|
+
// Ignore parse errors
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Check for Rush (rush.json)
|
|
226
|
+
const rushJsonPath = path.join(projectPath, 'rush.json')
|
|
227
|
+
if (fs.existsSync(rushJsonPath)) {
|
|
228
|
+
result.isMonorepo = true
|
|
229
|
+
result.type = 'rush'
|
|
230
|
+
result.tool = 'rush'
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Check for Lerna (lerna.json)
|
|
234
|
+
const lernaJsonPath = path.join(projectPath, 'lerna.json')
|
|
235
|
+
if (fs.existsSync(lernaJsonPath)) {
|
|
236
|
+
result.isMonorepo = true
|
|
237
|
+
result.type = 'lerna'
|
|
238
|
+
result.tool = 'lerna'
|
|
239
|
+
try {
|
|
240
|
+
const lernaJson = JSON.parse(fs.readFileSync(lernaJsonPath, 'utf8'))
|
|
241
|
+
result.packages = lernaJson.packages || ['packages/*']
|
|
242
|
+
} catch {
|
|
243
|
+
result.packages = ['packages/*']
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check for pnpm workspaces (pnpm-workspace.yaml)
|
|
248
|
+
const pnpmWorkspacePath = path.join(projectPath, 'pnpm-workspace.yaml')
|
|
249
|
+
if (fs.existsSync(pnpmWorkspacePath)) {
|
|
250
|
+
result.isMonorepo = true
|
|
251
|
+
result.type = result.type || 'workspaces'
|
|
252
|
+
result.tool = result.tool || 'pnpm'
|
|
253
|
+
try {
|
|
254
|
+
const yaml = fs.readFileSync(pnpmWorkspacePath, 'utf8')
|
|
255
|
+
// Simple line-by-line YAML parsing (safer than regex)
|
|
256
|
+
const lines = yaml.split('\n')
|
|
257
|
+
let inPackages = false
|
|
258
|
+
const packages = []
|
|
259
|
+
for (const line of lines) {
|
|
260
|
+
if (line.trim() === 'packages:') {
|
|
261
|
+
inPackages = true
|
|
262
|
+
continue
|
|
263
|
+
}
|
|
264
|
+
if (inPackages) {
|
|
265
|
+
// Check if line is a list item (starts with -)
|
|
266
|
+
const match = line.match(/^\s*-\s*['"]?([^'"]+)['"]?\s*$/)
|
|
267
|
+
if (match) {
|
|
268
|
+
packages.push(match[1])
|
|
269
|
+
} else if (
|
|
270
|
+
line.trim() &&
|
|
271
|
+
!line.startsWith(' ') &&
|
|
272
|
+
!line.startsWith('\t')
|
|
273
|
+
) {
|
|
274
|
+
// New top-level key, stop parsing packages
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (packages.length > 0) {
|
|
280
|
+
result.packages = packages
|
|
281
|
+
}
|
|
282
|
+
} catch {
|
|
283
|
+
// Ignore parse errors
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Check for npm/yarn workspaces in package.json
|
|
288
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
289
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
290
|
+
try {
|
|
291
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
292
|
+
if (packageJson.workspaces) {
|
|
293
|
+
result.isMonorepo = true
|
|
294
|
+
result.type = result.type || 'workspaces'
|
|
295
|
+
// workspaces can be array or object with packages key
|
|
296
|
+
const workspaces = Array.isArray(packageJson.workspaces)
|
|
297
|
+
? packageJson.workspaces
|
|
298
|
+
: packageJson.workspaces.packages || []
|
|
299
|
+
result.packages = result.packages.length ? result.packages : workspaces
|
|
300
|
+
if (!result.tool) {
|
|
301
|
+
result.tool =
|
|
302
|
+
result.packageManager === 'yarn' ? 'yarn' : result.packageManager
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
} catch {
|
|
306
|
+
// Ignore parse errors
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Resolve workspace package paths to actual directories
|
|
311
|
+
if (result.isMonorepo && result.packages.length > 0) {
|
|
312
|
+
result.resolvedPackages = resolveWorkspacePackages(
|
|
313
|
+
projectPath,
|
|
314
|
+
result.packages
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return result
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Resolve workspace glob patterns to actual package directories
|
|
323
|
+
* @param {string} projectPath - Root project path
|
|
324
|
+
* @param {Array<string>} patterns - Workspace patterns (e.g., ['packages/*', 'apps/*'])
|
|
325
|
+
* @returns {Array<Object>} Resolved packages with name and path
|
|
326
|
+
*/
|
|
327
|
+
function resolveWorkspacePackages(projectPath, patterns) {
|
|
328
|
+
const fs = require('fs')
|
|
329
|
+
const path = require('path')
|
|
330
|
+
const packages = []
|
|
331
|
+
|
|
332
|
+
for (const pattern of patterns) {
|
|
333
|
+
// Handle simple glob patterns like 'packages/*'
|
|
334
|
+
if (pattern.endsWith('/*')) {
|
|
335
|
+
const baseDir = path.join(projectPath, pattern.slice(0, -2))
|
|
336
|
+
if (fs.existsSync(baseDir)) {
|
|
337
|
+
try {
|
|
338
|
+
const entries = fs.readdirSync(baseDir, { withFileTypes: true })
|
|
339
|
+
for (const entry of entries) {
|
|
340
|
+
if (entry.isDirectory()) {
|
|
341
|
+
const pkgPath = path.join(baseDir, entry.name)
|
|
342
|
+
const pkgJsonPath = path.join(pkgPath, 'package.json')
|
|
343
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
344
|
+
try {
|
|
345
|
+
const pkgJson = JSON.parse(
|
|
346
|
+
fs.readFileSync(pkgJsonPath, 'utf8')
|
|
347
|
+
)
|
|
348
|
+
packages.push({
|
|
349
|
+
name: pkgJson.name || entry.name,
|
|
350
|
+
path: pkgPath,
|
|
351
|
+
relativePath: path.relative(projectPath, pkgPath),
|
|
352
|
+
})
|
|
353
|
+
} catch {
|
|
354
|
+
packages.push({
|
|
355
|
+
name: entry.name,
|
|
356
|
+
path: pkgPath,
|
|
357
|
+
relativePath: path.relative(projectPath, pkgPath),
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
} catch {
|
|
364
|
+
// Ignore read errors
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else if (!pattern.includes('*')) {
|
|
368
|
+
// Direct path without glob
|
|
369
|
+
const pkgPath = path.join(projectPath, pattern)
|
|
370
|
+
const pkgJsonPath = path.join(pkgPath, 'package.json')
|
|
371
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
372
|
+
try {
|
|
373
|
+
const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'))
|
|
374
|
+
packages.push({
|
|
375
|
+
name: pkgJson.name || path.basename(pkgPath),
|
|
376
|
+
path: pkgPath,
|
|
377
|
+
relativePath: pattern,
|
|
378
|
+
})
|
|
379
|
+
} catch {
|
|
380
|
+
packages.push({
|
|
381
|
+
name: path.basename(pkgPath),
|
|
382
|
+
path: pkgPath,
|
|
383
|
+
relativePath: pattern,
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return packages
|
|
391
|
+
}
|
|
392
|
+
|
|
180
393
|
module.exports = {
|
|
181
394
|
mergeScripts,
|
|
182
395
|
mergeDevDependencies,
|
|
@@ -184,4 +397,6 @@ module.exports = {
|
|
|
184
397
|
detectPackageManager,
|
|
185
398
|
getInstallCommand,
|
|
186
399
|
getAuditCommand,
|
|
400
|
+
detectMonorepoType,
|
|
401
|
+
resolveWorkspacePackages,
|
|
187
402
|
}
|
|
@@ -80,6 +80,10 @@ function applyProductionQualityFixes(projectPath = '.', options = {}) {
|
|
|
80
80
|
copyIntegrationTestTemplates(projectPath, projectType)
|
|
81
81
|
fixes.push(`✅ Added ${projectType} integration test templates`)
|
|
82
82
|
|
|
83
|
+
// Fix 6b: Add starter unit and e2e smoke test stubs
|
|
84
|
+
copyTestStubs(projectPath)
|
|
85
|
+
fixes.push('✅ Added unit and e2e smoke test stubs')
|
|
86
|
+
|
|
83
87
|
// Fix 7: Apply security-first configuration
|
|
84
88
|
const securityFixes = applySecurityFirstConfiguration(projectPath)
|
|
85
89
|
fixes.push('✅ Applied security-first configuration:')
|
|
@@ -201,6 +205,35 @@ function getTestTypesDocumentation(projectType) {
|
|
|
201
205
|
)
|
|
202
206
|
}
|
|
203
207
|
|
|
208
|
+
function copyTestStubs(projectPath) {
|
|
209
|
+
const stubDir = path.join(__dirname, '../templates/test-stubs')
|
|
210
|
+
if (!fs.existsSync(stubDir)) return
|
|
211
|
+
|
|
212
|
+
const targets = [
|
|
213
|
+
{
|
|
214
|
+
source: path.join(stubDir, 'unit.test.js'),
|
|
215
|
+
dest: path.join(projectPath, 'tests', 'unit', 'sample.test.js'),
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
source: path.join(stubDir, 'e2e.smoke.test.js'),
|
|
219
|
+
dest: path.join(projectPath, 'tests', 'e2e', 'smoke.test.js'),
|
|
220
|
+
},
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
targets.forEach(({ source, dest }) => {
|
|
224
|
+
if (!fs.existsSync(source)) return
|
|
225
|
+
|
|
226
|
+
const destDir = path.dirname(dest)
|
|
227
|
+
if (!fs.existsSync(destDir)) {
|
|
228
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (!fs.existsSync(dest)) {
|
|
232
|
+
fs.copyFileSync(source, dest)
|
|
233
|
+
}
|
|
234
|
+
})
|
|
235
|
+
}
|
|
236
|
+
|
|
204
237
|
/**
|
|
205
238
|
* Generate comprehensive pre-commit hook
|
|
206
239
|
* This replaces the narrow CLAUDE.md-only validation
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-qa-architect",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "QA Architect - Bootstrap quality automation for JavaScript/TypeScript and Python projects with GitHub Actions, pre-commit hooks, linting, formatting, and smart test strategy",
|
|
5
5
|
"main": "setup.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
"validate:comprehensive": "node setup.js --comprehensive --no-markdownlint",
|
|
22
22
|
"validate:all": "npm run validate:comprehensive && npm run security:audit && npm run validate:claude",
|
|
23
23
|
"validate:pre-push": "npm run test:patterns --if-present && npm run lint && npm run format:check && npm run test:commands --if-present && npm test --if-present",
|
|
24
|
-
"test": "node tests/setup.test.js && node tests/integration.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/parallel-validation.test.js && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/template-loader.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/real-purchase-flow.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/project-maturity-cli.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js && node tests/gitleaks-real-binary-test.js && node tests/tier-enforcement.test.js",
|
|
25
|
-
"test:unit": "node tests/setup.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/template-loader.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js",
|
|
24
|
+
"test": "export QAA_DEVELOPER=true && node tests/setup.test.js && node tests/integration.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/parallel-validation.test.js && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/template-loader.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/real-purchase-flow.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/project-maturity-cli.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js && node tests/gitleaks-real-binary-test.js && node tests/tier-enforcement.test.js",
|
|
25
|
+
"test:unit": "export QAA_DEVELOPER=true && node tests/setup.test.js && node tests/error-paths.test.js && node tests/error-messages.test.js && node tests/cache-manager.test.js && node tests/template-loader.test.js && node tests/telemetry.test.js && node tests/error-reporter.test.js && node tests/validation-factory.test.js && node tests/setup-error-coverage.test.js && node tests/licensing.test.js && node tests/security-licensing.test.js && node tests/base-validator.test.js && node tests/dependency-monitoring-basic.test.js && node tests/workflow-validation.test.js && node tests/setup-critical-paths.test.js && node tests/project-maturity.test.js && node tests/package-manager-detection.test.js && node tests/check-docs.test.js && node tests/validate-claude-md.test.js && node tests/validate-command-patterns.test.js && node tests/gitleaks-binary-resolution.test.js && node tests/gitleaks-production-checksums.test.js && node tests/gitleaks-checksum-verification.test.js",
|
|
26
26
|
"test:fast": "npm run test:unit",
|
|
27
27
|
"test:medium": "npm run test:fast && npm run test:patterns && npm run test:commands",
|
|
28
|
-
"test:slow": "node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/real-purchase-flow.test.js && node tests/project-maturity-cli.test.js && node tests/gitleaks-real-binary-test.
|
|
28
|
+
"test:slow": "export QAA_DEVELOPER=true && node tests/python-integration.test.js && node tests/interactive.test.js && node tests/monorepo.test.js && node tests/critical-fixes.test.js && node tests/interactive-routing-fix.test.js && node tests/premium-dependency-monitoring.test.js && node tests/multi-language-dependency-monitoring.test.js && node tests/cli-deps-integration.test.js && node tests/real-world-packages.test.js && node tests/python-detection-sensitivity.test.js && node tests/python-parser-fixes.test.js && node tests/real-purchase-flow.test.js && node tests/project-maturity-cli.test.js && node tests/gitleaks-real-binary-test.js && npm run test:e2e",
|
|
29
29
|
"test:comprehensive": "npm run test:patterns && npm test && npm run test:commands && npm run test:e2e && npm run security:audit",
|
|
30
30
|
"test:real-binary": "RUN_REAL_BINARY_TEST=1 node tests/gitleaks-real-binary-test.js",
|
|
31
|
-
"test:commands": "node tests/command-execution.test.js",
|
|
31
|
+
"test:commands": "export QAA_DEVELOPER=true && node tests/command-execution.test.js",
|
|
32
32
|
"test:patterns": "node scripts/validate-command-patterns.js",
|
|
33
33
|
"test:coverage": "c8 --reporter=html --reporter=text --reporter=lcov npm test",
|
|
34
|
-
"test:e2e": "bash scripts/test-e2e-package.sh",
|
|
34
|
+
"test:e2e": "export QAA_DEVELOPER=true && bash scripts/test-e2e-package.sh",
|
|
35
35
|
"test:all": "npm run test:patterns && npm test && npm run test:commands && npm run test:e2e",
|
|
36
36
|
"coverage": "npm run test:coverage && echo '\nCoverage report generated in coverage/index.html'",
|
|
37
37
|
"docs:check": "bash scripts/check-docs.sh",
|
|
@@ -63,14 +63,16 @@
|
|
|
63
63
|
"security-audit"
|
|
64
64
|
],
|
|
65
65
|
"author": "Brett Stark",
|
|
66
|
-
"license": "
|
|
66
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
67
67
|
"files": [
|
|
68
68
|
"setup.js",
|
|
69
69
|
"create-saas-monetization.js",
|
|
70
70
|
"config/",
|
|
71
|
+
"docs/",
|
|
71
72
|
"lib/",
|
|
72
73
|
"legal/",
|
|
73
74
|
"marketing/",
|
|
75
|
+
"templates/",
|
|
74
76
|
".github/",
|
|
75
77
|
".prettierrc",
|
|
76
78
|
".prettierignore",
|
package/setup.js
CHANGED
|
@@ -113,6 +113,27 @@ const STYLELINT_EXTENSION_GLOB = `*.{${STYLELINT_EXTENSIONS.join(',')}}`
|
|
|
113
113
|
const STYLELINT_SCAN_EXCLUDES = new Set(EXCLUDE_DIRECTORIES.STYLELINT)
|
|
114
114
|
const MAX_STYLELINT_SCAN_DEPTH = SCAN_LIMITS.STYLELINT_MAX_DEPTH
|
|
115
115
|
|
|
116
|
+
function injectCollaborationSteps(workflowContent, options = {}) {
|
|
117
|
+
const { enableSlackAlerts = false, enablePrComments = false } = options
|
|
118
|
+
let updated = workflowContent
|
|
119
|
+
|
|
120
|
+
if (workflowContent.includes('# ALERTS_PLACEHOLDER')) {
|
|
121
|
+
const alertsJob = enableSlackAlerts
|
|
122
|
+
? ` alerts:\n runs-on: ubuntu-latest\n needs: [summary]\n if: failure() || cancelled()\n steps:\n - name: Notify Slack on failures\n env:\n SLACK_WEBHOOK_URL: \${{ secrets.SLACK_WEBHOOK_URL }}\n run: |\n if [ -z "$SLACK_WEBHOOK_URL" ]; then\n echo "::warning::SLACK_WEBHOOK_URL not set; skipping Slack notification"\n exit 0\n fi\n payload='{"text":"❌ Quality checks failed for $GITHUB_REPOSITORY ($GITHUB_REF)"}'\n curl -X POST -H 'Content-type: application/json' --data "$payload" "$SLACK_WEBHOOK_URL"\n`
|
|
123
|
+
: ' # Slack alerts not enabled (use --alerts-slack to add)'
|
|
124
|
+
updated = updated.replace('# ALERTS_PLACEHOLDER', alertsJob)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (workflowContent.includes('# PR_COMMENTS_PLACEHOLDER')) {
|
|
128
|
+
const prSteps = enablePrComments
|
|
129
|
+
? ` - name: Post PR summary comment\n if: github.event_name == 'pull_request'\n uses: actions/github-script@v7\n with:\n script: |\n const summaryPath = process.env.GITHUB_STEP_SUMMARY\n const fs = require('fs')\n const body = summaryPath && fs.existsSync(summaryPath)\n ? fs.readFileSync(summaryPath, 'utf8')\n : 'Quality checks completed.'\n const { context, github } = require('@actions/github')\n await github.rest.issues.createComment({\n owner: context.repo.owner,\n repo: context.repo.repo,\n issue_number: context.payload.pull_request.number,\n body,\n })\n`
|
|
130
|
+
: ' # PR comment step not enabled (use --pr-comments to add)'
|
|
131
|
+
updated = updated.replace('# PR_COMMENTS_PLACEHOLDER', prSteps)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return updated
|
|
135
|
+
}
|
|
136
|
+
|
|
116
137
|
/**
|
|
117
138
|
* Safely reads directory contents without throwing on permission errors
|
|
118
139
|
*
|
|
@@ -312,6 +333,13 @@ function parseArguments(rawArgs) {
|
|
|
312
333
|
const isValidateConfigMode = sanitizedArgs.includes('--validate-config')
|
|
313
334
|
const isActivateLicenseMode = sanitizedArgs.includes('--activate-license')
|
|
314
335
|
const isDryRun = sanitizedArgs.includes('--dry-run')
|
|
336
|
+
const ciProviderIndex = sanitizedArgs.findIndex(arg => arg === '--ci')
|
|
337
|
+
const ciProvider =
|
|
338
|
+
ciProviderIndex !== -1 && sanitizedArgs[ciProviderIndex + 1]
|
|
339
|
+
? sanitizedArgs[ciProviderIndex + 1].toLowerCase()
|
|
340
|
+
: 'github'
|
|
341
|
+
const enableSlackAlerts = sanitizedArgs.includes('--alerts-slack')
|
|
342
|
+
const enablePrComments = sanitizedArgs.includes('--pr-comments')
|
|
315
343
|
|
|
316
344
|
// Custom template directory - use raw args to preserve valid path characters (&, <, >, etc.)
|
|
317
345
|
// Normalize path to prevent traversal attacks and make absolute
|
|
@@ -344,6 +372,9 @@ function parseArguments(rawArgs) {
|
|
|
344
372
|
isValidateConfigMode,
|
|
345
373
|
isActivateLicenseMode,
|
|
346
374
|
isDryRun,
|
|
375
|
+
ciProvider,
|
|
376
|
+
enableSlackAlerts,
|
|
377
|
+
enablePrComments,
|
|
347
378
|
customTemplatePath,
|
|
348
379
|
disableNpmAudit,
|
|
349
380
|
disableGitleaks,
|
|
@@ -378,6 +409,9 @@ function parseArguments(rawArgs) {
|
|
|
378
409
|
isValidateConfigMode,
|
|
379
410
|
isActivateLicenseMode,
|
|
380
411
|
isDryRun,
|
|
412
|
+
ciProvider,
|
|
413
|
+
enableSlackAlerts,
|
|
414
|
+
enablePrComments,
|
|
381
415
|
customTemplatePath,
|
|
382
416
|
disableNpmAudit,
|
|
383
417
|
disableGitleaks,
|
|
@@ -444,7 +478,12 @@ function parseArguments(rawArgs) {
|
|
|
444
478
|
isTelemetryStatusMode,
|
|
445
479
|
isErrorReportingStatusMode,
|
|
446
480
|
isCheckMaturityMode,
|
|
481
|
+
isValidateConfigMode,
|
|
482
|
+
isActivateLicenseMode,
|
|
447
483
|
isDryRun,
|
|
484
|
+
ciProvider,
|
|
485
|
+
enableSlackAlerts,
|
|
486
|
+
enablePrComments,
|
|
448
487
|
customTemplatePath,
|
|
449
488
|
disableNpmAudit,
|
|
450
489
|
disableGitleaks,
|
|
@@ -481,6 +520,7 @@ SETUP OPTIONS:
|
|
|
481
520
|
--update Update existing configuration
|
|
482
521
|
--deps Add basic dependency monitoring (Free Tier)
|
|
483
522
|
--dependency-monitoring Same as --deps
|
|
523
|
+
--ci <provider> Select CI provider: github (default) | gitlab | circleci
|
|
484
524
|
--template <path> Use custom templates from specified directory
|
|
485
525
|
--dry-run Preview changes without modifying files
|
|
486
526
|
|
|
@@ -506,6 +546,10 @@ GRANULAR TOOL CONTROL:
|
|
|
506
546
|
--no-markdownlint Disable markdownlint markdown formatting checks
|
|
507
547
|
--no-eslint-security Disable ESLint security rule checking
|
|
508
548
|
|
|
549
|
+
ALERTING & COLLABORATION (GitHub CI):
|
|
550
|
+
--alerts-slack Add Slack webhook notification step (expects secret SLACK_WEBHOOK_URL)
|
|
551
|
+
--pr-comments Add PR summary comment step (uses GitHub token)
|
|
552
|
+
|
|
509
553
|
EXAMPLES:
|
|
510
554
|
npx create-qa-architect@latest
|
|
511
555
|
→ Set up quality automation with all tools
|
|
@@ -697,7 +741,7 @@ HELP:
|
|
|
697
741
|
if (!capCheck.allowed) {
|
|
698
742
|
console.error(`❌ ${capCheck.reason}`)
|
|
699
743
|
console.error(
|
|
700
|
-
' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/
|
|
744
|
+
' Upgrade to Pro, Team, or Enterprise for unlimited runs: https://vibebuildlab.com/qaa'
|
|
701
745
|
)
|
|
702
746
|
process.exit(1)
|
|
703
747
|
}
|
|
@@ -961,7 +1005,7 @@ HELP:
|
|
|
961
1005
|
if (!repoCheck.allowed) {
|
|
962
1006
|
console.error(`\n❌ ${repoCheck.reason}`)
|
|
963
1007
|
console.error(
|
|
964
|
-
' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/
|
|
1008
|
+
' Upgrade to Pro for unlimited repos: https://vibebuildlab.com/qaa'
|
|
965
1009
|
)
|
|
966
1010
|
process.exit(1)
|
|
967
1011
|
}
|
|
@@ -969,7 +1013,7 @@ HELP:
|
|
|
969
1013
|
// Register this repo
|
|
970
1014
|
incrementUsage('repo', 1, repoId)
|
|
971
1015
|
console.log(
|
|
972
|
-
`✅ Registered repo (FREE tier: ${
|
|
1016
|
+
`✅ Registered repo (FREE tier: ${(repoCheck.usage?.repoCount || 0) + 1}/1 repos used)`
|
|
973
1017
|
)
|
|
974
1018
|
}
|
|
975
1019
|
}
|
|
@@ -1351,28 +1395,72 @@ HELP:
|
|
|
1351
1395
|
process.exit(1)
|
|
1352
1396
|
}
|
|
1353
1397
|
|
|
1354
|
-
// Create
|
|
1398
|
+
// Create CI configuration based on provider
|
|
1355
1399
|
const configSpinner = showProgress('Copying configuration files...')
|
|
1356
|
-
const
|
|
1357
|
-
if (!fs.existsSync(workflowDir)) {
|
|
1358
|
-
fs.mkdirSync(workflowDir, { recursive: true })
|
|
1359
|
-
console.log('📁 Created .github/workflows directory')
|
|
1360
|
-
}
|
|
1400
|
+
const githubWorkflowDir = path.join(process.cwd(), '.github', 'workflows')
|
|
1361
1401
|
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1402
|
+
if (ciProvider === 'gitlab') {
|
|
1403
|
+
const gitlabConfigPath = path.join(process.cwd(), '.gitlab-ci.yml')
|
|
1404
|
+
if (!fs.existsSync(gitlabConfigPath)) {
|
|
1405
|
+
const templateGitlab =
|
|
1406
|
+
templateLoader.getTemplate(
|
|
1407
|
+
templates,
|
|
1408
|
+
path.join('ci', 'gitlab-ci.yml')
|
|
1409
|
+
) ||
|
|
1410
|
+
fs.readFileSync(
|
|
1411
|
+
path.join(__dirname, 'templates/ci/gitlab-ci.yml'),
|
|
1412
|
+
'utf8'
|
|
1413
|
+
)
|
|
1414
|
+
fs.writeFileSync(gitlabConfigPath, templateGitlab)
|
|
1415
|
+
console.log('✅ Added GitLab CI workflow')
|
|
1416
|
+
}
|
|
1417
|
+
} else if (ciProvider === 'circleci') {
|
|
1418
|
+
const circleDir = path.join(process.cwd(), '.circleci')
|
|
1419
|
+
if (!fs.existsSync(circleDir)) {
|
|
1420
|
+
fs.mkdirSync(circleDir, { recursive: true })
|
|
1421
|
+
console.log('📁 Created .circleci directory')
|
|
1422
|
+
}
|
|
1423
|
+
const circleConfigPath = path.join(circleDir, 'config.yml')
|
|
1424
|
+
if (!fs.existsSync(circleConfigPath)) {
|
|
1425
|
+
const templateCircle =
|
|
1426
|
+
templateLoader.getTemplate(
|
|
1427
|
+
templates,
|
|
1428
|
+
path.join('ci', 'circleci-config.yml')
|
|
1429
|
+
) ||
|
|
1430
|
+
fs.readFileSync(
|
|
1431
|
+
path.join(__dirname, 'templates/ci/circleci-config.yml'),
|
|
1432
|
+
'utf8'
|
|
1433
|
+
)
|
|
1434
|
+
fs.writeFileSync(circleConfigPath, templateCircle)
|
|
1435
|
+
console.log('✅ Added CircleCI workflow')
|
|
1436
|
+
}
|
|
1437
|
+
} else {
|
|
1438
|
+
// Default: GitHub Actions
|
|
1439
|
+
if (!fs.existsSync(githubWorkflowDir)) {
|
|
1440
|
+
fs.mkdirSync(githubWorkflowDir, { recursive: true })
|
|
1441
|
+
console.log('📁 Created .github/workflows directory')
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const workflowFile = path.join(githubWorkflowDir, 'quality.yml')
|
|
1445
|
+
if (!fs.existsSync(workflowFile)) {
|
|
1446
|
+
let templateWorkflow =
|
|
1447
|
+
templateLoader.getTemplate(
|
|
1448
|
+
templates,
|
|
1449
|
+
path.join('.github', 'workflows', 'quality.yml')
|
|
1450
|
+
) ||
|
|
1451
|
+
fs.readFileSync(
|
|
1452
|
+
path.join(__dirname, '.github/workflows/quality.yml'),
|
|
1453
|
+
'utf8'
|
|
1454
|
+
)
|
|
1455
|
+
|
|
1456
|
+
templateWorkflow = injectCollaborationSteps(templateWorkflow, {
|
|
1457
|
+
enableSlackAlerts,
|
|
1458
|
+
enablePrComments,
|
|
1459
|
+
})
|
|
1460
|
+
|
|
1461
|
+
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
1462
|
+
console.log('✅ Added GitHub Actions workflow')
|
|
1463
|
+
}
|
|
1376
1464
|
}
|
|
1377
1465
|
|
|
1378
1466
|
// Copy Prettier config if it doesn't exist
|
|
@@ -1602,7 +1690,7 @@ try {
|
|
|
1602
1690
|
const CAP = 50
|
|
1603
1691
|
if (usage.prePushRuns >= CAP) {
|
|
1604
1692
|
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/
|
|
1693
|
+
console.error(' Upgrade to Pro, Team, or Enterprise: https://vibebuildlab.com/qaa')
|
|
1606
1694
|
process.exit(1)
|
|
1607
1695
|
}
|
|
1608
1696
|
|
|
@@ -1762,20 +1850,25 @@ echo "✅ Pre-push validation passed!"
|
|
|
1762
1850
|
console.log('✅ Added requirements-dev.txt')
|
|
1763
1851
|
}
|
|
1764
1852
|
|
|
1765
|
-
// Copy Python workflow
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1853
|
+
// Copy Python workflow (GitHub Actions only)
|
|
1854
|
+
if (ciProvider === 'github') {
|
|
1855
|
+
const pythonWorkflowFile = path.join(
|
|
1856
|
+
githubWorkflowDir,
|
|
1857
|
+
'quality-python.yml'
|
|
1858
|
+
)
|
|
1859
|
+
if (!fs.existsSync(pythonWorkflowFile)) {
|
|
1860
|
+
const templatePythonWorkflow =
|
|
1861
|
+
templateLoader.getTemplate(
|
|
1862
|
+
templates,
|
|
1863
|
+
path.join('config', 'quality-python.yml')
|
|
1864
|
+
) ||
|
|
1865
|
+
fs.readFileSync(
|
|
1866
|
+
path.join(__dirname, 'config/quality-python.yml'),
|
|
1867
|
+
'utf8'
|
|
1868
|
+
)
|
|
1869
|
+
fs.writeFileSync(pythonWorkflowFile, templatePythonWorkflow)
|
|
1870
|
+
console.log('✅ Added Python GitHub Actions workflow')
|
|
1871
|
+
}
|
|
1779
1872
|
}
|
|
1780
1873
|
|
|
1781
1874
|
// Create tests directory if it doesn't exist
|