create-qa-architect 5.12.1 → 5.13.3
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/dependabot.yml +10 -30
- package/.github/workflows/claude-md-validation.yml +5 -7
- package/.github/workflows/dependabot-auto-merge.yml +1 -0
- package/.github/workflows/quality.yml +26 -12
- package/.github/workflows/release.yml +2 -1
- package/.github/workflows/stale-prs.yml +42 -0
- package/.github/workflows/weekly-gitleaks-verification.yml +6 -4
- package/LICENSE +5 -5
- package/README.md +22 -21
- package/config/defaults.js +2 -3
- package/config/quality-config.schema.json +1 -1
- package/docs/CI-COST-ANALYSIS.md +8 -8
- package/docs/DEPLOYMENT.md +1 -1
- package/docs/DEVELOPMENT-WORKFLOW.md +2 -2
- package/docs/TURBOREPO-SUPPORT.md +3 -3
- package/docs/dev_guide/CONVENTIONS.md +132 -0
- package/eslint.config.cjs +25 -0
- package/lib/blob-storage.js +57 -0
- package/lib/commands/analyze-ci.js +267 -27
- package/lib/commands/deps.js +5 -5
- package/lib/commands/license-commands.js +2 -2
- package/lib/commands/maturity-check.js +20 -2
- package/lib/dependency-monitoring-basic.js +4 -4
- package/lib/dependency-monitoring-premium.js +5 -5
- package/lib/license-validator.js +2 -2
- package/lib/licensing.js +7 -9
- package/lib/smart-strategy-generator.js +1 -1
- package/lib/validation/documentation.js +2 -0
- package/lib/workflow-config.js +176 -73
- package/package.json +53 -23
- package/scripts/deploy-consumers.sh +369 -0
- package/scripts/pattern-check.sh +607 -0
- package/scripts/run-semgrep.sh +244 -0
- package/scripts/smart-test-strategy.sh +1 -1
- package/setup.js +119 -71
- package/templates/CLAUDE_WORKFLOW_POLICY.md +3 -3
- package/templates/scripts/smart-test-strategy.sh +1 -1
- package/.github/workflows/auto-release.yml +0 -39
|
@@ -10,11 +10,29 @@
|
|
|
10
10
|
*/
|
|
11
11
|
function handleMaturityCheck() {
|
|
12
12
|
const { ProjectMaturityDetector } = require('../project-maturity')
|
|
13
|
+
const githubActions = process.argv.includes('--github-actions')
|
|
13
14
|
const detector = new ProjectMaturityDetector({
|
|
14
15
|
projectPath: process.cwd(),
|
|
15
|
-
verbose:
|
|
16
|
+
verbose: !githubActions,
|
|
16
17
|
})
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if (githubActions) {
|
|
20
|
+
const output = detector.generateGitHubActionsOutput()
|
|
21
|
+
console.log(`maturity=${output.maturity}`)
|
|
22
|
+
console.log(`source-count=${output.sourceCount}`)
|
|
23
|
+
console.log(`test-count=${output.testCount}`)
|
|
24
|
+
console.log(`has-deps=${output.hasDeps}`)
|
|
25
|
+
console.log(`has-docs=${output.hasDocs}`)
|
|
26
|
+
console.log(`has-css=${output.hasCss}`)
|
|
27
|
+
console.log(`has-shell=${output.hasShell}`)
|
|
28
|
+
console.log(`shell-count=${output.shellCount}`)
|
|
29
|
+
console.log(`is-shell-project=${output.isShellProject}`)
|
|
30
|
+
console.log(`required-checks=${output.requiredChecks}`)
|
|
31
|
+
console.log(`optional-checks=${output.optionalChecks}`)
|
|
32
|
+
console.log(`disabled-checks=${output.disabledChecks}`)
|
|
33
|
+
} else {
|
|
34
|
+
detector.printReport()
|
|
35
|
+
}
|
|
18
36
|
process.exit(0)
|
|
19
37
|
}
|
|
20
38
|
|
|
@@ -22,7 +22,7 @@ function hasNpmProject(projectPath) {
|
|
|
22
22
|
function generateBasicDependabotConfig(options = {}) {
|
|
23
23
|
const {
|
|
24
24
|
projectPath = '.',
|
|
25
|
-
schedule = '
|
|
25
|
+
schedule = 'monthly',
|
|
26
26
|
day = 'monday',
|
|
27
27
|
time = '09:00',
|
|
28
28
|
monorepoInfo = null, // Optional monorepo detection result
|
|
@@ -50,7 +50,7 @@ function generateBasicDependabotConfig(options = {}) {
|
|
|
50
50
|
day: day,
|
|
51
51
|
time: time,
|
|
52
52
|
},
|
|
53
|
-
'open-pull-requests-limit':
|
|
53
|
+
'open-pull-requests-limit': 2,
|
|
54
54
|
labels: ['dependencies', 'root'],
|
|
55
55
|
'commit-message': {
|
|
56
56
|
prefix: 'deps(root)',
|
|
@@ -69,7 +69,7 @@ function generateBasicDependabotConfig(options = {}) {
|
|
|
69
69
|
day: day,
|
|
70
70
|
time: time,
|
|
71
71
|
},
|
|
72
|
-
'open-pull-requests-limit':
|
|
72
|
+
'open-pull-requests-limit': 2,
|
|
73
73
|
labels: ['dependencies', pkg.name],
|
|
74
74
|
'commit-message': {
|
|
75
75
|
prefix: `deps(${pkg.name})`,
|
|
@@ -87,7 +87,7 @@ function generateBasicDependabotConfig(options = {}) {
|
|
|
87
87
|
day: day,
|
|
88
88
|
time: time,
|
|
89
89
|
},
|
|
90
|
-
'open-pull-requests-limit':
|
|
90
|
+
'open-pull-requests-limit': 2,
|
|
91
91
|
labels: ['dependencies'],
|
|
92
92
|
'commit-message': {
|
|
93
93
|
prefix: 'deps',
|
|
@@ -1294,7 +1294,7 @@ function generatePremiumDependabotConfig(options = {}) {
|
|
|
1294
1294
|
|
|
1295
1295
|
const {
|
|
1296
1296
|
projectPath = '.',
|
|
1297
|
-
schedule = '
|
|
1297
|
+
schedule = 'monthly',
|
|
1298
1298
|
day = 'monday',
|
|
1299
1299
|
time = '09:00',
|
|
1300
1300
|
} = options
|
|
@@ -1316,7 +1316,7 @@ function generatePremiumDependabotConfig(options = {}) {
|
|
|
1316
1316
|
'package-ecosystem': 'npm',
|
|
1317
1317
|
directory: '/',
|
|
1318
1318
|
schedule: { interval: schedule, day, time },
|
|
1319
|
-
'open-pull-requests-limit':
|
|
1319
|
+
'open-pull-requests-limit': 3,
|
|
1320
1320
|
labels: ['dependencies', 'npm'],
|
|
1321
1321
|
'commit-message': { prefix: 'deps(npm)', include: 'scope' },
|
|
1322
1322
|
...(Object.keys(npmGroups).length > 0 && { groups: npmGroups }),
|
|
@@ -1330,7 +1330,7 @@ function generatePremiumDependabotConfig(options = {}) {
|
|
|
1330
1330
|
'package-ecosystem': 'pip',
|
|
1331
1331
|
directory: '/',
|
|
1332
1332
|
schedule: { interval: schedule, day, time },
|
|
1333
|
-
'open-pull-requests-limit':
|
|
1333
|
+
'open-pull-requests-limit': 3,
|
|
1334
1334
|
labels: ['dependencies', 'python'],
|
|
1335
1335
|
'commit-message': { prefix: 'deps(python)' },
|
|
1336
1336
|
...(Object.keys(pipGroups).length > 0 && { groups: pipGroups }),
|
|
@@ -1344,7 +1344,7 @@ function generatePremiumDependabotConfig(options = {}) {
|
|
|
1344
1344
|
'package-ecosystem': 'cargo',
|
|
1345
1345
|
directory: '/',
|
|
1346
1346
|
schedule: { interval: schedule, day, time },
|
|
1347
|
-
'open-pull-requests-limit':
|
|
1347
|
+
'open-pull-requests-limit': 3,
|
|
1348
1348
|
labels: ['dependencies', 'rust'],
|
|
1349
1349
|
'commit-message': { prefix: 'deps(rust)' },
|
|
1350
1350
|
...(Object.keys(cargoGroups).length > 0 && { groups: cargoGroups }),
|
|
@@ -1358,7 +1358,7 @@ function generatePremiumDependabotConfig(options = {}) {
|
|
|
1358
1358
|
'package-ecosystem': 'bundler',
|
|
1359
1359
|
directory: '/',
|
|
1360
1360
|
schedule: { interval: schedule, day, time },
|
|
1361
|
-
'open-pull-requests-limit':
|
|
1361
|
+
'open-pull-requests-limit': 3,
|
|
1362
1362
|
labels: ['dependencies', 'ruby'],
|
|
1363
1363
|
'commit-message': { prefix: 'deps(ruby)' },
|
|
1364
1364
|
...(Object.keys(bundlerGroups).length > 0 && { groups: bundlerGroups }),
|
package/lib/license-validator.js
CHANGED
|
@@ -85,7 +85,7 @@ class LicenseValidator {
|
|
|
85
85
|
// Allow enterprises to host their own registry
|
|
86
86
|
this.licenseDbUrl =
|
|
87
87
|
process.env.QAA_LICENSE_DB_URL ||
|
|
88
|
-
'https://
|
|
88
|
+
'https://buildproven.ai/api/licenses/qa-architect.json'
|
|
89
89
|
|
|
90
90
|
this.licensePublicKey = loadKeyFromEnv(
|
|
91
91
|
process.env.QAA_LICENSE_PUBLIC_KEY,
|
|
@@ -691,4 +691,4 @@ class LicenseValidator {
|
|
|
691
691
|
}
|
|
692
692
|
}
|
|
693
693
|
|
|
694
|
-
module.exports = { LicenseValidator }
|
|
694
|
+
module.exports = { LicenseValidator, validateLicenseDir }
|
package/lib/licensing.js
CHANGED
|
@@ -16,15 +16,16 @@ const {
|
|
|
16
16
|
verifyPayload,
|
|
17
17
|
loadKeyFromEnv,
|
|
18
18
|
} = require('./license-signing')
|
|
19
|
+
const { validateLicenseDir } = require('./license-validator')
|
|
19
20
|
|
|
20
21
|
// License storage locations
|
|
21
22
|
// Support environment variable override for testing (like telemetry/error-reporter)
|
|
22
23
|
// Use getter functions to allow env override before module load
|
|
23
24
|
function getLicenseDir() {
|
|
24
|
-
|
|
25
|
+
const requestedDir =
|
|
25
26
|
process.env.QAA_LICENSE_DIR ||
|
|
26
27
|
path.join(os.homedir(), '.create-qa-architect')
|
|
27
|
-
)
|
|
28
|
+
return validateLicenseDir(requestedDir)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function getLicenseFile() {
|
|
@@ -399,7 +400,7 @@ function showUpgradeMessage(feature) {
|
|
|
399
400
|
console.log('')
|
|
400
401
|
console.log(' 🎁 Start 14-day free trial - no credit card required')
|
|
401
402
|
console.log('')
|
|
402
|
-
console.log('🚀 Upgrade: https://
|
|
403
|
+
console.log('🚀 Upgrade: https://buildproven.ai/qa-architect')
|
|
403
404
|
console.log(
|
|
404
405
|
'🔑 Activate: npx create-qa-architect@latest --activate-license'
|
|
405
406
|
)
|
|
@@ -586,10 +587,7 @@ async function addLegitimateKey(
|
|
|
586
587
|
}
|
|
587
588
|
|
|
588
589
|
const normalizedKey = normalizeLicenseKey(licenseKey)
|
|
589
|
-
|
|
590
|
-
const licenseDir =
|
|
591
|
-
process.env.QAA_LICENSE_DIR ||
|
|
592
|
-
path.join(os.homedir(), '.create-qa-architect')
|
|
590
|
+
const licenseDir = getLicenseDir()
|
|
593
591
|
const legitimateDBFile = path.join(licenseDir, 'legitimate-licenses.json')
|
|
594
592
|
const privateKey = loadKeyFromEnv(
|
|
595
593
|
process.env.LICENSE_REGISTRY_PRIVATE_KEY,
|
|
@@ -745,7 +743,7 @@ async function promptLicenseActivation() {
|
|
|
745
743
|
console.log(
|
|
746
744
|
' If you purchased this license, please contact support at:'
|
|
747
745
|
)
|
|
748
|
-
console.log(' Email: support@
|
|
746
|
+
console.log(' Email: support@buildproven.ai')
|
|
749
747
|
console.log(
|
|
750
748
|
' Include your license key and purchase email for verification.'
|
|
751
749
|
)
|
|
@@ -1140,7 +1138,7 @@ function showLicenseStatus() {
|
|
|
1140
1138
|
// Show upgrade path
|
|
1141
1139
|
if (license.tier === LICENSE_TIERS.FREE) {
|
|
1142
1140
|
console.log('\n💡 Upgrade to PRO for unlimited access + security scanning')
|
|
1143
|
-
console.log(' → https://
|
|
1141
|
+
console.log(' → https://buildproven.ai/qa-architect')
|
|
1144
1142
|
}
|
|
1145
1143
|
}
|
|
1146
1144
|
|
|
@@ -255,7 +255,7 @@ function generateSmartStrategy(options = {}) {
|
|
|
255
255
|
`Troubleshooting steps:\n` +
|
|
256
256
|
`1. Reinstall the package: npm install -g create-qa-architect@latest\n` +
|
|
257
257
|
`2. Check file permissions: ls -la ${path.dirname(templatePath)}\n` +
|
|
258
|
-
`3. Report issue if problem persists: https://github.com/
|
|
258
|
+
`3. Report issue if problem persists: https://github.com/buildproven/qa-architect/issues/new`
|
|
259
259
|
)
|
|
260
260
|
}
|
|
261
261
|
|
|
@@ -267,6 +267,8 @@ class DocumentationValidator {
|
|
|
267
267
|
'quality-python.yml',
|
|
268
268
|
'ci.yml', // Common workflow name users might have
|
|
269
269
|
'test.yml', // Common workflow name users might have
|
|
270
|
+
'tests.yml', // Common workflow name users might have
|
|
271
|
+
'quality-legacy.yml', // Legacy workflow name referenced in cleanup docs
|
|
270
272
|
// Optional tooling configs
|
|
271
273
|
'.lighthouserc.js',
|
|
272
274
|
'vercel.json',
|
package/lib/workflow-config.js
CHANGED
|
@@ -65,8 +65,144 @@ function detectExistingWorkflowMode(projectPath) {
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Detect whether matrix testing is enabled in an existing workflow.
|
|
70
|
+
* @param {string} projectPath - Path to project
|
|
71
|
+
* @returns {boolean} True when matrix testing is enabled
|
|
72
|
+
*/
|
|
73
|
+
function detectExistingMatrix(projectPath) {
|
|
74
|
+
const workflowPath = path.join(
|
|
75
|
+
projectPath,
|
|
76
|
+
'.github',
|
|
77
|
+
'workflows',
|
|
78
|
+
'quality.yml'
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(workflowPath)) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const content = fs.readFileSync(workflowPath, 'utf8')
|
|
87
|
+
return (
|
|
88
|
+
content.includes('# MATRIX_ENABLED: true') ||
|
|
89
|
+
content.includes('node-version: [20, 22]')
|
|
90
|
+
)
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.warn(
|
|
93
|
+
`⚠️ Could not detect existing matrix configuration: ${error.message}`
|
|
94
|
+
)
|
|
95
|
+
return false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Strip a named section from workflow content.
|
|
101
|
+
* Removes everything between # {{NAME_BEGIN}} and # {{NAME_END}} markers (inclusive).
|
|
102
|
+
* Leaves a single newline to avoid collapsing adjacent YAML blocks.
|
|
103
|
+
* @param {string} content - Workflow content
|
|
104
|
+
* @param {string} sectionName - Section name (e.g. 'QA_ARCHITECT_ONLY', 'FULL_DETECTION')
|
|
105
|
+
* @returns {string} Content with section removed
|
|
106
|
+
*/
|
|
107
|
+
function stripSection(content, sectionName) {
|
|
108
|
+
if (!/^[A-Z_]+$/.test(sectionName)) {
|
|
109
|
+
throw new Error(`Invalid section name: ${sectionName}`)
|
|
110
|
+
}
|
|
111
|
+
// eslint-disable-next-line security/detect-non-literal-regexp -- sectionName validated above
|
|
112
|
+
const pattern = new RegExp(
|
|
113
|
+
`[^\\S\\n]*# \\{\\{${sectionName}_BEGIN\\}\\}[\\s\\S]*?# \\{\\{${sectionName}_END\\}\\}\\n?`,
|
|
114
|
+
'g'
|
|
115
|
+
)
|
|
116
|
+
return content.replace(pattern, '')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Remove a paths-ignore block from a top-level workflow trigger.
|
|
121
|
+
* @param {string} content - Workflow content
|
|
122
|
+
* @param {string} triggerName - Trigger key (e.g. push, pull_request)
|
|
123
|
+
* @returns {string} Updated content
|
|
124
|
+
*/
|
|
125
|
+
function removeTriggerPathsIgnore(content, triggerName) {
|
|
126
|
+
const lines = content.split('\n')
|
|
127
|
+
const output = []
|
|
128
|
+
|
|
129
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
130
|
+
const line = lines[index]
|
|
131
|
+
output.push(line)
|
|
132
|
+
|
|
133
|
+
if (
|
|
134
|
+
line !== ` ${triggerName}:` &&
|
|
135
|
+
!line.startsWith(` ${triggerName}: `)
|
|
136
|
+
) {
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let scanIndex = index + 1
|
|
141
|
+
while (scanIndex < lines.length) {
|
|
142
|
+
const nextLine = lines[scanIndex]
|
|
143
|
+
|
|
144
|
+
if (nextLine.startsWith(' ') && !nextLine.startsWith(' ')) {
|
|
145
|
+
break
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (nextLine === ' paths-ignore:') {
|
|
149
|
+
scanIndex += 1
|
|
150
|
+
while (
|
|
151
|
+
scanIndex < lines.length &&
|
|
152
|
+
lines[scanIndex].startsWith(' - ')
|
|
153
|
+
) {
|
|
154
|
+
scanIndex += 1
|
|
155
|
+
}
|
|
156
|
+
index = scanIndex - 1
|
|
157
|
+
break
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
output.push(nextLine)
|
|
161
|
+
index = scanIndex
|
|
162
|
+
scanIndex += 1
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return output.join('\n')
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Restrict the tests job to main branch pushes in standard mode.
|
|
171
|
+
* @param {string} content - Workflow content
|
|
172
|
+
* @returns {string} Updated content
|
|
173
|
+
*/
|
|
174
|
+
function addStandardTestsBranchGate(content) {
|
|
175
|
+
const lines = content.split('\n')
|
|
176
|
+
const output = []
|
|
177
|
+
let inTestsJob = false
|
|
178
|
+
let branchGateInserted = false
|
|
179
|
+
|
|
180
|
+
for (const line of lines) {
|
|
181
|
+
if (line === ' tests:') {
|
|
182
|
+
inTestsJob = true
|
|
183
|
+
} else if (
|
|
184
|
+
inTestsJob &&
|
|
185
|
+
line.startsWith(' ') &&
|
|
186
|
+
!line.startsWith(' ')
|
|
187
|
+
) {
|
|
188
|
+
inTestsJob = false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
output.push(line)
|
|
192
|
+
|
|
193
|
+
if (inTestsJob && line === ' if: |' && !branchGateInserted) {
|
|
194
|
+
output.push(" github.ref == 'refs/heads/main' &&")
|
|
195
|
+
branchGateInserted = true
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return output.join('\n')
|
|
200
|
+
}
|
|
201
|
+
|
|
68
202
|
/**
|
|
69
203
|
* Inject workflow mode-specific configuration into quality.yml
|
|
204
|
+
* Uses section markers (# {{SECTION_BEGIN/END}}) for reliable content removal
|
|
205
|
+
* instead of fragile structural regex patterns.
|
|
70
206
|
* @param {string} workflowContent - Template content
|
|
71
207
|
* @param {'minimal'|'standard'|'comprehensive'} mode - Selected mode
|
|
72
208
|
* @returns {string} Modified workflow content
|
|
@@ -74,101 +210,66 @@ function detectExistingWorkflowMode(projectPath) {
|
|
|
74
210
|
function injectWorkflowMode(workflowContent, mode) {
|
|
75
211
|
let updated = workflowContent
|
|
76
212
|
|
|
213
|
+
// Set workflow mode marker
|
|
77
214
|
const versionMarker = `# WORKFLOW_MODE: ${mode}`
|
|
78
215
|
if (updated.includes('# WORKFLOW_MODE:')) {
|
|
79
|
-
// Replace existing marker with new mode
|
|
80
216
|
updated = updated.replace(
|
|
81
217
|
/# WORKFLOW_MODE: (minimal|standard|comprehensive)/,
|
|
82
218
|
versionMarker
|
|
83
219
|
)
|
|
84
220
|
} else {
|
|
85
|
-
// Add marker before jobs:
|
|
86
221
|
updated = updated.replace(/(\n\njobs:)/, `\n${versionMarker}\n$1`)
|
|
87
222
|
}
|
|
88
223
|
|
|
89
|
-
//
|
|
90
|
-
|
|
224
|
+
// All consumer workflows: strip qa-architect-only content
|
|
225
|
+
updated = stripSection(updated, 'QA_ARCHITECT_ONLY')
|
|
91
226
|
|
|
92
|
-
//
|
|
227
|
+
// Mode-specific transformations
|
|
93
228
|
if (mode === 'standard') {
|
|
94
|
-
// Standard:
|
|
95
|
-
// Handle both single-line and multi-line if: formats
|
|
96
|
-
|
|
97
|
-
// First try multi-line format (if: | block)
|
|
229
|
+
// Standard: run tests on main only to keep CI costs bounded.
|
|
98
230
|
if (
|
|
99
|
-
updated.includes('tests:') &&
|
|
100
|
-
updated.includes('
|
|
231
|
+
updated.includes(' tests:') &&
|
|
232
|
+
updated.includes(' if: |') &&
|
|
233
|
+
!updated.includes("github.ref == 'refs/heads/main'")
|
|
101
234
|
) {
|
|
102
|
-
|
|
103
|
-
updated = updated.replace(
|
|
104
|
-
/(tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+if: \|\s*\n\s+)fromJSON\(needs\.detect-maturity\.outputs\.test-count\)/,
|
|
105
|
-
"$1github.ref == 'refs/heads/main' &&\n fromJSON(needs.detect-maturity.outputs.test-count)"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
// Also try single-line format as fallback
|
|
109
|
-
updated = updated.replace(
|
|
110
|
-
/(\s+tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+)if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0/,
|
|
111
|
-
"$1if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0"
|
|
112
|
-
)
|
|
235
|
+
updated = addStandardTestsBranchGate(updated)
|
|
113
236
|
}
|
|
114
|
-
|
|
115
|
-
// Standard: Change matrix to [20, 22]
|
|
116
|
-
updated = updated.replace(/node-version: \[22\]/g, 'node-version: [20, 22]')
|
|
117
237
|
} else if (mode === 'comprehensive') {
|
|
118
|
-
// Comprehensive: Remove paths-ignore blocks
|
|
119
|
-
updated = updated
|
|
120
|
-
|
|
121
|
-
'$1'
|
|
122
|
-
)
|
|
123
|
-
updated = updated.replace(
|
|
124
|
-
/(\s+pull_request:\s+branches:[^\n]+)\s+paths-ignore:\s+- '\*\*\.md'\s+- 'docs\/\*\*'\s+- 'LICENSE'\s+- '\.gitignore'\s+- '\.editorconfig'/g,
|
|
125
|
-
'$1'
|
|
126
|
-
)
|
|
127
|
-
|
|
238
|
+
// Comprehensive: Remove paths-ignore blocks
|
|
239
|
+
updated = removeTriggerPathsIgnore(updated, 'push')
|
|
240
|
+
updated = removeTriggerPathsIgnore(updated, 'pull_request')
|
|
128
241
|
// Comprehensive: Remove schedule trigger (security runs inline)
|
|
129
242
|
updated = updated.replace(/\s+schedule:\s+- cron:[^\n]+[^\n]*\n?/g, '\n')
|
|
130
|
-
|
|
131
243
|
// Comprehensive: Remove schedule condition from security job
|
|
132
244
|
updated = updated.replace(
|
|
133
245
|
/if: \(github\.event_name == 'schedule' \|\| github\.event_name == 'workflow_dispatch'\) && /g,
|
|
134
246
|
'if: '
|
|
135
247
|
)
|
|
136
|
-
|
|
137
|
-
// Comprehensive: Change matrix to [20, 22]
|
|
138
248
|
updated = updated.replace(/node-version: \[22\]/g, 'node-version: [20, 22]')
|
|
139
249
|
}
|
|
140
|
-
// Minimal mode: Remove expensive dependency install and maturity detection steps
|
|
141
|
-
// Keep only: checkout, setup-node, detect-pm, setup-pnpm/bun, simplified report
|
|
142
|
-
if (mode === 'minimal') {
|
|
143
|
-
// 1. Remove "Install dependencies for maturity detection" step
|
|
144
|
-
updated = updated.replace(
|
|
145
|
-
/\s+- name: Install dependencies for maturity detection\s+run: \$\{\{ steps\.detect-pm\.outputs\.install-cmd \}\}\s*\n/,
|
|
146
|
-
'\n'
|
|
147
|
-
)
|
|
148
250
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
251
|
+
// Minimal mode: use section markers to strip detection, hardcode outputs
|
|
252
|
+
if (mode === 'minimal') {
|
|
253
|
+
// Strip full detection and report sections via markers
|
|
254
|
+
updated = stripSection(updated, 'FULL_DETECTION')
|
|
255
|
+
updated = stripSection(updated, 'FULL_REPORT')
|
|
154
256
|
|
|
155
|
-
//
|
|
156
|
-
// These sensible defaults allow basic checks to run without expensive detection
|
|
257
|
+
// Hardcode maturity outputs (since we skip detection)
|
|
157
258
|
updated = updated.replace(
|
|
158
259
|
/maturity: \$\{\{ steps\.detect\.outputs\.maturity \}\}/,
|
|
159
260
|
"maturity: 'minimal'"
|
|
160
261
|
)
|
|
161
262
|
updated = updated.replace(
|
|
162
263
|
/source-count: \$\{\{ steps\.detect\.outputs\.source-count \}\}/,
|
|
163
|
-
"source-count: '
|
|
264
|
+
"source-count: '0'"
|
|
164
265
|
)
|
|
165
266
|
updated = updated.replace(
|
|
166
267
|
/test-count: \$\{\{ steps\.detect\.outputs\.test-count \}\}/,
|
|
167
|
-
"test-count: '
|
|
268
|
+
"test-count: '0'"
|
|
168
269
|
)
|
|
169
270
|
updated = updated.replace(
|
|
170
271
|
/has-deps: \$\{\{ steps\.detect\.outputs\.has-deps \}\}/,
|
|
171
|
-
"has-deps: '
|
|
272
|
+
"has-deps: 'false'"
|
|
172
273
|
)
|
|
173
274
|
updated = updated.replace(
|
|
174
275
|
/has-docs: \$\{\{ steps\.detect\.outputs\.has-docs \}\}/,
|
|
@@ -179,30 +280,30 @@ function injectWorkflowMode(workflowContent, mode) {
|
|
|
179
280
|
"has-css: 'false'"
|
|
180
281
|
)
|
|
181
282
|
|
|
182
|
-
//
|
|
183
|
-
|
|
184
|
-
updated = updated.replace(
|
|
185
|
-
/\s+- name: Cache gitleaks binary for real download test[\s\S]*?restore-keys: \|[^\n]*\n[^\n]*\n[^\n]*\n/,
|
|
186
|
-
'\n'
|
|
187
|
-
)
|
|
188
|
-
updated = updated.replace(
|
|
189
|
-
/\s+- name: Run real gitleaks binary verification test[\s\S]*?node tests\/gitleaks-real-binary-test\.js\n/,
|
|
190
|
-
'\n'
|
|
191
|
-
)
|
|
192
|
-
|
|
193
|
-
// 5. Simplify "Display Detection Report" to show only package manager info
|
|
194
|
-
updated = updated.replace(
|
|
195
|
-
/- name: Display Detection Report\s+run: \|[\s\S]*?echo "Has CSS files:[^"]*"/,
|
|
196
|
-
`- name: Display Detection Report
|
|
283
|
+
// Insert simplified detection report after the last setup step
|
|
284
|
+
const minimalReport = ` - name: Display Detection Report
|
|
197
285
|
run: |
|
|
198
286
|
echo "📊 Project Detection Results (Minimal Mode)"
|
|
199
287
|
echo "Package Manager: \${{ steps.detect-pm.outputs.manager }}"
|
|
200
288
|
echo "Install Command: \${{ steps.detect-pm.outputs.install-cmd }}"
|
|
201
289
|
echo "Turborepo: \${{ steps.detect-pm.outputs.is-turborepo }}"
|
|
202
|
-
echo "
|
|
203
|
-
|
|
290
|
+
echo "Mode goal: keep GitHub Actions usage under ~1000 min/month by default"
|
|
291
|
+
echo "Checks: detection-only in CI (tests/security/docs disabled in minimal mode)"
|
|
292
|
+
echo "Use --workflow-standard or --workflow-comprehensive to enable heavier CI checks"`
|
|
293
|
+
|
|
294
|
+
// Insert report before the "Note: Lint/format" comment that follows detect-maturity job
|
|
295
|
+
if (!updated.includes('Display Detection Report')) {
|
|
296
|
+
updated = updated.replace(
|
|
297
|
+
/(\n {2}# Note: Lint\/format jobs REMOVED)/,
|
|
298
|
+
`\n${minimalReport}\n$1`
|
|
299
|
+
)
|
|
300
|
+
}
|
|
204
301
|
}
|
|
205
302
|
|
|
303
|
+
// Strip any remaining section markers from output (belt-and-suspenders)
|
|
304
|
+
// Use [ \t]* (horizontal whitespace only) — \s* would eat newlines and collapse YAML lines
|
|
305
|
+
updated = updated.replace(/[ \t]*# \{\{[A-Z_]+_(BEGIN|END)\}\}\n?/g, '')
|
|
306
|
+
|
|
206
307
|
return updated
|
|
207
308
|
}
|
|
208
309
|
|
|
@@ -241,6 +342,8 @@ function injectMatrix(workflowContent, enableMatrix) {
|
|
|
241
342
|
|
|
242
343
|
module.exports = {
|
|
243
344
|
detectExistingWorkflowMode,
|
|
345
|
+
detectExistingMatrix,
|
|
244
346
|
injectWorkflowMode,
|
|
245
347
|
injectMatrix,
|
|
348
|
+
stripSection,
|
|
246
349
|
}
|