๐Ÿ› ๏ธ Tooling configs ยท copy-paste ready

Planet Module System foundation ยท 2026-05-16

Mother fase-0 committa questi file su feat/pms-foundation-base prima 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 config
  • playwright.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