feat: rename rails/ to tools/ and add service tool suites

Rename the `rails/` directory to `tools/` for agent discoverability —
agents frequently failed to locate helper scripts due to the non-intuitive
directory name. Add backward-compat symlink `rails/ → tools/`.

New tool suites:
- Authentik: auth-token, user-list, user-create, group-list, app-list,
  flow-list, admin-status (8 scripts)
- Coolify: team-list, project-list, service-list, service-status, deploy,
  env-set (7 scripts)
- Woodpecker: pipeline-list, pipeline-status, pipeline-trigger (3 stubs)
- GLPI: session-init, computer-list, ticket-list, ticket-create, user-list
  (6 scripts)
- Health: stack-health.sh — stack-wide connectivity check

Infrastructure:
- Shared credential loader at tools/_lib/credentials.sh
- install.sh creates symlink + chmod on tool scripts
- All ~253 rails/ path references updated across 68+ files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-22 11:51:39 -06:00
parent 248db8935c
commit 80c3680ccb
158 changed files with 2481 additions and 213 deletions

View File

@@ -0,0 +1,64 @@
// 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',
},
},
],
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -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',
],
};

View File

@@ -0,0 +1,67 @@
# Woodpecker CI Quality Enforcement Pipeline - Monorepo
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
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

View File

@@ -0,0 +1,96 @@
# 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).

View File

@@ -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"
}
}

View File

@@ -0,0 +1,3 @@
packages:
- 'apps/*'
- 'packages/*'

View File

@@ -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"]
}

View File

@@ -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
}
}
}

View File

@@ -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'],
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -0,0 +1,9 @@
module.exports = {
'*.{ts,tsx}': [
'eslint --fix --max-warnings=0',
'bash -c "tsc --noEmit"',
],
'*.{js,jsx,ts,tsx,json,md,css}': [
'prettier --write',
],
};

View File

@@ -0,0 +1,67 @@
# Woodpecker CI Quality Enforcement Pipeline - Next.js
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
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

View File

@@ -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;

View File

@@ -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"
}
}

View File

@@ -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"]
}

View File

@@ -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',
},
],
},
};

View File

@@ -0,0 +1,2 @@
npx lint-staged
npx git-secrets --scan || echo "Warning: git-secrets not installed"

View File

@@ -0,0 +1,9 @@
module.exports = {
'*.{ts,tsx}': [
'eslint --fix --max-warnings=0',
'bash -c "tsc --noEmit"',
],
'*.{js,jsx,ts,tsx,json,md}': [
'prettier --write',
],
};

View File

@@ -0,0 +1,66 @@
# Woodpecker CI Quality Enforcement Pipeline
# Runs on: push, pull_request, manual
when:
- event: [push, pull_request, manual]
variables:
- &node_image "node:20-alpine"
- &install_deps |
corepack enable
npm ci --ignore-scripts
steps:
# 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

View File

@@ -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"
}
}

View File

@@ -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"]
}