๐ ๏ธ Tooling configs ยท copy-paste ready
Planet Module System foundation ยท 2026-05-16
Mother fase-0 committa questi file su
feat/pms-foundation-baseprima di spawnare i Child. Tutti i blocchi sono copy-paste ready โ versionati 2026-05-16.
1. pnpm-workspace.yaml (root repo)
packages:
- 'packages/*'
- 'frontend/multiverbe-app'
- 'frontend/multiverbe-bos-app'
- 'sites-runtime'
2. Root package.json ยท scripts blocco
{
"name": "@multiverbe/monorepo",
"private": true,
"type": "module",
"engines": { "node": ">=22", "pnpm": ">=9" },
"packageManager": "pnpm@9.15.0",
"scripts": {
"build": "pnpm -r --filter=./packages/* build && pnpm --filter ./frontend/multiverbe-app build",
"typecheck": "pnpm -r typecheck",
"lint": "eslint . --max-warnings=0",
"lint:fix": "eslint . --fix",
"format": "prettier --write .",
"test": "vitest run",
"test:watch": "vitest",
"test:e2e": "playwright test",
"test:visual": "playwright test --config=playwright.visual.config.ts",
"test:a11y": "playwright test --config=playwright.a11y.config.ts",
"test:mutation": "pnpm --filter @multiverbe/planet-core test:mutation",
"test:load": "k6 run tests/load/health-planets.k6.js",
"size": "size-limit",
"size:why": "size-limit --why",
"scaffold:planet": "tsx scripts/scaffold-planet.ts",
"prepare": "husky",
"changeset": "changeset",
"version": "changeset version",
"release": "pnpm build && changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.27.7",
"@commitlint/cli": "^19.5.0",
"@commitlint/config-conventional": "^19.5.0",
"@playwright/test": "^1.48.0",
"@axe-core/playwright": "^4.10.0",
"@stryker-mutator/core": "^8.6.0",
"@stryker-mutator/vitest-runner": "^8.6.0",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@vitest/coverage-v8": "^2.1.5",
"eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.1.0",
"husky": "^9.1.7",
"lint-staged": "^15.2.10",
"madge": "^8.0.0",
"prettier": "^3.4.2",
"size-limit": "^11.1.6",
"@size-limit/preset-small-lib": "^11.1.6",
"tsup": "^8.3.5",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^2.1.5"
},
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md,yml,yaml}": ["prettier --write"]
}
}
3. .husky/pre-commit + .husky/commit-msg
.husky/pre-commit:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm lint-staged
.husky/commit-msg:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
pnpm dlx commitlint --edit "$1"
Set executable: chmod +x .husky/pre-commit .husky/commit-msg (skip on Windows ยท CI runs them).
4. commitlint.config.js
/** @type {import('@commitlint/types').UserConfig} */
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [2, 'always', [
'feat', 'fix', 'refactor', 'docs', 'test', 'chore', 'perf', 'ci', 'build', 'revert', 'style', 'security',
]],
'scope-enum': [2, 'always', [
'planet-system', 'planet-core', 'planet-web', 'planet-booking', 'planet-crm', 'planet-sales',
'planet-content-studio', 'planet-marketing', 'planet-hr',
'sector-adapters', 'contracts', 'testkit',
'auth', 'compliance', 'deploy', 'deps', 'ci', 'adr', 'docs', 'scripts',
]],
'subject-case': [2, 'always', ['sentence-case', 'lower-case']],
'header-max-length': [2, 'always', 100],
'body-max-line-length': [1, 'always', 120],
},
};
5. eslint.config.js ยท flat config (ESLint 9)
import js from '@eslint/js';
import ts from '@typescript-eslint/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import a11y from 'eslint-plugin-jsx-a11y';
import importPlugin from 'eslint-plugin-import';
import prettier from 'eslint-config-prettier';
export default [
js.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: { project: ['./tsconfig.json', './packages/*/tsconfig.json'] },
},
plugins: { '@typescript-eslint': ts, react, 'react-hooks': reactHooks, 'jsx-a11y': a11y, import: importPlugin },
rules: {
// Hard gates
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/ban-ts-comment': ['error', {
'ts-ignore': 'allow-with-description',
'ts-expect-error': 'allow-with-description',
minimumDescriptionLength: 10,
}],
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'warn',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'no-restricted-imports': ['error', {
patterns: [
// Boundary discipline ยท packages NON importano da app
{ group: ['frontend/*', '../frontend/*'], message: 'Packages must not import from frontend/ ยท use contracts boundary' },
// Internal hidden
{ group: ['*/internal/*', '*/internal'], message: 'Internal modules not for cross-package import' },
],
}],
// React
'react/jsx-key': 'error',
'react/no-array-index-key': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// A11y
'jsx-a11y/no-autofocus': 'warn',
'jsx-a11y/click-events-have-key-events': 'error',
'jsx-a11y/no-static-element-interactions': 'error',
// Import order
'import/order': ['error', {
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
'newlines-between': 'always',
alphabetize: { order: 'asc' },
}],
'import/no-cycle': ['error', { maxDepth: 10 }],
},
},
prettier,
{
ignores: ['**/dist/**', '**/node_modules/**', '**/.next/**', 'coverage/**', '.changeset/**'],
},
];
6. .size-limit.cjs
/** @type {import('size-limit').SizeLimitConfig} */
module.exports = [
{
name: '@multiverbe/contracts',
path: 'packages/contracts/dist/index.js',
limit: '8 KB',
gzip: true,
},
{
name: '@multiverbe/planet-core',
path: 'packages/planet-core/dist/index.js',
limit: '25 KB',
gzip: true,
},
{
name: '@multiverbe/planet-testkit',
path: 'packages/planet-testkit/dist/index.js',
limit: '20 KB',
gzip: true,
},
{
name: '@multiverbe/sector-adapters ยท ristorazione',
path: 'packages/sector-adapters/dist/ristorazione/index.js',
limit: '15 KB',
gzip: true,
},
{
name: '@multiverbe/sector-adapters ยท parrucchieri',
path: 'packages/sector-adapters/dist/parrucchieri/index.js',
limit: '15 KB',
gzip: true,
},
{
name: '@multiverbe/sector-adapters ยท studi-medici',
path: 'packages/sector-adapters/dist/studi-medici/index.js',
limit: '15 KB',
gzip: true,
},
{
name: '@multiverbe/planet-web',
path: 'packages/planet-web/dist/index.js',
limit: '80 KB',
gzip: true,
},
{
name: '@multiverbe/planet-booking',
path: 'packages/planet-booking/dist/index.js',
limit: '80 KB',
gzip: true,
},
];
7. stryker.conf.json ยท mutation testing
{
"$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json",
"packageManager": "pnpm",
"testRunner": "vitest",
"vitest": {
"configFile": "packages/planet-core/vitest.config.ts"
},
"reporters": ["html", "clear-text", "progress", "dashboard"],
"mutate": [
"packages/planet-core/src/**/*.ts",
"!packages/planet-core/src/index.ts",
"!packages/planet-core/src/**/*.test.ts",
"!packages/planet-core/src/**/*.spec.ts"
],
"thresholds": {
"high": 80,
"low": 70,
"break": 60
},
"timeoutMS": 60000,
"concurrency": 4,
"htmlReporter": { "fileName": "reports/mutation/mutation-report.html" },
"tempDirName": ".stryker-tmp",
"cleanTempDir": true
}
8. tsconfig.base.json ยท strict baseline
{
"compilerOptions": {
"target": "ES2023",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"jsx": "react-jsx",
"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,
"exactOptionalPropertyTypes": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
}
}
9. tsup.config.ts template per ogni package
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['esm'],
dts: true,
sourcemap: true,
clean: true,
splitting: false,
treeshake: true,
minify: 'terser',
external: ['react', 'react-dom', '@supabase/supabase-js'],
target: 'es2022',
});
10. vitest.config.ts template per ogni package
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom', // 'node' per packages senza React
globals: true,
setupFiles: ['./tests/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json-summary', 'html'],
include: ['src/**/*.ts', 'src/**/*.tsx'],
exclude: ['src/index.ts', 'src/**/*.d.ts', 'src/**/*.test.{ts,tsx}'],
thresholds: {
lines: 80,
branches: 70,
functions: 80,
statements: 80,
},
},
},
});
11. .changeset/config.json
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [["@multiverbe/contracts", "@multiverbe/planet-core", "@multiverbe/planet-testkit"]],
"access": "restricted",
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": ["@multiverbe/planet-crm", "@multiverbe/planet-sales", "@multiverbe/planet-content-studio", "@multiverbe/planet-hr"]
}
12. renovate.json ยท auto-bump policy
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:base", ":dependencyDashboard"],
"schedule": ["before 6am on monday"],
"packageRules": [
{
"matchPackagePatterns": ["^@multiverbe/"],
"enabled": false,
"description": "Internal monorepo packages handled by changesets"
},
{
"groupName": "planet-system tooling",
"matchPackagePatterns": [
"^@stryker-mutator/",
"^@playwright/",
"^@axe-core/",
"^size-limit",
"^@size-limit/",
"^vitest",
"^@vitest/"
]
},
{
"groupName": "react ecosystem",
"matchPackagePatterns": ["^react", "^@types/react"]
},
{
"matchUpdateTypes": ["major"],
"dependencyDashboardApproval": true
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
}
}
13. playwright.config.ts ยท multi-config
Root playwright.config.ts:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/e2e',
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
fullyParallel: true,
reporter: process.env.CI ? [['list'], ['html', { open: 'never' }], ['junit', { outputFile: 'test-results/junit.xml' }]] : 'list',
use: {
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'mobile', use: { ...devices['Pixel 5'] } },
],
});
Variants:
playwright.visual.config.tsโtestDir: './tests/visual'+ snapshot configplaywright.a11y.config.tsโtestDir: './tests/a11y'+ reporter axe-core
14. .prettierrc.json
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 110,
"tabWidth": 2,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSpacing": true,
"jsxSingleQuote": false
}
15. tests/setup.ts ยท vitest globals
import '@testing-library/jest-dom/vitest';
import { afterEach, beforeAll } from 'vitest';
import { cleanup } from '@testing-library/react';
beforeAll(() => {
process.env.TZ = 'UTC';
});
afterEach(() => {
cleanup();
});
16. Quick install commands (Mother fase-0)
cd D:\Desktop\Multiverbe
# 1. Verify Node 22 + pnpm 9
node -v # v22.x
pnpm -v # 9.x
# 2. Install root devDependencies
pnpm add -Dw \
@changesets/cli @commitlint/cli @commitlint/config-conventional \
@playwright/test @axe-core/playwright \
@stryker-mutator/core @stryker-mutator/vitest-runner \
@typescript-eslint/eslint-plugin @typescript-eslint/parser \
@vitest/coverage-v8 eslint eslint-config-prettier \
eslint-plugin-import eslint-plugin-jsx-a11y \
eslint-plugin-react eslint-plugin-react-hooks \
husky lint-staged madge prettier \
size-limit @size-limit/preset-small-lib \
tsup tsx typescript vitest
# 3. Husky setup
pnpm exec husky init
# 4. Playwright browsers
pnpm dlx playwright install --with-deps chromium
# 5. Verify
pnpm typecheck
pnpm lint
pnpm test --run
pnpm build
17. Boundary discipline ยท architectural fitness functions
Aggiungere a eslint.config.js regola no-restricted-imports (giร inclusa sopra) + script di check:
# scripts/check-boundaries.sh
#!/usr/bin/env bash
set -e
echo "Checking package boundaries..."
# 1. packages/* must NOT import frontend/*
if grep -r --include="*.ts" --include="*.tsx" "from ['\"]\\.\\.[/].*frontend" packages/; then
echo "โ Boundary violation: packages/* importing from frontend/"
exit 1
fi
# 2. planet-{kind} must NOT import other planet-{kind}
for planet in packages/planet-*/; do
kind=$(basename "$planet" | sed 's/planet-//')
if grep -r --include="*.ts" --include="*.tsx" "from ['\"]@multiverbe/planet-" "$planet"/src | grep -v "planet-core" | grep -v "planet-testkit" | grep -v "$kind"; then
echo "โ Boundary violation: $planet importing another planet-* directly"
exit 1
fi
done
# 3. sector-adapters must NOT import planet-*
if grep -r --include="*.ts" "from ['\"]@multiverbe/planet-" packages/sector-adapters/src; then
echo "โ Boundary violation: sector-adapters importing planet-*"
exit 1
fi
echo "โ
All boundaries respected"
Aggiungere a CI come step separato.
Mother fase-0 commit message template:
feat(planet-system): bootstrap monorepo foundation ยท contracts + CI + tooling
- packages/contracts/src/planet.ts (ports + Zod runtime + branded types + Result<T,E>)
- .github/workflows/planet-system-ci.yml (L1-L9 hard gates + L5/L10 soft)
- Tooling: tsconfig strict ยท eslint flat ยท commitlint conventional ยท husky + lint-staged
- Mutation: stryker.conf.json (planet-core target โฅ60%)
- Size budget: per-package โค25/15/80 KB gz
- Changesets + Renovate group ยท SBOM CycloneDX
Refs: BOS#planet-module-system-foundation-2026-05-16