Add precommit hook

This commit is contained in:
icld
2025-02-27 10:48:04 -08:00
parent 476d49c204
commit b0de546668
33 changed files with 835 additions and 192 deletions
+65
View File
@@ -0,0 +1,65 @@
{
"name": "Laconic Core Development",
"image": "mcr.microsoft.com/devcontainers/javascript-node:0-18",
// Add environment variables
"containerEnv": {
"NODE_ENV": "development"
},
// Mount your SSH keys for git authentication
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly"
],
"customizations": {
"vscode": {
"extensions": [
"biomejs.biome",
"ms-vscode.vscode-typescript-next",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"christian-kohler.path-intellisense",
"github.vscode-pull-request-github",
"streetsidesoftware.code-spell-checker",
"eamodio.gitlens"
],
"settings": {
"editor.defaultFormatter": "biomejs.biome",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.fixAll": "explicit",
"source.addMissingImports.ts": "explicit",
"source.fixAll.ts": "explicit",
"source.removeUnusedImports": "explicit"
},
// Additional helpful settings
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"terminal.integrated.defaultProfile.linux": "bash",
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.updateImportsOnFileMove.enabled": "always",
"files.exclude": {
"**/.git": true,
"**/node_modules": true,
"**/dist": true
}
}
}
},
// Forward common development ports
"forwardPorts": [3000, 4000, 5000, 8000],
// Set up git configuration
"postCreateCommand": "pnpm install && git config --global pull.rebase true",
"remoteUser": "node",
// Features to install in the container
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/git:1": {}
}
}
+13
View File
@@ -0,0 +1,13 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
rules: {
// Add rules that can be auto-fixed
'@typescript-eslint/no-unused-vars': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'warn'
}
}
+30
View File
@@ -0,0 +1,30 @@
# Setting Up Contribution Guidelines with CODEOWNERS
You can create a CODEOWNERS file to define which files require specific approval before changes can be merged. This helps protect critical files in your project.
## Steps to implement CODEOWNERS:
1. Create a CODEOWNERS file in either the root directory, .github directory, or docs directory
2. Define file patterns and the users/teams who own those files
Here's how to implement it:
# CODEOWNERS file defines who needs to approve changes to specific files
# Format: file-pattern @user-or-team
# Dev container configuration
.devcontainer/* @your-username
# Core configuration files
.vscode/* @your-username
*.json @your-username
# Critical application files that need review
/src/core/* @your-username @another-team-member
/services/api/* @api-team-name
# Documentation changes can be reviewed by docs team
/docs/* @docs-team-or-username
# Default owners for everything else
* @default-team-or-username
+46
View File
@@ -0,0 +1,46 @@
# Contributing to Laconic Core
Thank you for considering contributing to our project! Here are some guidelines to help you get started.
## Development Environment
This project uses dev containers to ensure consistent development environments. To get started:
1. Install [Docker](https://www.docker.com/products/docker-desktop) and [VS Code](https://code.visualstudio.com/)
2. Install the [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extension
3. Clone the repository and open it in VS Code
4. Click the green button in the bottom-left corner and select "Reopen in Container"
## Protected Files
Some files in this repository are protected and require specific approval before changes can be merged:
- `.devcontainer/*` - Dev container configuration
- `.vscode/*` - VS Code workspace settings
- `core configuration files` - Project configuration files
- `critical application files` - Core functionality
Please discuss any changes to these files with the maintainers before submitting pull requests.
## Code Style
This project uses Biome for formatting and linting. The dev container will automatically configure your editor to use the correct settings.
## Submitting Changes
1. Fork the repository
2. Create a new branch for your changes
3. Make your changes following the code style guidelines
4. Write tests for your changes
5. Submit a pull request
## Pull Request Process
1. Update the README.md with details of changes if applicable
2. The version numbers will be updated by maintainers following semantic versioning
3. Pull requests require approval from at least one maintainer
4. Once approved, maintainers will merge the PR
## Questions?
If you have questions, please open an issue or contact the maintainers.
+3 -1
View File
@@ -69,4 +69,6 @@ logs
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
Thumbs.db
.pnpm-store
+2
View File
@@ -0,0 +1,2 @@
legacy-peer-deps=true
strict-peer-dependencies=false
+7
View File
@@ -0,0 +1,7 @@
{
"recommendations": [
"biomejs.biome",
"ms-vscode-remote.remote-containers",
"ms-typescript.vscode-typescript-next"
]
}
+29 -2
View File
@@ -1,3 +1,30 @@
{
"editor.defaultFormatter": "biomejs.biome"
}
// This file can be empty since all settings are in devcontainer.json
"typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true,
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.organizeImports": true
},
"editor.defaultFormatter": "biomejs.biome",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[json]": {
"editor.defaultFormatter": "biomejs.biome"
},
"typescript.validate.enable": true,
"javascript.validate.enable": true,
"typescript.reportStyleChecksAsWarnings": true,
"typescript.surveys.enabled": false
}
+8 -8
View File
@@ -1,20 +1,20 @@
import { Geist, Geist_Mono } from "next/font/google"
import '@workspace/ui/globals.css'
import "@workspace/ui/globals.css"
import { Providers } from "@/components/providers"
import { Providers } from '@/components/providers'
import { Geist, Geist_Mono } from 'next/font/google'
const fontSans = Geist({
subsets: ["latin"],
variable: "--font-sans",
subsets: ['latin'],
variable: '--font-sans'
})
const fontMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-mono",
subsets: ['latin'],
variable: '--font-mono'
})
export default function RootLayout({
children,
children
}: Readonly<{
children: React.ReactNode
}>) {
+1 -1
View File
@@ -1,4 +1,4 @@
import { Button } from "@workspace/ui/components/button"
import { Button } from '@workspace/ui/components/button'
export default function Page() {
return (
+3 -3
View File
@@ -1,7 +1,7 @@
"use client"
'use client'
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { ThemeProvider as NextThemesProvider } from 'next-themes'
import type * as React from 'react'
export function Providers({ children }: { children: React.ReactNode }) {
return (
+3 -3
View File
@@ -1,4 +1,4 @@
import * as React from "react"
import * as React from 'react'
const MOBILE_BREAKPOINT = 768
@@ -10,9 +10,9 @@ export function useIsMobile() {
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
mql.addEventListener('change', onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
return () => mql.removeEventListener('change', onChange)
}, [])
return !!isMobile
+1 -1
View File
@@ -1,6 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: ["@workspace/ui"],
transpilePackages: ['@workspace/ui']
}
export default nextConfig
+8 -1
View File
@@ -7,9 +7,16 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "biome check .",
"lint:fix": "biome check --apply .",
"format": "biome format .",
"format:fix": "biome format --write .",
"check-types": "tsc --noEmit",
"fix-types": "tsc --noEmit --pretty --incremental"
},
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@biomejs/monorepo": "github:biomejs/biome",
"@hookform/resolvers": "^4.1.2",
"@radix-ui/react-accordion": "^1.2.3",
"@radix-ui/react-alert-dialog": "^1.1.6",
+1 -1
View File
@@ -1 +1 @@
export { default } from "@workspace/ui/postcss.config";
export { default } from '@workspace/ui/postcss.config'
+1 -1
View File
@@ -1 +1 @@
export * from "@workspace/ui/tailwind.config";
export * from '@workspace/ui/tailwind.config'
+39 -31
View File
@@ -1,33 +1,41 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": []
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"trailingCommas": "none"
}
}
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false,
"ignore": ["node_modules", "dist", "build"]
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"enabled": true,
"quoteStyle": "single",
"trailingCommas": "none",
"semicolons": "asNeeded"
}
},
"json": {
"formatter": {
"enabled": true,
"trailingCommas": "none"
}
}
}
+23
View File
@@ -0,0 +1,23 @@
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts,jsx,tsx}"
run: pnpm lint:fix {staged_files}
stage_fixed: true
format:
glob: "*.{js,ts,jsx,tsx}"
run: pnpm format:fix {staged_files}
stage_fixed: true
typecheck:
glob: "*.{ts,tsx}"
run: pnpm check-types
# TypeScript requires checking the whole project context
# We can't easily check only staged files while maintaining type relationships
# Add a manual hook that can be run with `npx lefthook run fix-all`
fix-all:
commands:
fix-code:
glob: "*.{js,ts,jsx,tsx}"
run: pnpm fix-all
+14
View File
@@ -0,0 +1,14 @@
pre-commit:
commands:
check:
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
# run: npx @biomejs/biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files}
run: pnpm biome format --write {staged_files}
stage_fixed: true
pre-push:
parallel: true
commands:
check:
glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}"
run: npx @biomejs/biome check --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {push_files}
+19 -9
View File
@@ -1,25 +1,35 @@
{
"name": "qwrk-laconic-core",
"version": "1.0.0",
"description": "",
"description": "Monorepo for Laconic frontend codebases",
"main": "index.js",
"scripts": {
"build": "turbo build",
"dev": "turbo dev",
"lint": "turbo lint",
"lint:fix": "biome lint --write",
"test": "echo \"Error: no test specified\" && exit 1"
"lint:fix": "turbo lint:fix",
"format": "turbo format",
"format:fix": "turbo format:fix",
"check-types": "turbo check-types",
"fix-types": "turbo fix-types",
"validate": "turbo lint check-types format",
"fix-all": "turbo lint:fix fix-types format:fix",
"test": "echo \"Error: no test specified\" && exit 1",
"test:hooks": "lefthook run pre-commit"
},
"keywords": [],
"author": "",
"author": "QWRK",
"license": "ISC",
"packageManager": "pnpm@10.5.1",
"devDependencies": {
"turbo": "^2.4.4",
"@workspace/typescript-config": "workspace:*",
"@biomejs/biome": "^1.9.4",
"@workspace/typescript-config": "workspace:*",
"chalk": "5",
"lefthook": "^1.11.2",
"turbo": "^2.4.4",
"typescript": "^5.7.3"
},
"packageManager": "pnpm@10.5.1",
"engines": {
"node": ">=22.14.0"
}
}
+131 -4
View File
@@ -14,6 +14,12 @@ importers:
'@workspace/typescript-config':
specifier: workspace:*
version: link:services/typescript-config
chalk:
specifier: '5'
version: 5.4.1
lefthook:
specifier: ^1.11.2
version: 1.11.2
turbo:
specifier: ^2.4.4
version: 2.4.4
@@ -23,6 +29,12 @@ importers:
apps/web:
dependencies:
'@biomejs/biome':
specifier: ^1.9.4
version: 1.9.4
'@biomejs/monorepo':
specifier: github:biomejs/biome
version: https://codeload.github.com/biomejs/biome/tar.gz/d95df40a86c8debb369fdc9070c91642325bfe1f
'@hookform/resolvers':
specifier: ^4.1.2
version: 4.1.2(react-hook-form@7.54.2(react@19.0.0))
@@ -185,6 +197,12 @@ importers:
services/ui:
dependencies:
'@biomejs/biome':
specifier: ^1.9.4
version: 1.9.4
'@biomejs/monorepo':
specifier: github:biomejs/biome
version: https://codeload.github.com/biomejs/biome/tar.gz/d95df40a86c8debb369fdc9070c91642325bfe1f
'@radix-ui/react-slot':
specifier: ^1.1.1
version: 1.1.2(@types/react@18.3.0)(react@19.0.0)
@@ -307,6 +325,10 @@ packages:
cpu: [x64]
os: [win32]
'@biomejs/monorepo@https://codeload.github.com/biomejs/biome/tar.gz/d95df40a86c8debb369fdc9070c91642325bfe1f':
resolution: {tarball: https://codeload.github.com/biomejs/biome/tar.gz/d95df40a86c8debb369fdc9070c91642325bfe1f}
version: 0.0.0
'@cspotcode/source-map-support@0.8.1':
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
@@ -1468,6 +1490,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chalk@5.4.1:
resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==}
engines: {node: ^12.17.0 || ^14.13 || >=16.0.0}
change-case@3.1.0:
resolution: {integrity: sha512-2AZp7uJZbYEzRPsFoa+ijKdvp9zsrnnt6+yFokfwEpeJm0xuJDVoxiRCAaTzyJND8GJkofo2IcKWaUZ/OECVzw==}
@@ -1652,8 +1678,8 @@ packages:
dot-case@2.1.1:
resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
electron-to-chromium@1.5.106:
resolution: {integrity: sha512-12keJGdXQWPnfVOu6/6ZzZgPPNodiDOSe3LjA8qk2yXTjnCnw2LeGUsAmtlNAmH4UW0K7tOLcz0j9lI2eJCJRA==}
electron-to-chromium@1.5.107:
resolution: {integrity: sha512-dJr1o6yCntRkXElnhsHh1bAV19bo/hKyFf7tCcWgpXbuFIF0Lakjgqv5LRfSDaNzAII8Fnxg2tqgHkgCvxdbxw==}
embla-carousel-react@8.5.2:
resolution: {integrity: sha512-Tmx+uY3MqseIGdwp0ScyUuxpBgx5jX1f7od4Cm5mDwg/dptEiTKf9xp6tw0lZN2VA9JbnVMl/aikmbc53c6QFA==}
@@ -1919,6 +1945,60 @@ packages:
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
lefthook-darwin-arm64@1.11.2:
resolution: {integrity: sha512-8DpvrybtWdt6UmfZk+hA8daYXr6zkpJVogZ8M49BQx6ISSKUaC03xzO1m4MrAsoKok77ka4JAidYhOa2gCu15A==}
cpu: [arm64]
os: [darwin]
lefthook-darwin-x64@1.11.2:
resolution: {integrity: sha512-DrL1SOT8lJksjudRu6fTZTp3M0EbpCP2RQ22MDT71clS8BMrFL8x3h9Ziw+uNH76j9zA241tW5zMxWMSv+foAA==}
cpu: [x64]
os: [darwin]
lefthook-freebsd-arm64@1.11.2:
resolution: {integrity: sha512-AliG4Wi8BNC27hCSnuFBeUXh/eA3fppnUbQQPISy/G94yfwRkzyml9MZzvb7HKmUpw1LT0sq9RQ6FQPxBZ2DYA==}
cpu: [arm64]
os: [freebsd]
lefthook-freebsd-x64@1.11.2:
resolution: {integrity: sha512-V6cgRCoi5+jcq6XBIdRYraeEOK1UhBrtL/XZlNypAIkhPoBtfTP9u2wSprGMDzZvJCRriLXZxV/d0v94laKXzA==}
cpu: [x64]
os: [freebsd]
lefthook-linux-arm64@1.11.2:
resolution: {integrity: sha512-VKcK7sjIK8UpXX/qK6Fxa0Lnwr4gzRtlXDS17jzxThcyFk8iGBpQ+9ZnPLv2yAaEIzmGhJUG9sDgOb9IQ5kpBQ==}
cpu: [arm64]
os: [linux]
lefthook-linux-x64@1.11.2:
resolution: {integrity: sha512-aGa2Krph14YwSW7KF0PrlCBK9P7V/Z4oFklonmz3r2Fjm8EdhA750y7OQvA9KerXRleIb5SaUH/cz1azG/izeQ==}
cpu: [x64]
os: [linux]
lefthook-openbsd-arm64@1.11.2:
resolution: {integrity: sha512-f7owNQ9Ki6Y07KBgdXdH28EYO0eBdZuGTpIggMeHNhYFVDavxuINP2BjmbXtzpUu8K5BX6exGx0umtWhRhXbvQ==}
cpu: [arm64]
os: [openbsd]
lefthook-openbsd-x64@1.11.2:
resolution: {integrity: sha512-HKv6PV64vOjqPrlxAqo07N9+Z34jdPDBfeExqi0ldR7vACFaBJFIdhWCLLP+3uQUrNKc8GXlikqplZn8MgRSQw==}
cpu: [x64]
os: [openbsd]
lefthook-windows-arm64@1.11.2:
resolution: {integrity: sha512-042jCKZ/H+lS6XYoMIf2FWMP2hxXqfAT52UW6lYObIOvQ5xu/epUXFjtmXRyYxCv57No3JYYMg1Yr06xdzTKkQ==}
cpu: [arm64]
os: [win32]
lefthook-windows-x64@1.11.2:
resolution: {integrity: sha512-1Map6Ck2AyfY6ptN9T19N41HFKFqRTzmILtGaRGJABEzHiE4+gSWcq5YT1R6cCtkVlewD3Lx+J/80D/Kb/cVtw==}
cpu: [x64]
os: [win32]
lefthook@1.11.2:
resolution: {integrity: sha512-/5royc/WbL2KTfFJ54wEdvxUZOBXwc54v/fW2Bz4LMOkAA3LWIxnoUiybSiauu+nhdTG98qERxH1YHwF2wZlAA==}
hasBin: true
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
@@ -2650,6 +2730,8 @@ snapshots:
'@biomejs/cli-win32-x64@1.9.4':
optional: true
'@biomejs/monorepo@https://codeload.github.com/biomejs/biome/tar.gz/d95df40a86c8debb369fdc9070c91642325bfe1f': {}
'@cspotcode/source-map-support@0.8.1':
dependencies:
'@jridgewell/trace-mapping': 0.3.9
@@ -3783,7 +3865,7 @@ snapshots:
browserslist@4.24.4:
dependencies:
caniuse-lite: 1.0.30001701
electron-to-chromium: 1.5.106
electron-to-chromium: 1.5.107
node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4)
@@ -3819,6 +3901,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
chalk@5.4.1: {}
change-case@3.1.0:
dependencies:
camel-case: 3.0.0
@@ -4008,7 +4092,7 @@ snapshots:
dependencies:
no-case: 2.3.2
electron-to-chromium@1.5.106: {}
electron-to-chromium@1.5.107: {}
embla-carousel-react@8.5.2(react@19.0.0):
dependencies:
@@ -4295,6 +4379,49 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
lefthook-darwin-arm64@1.11.2:
optional: true
lefthook-darwin-x64@1.11.2:
optional: true
lefthook-freebsd-arm64@1.11.2:
optional: true
lefthook-freebsd-x64@1.11.2:
optional: true
lefthook-linux-arm64@1.11.2:
optional: true
lefthook-linux-x64@1.11.2:
optional: true
lefthook-openbsd-arm64@1.11.2:
optional: true
lefthook-openbsd-x64@1.11.2:
optional: true
lefthook-windows-arm64@1.11.2:
optional: true
lefthook-windows-x64@1.11.2:
optional: true
lefthook@1.11.2:
optionalDependencies:
lefthook-darwin-arm64: 1.11.2
lefthook-darwin-x64: 1.11.2
lefthook-freebsd-arm64: 1.11.2
lefthook-freebsd-x64: 1.11.2
lefthook-linux-arm64: 1.11.2
lefthook-linux-x64: 1.11.2
lefthook-openbsd-arm64: 1.11.2
lefthook-openbsd-x64: 1.11.2
lefthook-windows-arm64: 1.11.2
lefthook-windows-x64: 1.11.2
lodash.get@4.4.2: {}
lodash@4.17.21: {}
+126 -1
View File
@@ -1,5 +1,42 @@
# QWRK-Laconic Turborepo Migration Technical Specification
## Implementation Starting Points
This migration specification supports multiple entry points depending on the current state of your repository:
### Scenario 1: New Repository Implementation
- Follow all steps sequentially starting from "Repository Initialization"
- Execute all setup commands to build the complete architecture from scratch
### Scenario 2: Existing Repository Conversion
If the repository already exists but requires Turborepo integration:
1. Preserve existing package structure and Git history
2. Add Turborepo configuration (`turbo.json`, `pnpm-workspace.yaml`)
3. Adapt existing packages to the recommended structure
4. Integrate shared package dependencies
5. Skip initialization steps that would conflict with existing setup
### Scenario 3: Partial Turborepo Implementation
For repositories with partial Turborepo architecture:
1. Audit existing configuration against this specification
2. Upgrade task definitions to latest syntax (`tasks` vs legacy `pipeline`)
3. Apply missing architectural components
4. Align package structure with recommended patterns
5. Integrate modular services approach
### Prerequisite Validation
Run the following diagnostic commands to determine your starting point:
```bash
# Check for existing Turborepo configuration
[ -f turbo.json ] && echo "Existing turbo.json found, requires update" || echo "New turbo.json needed"
# Verify package manager
command -v pnpm >/dev/null 2>&1 && echo "pnpm installed" || echo "pnpm installation required"
# Check Next.js applications
find . -path "*/package.json" -exec grep -l "next" {} \; | xargs -I{} dirname {}
```
## Architecture Overview
```mermaid
@@ -84,6 +121,7 @@ qwrk-laconic-mono/
### 1. Repository Initialization
#### New Repository Approach
```bash
# Initialize Turborepo with pnpm
pnpm dlx create-turbo@latest qwrk-laconic-mono --package-manager=pnpm
@@ -93,6 +131,23 @@ cd qwrk-laconic-mono
rm -rf apps/docs apps/web
```
#### Existing Repository Approach
```bash
# If repository already exists and initialized:
git clone <existing-repo-url> qwrk-laconic-mono
cd qwrk-laconic-mono
# Create workspace structure if not exists
mkdir -p apps packages
# Save existing structure (optional)
find . -maxdepth 2 -type d -not -path "*/\.*" -not -path "./node_modules" \
-not -path "./apps" -not -path "./packages" > existing_directories.txt
# Create migration branch
git checkout -b feature/turborepo-migration
```
### 2. Configure Package Manager
```bash
@@ -940,6 +995,76 @@ flowchart TD
- Create project management features
- Implement deployment functionality
## Repository Structural Migration
For existing repositories that require Turborepo conversion, follow this structural migration approach:
```mermaid
flowchart TB
subgraph "Initial State"
A[Existing Repository]
B[Frontend Application]
C[Backend Services]
D[Shared Libraries]
end
subgraph "Migration Process"
E[Create Migration Branch]
F[Add Turborepo Config]
G[Restructure Packages]
H[Refactor Dependencies]
I[Implement Build System]
end
subgraph "Final State"
J[apps/laconic-web]
K[apps/laconic-deploy]
L[apps/docs]
M[packages/ui]
N[packages/utils]
O[packages/services]
end
A --> E
E --> F
F --> G
G --> H
H --> I
I --> J & K & L & M & N & O
```
### Repository Structure Analysis
Before migration, analyze the existing code structure:
```bash
# Identify package dependencies
find . -name "package.json" -not -path "*/node_modules/*" -exec jq -r '.dependencies | keys[]' {} \; | sort | uniq -c | sort -nr
# Identify file distribution
find . -type f -name "*.ts*" | grep -v "node_modules" | grep -v ".turbo" | grep -v "dist" | wc -l
# Analyze potential shared components
find . -type f -name "*.tsx" -not -path "*/node_modules/*" | xargs grep -l "export" | sort
```
### Incremental Migration Strategy
1. **Coexistence Phase**
- Implement Turborepo alongside existing structure
- Gradually move components to new architecture
- Maintain backward compatibility during transition
2. **Dual Build System**
- Configure builds for both legacy and new structure
- Implement feature flags for incremental adoption
- Monitor performance metrics across both systems
3. **Final Migration**
- Complete transition to Turborepo architecture
- Remove legacy configuration
- Optimize build pipeline for new structure
## Best Practices and Guidelines
### TypeScript Configuration
@@ -1045,4 +1170,4 @@ jobs:
This technical specification provides a comprehensive guide for migrating the qwrk-laconic repository to a modern Turborepo structure with Next.js 15. By following this guide, you can create a modular, maintainable monorepo that leverages the latest technologies and best practices for web development.
The migration approach prioritizes incremental adoption, allowing for gradual transition while maintaining backward compatibility with existing systems.
The migration approach prioritizes incremental adoption, allowing for gradual transition while maintaining backward compatibility with existing systems.
+36
View File
@@ -0,0 +1,36 @@
# Scripts and Hooks
This directory contains utility scripts for the project.
## Lefthook Configuration
The project uses [Lefthook](https://github.com/evilmartians/lefthook) for git hooks to ensure code quality before commits.
### Pre-commit Hooks
The pre-commit hooks run:
1. **Linting** - Checks and fixes code style issues
2. **Formatting** - Ensures consistent code formatting
3. **Type Checking** - Verifies TypeScript types
These hooks are configured in `lefthook.yaml` in the root directory.
### Usage
The hooks run automatically when you commit code. You can also run them manually:
```bash
# Run all pre-commit hooks
pnpm test:hooks
# Run individual commands
pnpm lint:fix
pnpm format:fix
pnpm check-types
pnpm fix-all
```
## VS Code Integration
TypeScript errors will show up directly in VS Code thanks to the configuration in `.vscode/settings.json`.
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env node
import chalk from 'chalk'
const command = process.argv[2]
const files = process.argv.slice(3)
// Format the command name for display
const formatCommand = (cmd) => {
switch (cmd) {
case 'lint:fix':
return chalk.blue.bold('LINTING')
case 'format:fix':
return chalk.magenta.bold('FORMATTING')
case 'check-types':
return chalk.yellow.bold('TYPE CHECKING')
case 'fix-all':
return chalk.green.bold('FIXING ALL ISSUES')
default:
return chalk.white.bold(cmd.toUpperCase())
}
}
// Display a header for the command
console.log(
'\n' +
chalk.gray(
'╔═════════════════════════════════════════════════════════════════════════════╗'
)
)
console.log(
chalk.gray('║ ') +
chalk.white.bold(`LEFTHOOK: ${formatCommand(command)}`) +
chalk.gray(' ║')
)
console.log(
chalk.gray(
'╚═════════════════════════════════════════════════════════════════════════════╝\n'
)
)
// If we have files, display them
if (files.length > 0) {
console.log(chalk.gray('Files being processed:'))
for (const file of files) {
console.log(chalk.cyan(`${file}`))
}
console.log('')
}
// Execute the original command
const { execSync } = await import('node:child_process')
try {
// Construct the command based on what was passed
let fullCommand
if (files.length > 0 && command !== 'check-types') {
// For commands that support specific files
fullCommand = `pnpm ${command} ${files.join(' ')}`
} else {
// For commands that run on the whole project
fullCommand = `pnpm ${command}`
}
// Execute the command
execSync(fullCommand, { stdio: 'inherit' })
// Show success message
console.log(
'\n' +
chalk.green.bold('✓ SUCCESS') +
chalk.green(` ${formatCommand(command)} completed successfully`)
)
} catch (error) {
// Show error message
console.log(
'\n' +
chalk.red.bold('✗ ERROR') +
chalk.red(` ${formatCommand(command)} failed`)
)
process.exit(1)
}
@@ -3,6 +3,6 @@
"display": "React Library",
"extends": "./base.json",
"compilerOptions": {
"jsx": "react-jsx",
"jsx": "react-jsx"
}
}
+5 -1
View File
@@ -3,8 +3,12 @@
"version": "0.0.0",
"type": "module",
"private": true,
"scripts": {},
"scripts": {
"lint": "biome format --write"
},
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@biomejs/monorepo": "github:biomejs/biome",
"@radix-ui/react-slot": "^1.1.1",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
+4 -4
View File
@@ -2,8 +2,8 @@
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
autoprefixer: {}
}
}
export default config;
export default config
+21 -21
View File
@@ -1,35 +1,35 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from '@radix-ui/react-slot'
import { type VariantProps, cva } from 'class-variance-authority'
import * as React from 'react'
import { cn } from "@workspace/ui/lib/utils"
import { cn } from '@workspace/ui/lib/utils'
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline'
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10'
}
},
defaultVariants: {
variant: "default",
size: "default",
},
variant: 'default',
size: 'default'
}
}
)
@@ -41,7 +41,7 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : 'button'
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
@@ -51,6 +51,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
)
}
)
Button.displayName = "Button"
Button.displayName = 'Button'
export { Button, buttonVariants }
+2 -2
View File
@@ -1,5 +1,5 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
-2
View File
@@ -83,8 +83,6 @@
}
}
@layer base {
* {
@apply border-border outline-ring/50;
+85 -91
View File
@@ -1,100 +1,94 @@
import type { Config } from "tailwindcss"
import defaultTheme from "tailwindcss"
import tailwindcssAnimate from "tailwindcss-animate"
import type { Config } from 'tailwindcss'
import defaultTheme from 'tailwindcss'
import tailwindcssAnimate from 'tailwindcss-animate'
const config = {
// darkMode: ["class"],
content: [
"app/**/*.{ts,tsx}",
"components/**/*.{ts,tsx}",
"../../services/ui/src/components/**/*.{ts,tsx}",
'app/**/*.{ts,tsx}',
'components/**/*.{ts,tsx}',
'../../services/ui/src/components/**/*.{ts,tsx}'
],
theme: {
extend: {
fontFamily: {
sans: [
'var(--font-sans)',
...defaultTheme.fontFamily.sans
],
mono: [
'var(--font-mono)',
...defaultTheme.fontFamily.mono
]
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
extend: {
fontFamily: {
sans: ['var(--font-sans)', ...defaultTheme.fontFamily.sans],
mono: ['var(--font-mono)', ...defaultTheme.fontFamily.mono]
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
primary: 'hsl(var(--sidebar-primary))',
'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
accent: 'hsl(var(--sidebar-accent))',
'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
border: 'hsl(var(--sidebar-border))',
ring: 'hsl(var(--sidebar-ring))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
keyframes: {
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
},
plugins: [tailwindcssAnimate],
plugins: [tailwindcssAnimate]
} satisfies Config
export default config
-1
View File
@@ -1,4 +1,3 @@
{
"extends": "@workspace/typescript-config/base.json"
}
+27 -2
View File
@@ -2,15 +2,40 @@
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"check-types": {
"dependsOn": ["^check-types"]
"dependsOn": ["^build"],
"outputs": []
},
"fix-types": {
"dependsOn": ["^build"],
"outputs": []
},
"format": {
"dependsOn": ["^build"],
"outputs": []
},
"format:fix": {
"dependsOn": ["^build"],
"outputs": []
},
"test": {
"outputs": ["coverage/**"],
"dependsOn": []
},
"lint": {
"dependsOn": ["^build"]
},
"lint:fix": {
"dependsOn": ["^build"]
},
"dev": {
"dependsOn": ["^build"],
"persistent": true,
"cache": false
}
}
}
}