Initial push of Plunk Next
This commit is contained in:
+66
-5
@@ -1,6 +1,67 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules
|
||||||
|
.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# Build outputs
|
||||||
|
dist
|
||||||
|
.next
|
||||||
|
.turbo
|
||||||
|
out
|
||||||
|
|
||||||
|
# Development
|
||||||
.env
|
.env
|
||||||
.next/
|
.env*.local
|
||||||
.github/
|
*.log
|
||||||
dist/
|
npm-debug.log*
|
||||||
assets/
|
yarn-debug.log*
|
||||||
node_modules/
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
**/__tests__
|
||||||
|
**/*.test.ts
|
||||||
|
**/*.test.tsx
|
||||||
|
**/*.test.js
|
||||||
|
**/*.test.jsx
|
||||||
|
**/*.spec.ts
|
||||||
|
**/*.spec.tsx
|
||||||
|
**/*.spec.js
|
||||||
|
**/*.spec.jsx
|
||||||
|
test/
|
||||||
|
vitest.config.ts
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
|
# CI/CD
|
||||||
|
.github
|
||||||
|
.gitlab-ci.yml
|
||||||
|
|
||||||
|
# Documentation (not needed in image)
|
||||||
|
*.md
|
||||||
|
!README.md
|
||||||
|
|
||||||
|
# Docker
|
||||||
|
Dockerfile*
|
||||||
|
docker
|
||||||
|
!docker/nginx
|
||||||
|
.dockerignore
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.cursor
|
||||||
|
.eslintcache
|
||||||
|
.cache
|
||||||
|
tmp
|
||||||
|
temp
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
# ========================================
|
||||||
|
# Plunk Self-Hosting Configuration
|
||||||
|
# ========================================
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# REQUIRED: Security & Database
|
||||||
|
# ========================================
|
||||||
|
DB_PASSWORD=changeme123
|
||||||
|
JWT_SECRET=
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# REQUIRED: Domains
|
||||||
|
# ========================================
|
||||||
|
# Replace example.com with your domain
|
||||||
|
# Or use *.localhost for local testing
|
||||||
|
API_DOMAIN=api.example.com
|
||||||
|
DASHBOARD_DOMAIN=app.example.com
|
||||||
|
LANDING_DOMAIN=www.example.com
|
||||||
|
WIKI_DOMAIN=docs.example.com
|
||||||
|
|
||||||
|
# Set to 'true' if using HTTPS in production (behind a reverse proxy/load balancer)
|
||||||
|
# This affects how application URIs are auto-generated from domain names
|
||||||
|
USE_HTTPS=false
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# REQUIRED: AWS SES (Email Sending)
|
||||||
|
# ========================================
|
||||||
|
AWS_SES_REGION=us-east-1
|
||||||
|
AWS_SES_ACCESS_KEY_ID=
|
||||||
|
AWS_SES_SECRET_ACCESS_KEY=
|
||||||
|
|
||||||
|
# Configuration sets for email tracking
|
||||||
|
# SES_CONFIGURATION_SET: Default configuration with open/click tracking enabled
|
||||||
|
SES_CONFIGURATION_SET=plunk-configuration-set
|
||||||
|
|
||||||
|
# SES_CONFIGURATION_SET_NO_TRACKING: Optional configuration without tracking
|
||||||
|
# If not set, the tracking toggle will be hidden in project settings
|
||||||
|
# When set, projects can choose to disable email tracking
|
||||||
|
SES_CONFIGURATION_SET_NO_TRACKING=plunk-no-tracking-configuration-set
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# OPTIONAL: OAuth Login
|
||||||
|
# ========================================
|
||||||
|
GITHUB_OAUTH_CLIENT=
|
||||||
|
GITHUB_OAUTH_SECRET=
|
||||||
|
GOOGLE_OAUTH_CLIENT=
|
||||||
|
GOOGLE_OAUTH_SECRET=
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# OPTIONAL: Stripe Billing
|
||||||
|
# ========================================
|
||||||
|
STRIPE_SK=
|
||||||
|
STRIPE_WEBHOOK_SECRET=
|
||||||
|
STRIPE_PRICE_ONBOARDING=
|
||||||
|
STRIPE_PRICE_EMAIL_USAGE=
|
||||||
|
STRIPE_METER_EVENT_NAME=emails
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# OPTIONAL: File Storage (Minio)
|
||||||
|
# ========================================
|
||||||
|
# Minio is included by default in Docker Compose
|
||||||
|
# These credentials match the Minio service configuration
|
||||||
|
# Leave defaults unless you're using external storage
|
||||||
|
MINIO_ROOT_USER=plunk
|
||||||
|
MINIO_ROOT_PASSWORD=plunkminiopass
|
||||||
|
MINIO_API_PORT=9000
|
||||||
|
MINIO_CONSOLE_PORT=9001
|
||||||
|
|
||||||
|
# S3-compatible storage configuration
|
||||||
|
# For self-hosted: uses internal Minio service (defaults work out of the box)
|
||||||
|
S3_ENDPOINT=http://minio:9000
|
||||||
|
S3_ACCESS_KEY_ID=plunk
|
||||||
|
S3_ACCESS_KEY_SECRET=plunkminiopass
|
||||||
|
S3_BUCKET=uploads
|
||||||
|
S3_PUBLIC_URL=http://localhost:9000/uploads
|
||||||
|
S3_FORCE_PATH_STYLE=true
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# OPTIONAL: SMTP Server
|
||||||
|
# ========================================
|
||||||
|
# The SMTP relay server allows sending emails via SMTP protocol
|
||||||
|
# TLS certificates can be mounted via:
|
||||||
|
# 1. Traefik acme.json (requires SMTP_DOMAIN to select the right cert)
|
||||||
|
# 2. PEM files (privkey.pem and fullchain.pem)
|
||||||
|
|
||||||
|
# SMTP domain - Required if using Traefik acme.json with multiple certificates
|
||||||
|
# Optional if using PEM files
|
||||||
|
SMTP_DOMAIN=smtp.example.com
|
||||||
|
|
||||||
|
# SMTP Ports (defaults work for most setups)
|
||||||
|
# PORT_SECURE=465 # SMTPS (implicit TLS)
|
||||||
|
# PORT_SUBMISSION=587 # SMTP Submission (STARTTLS)
|
||||||
|
|
||||||
|
# Maximum recipients per email (default: 5)
|
||||||
|
# MAX_RECIPIENTS=5
|
||||||
|
|
||||||
|
# ========================================
|
||||||
|
# ADVANCED (rarely needed)
|
||||||
|
# ========================================
|
||||||
|
# NGINX_PORT=80
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/** @type {import('eslint').Linter.Config} */
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: true,
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
ecmaVersion: 2024,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended-type-checked',
|
||||||
|
'plugin:@typescript-eslint/stylistic-type-checked',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
'plugin:jsx-a11y/recommended',
|
||||||
|
'next/core-web-vitals',
|
||||||
|
'prettier',
|
||||||
|
],
|
||||||
|
plugins: ['@typescript-eslint', 'react', 'jsx-a11y', 'import'],
|
||||||
|
rules: {
|
||||||
|
'react/react-in-jsx-scope': 'off',
|
||||||
|
'react/prop-types': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': [
|
||||||
|
'warn',
|
||||||
|
{argsIgnorePattern: '^_', varsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_'},
|
||||||
|
],
|
||||||
|
'@typescript-eslint/consistent-type-imports': ['warn', {prefer: 'type-imports'}],
|
||||||
|
'import/order': [
|
||||||
|
'warn',
|
||||||
|
{
|
||||||
|
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling'],
|
||||||
|
'newlines-between': 'always',
|
||||||
|
'alphabetize': {order: 'asc'},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: 'detect',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ignorePatterns: ['node_modules/', 'dist/', '.next/', '*.config.js', '*.config.ts'],
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
## Description
|
||||||
|
|
||||||
|
<!-- Describe your changes in detail -->
|
||||||
|
|
||||||
|
## Type of Change
|
||||||
|
|
||||||
|
<!-- Mark the appropriate option with an 'x' -->
|
||||||
|
|
||||||
|
- [ ] `feat:` New feature (MINOR version bump)
|
||||||
|
- [ ] `fix:` Bug fix (PATCH version bump)
|
||||||
|
- [ ] `feat!:` Breaking change - new feature (MAJOR version bump)
|
||||||
|
- [ ] `fix!:` Breaking change - bug fix (MAJOR version bump)
|
||||||
|
- [ ] `docs:` Documentation update (no version bump)
|
||||||
|
- [ ] `chore:` Maintenance/dependencies (no version bump)
|
||||||
|
- [ ] `refactor:` Code refactoring (no version bump)
|
||||||
|
- [ ] `test:` Adding tests (no version bump)
|
||||||
|
- [ ] `perf:` Performance improvement (PATCH version bump)
|
||||||
|
|
||||||
|
## PR Title Format
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Your PR title should follow conventional commits format:
|
||||||
|
✅ feat: add email template editor
|
||||||
|
✅ fix: resolve memory leak in worker
|
||||||
|
✅ feat!: redesign API authentication
|
||||||
|
❌ Added new feature (missing type prefix)
|
||||||
|
|
||||||
|
See VERSIONING.md for more details
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
<!-- Describe how you tested your changes -->
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] PR title follows conventional commits format
|
||||||
|
- [ ] Code builds successfully
|
||||||
|
- [ ] Tests pass locally
|
||||||
|
- [ ] Documentation updated (if needed)
|
||||||
|
|
||||||
|
## Related Issues
|
||||||
|
|
||||||
|
<!-- Link any related issues here -->
|
||||||
|
Closes #
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test Suite
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Service containers for database, Redis, and Minio
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: plunk_test
|
||||||
|
options: >-
|
||||||
|
--health-cmd pg_isready
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
options: >-
|
||||||
|
--health-cmd "redis-cli ping"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
minio:
|
||||||
|
image: minio/minio:edge-cicd
|
||||||
|
env:
|
||||||
|
MINIO_ROOT_USER: plunk
|
||||||
|
MINIO_ROOT_PASSWORD: plunkminiopass
|
||||||
|
ports:
|
||||||
|
- 9000:9000
|
||||||
|
options: >-
|
||||||
|
--health-cmd "curl -f http://localhost:9000/minio/health/live || exit 1"
|
||||||
|
--health-interval 10s
|
||||||
|
--health-timeout 5s
|
||||||
|
--health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Setup environment variables
|
||||||
|
run: |
|
||||||
|
cat > .env << EOF
|
||||||
|
NODE_ENV=test
|
||||||
|
|
||||||
|
# Database
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
DIRECT_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
|
||||||
|
# Security
|
||||||
|
JWT_SECRET=test-jwt-secret-for-ci-only
|
||||||
|
|
||||||
|
# S3 (Minio)
|
||||||
|
S3_ENDPOINT=http://localhost:9000
|
||||||
|
S3_ACCESS_KEY_ID=plunk
|
||||||
|
S3_ACCESS_KEY_SECRET=plunkminiopass
|
||||||
|
S3_BUCKET=uploads
|
||||||
|
S3_PUBLIC_URL=http://localhost:9000/uploads
|
||||||
|
S3_FORCE_PATH_STYLE=true
|
||||||
|
|
||||||
|
# Application URLs (not needed for tests but required by schema)
|
||||||
|
API_URI=http://localhost:8080
|
||||||
|
DASHBOARD_URI=http://localhost:3000
|
||||||
|
LANDING_URI=http://localhost:4000
|
||||||
|
WIKI_URI=http://localhost:1000
|
||||||
|
|
||||||
|
# AWS SES (mock values for tests)
|
||||||
|
AWS_SES_REGION=us-east-1
|
||||||
|
AWS_SES_ACCESS_KEY_ID=mock
|
||||||
|
AWS_SES_SECRET_ACCESS_KEY=mock
|
||||||
|
SES_CONFIGURATION_SET=test
|
||||||
|
SES_CONFIGURATION_SET_NO_TRACKING=test-no-tracking
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build shared packages
|
||||||
|
run: yarn build --filter="@plunk/shared" --filter="@plunk/db"
|
||||||
|
|
||||||
|
- name: Generate Prisma Client
|
||||||
|
run: yarn workspace @plunk/db db:generate
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
|
||||||
|
- name: Run database migrations
|
||||||
|
run: yarn workspace @plunk/db migrate:prod
|
||||||
|
env:
|
||||||
|
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
DIRECT_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/plunk_test
|
||||||
|
|
||||||
|
- name: Create MinIO bucket
|
||||||
|
run: |
|
||||||
|
docker run --rm --network host \
|
||||||
|
--entrypoint /bin/sh minio/mc:latest \
|
||||||
|
-c "mc alias set local http://localhost:9000 plunk plunkminiopass && \
|
||||||
|
mc mb local/uploads --ignore-existing"
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: yarn test:run
|
||||||
|
env:
|
||||||
|
NODE_ENV: test
|
||||||
|
|
||||||
|
- name: Upload test results
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: test-results
|
||||||
|
path: |
|
||||||
|
coverage/
|
||||||
|
**/*.test.ts.log
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint & Type Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'yarn'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: yarn install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Setup minimal env (for build)
|
||||||
|
run: |
|
||||||
|
cat > .env << EOF
|
||||||
|
NODE_ENV=development
|
||||||
|
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/plunk
|
||||||
|
DIRECT_DATABASE_URL=postgresql://postgres:postgres@localhost:5432/plunk
|
||||||
|
REDIS_URL=redis://localhost:6379
|
||||||
|
JWT_SECRET=test
|
||||||
|
|
||||||
|
# S3 (minimal - not needed but schema may require)
|
||||||
|
S3_ENDPOINT=http://localhost:9000
|
||||||
|
S3_ACCESS_KEY_ID=mock
|
||||||
|
S3_ACCESS_KEY_SECRET=mock
|
||||||
|
S3_BUCKET=uploads
|
||||||
|
S3_PUBLIC_URL=http://localhost:9000/uploads
|
||||||
|
S3_FORCE_PATH_STYLE=true
|
||||||
|
|
||||||
|
# Application URLs
|
||||||
|
API_URI=http://localhost:8080
|
||||||
|
DASHBOARD_URI=http://localhost:3000
|
||||||
|
LANDING_URI=http://localhost:4000
|
||||||
|
WIKI_URI=http://localhost:1000
|
||||||
|
|
||||||
|
# AWS SES (mock)
|
||||||
|
AWS_SES_REGION=us-east-1
|
||||||
|
AWS_SES_ACCESS_KEY_ID=mock
|
||||||
|
AWS_SES_SECRET_ACCESS_KEY=mock
|
||||||
|
SES_CONFIGURATION_SET=test
|
||||||
|
SES_CONFIGURATION_SET_NO_TRACKING=test
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Build shared packages
|
||||||
|
run: yarn build --filter="@plunk/shared" --filter="@plunk/db"
|
||||||
|
|
||||||
|
- name: Run linter
|
||||||
|
run: yarn lint
|
||||||
|
|
||||||
|
- name: Type check
|
||||||
|
run: yarn build --filter="api" --filter="web" --filter="landing" --filter="wiki" || true
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
name: Build and Push Docker image (Canary)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- canary
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
driaug/plunk:canary
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
name: Build and Push Docker image (Production)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Check out the repo
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
|
|
||||||
- name: Get version from package.json
|
|
||||||
id: get_version
|
|
||||||
run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
file: ./Dockerfile
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
driaug/plunk:${{ env.VERSION }}
|
|
||||||
driaug/plunk:latest
|
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
name: Docker Build and Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
attestations: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup Turborepo cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: .turbo
|
||||||
|
key: ${{ runner.os }}-turbo-${{ github.sha }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-turbo-
|
||||||
|
|
||||||
|
- name: Check if this commit is a release
|
||||||
|
id: check-release
|
||||||
|
run: |
|
||||||
|
git fetch --tags
|
||||||
|
|
||||||
|
# Check if this is a release-please commit
|
||||||
|
if git log -1 --pretty=%B | grep -q "release-please--branches--next"; then
|
||||||
|
echo "This is a release commit, waiting for tag..."
|
||||||
|
# Wait up to 60 seconds for release-please to create the tag
|
||||||
|
for i in {1..12}; do
|
||||||
|
sleep 5
|
||||||
|
git fetch --tags
|
||||||
|
TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || echo "")
|
||||||
|
if [[ -n "$TAG" ]]; then
|
||||||
|
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "release_tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "Found release tag: $TAG"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Waiting for tag... ($i/12)"
|
||||||
|
done
|
||||||
|
echo "ERROR: Release commit but no tag found after 60s"
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
# Regular commit, check for tag immediately
|
||||||
|
TAG=$(git tag --points-at HEAD | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -n1 || echo "")
|
||||||
|
if [[ -n "$TAG" ]]; then
|
||||||
|
echo "is_release=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "release_tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "This is a release: $TAG"
|
||||||
|
else
|
||||||
|
echo "is_release=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "This is a regular commit"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Free disk space
|
||||||
|
run: |
|
||||||
|
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc /opt/hostedtoolcache/CodeQL
|
||||||
|
docker system prune -af --volumes
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
|
||||||
|
- name: Log in to GHCR
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Compute tags
|
||||||
|
id: tags
|
||||||
|
run: |
|
||||||
|
if [[ "${{ steps.check-release.outputs.is_release }}" == "true" ]]; then
|
||||||
|
# This is a release: use semver tags
|
||||||
|
TAG="${{ steps.check-release.outputs.release_tag }}"
|
||||||
|
VERSION="${TAG#v}"
|
||||||
|
MAJOR=$(echo "$VERSION" | cut -d. -f1)
|
||||||
|
MINOR=$(echo "$VERSION" | cut -d. -f2)
|
||||||
|
TAGS="${VERSION},${MAJOR}.${MINOR},${MAJOR},latest"
|
||||||
|
echo "Building RELEASE with tags: $TAGS"
|
||||||
|
else
|
||||||
|
# Regular commit: use SHA tags
|
||||||
|
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||||
|
TAGS="sha-${SHORT_SHA},latest"
|
||||||
|
echo "Building COMMIT with tags: $TAGS"
|
||||||
|
fi
|
||||||
|
echo "tags=$TAGS" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
run: |
|
||||||
|
IMAGE="ghcr.io/${{ github.repository }}"
|
||||||
|
TAGS="${{ steps.tags.outputs.tags }}"
|
||||||
|
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--push \
|
||||||
|
$(echo "$TAGS" | tr ',' '\n' | sed "s|^|--tag ${IMAGE}:|") \
|
||||||
|
--cache-from type=gha \
|
||||||
|
--cache-to type=gha,mode=max \
|
||||||
|
--build-arg BUILDTIME="$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
|
||||||
|
--build-arg VERSION="${{ github.ref_name }}" \
|
||||||
|
--build-arg REVISION="${{ github.sha }}" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
IMAGE="ghcr.io/${{ github.repository }}"
|
||||||
|
TAGS="${{ steps.tags.outputs.tags }}"
|
||||||
|
echo "## Docker Image Published 🚀" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [[ "${{ steps.check-release.outputs.is_release }}" == "true" ]]; then
|
||||||
|
echo "**Type:** Release (${{ steps.check-release.outputs.release_tag }})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "**Type:** Commit (sha-${GITHUB_SHA:0:7})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "$TAGS" | tr ',' '\n' | sed "s|^|${IMAGE}:|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
name: Release Please
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- next
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-please:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Release Please
|
||||||
|
uses: googleapis/release-please-action@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
+46
-8
@@ -1,12 +1,50 @@
|
|||||||
|
# Dependencies
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
.pnp
|
||||||
.idea
|
.pnp.js
|
||||||
.vscode
|
|
||||||
*.log
|
# Local env files
|
||||||
.env
|
.env
|
||||||
.tscache
|
.env.local
|
||||||
.next
|
.env.development.local
|
||||||
.out
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Turbo
|
||||||
|
.turbo
|
||||||
|
|
||||||
|
# Vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# Build Outputs
|
||||||
|
.next/
|
||||||
|
out/
|
||||||
build
|
build
|
||||||
|
dist
|
||||||
|
.source/
|
||||||
|
|
||||||
|
# Debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.yarn/install-state.gz
|
*.pem
|
||||||
|
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
robots.txt
|
||||||
|
*sitemap*.xml
|
||||||
|
|
||||||
|
.contentlayer
|
||||||
|
.content-collections
|
||||||
|
.source
|
||||||
|
|
||||||
|
install-state.gz
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 120,
|
||||||
|
"quoteProps": "consistent",
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"singleQuote": true,
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"useTabs": false,
|
||||||
|
"importOrderSeparation": true,
|
||||||
|
"importOrderSortSpecifiers": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
".": "0.0.1"
|
||||||
|
}
|
||||||
Vendored
-894
File diff suppressed because one or more lines are too long
+948
File diff suppressed because one or more lines are too long
+7
-1
@@ -1,3 +1,9 @@
|
|||||||
nodeLinker: node-modules
|
nodeLinker: node-modules
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.3.0.cjs
|
# Support multiple architectures - download pre-built binaries instead of compiling
|
||||||
|
supportedArchitectures:
|
||||||
|
os: [ "linux", "darwin" ]
|
||||||
|
cpu: [ "x64", "arm64" ]
|
||||||
|
libc: [ "glibc" ]
|
||||||
|
|
||||||
|
yarnPath: .yarn/releases/yarn-4.9.1.cjs
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,33 +0,0 @@
|
|||||||
// source.config.ts
|
|
||||||
import { defineConfig, defineDocs } from "fumadocs-mdx/config";
|
|
||||||
|
|
||||||
// lib/remark-replace-env.mjs
|
|
||||||
import { visit } from "unist-util-visit";
|
|
||||||
var API_URL = process.env.NEXT_PUBLIC_API_URI || "https://api.useplunk.com";
|
|
||||||
var DASHBOARD_URL = process.env.NEXT_PUBLIC_DASHBOARD_URI || "https://app.useplunk.com";
|
|
||||||
function remarkReplaceEnv() {
|
|
||||||
return (tree) => {
|
|
||||||
visit(tree, ["code", "inlineCode", "text", "link"], (node) => {
|
|
||||||
if (node.value && typeof node.value === "string") {
|
|
||||||
node.value = node.value.replace(/\{\{API_URL\}\}/g, API_URL).replace(/\{\{DASHBOARD_URL\}\}/g, DASHBOARD_URL);
|
|
||||||
}
|
|
||||||
if (node.url && typeof node.url === "string") {
|
|
||||||
node.url = node.url.replace(/\{\{API_URL\}\}/g, API_URL).replace(/\{\{DASHBOARD_URL\}\}/g, DASHBOARD_URL);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// source.config.ts
|
|
||||||
var docs = defineDocs({
|
|
||||||
dir: "content/docs"
|
|
||||||
});
|
|
||||||
var source_config_default = defineConfig({
|
|
||||||
mdxOptions: {
|
|
||||||
remarkPlugins: [remarkReplaceEnv]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
export {
|
|
||||||
source_config_default as default,
|
|
||||||
docs
|
|
||||||
};
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -1,224 +0,0 @@
|
|||||||
body, html {
|
|
||||||
margin:0; padding: 0;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
font-family: Helvetica Neue, Helvetica, Arial;
|
|
||||||
font-size: 14px;
|
|
||||||
color:#333;
|
|
||||||
}
|
|
||||||
.small { font-size: 12px; }
|
|
||||||
*, *:after, *:before {
|
|
||||||
-webkit-box-sizing:border-box;
|
|
||||||
-moz-box-sizing:border-box;
|
|
||||||
box-sizing:border-box;
|
|
||||||
}
|
|
||||||
h1 { font-size: 20px; margin: 0;}
|
|
||||||
h2 { font-size: 14px; }
|
|
||||||
pre {
|
|
||||||
font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
-moz-tab-size: 2;
|
|
||||||
-o-tab-size: 2;
|
|
||||||
tab-size: 2;
|
|
||||||
}
|
|
||||||
a { color:#0074D9; text-decoration:none; }
|
|
||||||
a:hover { text-decoration:underline; }
|
|
||||||
.strong { font-weight: bold; }
|
|
||||||
.space-top1 { padding: 10px 0 0 0; }
|
|
||||||
.pad2y { padding: 20px 0; }
|
|
||||||
.pad1y { padding: 10px 0; }
|
|
||||||
.pad2x { padding: 0 20px; }
|
|
||||||
.pad2 { padding: 20px; }
|
|
||||||
.pad1 { padding: 10px; }
|
|
||||||
.space-left2 { padding-left:55px; }
|
|
||||||
.space-right2 { padding-right:20px; }
|
|
||||||
.center { text-align:center; }
|
|
||||||
.clearfix { display:block; }
|
|
||||||
.clearfix:after {
|
|
||||||
content:'';
|
|
||||||
display:block;
|
|
||||||
height:0;
|
|
||||||
clear:both;
|
|
||||||
visibility:hidden;
|
|
||||||
}
|
|
||||||
.fl { float: left; }
|
|
||||||
@media only screen and (max-width:640px) {
|
|
||||||
.col3 { width:100%; max-width:100%; }
|
|
||||||
.hide-mobile { display:none!important; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.quiet {
|
|
||||||
color: #7f7f7f;
|
|
||||||
color: rgba(0,0,0,0.5);
|
|
||||||
}
|
|
||||||
.quiet a { opacity: 0.7; }
|
|
||||||
|
|
||||||
.fraction {
|
|
||||||
font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace;
|
|
||||||
font-size: 10px;
|
|
||||||
color: #555;
|
|
||||||
background: #E8E8E8;
|
|
||||||
padding: 4px 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.path a:link, div.path a:visited { color: #333; }
|
|
||||||
table.coverage {
|
|
||||||
border-collapse: collapse;
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.coverage td {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
table.coverage td.line-count {
|
|
||||||
text-align: right;
|
|
||||||
padding: 0 5px 0 20px;
|
|
||||||
}
|
|
||||||
table.coverage td.line-coverage {
|
|
||||||
text-align: right;
|
|
||||||
padding-right: 10px;
|
|
||||||
min-width:20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.coverage td span.cline-any {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 5px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.missing-if-branch {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
border-radius: 3px;
|
|
||||||
position: relative;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #333;
|
|
||||||
color: yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-if-branch {
|
|
||||||
display: none;
|
|
||||||
margin-right: 10px;
|
|
||||||
position: relative;
|
|
||||||
padding: 0 4px;
|
|
||||||
background: #ccc;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.missing-if-branch .typ, .skip-if-branch .typ {
|
|
||||||
color: inherit !important;
|
|
||||||
}
|
|
||||||
.coverage-summary {
|
|
||||||
border-collapse: collapse;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.coverage-summary tr { border-bottom: 1px solid #bbb; }
|
|
||||||
.keyline-all { border: 1px solid #ddd; }
|
|
||||||
.coverage-summary td, .coverage-summary th { padding: 10px; }
|
|
||||||
.coverage-summary tbody { border: 1px solid #bbb; }
|
|
||||||
.coverage-summary td { border-right: 1px solid #bbb; }
|
|
||||||
.coverage-summary td:last-child { border-right: none; }
|
|
||||||
.coverage-summary th {
|
|
||||||
text-align: left;
|
|
||||||
font-weight: normal;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
.coverage-summary th.file { border-right: none !important; }
|
|
||||||
.coverage-summary th.pct { }
|
|
||||||
.coverage-summary th.pic,
|
|
||||||
.coverage-summary th.abs,
|
|
||||||
.coverage-summary td.pct,
|
|
||||||
.coverage-summary td.abs { text-align: right; }
|
|
||||||
.coverage-summary td.file { white-space: nowrap; }
|
|
||||||
.coverage-summary td.pic { min-width: 120px !important; }
|
|
||||||
.coverage-summary tfoot td { }
|
|
||||||
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
height: 10px;
|
|
||||||
width: 7px;
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 0.5em;
|
|
||||||
background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent;
|
|
||||||
}
|
|
||||||
.coverage-summary .sorted .sorter {
|
|
||||||
background-position: 0 -20px;
|
|
||||||
}
|
|
||||||
.coverage-summary .sorted-desc .sorter {
|
|
||||||
background-position: 0 -10px;
|
|
||||||
}
|
|
||||||
.status-line { height: 10px; }
|
|
||||||
/* yellow */
|
|
||||||
.cbranch-no { background: yellow !important; color: #111; }
|
|
||||||
/* dark red */
|
|
||||||
.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 }
|
|
||||||
.low .chart { border:1px solid #C21F39 }
|
|
||||||
.highlighted,
|
|
||||||
.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{
|
|
||||||
background: #C21F39 !important;
|
|
||||||
}
|
|
||||||
/* medium red */
|
|
||||||
.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE }
|
|
||||||
/* light red */
|
|
||||||
.low, .cline-no { background:#FCE1E5 }
|
|
||||||
/* light green */
|
|
||||||
.high, .cline-yes { background:rgb(230,245,208) }
|
|
||||||
/* medium green */
|
|
||||||
.cstat-yes { background:rgb(161,215,106) }
|
|
||||||
/* dark green */
|
|
||||||
.status-line.high, .high .cover-fill { background:rgb(77,146,33) }
|
|
||||||
.high .chart { border:1px solid rgb(77,146,33) }
|
|
||||||
/* dark yellow (gold) */
|
|
||||||
.status-line.medium, .medium .cover-fill { background: #f9cd0b; }
|
|
||||||
.medium .chart { border:1px solid #f9cd0b; }
|
|
||||||
/* light yellow */
|
|
||||||
.medium { background: #fff4c2; }
|
|
||||||
|
|
||||||
.cstat-skip { background: #ddd; color: #111; }
|
|
||||||
.fstat-skip { background: #ddd; color: #111 !important; }
|
|
||||||
.cbranch-skip { background: #ddd !important; color: #111; }
|
|
||||||
|
|
||||||
span.cline-neutral { background: #eaeaea; }
|
|
||||||
|
|
||||||
.coverage-summary td.empty {
|
|
||||||
opacity: .5;
|
|
||||||
padding-top: 4px;
|
|
||||||
padding-bottom: 4px;
|
|
||||||
line-height: 1;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cover-fill, .cover-empty {
|
|
||||||
display:inline-block;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
.chart {
|
|
||||||
line-height: 0;
|
|
||||||
}
|
|
||||||
.cover-empty {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
.cover-full {
|
|
||||||
border-right: none !important;
|
|
||||||
}
|
|
||||||
pre.prettyprint {
|
|
||||||
border: none !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
}
|
|
||||||
.com { color: #999 !important; }
|
|
||||||
.ignore-none { color: #999; font-weight: normal; }
|
|
||||||
|
|
||||||
.wrapper {
|
|
||||||
min-height: 100%;
|
|
||||||
height: auto !important;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0 auto -48px;
|
|
||||||
}
|
|
||||||
.footer, .push {
|
|
||||||
height: 48px;
|
|
||||||
}
|
|
||||||
@@ -1,87 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
var jumpToCode = (function init() {
|
|
||||||
// Classes of code we would like to highlight in the file view
|
|
||||||
var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no'];
|
|
||||||
|
|
||||||
// Elements to highlight in the file listing view
|
|
||||||
var fileListingElements = ['td.pct.low'];
|
|
||||||
|
|
||||||
// We don't want to select elements that are direct descendants of another match
|
|
||||||
var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > `
|
|
||||||
|
|
||||||
// Selector that finds elements on the page to which we can jump
|
|
||||||
var selector =
|
|
||||||
fileListingElements.join(', ') +
|
|
||||||
', ' +
|
|
||||||
notSelector +
|
|
||||||
missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b`
|
|
||||||
|
|
||||||
// The NodeList of matching elements
|
|
||||||
var missingCoverageElements = document.querySelectorAll(selector);
|
|
||||||
|
|
||||||
var currentIndex;
|
|
||||||
|
|
||||||
function toggleClass(index) {
|
|
||||||
missingCoverageElements
|
|
||||||
.item(currentIndex)
|
|
||||||
.classList.remove('highlighted');
|
|
||||||
missingCoverageElements.item(index).classList.add('highlighted');
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeCurrent(index) {
|
|
||||||
toggleClass(index);
|
|
||||||
currentIndex = index;
|
|
||||||
missingCoverageElements.item(index).scrollIntoView({
|
|
||||||
behavior: 'smooth',
|
|
||||||
block: 'center',
|
|
||||||
inline: 'center'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToPrevious() {
|
|
||||||
var nextIndex = 0;
|
|
||||||
if (typeof currentIndex !== 'number' || currentIndex === 0) {
|
|
||||||
nextIndex = missingCoverageElements.length - 1;
|
|
||||||
} else if (missingCoverageElements.length > 1) {
|
|
||||||
nextIndex = currentIndex - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCurrent(nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToNext() {
|
|
||||||
var nextIndex = 0;
|
|
||||||
|
|
||||||
if (
|
|
||||||
typeof currentIndex === 'number' &&
|
|
||||||
currentIndex < missingCoverageElements.length - 1
|
|
||||||
) {
|
|
||||||
nextIndex = currentIndex + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
makeCurrent(nextIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return function jump(event) {
|
|
||||||
if (
|
|
||||||
document.getElementById('fileSearch') === document.activeElement &&
|
|
||||||
document.activeElement != null
|
|
||||||
) {
|
|
||||||
// if we're currently focused on the search input, we don't want to navigate
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.which) {
|
|
||||||
case 78: // n
|
|
||||||
case 74: // j
|
|
||||||
goToNext();
|
|
||||||
break;
|
|
||||||
case 66: // b
|
|
||||||
case 75: // k
|
|
||||||
case 80: // p
|
|
||||||
goToPrevious();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
window.addEventListener('keydown', jumpToCode);
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 445 B |
@@ -1,101 +0,0 @@
|
|||||||
|
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<title>Code coverage report for All files</title>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<link rel="stylesheet" href="prettify.css" />
|
|
||||||
<link rel="stylesheet" href="base.css" />
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<style type='text/css'>
|
|
||||||
.coverage-summary .sorter {
|
|
||||||
background-image: url(sort-arrow-sprite.png);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class='wrapper'>
|
|
||||||
<div class='pad1'>
|
|
||||||
<h1>All files</h1>
|
|
||||||
<div class='clearfix'>
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">Unknown% </span>
|
|
||||||
<span class="quiet">Statements</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">Unknown% </span>
|
|
||||||
<span class="quiet">Branches</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">Unknown% </span>
|
|
||||||
<span class="quiet">Functions</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class='fl pad1y space-right2'>
|
|
||||||
<span class="strong">Unknown% </span>
|
|
||||||
<span class="quiet">Lines</span>
|
|
||||||
<span class='fraction'>0/0</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="quiet">
|
|
||||||
Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
|
|
||||||
</p>
|
|
||||||
<template id="filterTemplate">
|
|
||||||
<div class="quiet">
|
|
||||||
Filter:
|
|
||||||
<input type="search" id="fileSearch">
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
<div class='status-line medium'></div>
|
|
||||||
<div class="pad1">
|
|
||||||
<table class="coverage-summary">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
|
|
||||||
<th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
|
|
||||||
<th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
|
|
||||||
<th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
|
|
||||||
<th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
|
|
||||||
<th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
<th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
|
|
||||||
<th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<div class='push'></div><!-- for sticky footer -->
|
|
||||||
</div><!-- /wrapper -->
|
|
||||||
<div class='footer quiet pad2 space-top1 center small'>
|
|
||||||
Code coverage generated by
|
|
||||||
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
|
|
||||||
at 2025-11-30T17:34:40.516Z
|
|
||||||
</div>
|
|
||||||
<script src="prettify.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function () {
|
|
||||||
prettyPrint();
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<script src="sorter.js"></script>
|
|
||||||
<script src="block-navigation.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
|
|
||||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 138 B |
@@ -1,210 +0,0 @@
|
|||||||
/* eslint-disable */
|
|
||||||
var addSorting = (function() {
|
|
||||||
'use strict';
|
|
||||||
var cols,
|
|
||||||
currentSort = {
|
|
||||||
index: 0,
|
|
||||||
desc: false
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns the summary table element
|
|
||||||
function getTable() {
|
|
||||||
return document.querySelector('.coverage-summary');
|
|
||||||
}
|
|
||||||
// returns the thead element of the summary table
|
|
||||||
function getTableHeader() {
|
|
||||||
return getTable().querySelector('thead tr');
|
|
||||||
}
|
|
||||||
// returns the tbody element of the summary table
|
|
||||||
function getTableBody() {
|
|
||||||
return getTable().querySelector('tbody');
|
|
||||||
}
|
|
||||||
// returns the th element for nth column
|
|
||||||
function getNthColumn(n) {
|
|
||||||
return getTableHeader().querySelectorAll('th')[n];
|
|
||||||
}
|
|
||||||
|
|
||||||
function onFilterInput() {
|
|
||||||
const searchValue = document.getElementById('fileSearch').value;
|
|
||||||
const rows = document.getElementsByTagName('tbody')[0].children;
|
|
||||||
|
|
||||||
// Try to create a RegExp from the searchValue. If it fails (invalid regex),
|
|
||||||
// it will be treated as a plain text search
|
|
||||||
let searchRegex;
|
|
||||||
try {
|
|
||||||
searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive
|
|
||||||
} catch (error) {
|
|
||||||
searchRegex = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
|
||||||
const row = rows[i];
|
|
||||||
let isMatch = false;
|
|
||||||
|
|
||||||
if (searchRegex) {
|
|
||||||
// If a valid regex was created, use it for matching
|
|
||||||
isMatch = searchRegex.test(row.textContent);
|
|
||||||
} else {
|
|
||||||
// Otherwise, fall back to the original plain text search
|
|
||||||
isMatch = row.textContent
|
|
||||||
.toLowerCase()
|
|
||||||
.includes(searchValue.toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
row.style.display = isMatch ? '' : 'none';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads the search box
|
|
||||||
function addSearchBox() {
|
|
||||||
var template = document.getElementById('filterTemplate');
|
|
||||||
var templateClone = template.content.cloneNode(true);
|
|
||||||
templateClone.getElementById('fileSearch').oninput = onFilterInput;
|
|
||||||
template.parentElement.appendChild(templateClone);
|
|
||||||
}
|
|
||||||
|
|
||||||
// loads all columns
|
|
||||||
function loadColumns() {
|
|
||||||
var colNodes = getTableHeader().querySelectorAll('th'),
|
|
||||||
colNode,
|
|
||||||
cols = [],
|
|
||||||
col,
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i = 0; i < colNodes.length; i += 1) {
|
|
||||||
colNode = colNodes[i];
|
|
||||||
col = {
|
|
||||||
key: colNode.getAttribute('data-col'),
|
|
||||||
sortable: !colNode.getAttribute('data-nosort'),
|
|
||||||
type: colNode.getAttribute('data-type') || 'string'
|
|
||||||
};
|
|
||||||
cols.push(col);
|
|
||||||
if (col.sortable) {
|
|
||||||
col.defaultDescSort = col.type === 'number';
|
|
||||||
colNode.innerHTML =
|
|
||||||
colNode.innerHTML + '<span class="sorter"></span>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cols;
|
|
||||||
}
|
|
||||||
// attaches a data attribute to every tr element with an object
|
|
||||||
// of data values keyed by column name
|
|
||||||
function loadRowData(tableRow) {
|
|
||||||
var tableCols = tableRow.querySelectorAll('td'),
|
|
||||||
colNode,
|
|
||||||
col,
|
|
||||||
data = {},
|
|
||||||
i,
|
|
||||||
val;
|
|
||||||
for (i = 0; i < tableCols.length; i += 1) {
|
|
||||||
colNode = tableCols[i];
|
|
||||||
col = cols[i];
|
|
||||||
val = colNode.getAttribute('data-value');
|
|
||||||
if (col.type === 'number') {
|
|
||||||
val = Number(val);
|
|
||||||
}
|
|
||||||
data[col.key] = val;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
// loads all row data
|
|
||||||
function loadData() {
|
|
||||||
var rows = getTableBody().querySelectorAll('tr'),
|
|
||||||
i;
|
|
||||||
|
|
||||||
for (i = 0; i < rows.length; i += 1) {
|
|
||||||
rows[i].data = loadRowData(rows[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// sorts the table using the data for the ith column
|
|
||||||
function sortByIndex(index, desc) {
|
|
||||||
var key = cols[index].key,
|
|
||||||
sorter = function(a, b) {
|
|
||||||
a = a.data[key];
|
|
||||||
b = b.data[key];
|
|
||||||
return a < b ? -1 : a > b ? 1 : 0;
|
|
||||||
},
|
|
||||||
finalSorter = sorter,
|
|
||||||
tableBody = document.querySelector('.coverage-summary tbody'),
|
|
||||||
rowNodes = tableBody.querySelectorAll('tr'),
|
|
||||||
rows = [],
|
|
||||||
i;
|
|
||||||
|
|
||||||
if (desc) {
|
|
||||||
finalSorter = function(a, b) {
|
|
||||||
return -1 * sorter(a, b);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < rowNodes.length; i += 1) {
|
|
||||||
rows.push(rowNodes[i]);
|
|
||||||
tableBody.removeChild(rowNodes[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
rows.sort(finalSorter);
|
|
||||||
|
|
||||||
for (i = 0; i < rows.length; i += 1) {
|
|
||||||
tableBody.appendChild(rows[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// removes sort indicators for current column being sorted
|
|
||||||
function removeSortIndicators() {
|
|
||||||
var col = getNthColumn(currentSort.index),
|
|
||||||
cls = col.className;
|
|
||||||
|
|
||||||
cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, '');
|
|
||||||
col.className = cls;
|
|
||||||
}
|
|
||||||
// adds sort indicators for current column being sorted
|
|
||||||
function addSortIndicators() {
|
|
||||||
getNthColumn(currentSort.index).className += currentSort.desc
|
|
||||||
? ' sorted-desc'
|
|
||||||
: ' sorted';
|
|
||||||
}
|
|
||||||
// adds event listeners for all sorter widgets
|
|
||||||
function enableUI() {
|
|
||||||
var i,
|
|
||||||
el,
|
|
||||||
ithSorter = function ithSorter(i) {
|
|
||||||
var col = cols[i];
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
var desc = col.defaultDescSort;
|
|
||||||
|
|
||||||
if (currentSort.index === i) {
|
|
||||||
desc = !currentSort.desc;
|
|
||||||
}
|
|
||||||
sortByIndex(i, desc);
|
|
||||||
removeSortIndicators();
|
|
||||||
currentSort.index = i;
|
|
||||||
currentSort.desc = desc;
|
|
||||||
addSortIndicators();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
for (i = 0; i < cols.length; i += 1) {
|
|
||||||
if (cols[i].sortable) {
|
|
||||||
// add the click event handler on the th so users
|
|
||||||
// dont have to click on those tiny arrows
|
|
||||||
el = getNthColumn(i).querySelector('.sorter').parentElement;
|
|
||||||
if (el.addEventListener) {
|
|
||||||
el.addEventListener('click', ithSorter(i));
|
|
||||||
} else {
|
|
||||||
el.attachEvent('onclick', ithSorter(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// adds sorting functionality to the UI
|
|
||||||
return function() {
|
|
||||||
if (!getTable()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cols = loadColumns();
|
|
||||||
loadData();
|
|
||||||
addSearchBox();
|
|
||||||
addSortIndicators();
|
|
||||||
enableUI();
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
window.addEventListener('load', addSorting);
|
|
||||||
Reference in New Issue
Block a user