Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/feat-env-var-secrets-245.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@stackwright/types": minor
"@stackwright/build-scripts": minor
---

Add environment variable resolution for integration secrets

This PR introduces support for referencing secrets from environment variables in integration configurations. Key changes include:

- New `SecretReference` type for env var secret resolution
- `SecretDetection` utilities for runtime secret validation
- Updated site config schema with integration secret support
- Prebuild script updates for env var substitution
9 changes: 9 additions & 0 deletions .changeset/fix-338-validation-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'stackwright': patch
---

Document shared validation module architecture (fixes #338)

- Add ADR 006: Shared Validation Module for Content Schema
- Document OSS→Pro bidirectional alignment for Python implementation
- Cross-reference from CLAUDE.md and CONTRIBUTING.md
5 changes: 5 additions & 0 deletions .changeset/fix-issue-339-icon-theme-tokens.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@stackwright/core": patch
---

Fix icon color prop to resolve theme tokens like 'accent' to CSS variables, enabling dark mode support
9 changes: 9 additions & 0 deletions .changeset/hooks-registry-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@stackwright/hooks-registry": minor
"@stackwright/scaffold-core": minor
"@stackwright/cli": patch
"@stackwright/docs": patch
"@stackwright/e2e": patch
---

Add shared hooks-registry package for cross-module singleton and fix fallback static export compatibility
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,22 @@ jobs:
restore-keys: |
${{ runner.os }}-turbo-

- name: Dependency vulnerability audit
run: pnpm audit --audit-level=high

- run: pnpm turbo:format
- run: pnpm turbo:lint

audit:
runs-on: ubuntu-latest
needs: [lint-and-format]
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-stackwright

- name: Dependency vulnerability audit
run: pnpm audit --audit-level=high

test:
runs-on: ubuntu-latest
needs: [lint-and-format]
Expand Down
77 changes: 22 additions & 55 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
# Deploy Docs Workflow
#
# This workflow replaces the old pattern of:
# - Vercel deployments for preview/production
# - Separate repo syncing to R2
#
# Now we build once and sync directly to R2 buckets with Cloudflare CDN serving.
# R2 uses Cloudflare API token auth — no AWS credentials needed.
# This workflow builds the documentation and deploys to Cloudflare Pages.
# The dev branch deploys to stackwright-docs-dev for preview/testing.
# The main branch deploys to stackwright-docs for production.
# Required secrets for Pages deployment:
# - CLOUDFLARE_API_TOKEN: Cloudflare API token with Pages edit permissions
# - CLOUDFLARE_ACCOUNT_ID: Cloudflare account ID

# Required secrets for R2 deployment:
# - CLOUDFLARE_API_TOKEN: Cloudflare API token with R2 read/write permissions
# - R2_ACCOUNT_ID: Cloudflare account ID
# - R2_BUCKET_STABLE: Production bucket name
# - R2_BUCKET_DEV: Development bucket name

name: Deploy Docs to R2
name: Deploy Docs to Pages

on:
push:
Expand Down Expand Up @@ -77,50 +69,25 @@ jobs:
# Deploy Steps
# ============================================

- name: Install rclone
run: |
# Download and install rclone binary
curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip
unzip rclone-current-linux-amd64.zip
sudo mv rclone-v1.73.4-linux-amd64/rclone /usr/local/bin/
rm -rf rclone-current-linux-amd64.zip rclone-v1.73.4-linux-amd64
rclone version

- name: Configure rclone for R2
run: |
# Create rclone config file with R2 remote
# R2 uses Cloudflare API token auth directly
cat > ~/.config/rclone/rclone.conf << EOF
[R2]
type = s3
provider = Cloudflare
access_key_id = ${{ secrets.R2_ACCESS_KEY_ID }}
secret_access_key = ${{ secrets.R2_SECRET_ACCESS_KEY }}
region = auto
endpoint = https://${{ secrets.R2_ACCOUNT_ID }}.r2.cloudflarestorage.com
acl = public-read
EOF

- name: Determine target bucket
id: bucket
- name: Set deployment variables
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "bucket=${{ secrets.R2_BUCKET_STABLE }}" >> $GITHUB_OUTPUT
echo "env=production" >> $GITHUB_OUTPUT
echo "DEPLOY_PROJECT=stackwright-docs" >> $GITHUB_ENV
echo "DEPLOY_ENV=production" >> $GITHUB_ENV
else
echo "bucket=${{ secrets.R2_BUCKET_DEV }}" >> $GITHUB_OUTPUT
echo "env=development" >> $GITHUB_OUTPUT
echo "DEPLOY_PROJECT=stackwright-docs-dev" >> $GITHUB_ENV
echo "DEPLOY_ENV=development" >> $GITHUB_ENV
fi
echo "Selected bucket: ${{ steps.bucket.outputs.bucket }}"
echo "Selected project: ${{ env.DEPLOY_PROJECT }}"

- name: Sync to R2
run: |
rclone sync \
--fast-list \
--exclude "node_modules/**" \
--progress \
examples/stackwright-docs/out/ \
"R2:${{ steps.bucket.outputs.bucket }}/"
- name: Deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ env.DEPLOY_PROJECT }}
directory: examples/stackwright-docs/out
gitHubToken: ${{ secrets.GITHUB_TOKEN }}

- name: Deployment summary
run: |
Expand All @@ -129,8 +96,8 @@ jobs:
echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ github.ref_name }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Environment | ${{ steps.bucket.outputs.env }} |" >> $GITHUB_STEP_SUMMARY
echo "| Bucket | \`${{ steps.bucket.outputs.bucket }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Environment | ${{ env.DEPLOY_ENV }} |" >> $GITHUB_STEP_SUMMARY
echo "| Project | \`${{ env.DEPLOY_PROJECT }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Commit | \`${{ github.sha }}\` |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note:** Cloudflare CDN serves directly from R2. No cache invalidation needed."
echo "**Deployed to Cloudflare Pages**"
101 changes: 101 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: Security Scan

on:
push:
branches: [main, dev]
paths-ignore:
- '**.md'
- 'docs/**'
- 'examples/stackwright-docs/content/**'
pull_request:
paths-ignore:
- '**.md'
- 'docs/**'
- 'examples/stackwright-docs/content/**'

permissions:
contents: read
security-events: write
actions: read

jobs:
secrets-scan:
name: Scan for Secrets (Gitleaks)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for gitleaks

- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: '1.21'

- name: Install Gitleaks
run: go install github.com/gitleaks/gitleaks/v9@latest

- name: Run Gitleaks
run: gitleaks detect --source . --config .gitleaks.toml --verbose --log-level info

dependency-audit:
name: Dependency Vulnerability Audit
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Dependency vulnerability audit
run: pnpm audit --audit-level=medium --filter='@stackwright/*'

sast-scan:
name: Static Analysis (Semgrep)
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 8

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Install Semgrep
run: pnpm add -D semgrep

- name: Run Semgrep
run: pnpm exec semgrep --config auto --json --output semgrep-results.json || echo "[]" > semgrep-results.json

- name: Check and upload Semgrep results
run: |
if [ -f semgrep-results.json ] && [ $(stat -f%z semgrep-results.json 2>/dev/null || stat -c%s semgrep-results.json) -gt 10 ]; then
pnpm exec semgrep --config auto --sarif --output semgrep.sarif || true
fi

- name: Upload Semgrep SARIF
if: always() && -f semgrep.sarif
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: semgrep.sarif
category: semgrep
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ examples/*/build-manifest.json
examples/*/cyclonedx.json
examples/*/spdx.json
examples/*/spdx.spdx

# Gitleaks reports
.gitleaks_reports/
gitleaks-report.sarif

# Dependency audit reports
audit-report.json
99 changes: 99 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# Gitleaks Configuration for Stackwright
# Prevents accidental credential commits in CI/CD

[allowlist]
description = "Allowed patterns for test fixtures and documentation"
regexes = [
# Allow env var references (strict: $VAR_NAME format, 2-31 chars)
'''\$[A-Z][A-Z0-9_]{1,30}''',
# Allow test/example credentials in fixtures
'''fake[_-]?(password|secret|key|token)\s*[:=]\s*['"]test['"]''',
'''example[_-]?(token|key|secret)''',
# Allow placeholder patterns in docs
'''your[_-]?(api[_-]?key|token|secret)''',
# Allow generic test patterns
'''test[_-]?fixture''',
]

paths = [
# Skip test fixtures directory
'''test/fixtures/.*''',
# Skip generated schemas
'''schemas/.*\.json''',
# Skip generated SBOMs
'''\.stackwright/sbom/.*''',
# Skip coverage reports
'''coverage/.*''',
# Skip node_modules
'''node_modules/.*''',
]

[[rules]]
description = "AWS Access Key ID"
regex = '''AKIA[0-9A-Z]{16}'''
severity = "critical"

[[rules]]
description = "AWS Secret Access Key"
regex = '''(?i)aws(.{0,20})?['"][0-9a-zA-Z\/+]{40}['"]'''
severity = "critical"

[[rules]]
description = "GitHub Personal Access Token"
regex = '''ghp_[a-zA-Z0-9]{36}'''
severity = "critical"

[[rules]]
description = "GitHub OAuth Access Token"
regex = '''gho_[a-zA-Z0-9]{36}'''
severity = "critical"

[[rules]]
description = "GitHub App Token"
regex = '''ghu_[a-zA-Z0-9]{36}'''
severity = "critical"

[[rules]]
description = "GitLab Personal Access Token"
regex = '''glpat-[a-zA-Z0-9\-]{20}'''
severity = "critical"

[[rules]]
description = "Google API Key"
regex = '''AIza[a-zA-Z0-9_-]{35}'''
severity = "critical"

[[rules]]
description = "OpenAI API Key"
regex = '''sk-[a-zA-Z0-9]{48}'''
severity = "critical"

[[rules]]
description = "NPM Token"
regex = '''npm_[a-zA-Z0-9]{36}'''
severity = "high"

[[rules]]
description = "Private Key (generic)"
regex = '''-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'''
severity = "critical"

[[rules]]
description = "Generic API Key"
regex = '''(?i)(api[_-]?key|apikey|api[_-]?secret)\s*[:=]\s*['"]?[a-zA-Z0-9]{16,64}['"]?'''
severity = "high"

[[rules]]
description = "Generic Secret"
regex = '''(?i)(secret|password|passwd|pwd|token|auth)\s*[:=]\s*['"]?[a-zA-Z0-9_\-]{8,64}['"]?'''
severity = "medium"

[[rules]]
description = "Azure Storage Account Key"
regex = '''(?i)DefaultEndpointsProtocol=https;AccountName=[a-zA-Z0-9]+;AccountKey=[a-zA-Z0-9+/]{86}=='''
severity = "critical"

[[rules]]
description = "Generic JWT Token"
regex = '''eyJ[a-zA-Z0-9_-]{10,}\.eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}'''
severity = "high"
1 change: 1 addition & 0 deletions .next/trace
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"name":"generate-buildid","duration":172,"timestamp":94929785894,"id":4,"parentId":1,"tags":{},"startTime":1775652916940,"traceId":"1cce27f927293e70"},{"name":"load-custom-routes","duration":210,"timestamp":94929786138,"id":5,"parentId":1,"tags":{},"startTime":1775652916941,"traceId":"1cce27f927293e70"},{"name":"create-dist-dir","duration":904,"timestamp":94929786365,"id":6,"parentId":1,"tags":{},"startTime":1775652916941,"traceId":"1cce27f927293e70"},{"name":"clean","duration":160,"timestamp":94929787784,"id":7,"parentId":1,"tags":{},"startTime":1775652916942,"traceId":"1cce27f927293e70"},{"name":"next-build","duration":30673,"timestamp":94929757414,"id":1,"tags":{"buildMode":"default","version":"16.2.2","bundler":"turbopack","failed":true},"startTime":1775652916912,"traceId":"1cce27f927293e70"}]
1 change: 1 addition & 0 deletions .next/trace-build
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"name":"next-build","duration":30673,"timestamp":94929757414,"id":1,"tags":{"buildMode":"default","version":"16.2.2","bundler":"turbopack","failed":true},"startTime":1775652916912,"traceId":"1cce27f927293e70"}]
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node-linker=hoisted
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,8 @@ Pro packages can extend the scaffold process using the hooks system in `@stackwr

### Overview

> ⚠️ **Security First:** When creating scaffold hooks, always consider the security implications. See [docs/PLUGIN_SECURITY.md](./docs/PLUGIN_SECURITY.md) for security guidelines, input validation requirements, and common vulnerability patterns to avoid.

The scaffold hooks system allows Pro packages to:
- Inject enterprise dependencies into `package.json`
- Configure custom MCP servers
Expand Down
Loading
Loading