feat: integrate framework files into monorepo under packages/mosaic/framework/
Moves all Mosaic framework runtime files from the separate bootstrap repo into the monorepo as canonical source. The @mosaic/mosaic npm package now ships the complete framework — bin scripts, runtime configs, tools, and templates — enabling standalone installation via npm install. Structure: packages/mosaic/framework/ ├── bin/ 28 CLI scripts (mosaic, mosaic-doctor, mosaic-sync-skills, etc.) ├── runtime/ Runtime adapters (claude, codex, opencode, pi, mcp) ├── tools/ Shell tooling (git, prdy, orchestrator, quality, etc.) ├── templates/ Agent and repo templates ├── defaults/ Default identity files (AGENTS.md, STANDARDS.md, SOUL.md, etc.) ├── install.sh Legacy bash installer └── remote-install.sh One-liner remote installer Key files with Pi support and recent fixes: - bin/mosaic: launch_pi() with skills-local loop - bin/mosaic-doctor: --fix auto-wiring for all 4 harnesses - bin/mosaic-sync-skills: Pi as 4th link target, symlink-aware find - bin/mosaic-link-runtime-assets: Pi settings.json patching - bin/mosaic-migrate-local-skills: Pi skill roots, symlink find - runtime/pi/RUNTIME.md + mosaic-extension.ts Package ships 251 framework files in the npm tarball (278KB compressed).
This commit is contained in:
162
packages/mosaic/framework/tools/quality/templates/.gitleaks.toml
Normal file
162
packages/mosaic/framework/tools/quality/templates/.gitleaks.toml
Normal file
@@ -0,0 +1,162 @@
|
||||
# Mosaic Quality Rails — gitleaks configuration
|
||||
# Shared across all project templates. Copied to project root by install.sh.
|
||||
# Built-in rules: https://github.com/gitleaks/gitleaks/tree/master/config
|
||||
# This file adds custom rules for patterns the 150+ built-in rules miss.
|
||||
|
||||
title = "Mosaic gitleaks config"
|
||||
|
||||
[allowlist]
|
||||
description = "Global allowlist — skip files that never contain real secrets"
|
||||
paths = [
|
||||
'''node_modules/''',
|
||||
'''dist/''',
|
||||
'''build/''',
|
||||
'''\.next/''',
|
||||
'''\.nuxt/''',
|
||||
'''\.output/''',
|
||||
'''coverage/''',
|
||||
'''__pycache__/''',
|
||||
'''\.venv/''',
|
||||
'''vendor/''',
|
||||
'''pnpm-lock\.yaml$''',
|
||||
'''package-lock\.json$''',
|
||||
'''yarn\.lock$''',
|
||||
'''\.lock$''',
|
||||
'''\.snap$''',
|
||||
'''\.min\.js$''',
|
||||
'''\.min\.css$''',
|
||||
'''\.gitleaks\.toml$''',
|
||||
]
|
||||
stopwords = [
|
||||
"localhost",
|
||||
"127.0.0.1",
|
||||
"changeme",
|
||||
"placeholder",
|
||||
"example",
|
||||
"example.com",
|
||||
"test",
|
||||
"dummy",
|
||||
"fake",
|
||||
"sample",
|
||||
"your-",
|
||||
"xxx",
|
||||
"CHANGEME",
|
||||
"PLACEHOLDER",
|
||||
"TODO",
|
||||
"REPLACE_ME",
|
||||
]
|
||||
|
||||
# ──────────────────────────────────────────────
|
||||
# Custom rules — patterns the built-in rules miss
|
||||
# ──────────────────────────────────────────────
|
||||
|
||||
[[rules]]
|
||||
id = "database-url-with-credentials"
|
||||
description = "Database connection URL with embedded password"
|
||||
regex = '''(?i)(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis|amqp)://[^:\s]+:[^@\s]+@[^/\s]+'''
|
||||
tags = ["database", "connection-string"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["localhost", "127.0.0.1", "changeme", "password", "example", "test_", "placeholder"]
|
||||
|
||||
[[rules]]
|
||||
id = "alembic-ini-sqlalchemy-url"
|
||||
description = "SQLAlchemy URL in alembic.ini with credentials"
|
||||
regex = '''sqlalchemy\.url\s*=\s*\S+://[^:\s]+:[^@\s]+@\S+'''
|
||||
paths = ['''alembic\.ini$''', '''\.ini$''']
|
||||
tags = ["python", "alembic", "database"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["localhost", "127.0.0.1", "changeme", "driver://user:pass"]
|
||||
|
||||
[[rules]]
|
||||
id = "dotenv-secret-value"
|
||||
description = "High-entropy secret value in .env file"
|
||||
regex = '''(?i)(?:SECRET|TOKEN|PASSWORD|KEY|CREDENTIALS|AUTH)[\w]*\s*=\s*['"]?[A-Za-z0-9/+=]{20,}['"]?\s*$'''
|
||||
paths = ['''\.env$''', '''\.env\.\w+$''']
|
||||
tags = ["dotenv", "secret"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example", "your_", "REPLACE", "TODO"]
|
||||
|
||||
[[rules]]
|
||||
id = "jdbc-url-with-password"
|
||||
description = "JDBC connection string with embedded password"
|
||||
regex = '''jdbc:[a-z]+://[^;\s]+password=[^;\s&]+'''
|
||||
tags = ["java", "jdbc", "database"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example"]
|
||||
|
||||
[[rules]]
|
||||
id = "dsn-inline-password"
|
||||
description = "DSN-style connection string with inline password"
|
||||
regex = '''(?i)(?:dsn|connection_string|conn_str)\s*[:=]\s*\S+://[^:\s]+:[^@\s]+@\S+'''
|
||||
tags = ["database", "connection-string"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["localhost", "127.0.0.1", "changeme", "example"]
|
||||
|
||||
[[rules]]
|
||||
id = "hardcoded-password-variable"
|
||||
description = "Hardcoded password assignment in source code"
|
||||
regex = '''(?i)(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]{8,}['"]'''
|
||||
tags = ["password", "hardcoded"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example", "test", "dummy", "password123", "your_password"]
|
||||
paths = [
|
||||
'''test[s]?/''',
|
||||
'''spec[s]?/''',
|
||||
'''__test__/''',
|
||||
'''fixture[s]?/''',
|
||||
'''mock[s]?/''',
|
||||
]
|
||||
|
||||
[[rules]]
|
||||
id = "bearer-token-in-code"
|
||||
description = "Hardcoded bearer token in source code"
|
||||
regex = '''(?i)['"]Bearer\s+[A-Za-z0-9\-._~+/]+=*['"]'''
|
||||
tags = ["auth", "bearer", "token"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["example", "test", "dummy", "placeholder", "fake"]
|
||||
|
||||
[[rules]]
|
||||
id = "spring-application-properties-password"
|
||||
description = "Password in Spring Boot application properties"
|
||||
regex = '''(?i)spring\.\w+\.password\s*=\s*\S+'''
|
||||
paths = ['''application\.properties$''', '''application\.yml$''', '''application-\w+\.properties$''', '''application-\w+\.yml$''']
|
||||
tags = ["java", "spring", "password"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "${"]
|
||||
|
||||
[[rules]]
|
||||
id = "docker-compose-env-secret"
|
||||
description = "Hardcoded secret in docker-compose environment"
|
||||
regex = '''(?i)(?:POSTGRES_PASSWORD|MYSQL_ROOT_PASSWORD|MYSQL_PASSWORD|REDIS_PASSWORD|RABBITMQ_DEFAULT_PASS|MONGO_INITDB_ROOT_PASSWORD)\s*[:=]\s*['"]?[^\s'"$]{8,}['"]?'''
|
||||
paths = ['''compose\.ya?ml$''', '''docker-compose\.ya?ml$''']
|
||||
tags = ["docker", "compose", "secret"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example", "${"]
|
||||
|
||||
[[rules]]
|
||||
id = "terraform-variable-secret"
|
||||
description = "Sensitive default value in Terraform variable"
|
||||
regex = '''(?i)default\s*=\s*"[^"]{8,}"'''
|
||||
paths = ['''variables\.tf$''', '''\.tf$''']
|
||||
tags = ["terraform", "secret"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example", "TODO"]
|
||||
|
||||
[[rules]]
|
||||
id = "private-key-pem-inline"
|
||||
description = "PEM-encoded private key in source"
|
||||
regex = '''-----BEGIN\s+(?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'''
|
||||
tags = ["key", "pem", "private-key"]
|
||||
|
||||
[[rules]]
|
||||
id = "base64-encoded-secret"
|
||||
description = "Base64 value assigned to secret-named variable"
|
||||
regex = '''(?i)(?:secret|token|key|password|credentials)[\w]*\s*[:=]\s*['"]?[A-Za-z0-9+/]{40,}={0,2}['"]?'''
|
||||
tags = ["base64", "encoded", "secret"]
|
||||
[rules.allowlist]
|
||||
stopwords = ["changeme", "placeholder", "example", "test"]
|
||||
paths = [
|
||||
'''test[s]?/''',
|
||||
'''spec[s]?/''',
|
||||
'''fixture[s]?/''',
|
||||
]
|
||||
@@ -0,0 +1,56 @@
|
||||
// Root ESLint config for monorepo
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'security'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:security/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
rules: {
|
||||
// Type Safety - STRICT
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
|
||||
// Promise/Async Safety
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
|
||||
// Code Quality
|
||||
'@typescript-eslint/no-var-requires': 'error',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
|
||||
// Prettier
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
ignorePatterns: ['node_modules', 'dist', 'build', '.next', 'out', 'coverage', '.turbo'],
|
||||
overrides: [
|
||||
{
|
||||
// Next.js apps
|
||||
files: ['apps/**/app/**/*.{ts,tsx}', 'apps/**/pages/**/*.{ts,tsx}'],
|
||||
extends: ['next/core-web-vitals'],
|
||||
},
|
||||
{
|
||||
// NestJS apps
|
||||
files: ['apps/**/*.controller.ts', 'apps/**/*.service.ts', 'apps/**/*.module.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
npx lint-staged
|
||||
|
||||
# Secret scanning — gitleaks is REQUIRED (not optional like git-secrets was)
|
||||
if ! command -v gitleaks &>/dev/null; then
|
||||
echo ""
|
||||
echo "ERROR: gitleaks is not installed. Secret scanning is required."
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo " Linux: curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.24.0_linux_x64.tar.gz | sudo tar -xz -C /usr/local/bin gitleaks"
|
||||
echo " macOS: brew install gitleaks"
|
||||
echo " Windows: winget install gitleaks"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
gitleaks git --pre-commit --redact --staged --verbose
|
||||
@@ -0,0 +1,29 @@
|
||||
// Monorepo-aware lint-staged configuration
|
||||
module.exports = {
|
||||
// TypeScript files across all packages
|
||||
'**/*.{ts,tsx}': (filenames) => {
|
||||
const commands = [`eslint ${filenames.join(' ')} --fix --max-warnings=0`];
|
||||
|
||||
// Only run tsc in packages that have tsconfig.json
|
||||
// NOTE: lint-staged passes absolute paths, so we need to match both:
|
||||
// - Relative: apps/api/src/file.ts
|
||||
// - Absolute: /home/user/project/apps/api/src/file.ts
|
||||
const packages = [
|
||||
...new Set(
|
||||
filenames.map((f) => {
|
||||
const match = f.match(/(?:^|\/)(apps|packages)\/([^/]+)\//);
|
||||
return match ? `${match[1]}/${match[2]}` : null;
|
||||
}),
|
||||
),
|
||||
].filter(Boolean);
|
||||
|
||||
packages.forEach((pkg) => {
|
||||
commands.push(`tsc --project ${pkg}/tsconfig.json --noEmit`);
|
||||
});
|
||||
|
||||
return commands;
|
||||
},
|
||||
|
||||
// Format all files
|
||||
'**/*.{js,jsx,ts,tsx,json,md,yml,yaml}': ['prettier --write'],
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
# Woodpecker CI Quality Enforcement Pipeline - Monorepo
|
||||
when:
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
variables:
|
||||
- &node_image 'node:20-alpine'
|
||||
- &gitleaks_image 'ghcr.io/gitleaks/gitleaks:v8.24.0'
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci --ignore-scripts
|
||||
|
||||
steps:
|
||||
# Secret scanning (runs in parallel with install, no deps)
|
||||
secret-scan:
|
||||
image: *gitleaks_image
|
||||
commands:
|
||||
- gitleaks git --redact --verbose --log-opts="HEAD~1..HEAD"
|
||||
depends_on: []
|
||||
|
||||
install:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
|
||||
security-audit:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm audit --audit-level=high
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
lint:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run lint
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
typecheck:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run type-check
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
test:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
build:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
NODE_ENV: 'production'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run build
|
||||
depends_on:
|
||||
- lint
|
||||
- typecheck
|
||||
- test
|
||||
- security-audit
|
||||
- secret-scan
|
||||
@@ -0,0 +1,102 @@
|
||||
# Monorepo Structure
|
||||
|
||||
This quality-rails monorepo template supports the following structure:
|
||||
|
||||
```
|
||||
monorepo/
|
||||
├── apps/
|
||||
│ ├── web/ # Next.js frontend
|
||||
│ │ ├── package.json
|
||||
│ │ ├── tsconfig.json # extends ../../tsconfig.base.json
|
||||
│ │ └── .eslintrc.js # extends ../../.eslintrc.strict.js
|
||||
│ └── api/ # NestJS backend
|
||||
│ ├── package.json
|
||||
│ ├── tsconfig.json
|
||||
│ └── .eslintrc.js
|
||||
├── packages/
|
||||
│ ├── shared-types/ # Shared TypeScript types
|
||||
│ ├── ui/ # Shared UI components
|
||||
│ └── config/ # Shared configuration
|
||||
├── .husky/
|
||||
│ └── pre-commit
|
||||
├── .lintstagedrc.js # Multi-package aware
|
||||
├── .eslintrc.strict.js # Root ESLint config
|
||||
├── tsconfig.base.json # Base TypeScript config
|
||||
├── turbo.json # TurboRepo configuration
|
||||
├── pnpm-workspace.yaml # pnpm workspaces
|
||||
└── package.json # Root package with scripts
|
||||
```
|
||||
|
||||
## Package-Specific Configs
|
||||
|
||||
Each package extends the root configuration:
|
||||
|
||||
**apps/web/tsconfig.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"jsx": "preserve",
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules", ".next"]
|
||||
}
|
||||
```
|
||||
|
||||
**apps/api/tsconfig.json:**
|
||||
|
||||
```json
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "test"]
|
||||
}
|
||||
```
|
||||
|
||||
## Running Commands
|
||||
|
||||
**All packages:**
|
||||
|
||||
```bash
|
||||
npm run lint # Lint all packages
|
||||
npm run type-check # Type check all packages
|
||||
npm run test # Test all packages
|
||||
npm run build # Build all packages
|
||||
```
|
||||
|
||||
**Single package:**
|
||||
|
||||
```bash
|
||||
npm run lint --workspace=apps/web
|
||||
npm run dev --workspace=apps/api
|
||||
```
|
||||
|
||||
**With TurboRepo:**
|
||||
|
||||
```bash
|
||||
turbo run build # Build with caching
|
||||
turbo run dev --parallel # Run dev servers in parallel
|
||||
```
|
||||
|
||||
## Pre-Commit Enforcement
|
||||
|
||||
lint-staged automatically detects which packages contain modified files and runs:
|
||||
|
||||
- ESLint on changed files
|
||||
- TypeScript check on affected packages
|
||||
- Prettier on all changed files
|
||||
|
||||
Only runs checks on packages that have changes (efficient).
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "monorepo",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "turbo run build",
|
||||
"dev": "turbo run dev",
|
||||
"lint": "turbo run lint",
|
||||
"type-check": "turbo run type-check",
|
||||
"test": "turbo run test",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-security": "^3.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.0.0",
|
||||
"turbo": "^2.0.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
packages:
|
||||
- 'apps/*'
|
||||
- 'packages/*'
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"composite": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
// STRICT MODE - All enabled
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
// Additional Checks
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnreachableCode": false
|
||||
},
|
||||
"exclude": ["node_modules", "dist", "build", ".next", "out"]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://turbo.build/schema.json",
|
||||
"globalDependencies": ["**/.env.*local"],
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**", ".next/**", "build/**"]
|
||||
},
|
||||
"lint": {
|
||||
"cache": false
|
||||
},
|
||||
"type-check": {
|
||||
"cache": false
|
||||
},
|
||||
"test": {
|
||||
"cache": false
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
"persistent": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'next/core-web-vitals',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:security/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'security'],
|
||||
rules: {
|
||||
// Type Safety - STRICT
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
|
||||
// Promise/Async Safety
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
|
||||
// React/Next.js specific
|
||||
'react/no-unescaped-entities': 'off',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
|
||||
// Prettier
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
ignorePatterns: ['.next', 'out', 'public', '.eslintrc.js'],
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
npx lint-staged
|
||||
|
||||
# Secret scanning — gitleaks is REQUIRED (not optional like git-secrets was)
|
||||
if ! command -v gitleaks &>/dev/null; then
|
||||
echo ""
|
||||
echo "ERROR: gitleaks is not installed. Secret scanning is required."
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo " Linux: curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.24.0_linux_x64.tar.gz | sudo tar -xz -C /usr/local/bin gitleaks"
|
||||
echo " macOS: brew install gitleaks"
|
||||
echo " Windows: winget install gitleaks"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
gitleaks git --pre-commit --redact --staged --verbose
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
'*.{ts,tsx}': ['eslint --fix --max-warnings=0', 'bash -c "tsc --noEmit"'],
|
||||
'*.{js,jsx,ts,tsx,json,md,css}': ['prettier --write'],
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
# Woodpecker CI Quality Enforcement Pipeline - Next.js
|
||||
when:
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
variables:
|
||||
- &node_image 'node:20-alpine'
|
||||
- &gitleaks_image 'ghcr.io/gitleaks/gitleaks:v8.24.0'
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci --ignore-scripts
|
||||
|
||||
steps:
|
||||
# Secret scanning (runs in parallel with install, no deps)
|
||||
secret-scan:
|
||||
image: *gitleaks_image
|
||||
commands:
|
||||
- gitleaks git --redact --verbose --log-opts="HEAD~1..HEAD"
|
||||
depends_on: []
|
||||
|
||||
install:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
|
||||
security-audit:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm audit --audit-level=high
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
lint:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run lint
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
typecheck:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run type-check
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
test:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
build:
|
||||
image: *node_image
|
||||
environment:
|
||||
SKIP_ENV_VALIDATION: 'true'
|
||||
NODE_ENV: 'production'
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run build
|
||||
depends_on:
|
||||
- lint
|
||||
- typecheck
|
||||
- test
|
||||
- security-audit
|
||||
- secret-scan
|
||||
@@ -0,0 +1,50 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
swcMinify: true,
|
||||
|
||||
// Security headers
|
||||
async headers() {
|
||||
return [
|
||||
{
|
||||
source: '/:path*',
|
||||
headers: [
|
||||
{
|
||||
key: 'X-DNS-Prefetch-Control',
|
||||
value: 'on'
|
||||
},
|
||||
{
|
||||
key: 'Strict-Transport-Security',
|
||||
value: 'max-age=63072000; includeSubDomains; preload'
|
||||
},
|
||||
{
|
||||
key: 'X-Frame-Options',
|
||||
value: 'SAMEORIGIN'
|
||||
},
|
||||
{
|
||||
key: 'X-Content-Type-Options',
|
||||
value: 'nosniff'
|
||||
},
|
||||
{
|
||||
key: 'X-XSS-Protection',
|
||||
value: '1; mode=block'
|
||||
},
|
||||
{
|
||||
key: 'Referrer-Policy',
|
||||
value: 'origin-when-cross-origin'
|
||||
},
|
||||
{
|
||||
key: 'Permissions-Policy',
|
||||
value: 'camera=(), microphone=(), geolocation=()'
|
||||
},
|
||||
{
|
||||
key: 'Content-Security-Policy',
|
||||
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self';"
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = nextConfig;
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint --max-warnings=0",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^14.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-next": "^14.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-security": "^3.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.0.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["dom", "dom.iterable", "ES2022"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnreachableCode": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules", ".next", "out"]
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin', 'security'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:@typescript-eslint/recommended-requiring-type-checking',
|
||||
'plugin:security/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
// Type Safety - STRICT
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/explicit-function-return-type': 'warn',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
|
||||
// Promise/Async Safety
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
|
||||
// Type Assertions
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
|
||||
'@typescript-eslint/consistent-type-assertions': [
|
||||
'error',
|
||||
{ assertionStyle: 'as', objectLiteralTypeAssertions: 'never' },
|
||||
],
|
||||
|
||||
// Code Quality
|
||||
'@typescript-eslint/no-var-requires': 'error',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'warn',
|
||||
'@typescript-eslint/prefer-optional-chain': 'warn',
|
||||
'@typescript-eslint/strict-boolean-expressions': 'warn',
|
||||
|
||||
// Prettier
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
endOfLine: 'auto',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
npx lint-staged
|
||||
|
||||
# Secret scanning — gitleaks is REQUIRED (not optional like git-secrets was)
|
||||
if ! command -v gitleaks &>/dev/null; then
|
||||
echo ""
|
||||
echo "ERROR: gitleaks is not installed. Secret scanning is required."
|
||||
echo ""
|
||||
echo "Install:"
|
||||
echo " Linux: curl -sSfL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.24.0_linux_x64.tar.gz | sudo tar -xz -C /usr/local/bin gitleaks"
|
||||
echo " macOS: brew install gitleaks"
|
||||
echo " Windows: winget install gitleaks"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
gitleaks git --pre-commit --redact --staged --verbose
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
'*.{ts,tsx}': ['eslint --fix --max-warnings=0', 'bash -c "tsc --noEmit"'],
|
||||
'*.{js,jsx,ts,tsx,json,md}': ['prettier --write'],
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
# Woodpecker CI Quality Enforcement Pipeline
|
||||
# Runs on: push, pull_request, manual
|
||||
|
||||
when:
|
||||
- event: [push, pull_request, manual]
|
||||
|
||||
variables:
|
||||
- &node_image 'node:20-alpine'
|
||||
- &gitleaks_image 'ghcr.io/gitleaks/gitleaks:v8.24.0'
|
||||
- &install_deps |
|
||||
corepack enable
|
||||
npm ci --ignore-scripts
|
||||
|
||||
steps:
|
||||
# Secret scanning (runs in parallel with install, no deps)
|
||||
secret-scan:
|
||||
image: *gitleaks_image
|
||||
commands:
|
||||
- gitleaks git --redact --verbose --log-opts="HEAD~1..HEAD"
|
||||
depends_on: []
|
||||
|
||||
# Stage 1: Install
|
||||
install:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
|
||||
# Stage 2: Security Audit
|
||||
security-audit:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm audit --audit-level=high
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
# Stage 3: Lint
|
||||
lint:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run lint
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
# Stage 4: Type Check
|
||||
typecheck:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run type-check
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
# Stage 5: Test with Coverage
|
||||
test:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run test -- --coverage --coverageThreshold='{"global":{"branches":80,"functions":80,"lines":80,"statements":80}}'
|
||||
depends_on:
|
||||
- install
|
||||
|
||||
# Stage 6: Build
|
||||
build:
|
||||
image: *node_image
|
||||
commands:
|
||||
- *install_deps
|
||||
- npm run build
|
||||
depends_on:
|
||||
- lint
|
||||
- typecheck
|
||||
- test
|
||||
- security-audit
|
||||
- secret-scan
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"scripts": {
|
||||
"lint": "eslint 'src/**/*.{ts,tsx}' --max-warnings=0",
|
||||
"type-check": "tsc --noEmit",
|
||||
"test": "jest",
|
||||
"build": "tsc",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
||||
"@typescript-eslint/parser": "^7.0.0",
|
||||
"eslint": "^9.0.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-security": "^3.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^29.0.0",
|
||||
"lint-staged": "^16.2.7",
|
||||
"prettier": "^3.0.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"removeComments": true,
|
||||
"incremental": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
|
||||
// STRICT MODE - All enabled
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"strictBindCallApply": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
|
||||
// Additional Checks
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"allowUnusedLabels": false,
|
||||
"allowUnreachableCode": false
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user