create-qa-architect 5.13.2 → 5.13.4
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/release.yml +6 -2
- package/LICENSE +3 -15
- package/README.md +5 -3
- package/config/defaults.js +20 -5
- package/docs/STRIPE-LIVE-MODE-DEPLOYMENT.md +208 -0
- package/docs/TURBOREPO-SUPPORT.md +1 -2
- package/docs/dev_guide/CONVENTIONS.md +1 -1
- package/lib/billing-dashboard.html +3 -9
- package/lib/commands/analyze-ci.js +20 -19
- package/lib/commands/deps.js +3 -3
- package/lib/dependency-monitoring-premium.js +1 -1
- package/lib/license-validator.js +1 -1
- package/lib/licensing.js +6 -8
- package/lib/project-module-type.js +66 -0
- package/lib/quality-tools-generator.js +9 -2
- package/lib/workflow-config.js +70 -11
- package/package.json +12 -6
- package/setup.js +70 -51
- package/templates/scripts/smart-test-strategy.sh +1 -1
|
@@ -22,8 +22,12 @@ jobs:
|
|
|
22
22
|
node-version: '22'
|
|
23
23
|
registry-url: 'https://registry.npmjs.org'
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
# OIDC trusted publishing requires npm >= 11.5. We pin to 11.5.2 (the
|
|
26
|
+
# first OIDC-stable release) instead of `npm@latest` to avoid the
|
|
27
|
+
# MODULE_NOT_FOUND (promise-retry) regression hit when self-upgrading
|
|
28
|
+
# over the setup-node-provided npm on top of the newest 11.x.
|
|
29
|
+
- name: Pin npm for OIDC trusted publishing
|
|
30
|
+
run: npm install -g npm@11.5.2
|
|
27
31
|
|
|
28
32
|
- name: Install dependencies
|
|
29
33
|
run: npm ci --no-audit
|
package/LICENSE
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
VIBE BUILD LAB COMMERCIAL LICENSE
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2025
|
|
3
|
+
Copyright (c) 2025 BuildProven. All rights reserved.
|
|
4
4
|
|
|
5
5
|
COMMERCIAL SOFTWARE - FREEMIUM MODEL
|
|
6
6
|
|
|
7
7
|
This software and associated documentation files (the "Software") are
|
|
8
|
-
proprietary commercial products of
|
|
8
|
+
proprietary commercial products of BuildProven.
|
|
9
9
|
|
|
10
10
|
TERMS OF USE:
|
|
11
11
|
|
|
@@ -22,21 +22,9 @@ TERMS OF USE:
|
|
|
22
22
|
- Smart Test Strategy
|
|
23
23
|
- Multi-language support
|
|
24
24
|
- Unlimited repos
|
|
25
|
-
- Team: Contact us (coming soon)
|
|
26
|
-
- All Pro features
|
|
27
|
-
- RBAC and team policies
|
|
28
|
-
- Slack alerts
|
|
29
|
-
- Multi-repo dashboard
|
|
30
|
-
- Enterprise: Contact us (coming soon)
|
|
31
|
-
- All Team features
|
|
32
|
-
- SSO/SAML integration
|
|
33
|
-
- Custom policies
|
|
34
|
-
- Compliance pack
|
|
35
|
-
- Dedicated TAM
|
|
36
25
|
|
|
37
26
|
3. VIBE LAB PRO BUNDLE
|
|
38
27
|
Pro tier is included in the Vibe Lab Pro subscription.
|
|
39
|
-
Team and Enterprise tiers are standalone purchases.
|
|
40
28
|
|
|
41
29
|
4. PERMITTED USES
|
|
42
30
|
- Use the free tier without restriction
|
|
@@ -62,5 +50,5 @@ For licensing inquiries: support@buildproven.ai
|
|
|
62
50
|
|
|
63
51
|
---
|
|
64
52
|
|
|
65
|
-
|
|
53
|
+
BuildProven
|
|
66
54
|
https://buildproven.ai
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Quality automation CLI for JavaScript/TypeScript, Python, and shell script proje
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
> **Maintainer & Ownership**
|
|
10
|
-
> This project is maintained by **
|
|
10
|
+
> This project is maintained by **BuildProven**, a studio focused on AI-assisted product development, micro-SaaS, and "vibe coding" workflows for solo founders and small teams.
|
|
11
11
|
> Learn more at **https://buildproven.ai**.
|
|
12
12
|
|
|
13
13
|
---
|
|
@@ -158,7 +158,7 @@ npx create-qa-architect@latest --workflow-minimal
|
|
|
158
158
|
|
|
159
159
|
**Best for:** Small teams, client projects, production apps
|
|
160
160
|
|
|
161
|
-
-
|
|
161
|
+
- Single Node 22 testing **only on main branch**
|
|
162
162
|
- Security scans run monthly
|
|
163
163
|
- Path filters enabled
|
|
164
164
|
- **Runtime:** ~15-20 min/commit
|
|
@@ -276,6 +276,8 @@ npm install
|
|
|
276
276
|
npm run lint
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
+
`--update` refreshes the existing `quality.yml` from the latest template while preserving the detected workflow tier and existing matrix setting unless you explicitly override the tier with `--workflow-minimal`, `--workflow-standard`, or `--workflow-comprehensive`.
|
|
280
|
+
|
|
279
281
|
### Dependency Monitoring (Free)
|
|
280
282
|
|
|
281
283
|
```bash
|
|
@@ -453,4 +455,4 @@ Commercial freemium license — the base CLI is free to use; Pro features requir
|
|
|
453
455
|
|
|
454
456
|
---
|
|
455
457
|
|
|
456
|
-
> **
|
|
458
|
+
> **BuildProven** · [buildproven.ai](https://buildproven.ai)
|
package/config/defaults.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
const { detectDepMajor } = require('../lib/project-module-type')
|
|
4
|
+
|
|
3
5
|
const STYLELINT_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'pcss']
|
|
4
6
|
const DEFAULT_STYLELINT_TARGET = `**/*.{${STYLELINT_EXTENSIONS.join(',')}}`
|
|
5
7
|
|
|
@@ -19,7 +21,7 @@ const baseScripts = {
|
|
|
19
21
|
'test:coverage': 'vitest run --coverage',
|
|
20
22
|
'test:changed': 'vitest run --changed HEAD~1 --passWithNoTests',
|
|
21
23
|
'security:audit':
|
|
22
|
-
'[ -f pnpm-lock.yaml ]
|
|
24
|
+
'if [ -f pnpm-lock.yaml ]; then pnpm audit --audit-level high; elif [ -f yarn.lock ]; then yarn audit; else npm audit --audit-level high; fi',
|
|
23
25
|
'security:secrets':
|
|
24
26
|
"node -e \"const fs=require('fs');const content=fs.readFileSync('package.json','utf8');if(/[\\\"\\'][a-zA-Z0-9+/]{20,}[\\\"\\']/.test(content)){console.error('❌ Potential hardcoded secrets in package.json');process.exit(1)}else{console.log('✅ No secrets detected in package.json')}\"",
|
|
25
27
|
'security:config': 'npx create-qa-architect@latest --security-config',
|
|
@@ -28,8 +30,7 @@ const baseScripts = {
|
|
|
28
30
|
'validate:docs': 'npx create-qa-architect@latest --validate-docs',
|
|
29
31
|
'validate:comprehensive': 'npx create-qa-architect@latest --comprehensive',
|
|
30
32
|
'validate:all': 'npm run validate:comprehensive && npm run security:audit',
|
|
31
|
-
'validate:pre-push':
|
|
32
|
-
'npm run test:patterns --if-present && npm run test:commands --if-present && npm run test:changed --if-present || npm test --if-present',
|
|
33
|
+
'validate:pre-push': `npm run test:patterns --if-present && npm run test:commands --if-present && if node -e "const pkg=require('./package.json');process.exit(pkg.scripts&&pkg.scripts['test:changed']?0:1)" 2>/dev/null; then npm run test:changed; else npm test --if-present; fi`,
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const normalizeStylelintTargets = stylelintTargets => {
|
|
@@ -124,13 +125,27 @@ function getDefaultScripts({ stylelintTargets } = {}) {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
|
-
* @param {DefaultsOptions} [options]
|
|
128
|
+
* @param {DefaultsOptions & {projectPath?: string}} [options]
|
|
128
129
|
*/
|
|
129
|
-
function getDefaultDevDependencies({ typescript } = {}) {
|
|
130
|
+
function getDefaultDevDependencies({ typescript, projectPath } = {}) {
|
|
130
131
|
const devDeps = { ...clone(baseDevDependencies) }
|
|
131
132
|
if (typescript) {
|
|
132
133
|
Object.assign(devDeps, typeScriptDevDependencies)
|
|
133
134
|
}
|
|
135
|
+
|
|
136
|
+
// Align @vitest/coverage-v8 with the target project's installed vitest
|
|
137
|
+
// major. vitest and its coverage adapter must share a major or npm install
|
|
138
|
+
// fails with ERESOLVE. Without this, a project already on vitest@4 gets
|
|
139
|
+
// our @vitest/coverage-v8@^2.x injected and breaks.
|
|
140
|
+
if (projectPath) {
|
|
141
|
+
const vitestMajor = detectDepMajor(projectPath, 'vitest')
|
|
142
|
+
if (vitestMajor && vitestMajor >= 3) {
|
|
143
|
+
devDeps['@vitest/coverage-v8'] = `^${vitestMajor}.0.0`
|
|
144
|
+
// Also drop our pinned vitest so we don't downgrade the project
|
|
145
|
+
delete devDeps.vitest
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
134
149
|
return devDeps
|
|
135
150
|
}
|
|
136
151
|
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Stripe Live Mode Deployment Guide
|
|
2
|
+
|
|
3
|
+
Deploy the `webhook-handler.js` server to process real Stripe payments and issue signed Pro licenses automatically.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The payment flow:
|
|
8
|
+
1. Customer purchases Pro at `buildproven.ai/qa-architect`
|
|
9
|
+
2. Stripe fires a `checkout.session.completed` webhook to your server
|
|
10
|
+
3. `webhook-handler.js` validates the event, generates a signed license key, and saves it to Vercel Blob
|
|
11
|
+
4. Customer runs `npx create-qa-architect@latest --activate-license` and enters their key
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- A [Stripe](https://stripe.com) account with live mode enabled
|
|
18
|
+
- A [Vercel](https://vercel.com) account (for Blob storage and hosting)
|
|
19
|
+
- Node.js >=20
|
|
20
|
+
- The ED25519 private key used to sign licenses (in `public-key.pem` + your private key)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step 1: Create Stripe Products and Prices
|
|
25
|
+
|
|
26
|
+
In the [Stripe Dashboard](https://dashboard.stripe.com/products) → Products → Add product:
|
|
27
|
+
|
|
28
|
+
| Product | Price | Billing | Price ID (example) |
|
|
29
|
+
|---|---|---|---|
|
|
30
|
+
| QA Architect Pro | $49.00 | Monthly | `price_1St9K2Gv7Su9XNJbdYoH3K32` |
|
|
31
|
+
| QA Architect Pro | $490.00 | Yearly | `price_1St9KGGv7Su9XNJbrwKMsh1R` |
|
|
32
|
+
|
|
33
|
+
The price IDs above are already mapped in `webhook-handler.js:315-318`. If your actual Stripe price IDs differ, update `mapPriceToTier()` in `webhook-handler.js`.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Step 2: Set Up Vercel Blob Storage
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Install Vercel CLI
|
|
41
|
+
npm install -g vercel
|
|
42
|
+
|
|
43
|
+
# Link your project
|
|
44
|
+
vercel link
|
|
45
|
+
|
|
46
|
+
# Create a Blob store in the Vercel dashboard
|
|
47
|
+
# Dashboard → Storage → Create → Blob → name it "qa-architect-licenses"
|
|
48
|
+
# Copy the BLOB_READ_WRITE_TOKEN
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Step 3: Deploy to Vercel
|
|
54
|
+
|
|
55
|
+
`webhook-handler.js` exports an Express app compatible with Vercel serverless.
|
|
56
|
+
|
|
57
|
+
Create `vercel.json` in the project root (do not commit secrets):
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"version": 2,
|
|
62
|
+
"builds": [{ "src": "webhook-handler.js", "use": "@vercel/node" }],
|
|
63
|
+
"routes": [{ "src": "/(.*)", "dest": "/webhook-handler.js" }]
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Deploy:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
vercel --prod
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Your webhook URL will be: `https://your-project.vercel.app/webhook`
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Step 4: Set Environment Variables
|
|
78
|
+
|
|
79
|
+
In the Vercel dashboard → Project → Settings → Environment Variables, add:
|
|
80
|
+
|
|
81
|
+
| Variable | Value | Notes |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `STRIPE_SECRET_KEY` | `sk_live_...` | From Stripe Dashboard → Developers → API keys |
|
|
84
|
+
| `STRIPE_WEBHOOK_SECRET` | `whsec_...` | Generated in Step 5 below |
|
|
85
|
+
| `LICENSE_REGISTRY_PRIVATE_KEY` | Base64-encoded ED25519 private key | See note below |
|
|
86
|
+
| `LICENSE_REGISTRY_KEY_ID` | `default` | Or a versioned ID like `v1` |
|
|
87
|
+
| `BLOB_READ_WRITE_TOKEN` | `vercel_blob_...` | From Vercel Blob store settings |
|
|
88
|
+
| `STATUS_API_TOKEN` | A strong random string | Protects the `/status` endpoint |
|
|
89
|
+
| `NODE_ENV` | `production` | Enables HSTS and production error handling |
|
|
90
|
+
|
|
91
|
+
**Private key format:** The key must be a PEM-encoded ED25519 private key, base64-encoded as a single line (no newlines):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Encode your private key for the env var
|
|
95
|
+
base64 -w 0 < your-private-key.pem
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The `loadKeyFromEnv()` function in `lib/license-signing.js` decodes it automatically.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Step 5: Register the Stripe Webhook
|
|
103
|
+
|
|
104
|
+
In the [Stripe Dashboard](https://dashboard.stripe.com/webhooks) → Webhooks → Add endpoint:
|
|
105
|
+
|
|
106
|
+
- **URL:** `https://your-project.vercel.app/webhook`
|
|
107
|
+
- **Events to listen for:**
|
|
108
|
+
- `checkout.session.completed`
|
|
109
|
+
- `invoice.payment_succeeded`
|
|
110
|
+
- `customer.subscription.deleted`
|
|
111
|
+
|
|
112
|
+
Copy the **Signing secret** (`whsec_...`) and add it as `STRIPE_WEBHOOK_SECRET` in Vercel.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Step 6: Verify the Deployment
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Health check
|
|
120
|
+
curl https://your-project.vercel.app/health
|
|
121
|
+
|
|
122
|
+
# Expected response:
|
|
123
|
+
# {"status":"ok","timestamp":"...","database":"missing"}
|
|
124
|
+
# (missing is fine before any licenses are issued)
|
|
125
|
+
|
|
126
|
+
# Test license database endpoint (used by CLI)
|
|
127
|
+
curl https://your-project.vercel.app/legitimate-licenses.json
|
|
128
|
+
|
|
129
|
+
# Test status endpoint (replace TOKEN with your STATUS_API_TOKEN)
|
|
130
|
+
curl -H "Authorization: Bearer TOKEN" https://your-project.vercel.app/status
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Step 7: Test with Stripe Test Mode First
|
|
136
|
+
|
|
137
|
+
Before going live, test the full flow:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
# Install Stripe CLI
|
|
141
|
+
brew install stripe/stripe-cli/stripe
|
|
142
|
+
|
|
143
|
+
# Forward webhooks to your local server
|
|
144
|
+
stripe listen --forward-to localhost:3000/webhook
|
|
145
|
+
|
|
146
|
+
# Trigger a test checkout event
|
|
147
|
+
stripe trigger checkout.session.completed
|
|
148
|
+
|
|
149
|
+
# Verify a license was created
|
|
150
|
+
curl http://localhost:3000/legitimate-licenses.json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Switch to live mode keys once the test flow works end-to-end.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Step 8: Connect Your Checkout Page
|
|
158
|
+
|
|
159
|
+
Your Stripe Checkout session must:
|
|
160
|
+
- Use one of the two price IDs mapped in `mapPriceToTier()`
|
|
161
|
+
- Be a **subscription** mode checkout (not one-time payment)
|
|
162
|
+
- Collect a customer email
|
|
163
|
+
|
|
164
|
+
Example Stripe Checkout session creation (server-side):
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
const session = await stripe.checkout.sessions.create({
|
|
168
|
+
mode: 'subscription',
|
|
169
|
+
payment_method_types: ['card'],
|
|
170
|
+
line_items: [{ price: 'price_1St9K2Gv7Su9XNJbdYoH3K32', quantity: 1 }],
|
|
171
|
+
success_url: 'https://buildproven.ai/qa-architect/success?session_id={CHECKOUT_SESSION_ID}',
|
|
172
|
+
cancel_url: 'https://buildproven.ai/qa-architect',
|
|
173
|
+
})
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## License Key Delivery
|
|
179
|
+
|
|
180
|
+
After a successful checkout, the license key is stored in Vercel Blob. The customer retrieves it by running:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npx create-qa-architect@latest --activate-license
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
They enter their email and the license key format `QAA-XXXX-XXXX-XXXX-XXXX`.
|
|
187
|
+
|
|
188
|
+
The key is deterministic from the Stripe customer ID — you can regenerate it at any time using `admin-license.js` or `scripts/generate-license-keys.js`.
|
|
189
|
+
|
|
190
|
+
> **Note:** The current flow requires customers to manually enter their key. Consider adding an email delivery step in `handleCheckoutCompleted()` in `webhook-handler.js` (the comment at line 511 marks where to add this).
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Cancellation Handling
|
|
195
|
+
|
|
196
|
+
When a subscription is canceled in Stripe, the `customer.subscription.deleted` event fires and `handleSubscriptionCanceled()` marks the license as `status: "canceled"` in the database. The CLI checks this status during `getLicenseInfo()` and downgrades the user to FREE tier automatically.
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Troubleshooting
|
|
201
|
+
|
|
202
|
+
| Symptom | Likely cause | Fix |
|
|
203
|
+
|---|---|---|
|
|
204
|
+
| Webhook returns 400 | Wrong `STRIPE_WEBHOOK_SECRET` | Re-copy the signing secret from Stripe Dashboard |
|
|
205
|
+
| License not created after payment | Unknown price ID | Update `mapPriceToTier()` with your actual Stripe price IDs |
|
|
206
|
+
| CLI can't find license | Wrong blob URL | Check `BLOB_PATHS` in `lib/blob-storage.js` matches your Vercel Blob store |
|
|
207
|
+
| `sk_test_` warning in logs | Test key in production | Replace with `sk_live_...` key |
|
|
208
|
+
| `/status` returns 503 | `STATUS_API_TOKEN` not set | Add the env var in Vercel settings |
|
|
@@ -149,7 +149,7 @@ Turborepo caches and parallelizes tasks based on `turbo.json`:
|
|
|
149
149
|
|
|
150
150
|
### Issue: "package.json not found" in subdirectory
|
|
151
151
|
|
|
152
|
-
This is expected in monorepos. qa-architect gracefully handles missing package.json in workspace subdirectories
|
|
152
|
+
This is expected in monorepos. qa-architect gracefully handles missing package.json in workspace subdirectories.
|
|
153
153
|
|
|
154
154
|
## Testing
|
|
155
155
|
|
|
@@ -165,7 +165,6 @@ npx create-qa-architect@latest --dry-run
|
|
|
165
165
|
|
|
166
166
|
## Related Documentation
|
|
167
167
|
|
|
168
|
-
- [Monorepo Compatibility Fix](./MONOREPO-COMPATIBILITY-FIX.md) - Handling workspaces
|
|
169
168
|
- [CI Cost Analysis](./CI-COST-ANALYSIS.md) - Workflow tier pricing
|
|
170
169
|
- [pnpm CI Example](../.github/workflows/pnpm-ci.yml.example) - Complete example
|
|
171
170
|
|
|
@@ -11,7 +11,7 @@ QA Architect (`create-qa-architect`) is a CLI tool that bootstraps quality autom
|
|
|
11
11
|
|
|
12
12
|
**Entry point:** `setup.js` — CLI argument parsing and orchestration. Run as `npx create-qa-architect` or `node setup.js`.
|
|
13
13
|
|
|
14
|
-
**npm package:** `create-qa-architect` v5.13.
|
|
14
|
+
**npm package:** `create-qa-architect` v5.13.4 (published via GitHub trusted publishing)
|
|
15
15
|
|
|
16
16
|
## Directory Structure
|
|
17
17
|
|
|
@@ -442,15 +442,9 @@
|
|
|
442
442
|
)
|
|
443
443
|
} finally {
|
|
444
444
|
checkoutBtn.disabled = false
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
: 'Start Pro Subscription - $49/month'
|
|
449
|
-
} else {
|
|
450
|
-
checkoutBtn.textContent = isFounder
|
|
451
|
-
? 'Start Enterprise Subscription - $74.50/month (Founder Price)'
|
|
452
|
-
: 'Start Enterprise Subscription - $149/month'
|
|
453
|
-
}
|
|
445
|
+
checkoutBtn.textContent = isFounder
|
|
446
|
+
? 'Start Pro Subscription - $24.50/month (Founder Price)'
|
|
447
|
+
: 'Start Pro Subscription - $49/month'
|
|
454
448
|
}
|
|
455
449
|
}
|
|
456
450
|
|
|
@@ -12,6 +12,7 @@ const path = require('path')
|
|
|
12
12
|
const { execSync } = require('child_process')
|
|
13
13
|
const yaml = require('js-yaml')
|
|
14
14
|
const { showProgress } = require('../ui-helpers')
|
|
15
|
+
const { hasFeature, showUpgradeMessage } = require('../licensing')
|
|
15
16
|
|
|
16
17
|
const DAYS_PER_MONTH = 30
|
|
17
18
|
const DEFAULT_PULL_REQUEST_FACTOR = 0.8
|
|
@@ -383,17 +384,20 @@ function calculateMonthlyCosts(workflows, commitsPerDay, options = {}) {
|
|
|
383
384
|
const workflowRunsPerDay = totalWorkflowRunsPerMonth / DAYS_PER_MONTH
|
|
384
385
|
|
|
385
386
|
// GitHub Actions pricing (as of 2024)
|
|
386
|
-
const FREE_TIER_MINUTES = 2000 // Free
|
|
387
|
-
const
|
|
387
|
+
const FREE_TIER_MINUTES = 2000 // GitHub Free plan monthly limit
|
|
388
|
+
const GITHUB_TEAM_PLAN_MINUTES = 3000 // GitHub Team plan monthly limit ($4/user/month)
|
|
388
389
|
const COST_PER_MINUTE = 0.008 // $0.008/min for private repos
|
|
389
390
|
const TARGET_BUDGET_MINUTES = 1000
|
|
390
391
|
const STRETCH_BUDGET_MINUTES = 1500
|
|
391
392
|
|
|
392
393
|
const freeOverage = Math.max(0, minutesPerMonth - FREE_TIER_MINUTES)
|
|
393
|
-
const
|
|
394
|
+
const githubTeamOverage = Math.max(
|
|
395
|
+
0,
|
|
396
|
+
minutesPerMonth - GITHUB_TEAM_PLAN_MINUTES
|
|
397
|
+
)
|
|
394
398
|
|
|
395
399
|
const freeOverageCost = freeOverage * COST_PER_MINUTE
|
|
396
|
-
const
|
|
400
|
+
const githubTeamOverageCost = githubTeamOverage * COST_PER_MINUTE
|
|
397
401
|
|
|
398
402
|
return {
|
|
399
403
|
minutesPerMonth,
|
|
@@ -419,11 +423,11 @@ function calculateMonthlyCosts(workflows, commitsPerDay, options = {}) {
|
|
|
419
423
|
withinLimit: minutesPerMonth <= FREE_TIER_MINUTES,
|
|
420
424
|
},
|
|
421
425
|
team: {
|
|
422
|
-
limit:
|
|
423
|
-
overage:
|
|
424
|
-
cost:
|
|
425
|
-
withinLimit: minutesPerMonth <=
|
|
426
|
-
monthlyCost: 4, // $4/user/month
|
|
426
|
+
limit: GITHUB_TEAM_PLAN_MINUTES,
|
|
427
|
+
overage: githubTeamOverage,
|
|
428
|
+
cost: githubTeamOverageCost,
|
|
429
|
+
withinLimit: minutesPerMonth <= GITHUB_TEAM_PLAN_MINUTES,
|
|
430
|
+
monthlyCost: 4, // $4/user/month (GitHub Team plan price)
|
|
427
431
|
},
|
|
428
432
|
},
|
|
429
433
|
}
|
|
@@ -672,10 +676,10 @@ function generateReport(analysis) {
|
|
|
672
676
|
console.log('')
|
|
673
677
|
console.log('Alternative options:')
|
|
674
678
|
|
|
675
|
-
// Team tier
|
|
679
|
+
// GitHub Team plan comparison (GitHub's billing tier, not QA Architect tier)
|
|
676
680
|
if (costs.tiers.team.withinLimit) {
|
|
677
681
|
console.log(
|
|
678
|
-
` Team plan ($${costs.tiers.team.monthlyCost}/user/month): ✅ Stays within ${costs.tiers.team.limit.toLocaleString()} min limit`
|
|
682
|
+
` GitHub Team plan ($${costs.tiers.team.monthlyCost}/user/month): ✅ Stays within ${costs.tiers.team.limit.toLocaleString()} min limit`
|
|
679
683
|
)
|
|
680
684
|
const savings = costs.tiers.free.cost - costs.tiers.team.monthlyCost
|
|
681
685
|
if (savings > 0) {
|
|
@@ -683,7 +687,7 @@ function generateReport(analysis) {
|
|
|
683
687
|
}
|
|
684
688
|
} else {
|
|
685
689
|
console.log(
|
|
686
|
-
` Team plan ($${costs.tiers.team.monthlyCost}/user/month): Still exceeds (${costs.tiers.team.overage.toLocaleString()} min overage)`
|
|
690
|
+
` GitHub Team plan ($${costs.tiers.team.monthlyCost}/user/month): Still exceeds (${costs.tiers.team.overage.toLocaleString()} min overage)`
|
|
687
691
|
)
|
|
688
692
|
console.log(
|
|
689
693
|
` Total cost: $${(costs.tiers.team.monthlyCost + costs.tiers.team.cost).toFixed(2)}/month`
|
|
@@ -772,13 +776,10 @@ function generateReport(analysis) {
|
|
|
772
776
|
async function handleAnalyzeCi() {
|
|
773
777
|
const projectPath = process.cwd()
|
|
774
778
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// showUpgradeMessage('GitHub Actions cost analysis')
|
|
780
|
-
// process.exit(1)
|
|
781
|
-
// }
|
|
779
|
+
if (!hasFeature('ciCostAnalysis')) {
|
|
780
|
+
showUpgradeMessage('GitHub Actions cost analysis')
|
|
781
|
+
process.exit(1)
|
|
782
|
+
}
|
|
782
783
|
|
|
783
784
|
const spinner = showProgress('Analyzing GitHub Actions workflows...')
|
|
784
785
|
|
package/lib/commands/deps.js
CHANGED
|
@@ -60,7 +60,7 @@ function detectRubyProject(projectPath) {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
* Handle dependency monitoring command (Free/Pro
|
|
63
|
+
* Handle dependency monitoring command (Free/Pro)
|
|
64
64
|
*/
|
|
65
65
|
async function handleDependencyMonitoring() {
|
|
66
66
|
const projectPath = process.cwd()
|
|
@@ -95,7 +95,7 @@ async function handleDependencyMonitoring() {
|
|
|
95
95
|
if (!capCheck.allowed) {
|
|
96
96
|
console.error(`❌ ${capCheck.reason}`)
|
|
97
97
|
console.error(
|
|
98
|
-
' Upgrade to Pro
|
|
98
|
+
' Upgrade to Pro for unlimited runs: https://buildproven.ai/qa-architect'
|
|
99
99
|
)
|
|
100
100
|
process.exit(1)
|
|
101
101
|
}
|
|
@@ -108,7 +108,7 @@ async function handleDependencyMonitoring() {
|
|
|
108
108
|
// Free tier only supports npm projects. Fail fast with a clear message.
|
|
109
109
|
if (!shouldUsePremium && !hasNpm && (hasPython || hasRust || hasRuby)) {
|
|
110
110
|
console.error(
|
|
111
|
-
'❌ Dependency monitoring for this project requires a Pro
|
|
111
|
+
'❌ Dependency monitoring for this project requires a Pro license.'
|
|
112
112
|
)
|
|
113
113
|
console.error(
|
|
114
114
|
' Free tier supports npm projects only. Detected non-npm ecosystems.'
|
package/lib/license-validator.js
CHANGED
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() {
|
|
@@ -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,
|
|
@@ -949,9 +947,9 @@ function saveUsage(usage) {
|
|
|
949
947
|
|
|
950
948
|
throw error // Don't allow FREE tier to continue without tracking
|
|
951
949
|
} else {
|
|
952
|
-
// Pro
|
|
950
|
+
// Pro - warn but don't fail
|
|
953
951
|
console.warn(`⚠️ Failed to save usage data: ${error.message}`)
|
|
954
|
-
console.warn(` This won't affect Pro
|
|
952
|
+
console.warn(` This won't affect Pro functionality`)
|
|
955
953
|
return false
|
|
956
954
|
}
|
|
957
955
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Detect the target project's module system.
|
|
8
|
+
*
|
|
9
|
+
* Returns 'esm' if package.json has `"type": "module"`, otherwise 'cjs'.
|
|
10
|
+
* Missing or unreadable package.json falls back to 'cjs' (Node's default).
|
|
11
|
+
*
|
|
12
|
+
* @param {string} projectPath - Path to project root
|
|
13
|
+
* @returns {'esm'|'cjs'}
|
|
14
|
+
*/
|
|
15
|
+
function detectModuleType(projectPath) {
|
|
16
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
17
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
18
|
+
return 'cjs'
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
22
|
+
return pkg.type === 'module' ? 'esm' : 'cjs'
|
|
23
|
+
} catch {
|
|
24
|
+
return 'cjs'
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convenience: true when the target project is ESM.
|
|
30
|
+
* @param {string} projectPath
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function isESMProject(projectPath) {
|
|
34
|
+
return detectModuleType(projectPath) === 'esm'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Detect the major version of an installed dep in the target project's
|
|
39
|
+
* package.json (checks dependencies + devDependencies). Returns null if
|
|
40
|
+
* not present or unparseable.
|
|
41
|
+
*
|
|
42
|
+
* @param {string} projectPath
|
|
43
|
+
* @param {string} depName
|
|
44
|
+
* @returns {number|null}
|
|
45
|
+
*/
|
|
46
|
+
function detectDepMajor(projectPath, depName) {
|
|
47
|
+
const packageJsonPath = path.join(projectPath, 'package.json')
|
|
48
|
+
if (!fs.existsSync(packageJsonPath)) return null
|
|
49
|
+
try {
|
|
50
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
51
|
+
const range =
|
|
52
|
+
(pkg.dependencies && pkg.dependencies[depName]) ||
|
|
53
|
+
(pkg.devDependencies && pkg.devDependencies[depName])
|
|
54
|
+
if (!range) return null
|
|
55
|
+
const match = String(range).match(/(\d+)\./)
|
|
56
|
+
return match ? parseInt(match[1], 10) : null
|
|
57
|
+
} catch {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
detectModuleType,
|
|
64
|
+
isESMProject,
|
|
65
|
+
detectDepMajor,
|
|
66
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs')
|
|
12
12
|
const path = require('path')
|
|
13
|
+
const { isESMProject } = require('./project-module-type')
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Generate Lighthouse CI configuration
|
|
@@ -435,11 +436,17 @@ function writeSizeLimitConfig(projectPath, options = {}) {
|
|
|
435
436
|
}
|
|
436
437
|
|
|
437
438
|
/**
|
|
438
|
-
* Write commitlint config to project
|
|
439
|
+
* Write commitlint config to project.
|
|
440
|
+
* ESM projects ("type": "module" in package.json) need `.cjs` so Node
|
|
441
|
+
* loads our CommonJS `module.exports` template without throwing
|
|
442
|
+
* "module is not defined in ES module scope".
|
|
439
443
|
* @param {string} projectPath - Path to project
|
|
440
444
|
*/
|
|
441
445
|
function writeCommitlintConfig(projectPath) {
|
|
442
|
-
const
|
|
446
|
+
const filename = isESMProject(projectPath)
|
|
447
|
+
? 'commitlint.config.cjs'
|
|
448
|
+
: 'commitlint.config.js'
|
|
449
|
+
const configPath = path.join(projectPath, filename)
|
|
443
450
|
const config = generateCommitlintConfig()
|
|
444
451
|
|
|
445
452
|
try {
|
package/lib/workflow-config.js
CHANGED
|
@@ -65,6 +65,37 @@ 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
|
+
|
|
68
99
|
/**
|
|
69
100
|
* Strip a named section from workflow content.
|
|
70
101
|
* Removes everything between # {{NAME_BEGIN}} and # {{NAME_END}} markers (inclusive).
|
|
@@ -135,6 +166,39 @@ function removeTriggerPathsIgnore(content, triggerName) {
|
|
|
135
166
|
return output.join('\n')
|
|
136
167
|
}
|
|
137
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
|
+
|
|
138
202
|
/**
|
|
139
203
|
* Inject workflow mode-specific configuration into quality.yml
|
|
140
204
|
* Uses section markers (# {{SECTION_BEGIN/END}}) for reliable content removal
|
|
@@ -162,19 +226,13 @@ function injectWorkflowMode(workflowContent, mode) {
|
|
|
162
226
|
|
|
163
227
|
// Mode-specific transformations
|
|
164
228
|
if (mode === 'standard') {
|
|
165
|
-
// Standard:
|
|
229
|
+
// Standard: run tests on main only to keep CI costs bounded.
|
|
166
230
|
if (
|
|
167
|
-
updated.includes('tests:') &&
|
|
168
|
-
updated.includes('
|
|
231
|
+
updated.includes(' tests:') &&
|
|
232
|
+
updated.includes(' if: |') &&
|
|
233
|
+
!updated.includes("github.ref == 'refs/heads/main'")
|
|
169
234
|
) {
|
|
170
|
-
updated = updated
|
|
171
|
-
/(tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+if: \|\s*\n\s+)fromJSON\(needs\.detect-maturity\.outputs\.test-count\)/,
|
|
172
|
-
"$1github.ref == 'refs/heads/main' &&\n fromJSON(needs.detect-maturity.outputs.test-count)"
|
|
173
|
-
)
|
|
174
|
-
updated = updated.replace(
|
|
175
|
-
/(\s+tests:\s+runs-on:[^\n]+\s+needs:[^\n]+\s+)if: fromJSON\(needs\.detect-maturity\.outputs\.test-count\) > 0/,
|
|
176
|
-
"$1if: github.ref == 'refs/heads/main' && fromJSON(needs.detect-maturity.outputs.test-count) > 0"
|
|
177
|
-
)
|
|
235
|
+
updated = addStandardTestsBranchGate(updated)
|
|
178
236
|
}
|
|
179
237
|
} else if (mode === 'comprehensive') {
|
|
180
238
|
// Comprehensive: Remove paths-ignore blocks
|
|
@@ -284,6 +342,7 @@ function injectMatrix(workflowContent, enableMatrix) {
|
|
|
284
342
|
|
|
285
343
|
module.exports = {
|
|
286
344
|
detectExistingWorkflowMode,
|
|
345
|
+
detectExistingMatrix,
|
|
287
346
|
injectWorkflowMode,
|
|
288
347
|
injectMatrix,
|
|
289
348
|
stripSection,
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-qa-architect",
|
|
3
|
-
"version": "5.13.
|
|
3
|
+
"version": "5.13.4",
|
|
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": {
|
|
7
|
-
"create-qa-architect": "
|
|
7
|
+
"create-qa-architect": "setup.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"prepare": "[ \"$CI\" = \"true\" ] && echo 'Skipping Husky in CI' || husky",
|
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
"validate:comprehensive": "node setup.js --comprehensive --no-markdownlint",
|
|
21
21
|
"validate:all": "npm run validate:comprehensive && npm run security:audit",
|
|
22
22
|
"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",
|
|
23
|
-
"test": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/deps-edge-cases.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/workflow-tiers.test.js && node tests/analyze-ci.test.js && node tests/analyze-ci-integration.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-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 && node tests/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/consumer-workflow-integration.test.js",
|
|
24
|
-
"test:unit": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/workflow-tiers.test.js && node tests/analyze-ci.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-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/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js",
|
|
23
|
+
"test": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/deps-edge-cases.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/workflow-tiers.test.js && node tests/analyze-ci.test.js && node tests/analyze-ci-integration.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-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 && node tests/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/consumer-workflow-integration.test.js && node tests/esm-project-support.test.js",
|
|
24
|
+
"test:unit": "export QAA_DEVELOPER=true && node tests/result-types.test.js && 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/workflow-tiers.test.js && node tests/analyze-ci.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-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/lazy-loader.test.js && node tests/template-content-validation.test.js && node tests/ci-environment.test.js && node tests/turborepo-detection.test.js && node tests/esm-project-support.test.js",
|
|
25
25
|
"test:fast": "npm run test:unit",
|
|
26
26
|
"test:medium": "npm run test:fast && npm run test:patterns && npm run test:commands",
|
|
27
27
|
"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",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"dependency-monitoring",
|
|
81
81
|
"security-audit"
|
|
82
82
|
],
|
|
83
|
-
"author": "
|
|
83
|
+
"author": "BuildProven",
|
|
84
84
|
"license": "SEE LICENSE IN LICENSE",
|
|
85
85
|
"files": [
|
|
86
86
|
"setup.js",
|
|
@@ -121,7 +121,13 @@
|
|
|
121
121
|
"table": {
|
|
122
122
|
"ajv": "$ajv"
|
|
123
123
|
}
|
|
124
|
-
}
|
|
124
|
+
},
|
|
125
|
+
"picomatch": "^2.3.2",
|
|
126
|
+
"basic-ftp": "^5.3.0",
|
|
127
|
+
"tmp": "^0.2.5",
|
|
128
|
+
"external-editor": "^3.1.0",
|
|
129
|
+
"inquirer": "^13.4.1",
|
|
130
|
+
"esbuild": "^0.25.0"
|
|
125
131
|
},
|
|
126
132
|
"devDependencies": {
|
|
127
133
|
"@commitlint/cli": "^19.0.0",
|
package/setup.js
CHANGED
|
@@ -125,6 +125,7 @@ const { runInteractiveFlow } = require('./lib/interactive/questions')
|
|
|
125
125
|
const { TemplateLoader } = require('./lib/template-loader')
|
|
126
126
|
const {
|
|
127
127
|
detectExistingWorkflowMode,
|
|
128
|
+
detectExistingMatrix,
|
|
128
129
|
injectWorkflowMode,
|
|
129
130
|
injectMatrix,
|
|
130
131
|
} = require('./lib/workflow-config')
|
|
@@ -693,7 +694,7 @@ Usage: npx create-qa-architect@latest [options]
|
|
|
693
694
|
SETUP OPTIONS:
|
|
694
695
|
(no args) Run complete quality automation setup
|
|
695
696
|
--interactive Interactive mode with guided configuration prompts
|
|
696
|
-
--update Update existing configuration
|
|
697
|
+
--update Update existing configuration (refreshes quality.yml, preserves detected workflow tier)
|
|
697
698
|
--deps Add basic dependency monitoring (Free Tier)
|
|
698
699
|
--dependency-monitoring Same as --deps
|
|
699
700
|
--ci <provider> Select CI provider: github (default) | gitlab | circleci
|
|
@@ -703,7 +704,7 @@ SETUP OPTIONS:
|
|
|
703
704
|
WORKFLOW TIERS (GitHub Actions optimization):
|
|
704
705
|
--workflow-minimal Minimal CI (default) - Single Node version, monthly security
|
|
705
706
|
Budget-first mode: detection-only CI (target <1000 min/month)
|
|
706
|
-
--workflow-standard Standard CI -
|
|
707
|
+
--workflow-standard Standard CI - Main-branch tests, monthly security
|
|
707
708
|
~15-20 min/commit, ~$5-20/mo for typical projects
|
|
708
709
|
--workflow-comprehensive Comprehensive CI - Matrix on every push, security inline
|
|
709
710
|
~50-100 min/commit, ~$100-350/mo for typical projects
|
|
@@ -722,7 +723,7 @@ VALIDATION OPTIONS:
|
|
|
722
723
|
|
|
723
724
|
LICENSE, TELEMETRY & ERROR REPORTING:
|
|
724
725
|
--license-status Show current license tier and available features
|
|
725
|
-
--activate-license Activate Pro
|
|
726
|
+
--activate-license Activate Pro license key from Stripe purchase
|
|
726
727
|
--telemetry-status Show telemetry status and opt-in instructions
|
|
727
728
|
--error-reporting-status Show error reporting status and privacy information
|
|
728
729
|
|
|
@@ -749,7 +750,7 @@ EXAMPLES:
|
|
|
749
750
|
→ Show current license tier and upgrade options
|
|
750
751
|
|
|
751
752
|
npx create-qa-architect@latest --activate-license
|
|
752
|
-
→ Activate Pro
|
|
753
|
+
→ Activate Pro license after Stripe purchase
|
|
753
754
|
|
|
754
755
|
npx create-qa-architect@latest --telemetry-status
|
|
755
756
|
→ Show telemetry status and privacy information
|
|
@@ -1397,6 +1398,7 @@ HELP:
|
|
|
1397
1398
|
console.log('📦 Adding devDependencies...')
|
|
1398
1399
|
const defaultDevDependencies = getDefaultDevDependencies({
|
|
1399
1400
|
typescript: usesTypeScript,
|
|
1401
|
+
projectPath: process.cwd(),
|
|
1400
1402
|
})
|
|
1401
1403
|
packageJson.devDependencies = mergeDevDependencies(
|
|
1402
1404
|
packageJson.devDependencies || {},
|
|
@@ -1651,6 +1653,16 @@ HELP:
|
|
|
1651
1653
|
}
|
|
1652
1654
|
}
|
|
1653
1655
|
|
|
1656
|
+
const hasExplicitWorkflowMode =
|
|
1657
|
+
isWorkflowMinimal || isWorkflowStandard || isWorkflowComprehensive
|
|
1658
|
+
const existingMatrixEnabled =
|
|
1659
|
+
fs.existsSync(workflowFile) &&
|
|
1660
|
+
!isMatrixEnabled &&
|
|
1661
|
+
!hasExplicitWorkflowMode
|
|
1662
|
+
? detectExistingMatrix(process.cwd())
|
|
1663
|
+
: false
|
|
1664
|
+
const matrixEnabled = isMatrixEnabled || existingMatrixEnabled
|
|
1665
|
+
|
|
1654
1666
|
if (!fs.existsSync(workflowFile)) {
|
|
1655
1667
|
let templateWorkflow =
|
|
1656
1668
|
templateLoader.getTemplate(
|
|
@@ -1666,7 +1678,7 @@ HELP:
|
|
|
1666
1678
|
templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
|
|
1667
1679
|
|
|
1668
1680
|
// Inject matrix testing if enabled (for library authors)
|
|
1669
|
-
templateWorkflow = injectMatrix(templateWorkflow,
|
|
1681
|
+
templateWorkflow = injectMatrix(templateWorkflow, matrixEnabled)
|
|
1670
1682
|
|
|
1671
1683
|
// Inject collaboration steps
|
|
1672
1684
|
templateWorkflow = injectCollaborationSteps(templateWorkflow, {
|
|
@@ -1677,58 +1689,47 @@ HELP:
|
|
|
1677
1689
|
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
1678
1690
|
console.log(`✅ Added GitHub Actions workflow (${workflowMode} mode)`)
|
|
1679
1691
|
} else if (isUpdateMode) {
|
|
1680
|
-
//
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
templates,
|
|
1690
|
-
path.join('.github', 'workflows', 'quality.yml')
|
|
1691
|
-
) ||
|
|
1692
|
-
fs.readFileSync(
|
|
1693
|
-
path.join(__dirname, '.github/workflows/quality.yml'),
|
|
1694
|
-
'utf8'
|
|
1695
|
-
)
|
|
1696
|
-
|
|
1697
|
-
// Inject workflow mode configuration
|
|
1698
|
-
templateWorkflow = injectWorkflowMode(
|
|
1699
|
-
templateWorkflow,
|
|
1700
|
-
workflowMode
|
|
1692
|
+
// Refresh existing workflow from the current template.
|
|
1693
|
+
let templateWorkflow =
|
|
1694
|
+
templateLoader.getTemplate(
|
|
1695
|
+
templates,
|
|
1696
|
+
path.join('.github', 'workflows', 'quality.yml')
|
|
1697
|
+
) ||
|
|
1698
|
+
fs.readFileSync(
|
|
1699
|
+
path.join(__dirname, '.github/workflows/quality.yml'),
|
|
1700
|
+
'utf8'
|
|
1701
1701
|
)
|
|
1702
1702
|
|
|
1703
|
-
|
|
1704
|
-
|
|
1703
|
+
// Inject workflow mode configuration
|
|
1704
|
+
templateWorkflow = injectWorkflowMode(templateWorkflow, workflowMode)
|
|
1705
1705
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
const hasSlackAlerts =
|
|
1709
|
-
existingWorkflow.includes('SLACK_WEBHOOK_URL')
|
|
1710
|
-
const hasPrComments = existingWorkflow.includes(
|
|
1711
|
-
'PR_COMMENT_PLACEHOLDER'
|
|
1712
|
-
)
|
|
1706
|
+
// Inject matrix testing if enabled (for library authors)
|
|
1707
|
+
templateWorkflow = injectMatrix(templateWorkflow, matrixEnabled)
|
|
1713
1708
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1709
|
+
// Inject collaboration steps (preserve from existing if present)
|
|
1710
|
+
const existingWorkflow = fs.readFileSync(workflowFile, 'utf8')
|
|
1711
|
+
const hasSlackAlerts = existingWorkflow.includes('SLACK_WEBHOOK_URL')
|
|
1712
|
+
const hasPrComments = existingWorkflow.includes(
|
|
1713
|
+
'PR_COMMENT_PLACEHOLDER'
|
|
1714
|
+
)
|
|
1718
1715
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
)
|
|
1727
|
-
}
|
|
1716
|
+
templateWorkflow = injectCollaborationSteps(templateWorkflow, {
|
|
1717
|
+
enableSlackAlerts: hasSlackAlerts,
|
|
1718
|
+
enablePrComments: hasPrComments,
|
|
1719
|
+
})
|
|
1720
|
+
|
|
1721
|
+
fs.writeFileSync(workflowFile, templateWorkflow)
|
|
1722
|
+
if (workflowMode === 'minimal') {
|
|
1728
1723
|
console.log(
|
|
1729
|
-
|
|
1724
|
+
'⚠️ Minimal mode disables tests and security scans in CI (detection-only).'
|
|
1725
|
+
)
|
|
1726
|
+
console.log(
|
|
1727
|
+
' Use --workflow-standard to re-enable full CI checks.'
|
|
1730
1728
|
)
|
|
1731
1729
|
}
|
|
1730
|
+
console.log(
|
|
1731
|
+
`♻️ Updated GitHub Actions workflow to ${workflowMode} mode`
|
|
1732
|
+
)
|
|
1732
1733
|
}
|
|
1733
1734
|
}
|
|
1734
1735
|
|
|
@@ -1918,8 +1919,26 @@ const fs = require('fs')
|
|
|
1918
1919
|
const path = require('path')
|
|
1919
1920
|
const os = require('os')
|
|
1920
1921
|
|
|
1921
|
-
|
|
1922
|
+
function validateLicenseDir(dirPath) {
|
|
1923
|
+
const resolved = path.resolve(dirPath)
|
|
1924
|
+
const home = os.homedir()
|
|
1925
|
+
const tmp = os.tmpdir()
|
|
1926
|
+
const isInHome = resolved.startsWith(home + path.sep) || resolved === home
|
|
1927
|
+
const isInTmp = resolved.startsWith(tmp + path.sep) || resolved === tmp
|
|
1928
|
+
|
|
1929
|
+
if (!isInHome && !isInTmp) {
|
|
1930
|
+
console.warn(
|
|
1931
|
+
'⚠️ QAA_LICENSE_DIR must be within home or temp directory, ignoring: ' + dirPath
|
|
1932
|
+
)
|
|
1933
|
+
return path.join(home, '.create-qa-architect')
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
return resolved
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
const requestedLicenseDir =
|
|
1922
1940
|
process.env.QAA_LICENSE_DIR || path.join(os.homedir(), '.create-qa-architect')
|
|
1941
|
+
const licenseDir = validateLicenseDir(requestedLicenseDir)
|
|
1923
1942
|
const licenseFile = path.join(licenseDir, 'license.json')
|
|
1924
1943
|
const usageFile = path.join(licenseDir, 'usage.json')
|
|
1925
1944
|
const now = new Date()
|
|
@@ -1956,7 +1975,7 @@ try {
|
|
|
1956
1975
|
const CAP = 50
|
|
1957
1976
|
if (usage.prePushRuns >= CAP) {
|
|
1958
1977
|
console.error('❌ Free tier limit reached: ' + usage.prePushRuns + '/' + CAP + ' pre-push runs this month')
|
|
1959
|
-
console.error(' Upgrade to Pro
|
|
1978
|
+
console.error(' Upgrade to Pro: https://buildproven.ai/qa-architect')
|
|
1960
1979
|
process.exit(1)
|
|
1961
1980
|
}
|
|
1962
1981
|
|