create-qa-architect 5.0.1 → 5.0.6

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.
@@ -27,14 +27,12 @@ Use this checklist before any version bump or npm publication.
27
27
 
28
28
  ### Security Audit Compliance
29
29
 
30
- - [ ] `KEYFLASH_INSPIRED_SECURITY_AUDIT.md` findings remain resolved
30
+ - [ ] `npm audit` shows no high/critical vulnerabilities
31
+ - [ ] `gitleaks detect --source . --redact` passes (no secrets)
31
32
  - [ ] **CRITICAL**: Gitleaks checksums are real SHA256 values, not placeholders
32
33
  - [ ] `lib/validation/config-security.js` GITLEAKS_CHECKSUMS contains verified hashes
33
34
  - [ ] No "PLACEHOLDER_CHECKSUM" strings exist in security validation code
34
35
  - [ ] Gitleaks pinned version in code matches documented security version
35
- - [ ] No new security vulnerabilities introduced since audit
36
- - [ ] All security fixes from audit still in place
37
- - [ ] Security audit document references current version (or base version for pre-releases like `4.0.1-rc.1`)
38
36
 
39
37
  ### Real Binary Verification
40
38
 
@@ -0,0 +1,136 @@
1
+ # Daily Deploy Check Workflow
2
+ # Copy this to .github/workflows/daily-deploy-check.yml in each project
3
+ #
4
+ # Required secrets:
5
+ # AUDIT_WEBHOOK_SECRET - shared secret for authenticating with vibebuildlab API
6
+ #
7
+ # Required variables (set in repo settings):
8
+ # PRODUCTION_URL - the production URL to check (e.g., https://saas.vibebuildlab.com)
9
+ #
10
+ # This workflow:
11
+ # 1. Runs daily at 6am UTC
12
+ # 2. Checks if production URL responds and SSL is valid
13
+ # 3. Reports deploy stage status to vibebuildlab dashboard
14
+
15
+ name: Daily Deploy Check
16
+
17
+ on:
18
+ schedule:
19
+ # Every day at 6am UTC
20
+ - cron: '0 6 * * *'
21
+ workflow_dispatch: # Manual trigger
22
+
23
+ env:
24
+ VIBEBUILDLAB_API: https://dash.vibebuildlab.com/api/audit-results
25
+
26
+ jobs:
27
+ deploy-check:
28
+ runs-on: ubuntu-latest
29
+ timeout-minutes: 5
30
+
31
+ steps:
32
+ - name: Check URL responds
33
+ id: url_check
34
+ run: |
35
+ URL="${{ vars.PRODUCTION_URL }}"
36
+
37
+ if [ -z "$URL" ]; then
38
+ echo "::error::PRODUCTION_URL variable not set"
39
+ echo "responds=false" >> $GITHUB_OUTPUT
40
+ exit 0
41
+ fi
42
+
43
+ STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$URL" --max-time 30 || echo "000")
44
+
45
+ echo "status_code=$STATUS" >> $GITHUB_OUTPUT
46
+
47
+ if [ "$STATUS" = "200" ] || [ "$STATUS" = "301" ] || [ "$STATUS" = "302" ]; then
48
+ echo "responds=true" >> $GITHUB_OUTPUT
49
+ else
50
+ echo "responds=false" >> $GITHUB_OUTPUT
51
+ fi
52
+ continue-on-error: true
53
+
54
+ - name: Check SSL certificate
55
+ id: ssl_check
56
+ run: |
57
+ URL="${{ vars.PRODUCTION_URL }}"
58
+
59
+ if [ -z "$URL" ]; then
60
+ echo "valid=false" >> $GITHUB_OUTPUT
61
+ exit 0
62
+ fi
63
+
64
+ # Extract domain from URL
65
+ DOMAIN=$(echo "$URL" | sed -E 's|https?://([^/]+).*|\1|')
66
+
67
+ # Check SSL expiry
68
+ EXPIRY=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2 || echo "")
69
+
70
+ if [ -n "$EXPIRY" ]; then
71
+ echo "valid=true" >> $GITHUB_OUTPUT
72
+ echo "expiry=$EXPIRY" >> $GITHUB_OUTPUT
73
+
74
+ # Check if expiring within 7 days
75
+ EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s 2>/dev/null || date -j -f "%b %d %H:%M:%S %Y %Z" "$EXPIRY" +%s 2>/dev/null || echo "0")
76
+ NOW_EPOCH=$(date +%s)
77
+ DAYS_LEFT=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 ))
78
+
79
+ echo "days_left=$DAYS_LEFT" >> $GITHUB_OUTPUT
80
+
81
+ if [ "$DAYS_LEFT" -lt 7 ]; then
82
+ echo "::warning::SSL certificate expires in $DAYS_LEFT days!"
83
+ fi
84
+ else
85
+ echo "valid=false" >> $GITHUB_OUTPUT
86
+ fi
87
+ continue-on-error: true
88
+
89
+ - name: Determine deploy status
90
+ id: result
91
+ run: |
92
+ if [ "${{ steps.url_check.outputs.responds }}" = "true" ]; then
93
+ echo "deploy=pass" >> $GITHUB_OUTPUT
94
+ else
95
+ echo "deploy=fail" >> $GITHUB_OUTPUT
96
+ fi
97
+
98
+ - name: Report to vibebuildlab
99
+ if: always()
100
+ run: |
101
+ # Get project slug from repo name
102
+ PROJECT_SLUG="${{ github.event.repository.name }}"
103
+
104
+ curl -X POST "$VIBEBUILDLAB_API" \
105
+ -H "Content-Type: application/json" \
106
+ -H "Authorization: Bearer ${{ secrets.AUDIT_WEBHOOK_SECRET }}" \
107
+ -d "{
108
+ \"project\": \"${PROJECT_SLUG}\",
109
+ \"stages\": {
110
+ \"deploy\": \"${{ steps.result.outputs.deploy }}\"
111
+ },
112
+ \"details\": {
113
+ \"url_responds\": ${{ steps.url_check.outputs.responds }},
114
+ \"ssl_valid\": ${{ steps.ssl_check.outputs.valid || false }},
115
+ \"status_code\": ${{ steps.url_check.outputs.status_code || 0 }}
116
+ },
117
+ \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
118
+ }"
119
+ continue-on-error: true
120
+
121
+ - name: Summary
122
+ if: always()
123
+ run: |
124
+ echo "## Deploy Check Results" >> $GITHUB_STEP_SUMMARY
125
+ echo "" >> $GITHUB_STEP_SUMMARY
126
+ echo "**URL:** ${{ vars.PRODUCTION_URL }}" >> $GITHUB_STEP_SUMMARY
127
+ echo "" >> $GITHUB_STEP_SUMMARY
128
+ echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
129
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
130
+ echo "| URL Responds | ${{ steps.url_check.outputs.responds == 'true' && '✅' || '❌' }} (HTTP ${{ steps.url_check.outputs.status_code }}) |" >> $GITHUB_STEP_SUMMARY
131
+ echo "| SSL Valid | ${{ steps.ssl_check.outputs.valid == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
132
+ if [ -n "${{ steps.ssl_check.outputs.expiry }}" ]; then
133
+ echo "| SSL Expiry | ${{ steps.ssl_check.outputs.expiry }} (${{ steps.ssl_check.outputs.days_left }} days) |" >> $GITHUB_STEP_SUMMARY
134
+ fi
135
+ echo "" >> $GITHUB_STEP_SUMMARY
136
+ echo "**Deploy Stage:** ${{ steps.result.outputs.deploy }}" >> $GITHUB_STEP_SUMMARY
@@ -42,7 +42,7 @@ jobs:
42
42
  run: |
43
43
  echo "🔐 Running REAL gitleaks download and verification test..."
44
44
  echo "Platform: $(uname -s)-$(uname -m)"
45
- echo "Expected checksum: a65b5253807a68ac0cafa4414031fd740aeb55f54fb7e55f386acb52e6a840eb"
45
+ echo "Expected checksum: 5fd1b3b0073269484d40078662e921d07427340ab9e6ed526ccd215a565b3298"
46
46
 
47
47
  # Create a test script that downloads and verifies gitleaks
48
48
  cat > test-real-download.js << 'EOF'
@@ -4,6 +4,10 @@ on:
4
4
  push:
5
5
  tags: ['v*']
6
6
 
7
+ permissions:
8
+ id-token: write # Required for OIDC trusted publishing to npm
9
+ contents: write # Required for creating GitHub releases
10
+
7
11
  jobs:
8
12
  release:
9
13
  runs-on: ubuntu-latest
@@ -14,27 +18,25 @@ jobs:
14
18
  - name: Setup Node.js
15
19
  uses: actions/setup-node@v4
16
20
  with:
17
- node-version: '20'
21
+ node-version: '22'
18
22
  registry-url: 'https://registry.npmjs.org'
19
23
 
24
+ - name: Upgrade npm for OIDC trusted publishing
25
+ run: npm install -g npm@latest
26
+
20
27
  - name: Install dependencies
21
28
  run: npm ci
22
29
 
23
30
  - name: Run pre-release checks
24
31
  run: npm run prerelease
25
32
 
26
- - name: Publish to npm
27
- run: npm publish
28
- env:
29
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
33
+ - name: Publish to npm with provenance
34
+ run: npm publish --provenance
30
35
 
31
36
  - name: Create GitHub Release
32
- uses: actions/create-release@v1
33
- env:
34
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
37
+ uses: softprops/action-gh-release@v2
35
38
  with:
36
- tag_name: ${{ github.ref_name }}
37
- release_name: Release ${{ github.ref_name }}
39
+ name: Release ${{ github.ref_name }}
38
40
  body: |
39
41
  ## Changes in ${{ github.ref_name }}
40
42
 
@@ -0,0 +1,173 @@
1
+ # Weekly Audit Workflow
2
+ # Copy this to .github/workflows/weekly-audit.yml in each project
3
+ #
4
+ # Required secrets:
5
+ # AUDIT_WEBHOOK_SECRET - shared secret for authenticating with vibebuildlab API
6
+ #
7
+ # This workflow:
8
+ # 1. Runs weekly (Sunday 2am UTC) and on manual trigger
9
+ # 2. Checks build, tests, lint, typecheck, security
10
+ # 3. Reports results to vibebuildlab dashboard
11
+
12
+ name: Weekly Audit
13
+
14
+ on:
15
+ schedule:
16
+ # Every Sunday at 2am UTC
17
+ - cron: '0 2 * * 0'
18
+ workflow_dispatch: # Manual trigger
19
+
20
+ env:
21
+ VIBEBUILDLAB_API: https://dash.vibebuildlab.com/api/audit-results
22
+
23
+ jobs:
24
+ audit:
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 15
27
+
28
+ steps:
29
+ - name: Checkout
30
+ uses: actions/checkout@v4
31
+
32
+ - name: Setup Node.js
33
+ uses: actions/setup-node@v4
34
+ with:
35
+ node-version: '20'
36
+ cache: 'npm'
37
+
38
+ - name: Install dependencies
39
+ run: npm ci
40
+
41
+ - name: Run build
42
+ id: build
43
+ run: |
44
+ if npm run build; then
45
+ echo "success=true" >> $GITHUB_OUTPUT
46
+ else
47
+ echo "success=false" >> $GITHUB_OUTPUT
48
+ fi
49
+ continue-on-error: true
50
+
51
+ - name: Run tests
52
+ id: tests
53
+ run: |
54
+ # Run tests and capture output
55
+ TEST_OUTPUT=$(npm test 2>&1) || true
56
+ echo "$TEST_OUTPUT"
57
+
58
+ # Parse results
59
+ PASSED=$(echo "$TEST_OUTPUT" | grep -oE '[0-9]+ passed' | tail -1 | awk '{print $1}' || echo "0")
60
+ FAILED=$(echo "$TEST_OUTPUT" | grep -oE '[0-9]+ failed' | tail -1 | awk '{print $1}' || echo "0")
61
+
62
+ echo "passed=${PASSED:-0}" >> $GITHUB_OUTPUT
63
+ echo "failed=${FAILED:-0}" >> $GITHUB_OUTPUT
64
+
65
+ if [ "${FAILED:-0}" = "0" ] && [ "${PASSED:-0}" != "0" ]; then
66
+ echo "success=true" >> $GITHUB_OUTPUT
67
+ else
68
+ echo "success=false" >> $GITHUB_OUTPUT
69
+ fi
70
+ continue-on-error: true
71
+
72
+ - name: Run lint
73
+ id: lint
74
+ run: |
75
+ LINT_OUTPUT=$(npm run lint 2>&1) || true
76
+ ERRORS=$(echo "$LINT_OUTPUT" | grep -cE '^\s+[0-9]+:[0-9]+\s+error' || echo "0")
77
+ echo "errors=${ERRORS:-0}" >> $GITHUB_OUTPUT
78
+
79
+ if [ "${ERRORS:-0}" = "0" ]; then
80
+ echo "success=true" >> $GITHUB_OUTPUT
81
+ else
82
+ echo "success=false" >> $GITHUB_OUTPUT
83
+ fi
84
+ continue-on-error: true
85
+
86
+ - name: Run typecheck
87
+ id: typecheck
88
+ run: |
89
+ if npm run typecheck 2>/dev/null || npx tsc --noEmit 2>/dev/null; then
90
+ echo "success=true" >> $GITHUB_OUTPUT
91
+ echo "errors=0" >> $GITHUB_OUTPUT
92
+ else
93
+ echo "success=false" >> $GITHUB_OUTPUT
94
+ echo "errors=1" >> $GITHUB_OUTPUT
95
+ fi
96
+ continue-on-error: true
97
+
98
+ - name: Security audit
99
+ id: security
100
+ run: |
101
+ AUDIT_OUTPUT=$(npm audit 2>&1) || true
102
+
103
+ if echo "$AUDIT_OUTPUT" | grep -qiE '[0-9]+ (high|critical)'; then
104
+ echo "vulnerabilities=1" >> $GITHUB_OUTPUT
105
+ echo "success=false" >> $GITHUB_OUTPUT
106
+ else
107
+ echo "vulnerabilities=0" >> $GITHUB_OUTPUT
108
+ echo "success=true" >> $GITHUB_OUTPUT
109
+ fi
110
+ continue-on-error: true
111
+
112
+ - name: Determine audit result
113
+ id: result
114
+ run: |
115
+ # execute stage: build must pass
116
+ if [ "${{ steps.build.outputs.success }}" = "true" ]; then
117
+ echo "execute=pass" >> $GITHUB_OUTPUT
118
+ else
119
+ echo "execute=fail" >> $GITHUB_OUTPUT
120
+ fi
121
+
122
+ # audit stage: tests + lint + typecheck + security
123
+ if [ "${{ steps.tests.outputs.success }}" = "true" ] && \
124
+ [ "${{ steps.lint.outputs.success }}" = "true" ] && \
125
+ [ "${{ steps.typecheck.outputs.success }}" = "true" ] && \
126
+ [ "${{ steps.security.outputs.success }}" = "true" ]; then
127
+ echo "audit=pass" >> $GITHUB_OUTPUT
128
+ else
129
+ echo "audit=fail" >> $GITHUB_OUTPUT
130
+ fi
131
+
132
+ - name: Report to vibebuildlab
133
+ if: always()
134
+ run: |
135
+ # Get project slug from package.json name or repo name
136
+ PROJECT_SLUG=$(node -e "console.log(require('./package.json').name)" 2>/dev/null || echo "${{ github.event.repository.name }}")
137
+
138
+ curl -X POST "$VIBEBUILDLAB_API" \
139
+ -H "Content-Type: application/json" \
140
+ -H "Authorization: Bearer ${{ secrets.AUDIT_WEBHOOK_SECRET }}" \
141
+ -d "{
142
+ \"project\": \"${PROJECT_SLUG}\",
143
+ \"stages\": {
144
+ \"execute\": \"${{ steps.result.outputs.execute }}\",
145
+ \"audit\": \"${{ steps.result.outputs.audit }}\"
146
+ },
147
+ \"details\": {
148
+ \"tests_passed\": ${{ steps.tests.outputs.passed || 0 }},
149
+ \"tests_failed\": ${{ steps.tests.outputs.failed || 0 }},
150
+ \"lint_errors\": ${{ steps.lint.outputs.errors || 0 }},
151
+ \"type_errors\": ${{ steps.typecheck.outputs.errors || 0 }},
152
+ \"vulnerabilities\": ${{ steps.security.outputs.vulnerabilities || 0 }},
153
+ \"build_success\": ${{ steps.build.outputs.success }}
154
+ },
155
+ \"timestamp\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"
156
+ }"
157
+ continue-on-error: true
158
+
159
+ - name: Summary
160
+ if: always()
161
+ run: |
162
+ echo "## Audit Results" >> $GITHUB_STEP_SUMMARY
163
+ echo "" >> $GITHUB_STEP_SUMMARY
164
+ echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY
165
+ echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY
166
+ echo "| Build | ${{ steps.build.outputs.success == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
167
+ echo "| Tests | ${{ steps.tests.outputs.success == 'true' && '✅' || '❌' }} (${{ steps.tests.outputs.passed }} passed, ${{ steps.tests.outputs.failed }} failed) |" >> $GITHUB_STEP_SUMMARY
168
+ echo "| Lint | ${{ steps.lint.outputs.success == 'true' && '✅' || '❌' }} (${{ steps.lint.outputs.errors }} errors) |" >> $GITHUB_STEP_SUMMARY
169
+ echo "| TypeScript | ${{ steps.typecheck.outputs.success == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
170
+ echo "| Security | ${{ steps.security.outputs.success == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
171
+ echo "" >> $GITHUB_STEP_SUMMARY
172
+ echo "**Execute Stage:** ${{ steps.result.outputs.execute }}" >> $GITHUB_STEP_SUMMARY
173
+ echo "**Audit Stage:** ${{ steps.result.outputs.audit }}" >> $GITHUB_STEP_SUMMARY
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Quality automation CLI for JavaScript/TypeScript and Python projects. One command adds ESLint, Prettier, Husky, lint-staged, and GitHub Actions. Pro tiers add security scanning (Gitleaks), Smart Test Strategy, and multi-language support.
4
4
 
5
- **This repo = the free CLI.** For the Pro dashboard with repo analytics, CI integration, and automation workflows, see [QA Architect Pro](https://vibebuildlab.com/qa-architect-pro) (included in Vibe Lab Pro).
5
+ **This repo = the free CLI.** For the Pro dashboard with repo analytics, CI integration, and automation workflows, see [QA Architect Pro](https://vibebuildlab.com/tools/qa-architect) (included in VBL Starter Kit).
6
6
 
7
7
  ---
8
8
 
@@ -48,7 +48,7 @@ npx create-qa-architect@latest
48
48
  | **Team** | $15/user/mo (5-seat min) | + RBAC, Slack alerts, multi-repo dashboard, team audit log |
49
49
  | **Enterprise** | $249/mo + $499 onboarding | + SSO/SAML, custom policies, compliance pack, dedicated TAM |
50
50
 
51
- > **Pro included in [Vibe Lab Pro](https://vibebuildlab.com/pro)** — Team/Enterprise are standalone purchases.
51
+ > **Pro included in [VBL Starter Kit](https://vibebuildlab.com/starter-kit)** — Team/Enterprise are standalone purchases.
52
52
 
53
53
  ### Security Features by Tier
54
54
 
@@ -60,7 +60,7 @@ npx create-qa-architect@latest
60
60
 
61
61
  ### License
62
62
 
63
- **MIT License** for the CLI (this repository). Pro features require a paid subscription or Vibe Lab Pro membership. See [LICENSE](LICENSE).
63
+ **Commercial License (freemium)** free tier covers the basic CLI; Pro/Team/Enterprise features require a paid subscription. See [LICENSE](LICENSE).
64
64
 
65
65
  ## Tech Stack
66
66
 
@@ -214,7 +214,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
214
214
 
215
215
  ## License
216
216
 
217
- MIT License - the CLI is free to use in any project. Pro/Team/Enterprise features require a paid subscription. See [LICENSE](LICENSE) for details.
217
+ Commercial freemium license the base CLI is free to use; Pro/Team/Enterprise features require a paid subscription. See [LICENSE](LICENSE) for details.
218
218
 
219
219
  ## Legal
220
220
 
@@ -4,6 +4,13 @@
4
4
  const STYLELINT_EXTENSIONS = ['css', 'scss', 'sass', 'less', 'pcss']
5
5
  const DEFAULT_STYLELINT_TARGET = `**/*.{${STYLELINT_EXTENSIONS.join(',')}}`
6
6
 
7
+ /**
8
+ * @typedef {Object} DefaultsOptions
9
+ * @property {string[]=} stylelintTargets
10
+ * @property {boolean=} typescript
11
+ * @property {boolean=} python
12
+ */
13
+
7
14
  const baseScripts = {
8
15
  format: 'prettier --write .',
9
16
  'format:check': 'prettier --check .',
@@ -41,7 +48,12 @@ const stylelintBraceGroup = stylelintTargets => {
41
48
  return `{${targets.join(',')}}`
42
49
  }
43
50
 
44
- const baseLintScripts = ({ stylelintTargets }) => {
51
+ /**
52
+ * @param {DefaultsOptions} [options]
53
+ * @returns {Record<string, string>}
54
+ */
55
+ const baseLintScripts = (options = {}) => {
56
+ const { stylelintTargets } = options
45
57
  const stylelintTarget = stylelintBraceGroup(stylelintTargets)
46
58
  return {
47
59
  lint: `eslint . && stylelint "${stylelintTarget}" --allow-empty-input`,
@@ -96,6 +108,9 @@ const TS_LINT_STAGED_PATTERN = '**/*.{js,jsx,ts,tsx,mjs,cjs,html}'
96
108
 
97
109
  const clone = value => JSON.parse(JSON.stringify(value))
98
110
 
111
+ /**
112
+ * @param {DefaultsOptions} [options]
113
+ */
99
114
  function getDefaultScripts({ stylelintTargets } = {}) {
100
115
  return {
101
116
  ...clone(baseScripts),
@@ -103,6 +118,9 @@ function getDefaultScripts({ stylelintTargets } = {}) {
103
118
  }
104
119
  }
105
120
 
121
+ /**
122
+ * @param {DefaultsOptions} [options]
123
+ */
106
124
  function getDefaultDevDependencies({ typescript } = {}) {
107
125
  const devDeps = { ...clone(baseDevDependencies) }
108
126
  if (typescript) {
@@ -111,6 +129,9 @@ function getDefaultDevDependencies({ typescript } = {}) {
111
129
  return devDeps
112
130
  }
113
131
 
132
+ /**
133
+ * @param {DefaultsOptions} [options]
134
+ */
114
135
  function getDefaultLintStaged({ typescript, stylelintTargets, python } = {}) {
115
136
  const pattern = typescript ? TS_LINT_STAGED_PATTERN : JS_LINT_STAGED_PATTERN
116
137
  return clone(baseLintStaged(pattern, stylelintTargets, python))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "https://github.com/brettstark73/create-qa-architect/blob/main/config/quality-config.schema.json",
3
+ "$id": "https://github.com/vibebuildlab/qa-architect/blob/main/config/quality-config.schema.json",
4
4
  "title": "Quality Automation Configuration",
5
5
  "description": "Configuration for create-qa-architect progressive quality checks",
6
6
  "type": "object",
@@ -39,7 +39,7 @@ const FOUNDER_ENTERPRISE_PRICE = '74.50'
39
39
 
40
40
  class SaaSMonetizationBootstrap {
41
41
  constructor() {
42
- this.projectRoot = process.cwd()
42
+ this.projectRoot = path.resolve(process.cwd())
43
43
  this.config = {}
44
44
  this.templates = {
45
45
  stripe: this.getStripeTemplate(),
@@ -50,6 +50,56 @@ class SaaSMonetizationBootstrap {
50
50
  }
51
51
  }
52
52
 
53
+ resolveProjectPath(relativePath) {
54
+ const normalizedRoot = this.projectRoot.endsWith(path.sep)
55
+ ? this.projectRoot
56
+ : `${this.projectRoot}${path.sep}`
57
+ const resolvedPath = path.resolve(this.projectRoot, relativePath)
58
+
59
+ if (
60
+ resolvedPath !== this.projectRoot &&
61
+ !resolvedPath.startsWith(normalizedRoot)
62
+ ) {
63
+ throw new Error(
64
+ `Refusing to access path outside project root: ${relativePath}`
65
+ )
66
+ }
67
+
68
+ return resolvedPath
69
+ }
70
+
71
+ ensureDir(relativePath) {
72
+ const target = this.resolveProjectPath(relativePath)
73
+ // Path is constrained to the project root before touching the filesystem
74
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
75
+ if (!fs.existsSync(target)) {
76
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
77
+ fs.mkdirSync(target, { recursive: true })
78
+ }
79
+ return target
80
+ }
81
+
82
+ writeProjectFile(relativePath, content) {
83
+ const target = this.resolveProjectPath(relativePath)
84
+ // Path is constrained to the project root before touching the filesystem
85
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
86
+ fs.writeFileSync(target, content)
87
+ }
88
+
89
+ readProjectFile(relativePath) {
90
+ const target = this.resolveProjectPath(relativePath)
91
+ // Path is constrained to the project root before touching the filesystem
92
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
93
+ return fs.readFileSync(target, 'utf8')
94
+ }
95
+
96
+ projectFileExists(relativePath) {
97
+ const target = this.resolveProjectPath(relativePath)
98
+ // Path is constrained to the project root before touching the filesystem
99
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
100
+ return fs.existsSync(target)
101
+ }
102
+
53
103
  async run() {
54
104
  console.log('🚀 Create SaaS Monetization')
55
105
  console.log('═══════════════════════════════════')
@@ -159,10 +209,7 @@ class SaaSMonetizationBootstrap {
159
209
  const dirs = ['lib/monetization', 'legal', 'marketing', 'billing']
160
210
 
161
211
  dirs.forEach(dir => {
162
- const fullPath = path.join(this.projectRoot, dir)
163
- if (!fs.existsSync(fullPath)) {
164
- fs.mkdirSync(fullPath, { recursive: true })
165
- }
212
+ this.ensureDir(dir)
166
213
  })
167
214
  }
168
215
 
@@ -172,8 +219,8 @@ class SaaSMonetizationBootstrap {
172
219
  .replace(/{{PRO_PRICE}}/g, this.config.proPrice)
173
220
  .replace(/{{ENTERPRISE_PRICE}}/g, this.config.enterprisePrice)
174
221
 
175
- fs.writeFileSync(
176
- path.join(this.projectRoot, 'lib/monetization/stripe-integration.js'),
222
+ this.writeProjectFile(
223
+ path.join('lib/monetization', 'stripe-integration.js'),
177
224
  stripeCode
178
225
  )
179
226
  }
@@ -188,8 +235,8 @@ class SaaSMonetizationBootstrap {
188
235
  .replace(/{{FOUNDER_PRO_PRICE}}/g, this.config.founderProPrice)
189
236
  .replace(/{{DOMAIN}}/g, this.config.domain)
190
237
 
191
- fs.writeFileSync(
192
- path.join(this.projectRoot, 'lib/monetization/licensing.js'),
238
+ this.writeProjectFile(
239
+ path.join('lib/monetization', 'licensing.js'),
193
240
  licensingCode
194
241
  )
195
242
  }
@@ -204,7 +251,7 @@ class SaaSMonetizationBootstrap {
204
251
  .replace(/{{DESCRIPTION}}/g, this.config.description)
205
252
  .replace(/{{DATE}}/g, new Date().toISOString().split('T')[0])
206
253
 
207
- fs.writeFileSync(path.join(this.projectRoot, 'legal', filename), content)
254
+ this.writeProjectFile(path.join('legal', filename), content)
208
255
  }
209
256
  }
210
257
 
@@ -233,10 +280,7 @@ class SaaSMonetizationBootstrap {
233
280
  )
234
281
  .replace(/{{SUPPORT_EMAIL}}/g, this.config.supportEmail)
235
282
 
236
- fs.writeFileSync(
237
- path.join(this.projectRoot, 'marketing', filename),
238
- content
239
- )
283
+ this.writeProjectFile(path.join('marketing', filename), content)
240
284
  }
241
285
  }
242
286
 
@@ -252,17 +296,14 @@ class SaaSMonetizationBootstrap {
252
296
  )
253
297
  .replace(/{{PREMIUM_FEATURES}}/g, this.config.premiumFeatures)
254
298
 
255
- fs.writeFileSync(
256
- path.join(this.projectRoot, 'billing/dashboard.html'),
257
- billingCode
258
- )
299
+ this.writeProjectFile(path.join('billing', 'dashboard.html'), billingCode)
259
300
  }
260
301
 
261
302
  async updatePackageJson() {
262
- const packagePath = path.join(this.projectRoot, 'package.json')
303
+ const packagePath = 'package.json'
263
304
 
264
- if (fs.existsSync(packagePath)) {
265
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'))
305
+ if (this.projectFileExists(packagePath)) {
306
+ const pkg = JSON.parse(this.readProjectFile(packagePath))
266
307
 
267
308
  // Add monetization scripts
268
309
  pkg.scripts = pkg.scripts || {}
@@ -276,7 +317,7 @@ class SaaSMonetizationBootstrap {
276
317
  pkg.dependencies.stripe = '^14.15.0'
277
318
  pkg.dependencies.crypto = '^1.0.1'
278
319
 
279
- fs.writeFileSync(packagePath, JSON.stringify(pkg, null, 2))
320
+ this.writeProjectFile(packagePath, JSON.stringify(pkg, null, 2))
280
321
  }
281
322
  }
282
323
 
@@ -307,7 +348,7 @@ COMPANY_NAME=${this.config.companyName}
307
348
  # STRIPE_PUBLISHABLE_KEY=pk_live_your_live_key_here
308
349
  `
309
350
 
310
- fs.writeFileSync(path.join(this.projectRoot, '.env.template'), envTemplate)
351
+ this.writeProjectFile('.env.template', envTemplate)
311
352
  }
312
353
 
313
354
  async generateDeploymentGuide() {
@@ -483,10 +524,7 @@ For implementation questions:
483
524
  **Revenue Potential**: $1,500-5,000/month recurring
484
525
  `
485
526
 
486
- fs.writeFileSync(
487
- path.join(this.projectRoot, 'MONETIZATION_GUIDE.md'),
488
- guide
489
- )
527
+ this.writeProjectFile('MONETIZATION_GUIDE.md', guide)
490
528
  }
491
529
 
492
530
  // Template methods (condensed versions of our implementations)
@@ -51,4 +51,3 @@ Risk-based pre-push validation that adapts to change context:
51
51
  - `--security-config` - Security validation
52
52
  - `--check-maturity` - Project maturity report
53
53
  - `--comprehensive` - Full validation suite
54
-
@@ -59,5 +59,4 @@ npm deprecate create-qa-architect@VERSION "Critical bug, use VERSION instead"
59
59
  ## npm Registry
60
60
 
61
61
  - Package: https://www.npmjs.com/package/create-qa-architect
62
- - Documentation: https://github.com/vibebuildlab/create-qa-architect
63
-
62
+ - Documentation: https://github.com/vibebuildlab/qa-architect