From f56fac7a6083d99b0a2e990f2fbc9e814b2a0f33 Mon Sep 17 00:00:00 2001 From: manNomi Date: Sun, 1 Feb 2026 21:42:37 +0900 Subject: [PATCH] feat: optimize monorepo with auto PR labeling, CI improvements, and shared configs --- .env.guide.md | 11 +++ .github/workflows/ci.yml | 116 +++++++++++++++++++++++-- .github/workflows/pr-auto-label.yml | 74 ++++++++++++++++ apps/admin/biome.json | 74 ++++++++-------- apps/web/biome.json | 88 ++----------------- packages/config/biome/base.json | 111 +++++++++++++++++++++++ packages/config/package.json | 10 +++ packages/config/typescript/base.json | 23 +++++ packages/config/typescript/nextjs.json | 14 +++ packages/config/typescript/vite.json | 10 +++ 10 files changed, 408 insertions(+), 123 deletions(-) create mode 100644 .github/workflows/pr-auto-label.yml create mode 100644 packages/config/biome/base.json create mode 100644 packages/config/package.json create mode 100644 packages/config/typescript/base.json create mode 100644 packages/config/typescript/nextjs.json create mode 100644 packages/config/typescript/vite.json diff --git a/.env.guide.md b/.env.guide.md index 2bc6cc03..c6bf4d15 100644 --- a/.env.guide.md +++ b/.env.guide.md @@ -1,5 +1,16 @@ # Environment Variables Setup Guide +## Monorepo Structure + +이 프로젝트는 모노레포 구조로 web과 admin 앱을 포함합니다: + +- **apps/web**: Next.js 기반 웹 애플리케이션 (`NEXT_PUBLIC_*` 환경 변수) +- **apps/admin**: Vite 기반 어드민 애플리케이션 (`VITE_*` 환경 변수) + +각 앱은 독립적인 환경 변수를 가지며, 공통 빌드 도구 설정은 `packages/config`에서 관리됩니다. + +--- + ## Architecture Decision ### Why Only .env.development and .env.production? diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1df03c7..4cd9df31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,9 +7,40 @@ on: branches: [main, develop] jobs: - quality-check: - name: Code Quality Check + # 변경 감지 + detect-changes: + name: Detect Changes runs-on: ubuntu-latest + outputs: + web: ${{ steps.filter.outputs.web }} + admin: ${{ steps.filter.outputs.admin }} + root: ${{ steps.filter.outputs.root }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Check changed files + uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + web: + - 'apps/web/**' + admin: + - 'apps/admin/**' + root: + - 'package.json' + - 'pnpm-lock.yaml' + - 'pnpm-workspace.yaml' + - 'turbo.json' + - '.github/workflows/**' + + # Web 앱 품질 체크 + web-quality-check: + name: Web - Quality Check + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.web == 'true' || needs.detect-changes.outputs.root == 'true' steps: - name: Checkout repository uses: actions/checkout@v4 @@ -28,13 +59,80 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Run Biome (lint & format) & TypeScript - run: pnpm run ci:check + - name: Run checks (lint & typecheck) + run: pnpm --filter @solid-connect/web run ci:check + + # Admin 앱 품질 체크 + admin-quality-check: + name: Admin - Quality Check + runs-on: ubuntu-latest + needs: detect-changes + if: needs.detect-changes.outputs.admin == 'true' || needs.detect-changes.outputs.root == 'true' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run lint check + run: pnpm --filter @solid-connect/admin run lint + + - name: Run format check + run: pnpm --filter @solid-connect/admin run format + + # Web 앱 빌드 + web-build: + name: Web - Build + runs-on: ubuntu-latest + needs: [detect-changes, web-quality-check] + if: | + always() && + (needs.detect-changes.outputs.web == 'true' || needs.detect-changes.outputs.root == 'true') && + needs.web-quality-check.result == 'success' + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: "22.x" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build web application + run: pnpm --filter @solid-connect/web run build + env: + NODE_ENV: production - build: - name: Build Verification + # Admin 앱 빌드 + admin-build: + name: Admin - Build runs-on: ubuntu-latest - needs: quality-check + needs: [detect-changes, admin-quality-check] + if: | + always() && + (needs.detect-changes.outputs.admin == 'true' || needs.detect-changes.outputs.root == 'true') && + needs.admin-quality-check.result == 'success' steps: - name: Checkout repository uses: actions/checkout@v4 @@ -53,7 +151,7 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build Next.js application - run: pnpm run build + - name: Build admin application + run: pnpm --filter @solid-connect/admin run build env: NODE_ENV: production diff --git a/.github/workflows/pr-auto-label.yml b/.github/workflows/pr-auto-label.yml new file mode 100644 index 00000000..ae2361e7 --- /dev/null +++ b/.github/workflows/pr-auto-label.yml @@ -0,0 +1,74 @@ +name: Auto Label PR + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + label: + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed apps + id: detect + run: | + # Get changed files between base and head + BASE_SHA="${{ github.event.pull_request.base.sha }}" + HEAD_SHA="${{ github.event.pull_request.head.sha }}" + + FILES=$(git diff --name-only $BASE_SHA $HEAD_SHA) + + echo "Changed files:" + echo "$FILES" + + # Check for web changes + if echo "$FILES" | grep -q "^apps/web/"; then + echo "web=true" >> $GITHUB_OUTPUT + echo "✓ Detected changes in apps/web" + else + echo "web=false" >> $GITHUB_OUTPUT + fi + + # Check for admin changes + if echo "$FILES" | grep -q "^apps/admin/"; then + echo "admin=true" >> $GITHUB_OUTPUT + echo "✓ Detected changes in apps/admin" + else + echo "admin=false" >> $GITHUB_OUTPUT + fi + + - name: Add labels + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const labels = []; + + if ('${{ steps.detect.outputs.web }}' === 'true') { + labels.push('web'); + } + + if ('${{ steps.detect.outputs.admin }}' === 'true') { + labels.push('admin'); + } + + if (labels.length > 0) { + console.log(`Adding labels: ${labels.join(', ')}`); + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + labels: labels + }); + } else { + console.log('No app-specific changes detected'); + } diff --git a/apps/admin/biome.json b/apps/admin/biome.json index 64b4d31a..d12aad10 100644 --- a/apps/admin/biome.json +++ b/apps/admin/biome.json @@ -1,36 +1,42 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.4/schema.json", - "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false - }, - "files": { - "ignoreUnknown": false, - "includes": [ - "**/src/**/*", - "**/.vscode/**/*", - "**/index.html", - "**/vite.config.ts", - "!**/src/routeTree.gen.ts", - "!**/src/styles.css" - ] - }, - "formatter": { - "enabled": true, - "indentStyle": "tab" - }, - "assist": { "actions": { "source": { "organizeImports": "on" } } }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - } + "$schema": "https://biomejs.dev/schemas/2.3.12/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/src/**/*", + "**/.vscode/**/*", + "**/index.html", + "**/vite.config.ts", + "!**/src/routeTree.gen.ts", + "!**/src/styles.css" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120 + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } } - diff --git a/apps/web/biome.json b/apps/web/biome.json index 361404b3..6d730a59 100644 --- a/apps/web/biome.json +++ b/apps/web/biome.json @@ -1,41 +1,22 @@ { "$schema": "https://biomejs.dev/schemas/2.3.12/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": false - }, + "extends": ["../../packages/config/biome/base.json"], "files": { - "ignoreUnknown": false, "includes": ["**", "!node_modules", "!.next", "!build", "!dist", "!*.tsbuildinfo"] }, - "formatter": { - "enabled": true, - "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 120 + "vcs": { + "useIgnoreFile": false }, "linter": { - "enabled": true, "rules": { - "recommended": true, "a11y": { - "noLabelWithoutControl": "off", "noStaticElementInteractions": "off", "useKeyWithClickEvents": "off", - "useAriaPropsForRole": "warn", - "useAriaPropsSupportedByRole": "warn", "useButtonType": "off", "useFocusableInteractive": "off", "useHtmlLang": "off", "useSemanticElements": "off", - "noSvgWithoutTitle": "off", - "useValidAriaRole": "warn" - }, - "complexity": { - "noUselessTypeConstraint": "warn" + "noSvgWithoutTitle": "off" }, "security": { "noDangerouslySetInnerHtml": "off" @@ -44,70 +25,17 @@ "noImgElement": "off" }, "correctness": { - "noInvalidUseBeforeDeclaration": "off", "noUnusedVariables": "off", - "noUnusedFunctionParameters": "off", - "useExhaustiveDependencies": "warn" + "noUnusedFunctionParameters": "off" }, "style": { - "noCommonJs": "error", - "noNamespace": "error", - "noNonNullAssertion": "off", - "useArrayLiterals": "error", - "useAsConstAssertion": "error", - "useBlockStatements": "off", - "useConst": "error" + "noNonNullAssertion": "off" }, "suspicious": { - "noAlert": "off", - "noConsole": "off", "noExplicitAny": "off", - "noExtraNonNullAssertion": "error", - "noMisleadingInstantiator": "error", - "noNonNullAssertedOptionalChain": "error", - "noRedeclare": "warn", "noUnknownAtRules": "off", - "noUnsafeDeclarationMerging": "error", - "useIterableCallbackReturn": "off", - "useNamespaceKeyword": "error", - "noArrayIndexKey": "off" - } - } - }, - "javascript": { - "formatter": { - "jsxQuoteStyle": "double", - "quoteProperties": "asNeeded", - "trailingCommas": "all", - "semicolons": "always", - "arrowParentheses": "always", - "bracketSameLine": false, - "quoteStyle": "double", - "attributePosition": "auto", - "bracketSpacing": true - } - }, - "json": { - "formatter": { - "trailingCommas": "none" - } - }, - "css": { - "parser": { - "cssModules": true, - "tailwindDirectives": true - } - }, - "overrides": [ - { - "includes": [".eslintrc.{js,cjs}", "*.config.{js,mjs,ts}"], - "linter": { - "rules": { - "style": { - "noCommonJs": "off" - } - } + "useIterableCallbackReturn": "off" } } - ] + } } diff --git a/packages/config/biome/base.json b/packages/config/biome/base.json new file mode 100644 index 00000000..ac57e69b --- /dev/null +++ b/packages/config/biome/base.json @@ -0,0 +1,111 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "formatWithErrors": false, + "indentStyle": "space", + "indentWidth": 2, + "lineEnding": "lf", + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "a11y": { + "noLabelWithoutControl": "off", + "noStaticElementInteractions": "warn", + "useKeyWithClickEvents": "warn", + "useAriaPropsForRole": "warn", + "useAriaPropsSupportedByRole": "warn", + "useButtonType": "warn", + "useFocusableInteractive": "warn", + "useHtmlLang": "warn", + "useSemanticElements": "warn", + "noSvgWithoutTitle": "warn", + "useValidAriaRole": "warn" + }, + "complexity": { + "noUselessTypeConstraint": "warn" + }, + "security": { + "noDangerouslySetInnerHtml": "warn" + }, + "performance": { + "noImgElement": "warn" + }, + "correctness": { + "noInvalidUseBeforeDeclaration": "off", + "noUnusedVariables": "warn", + "useExhaustiveDependencies": "warn" + }, + "style": { + "noCommonJs": "error", + "noNamespace": "error", + "noNonNullAssertion": "warn", + "useArrayLiterals": "error", + "useAsConstAssertion": "error", + "useBlockStatements": "off", + "useConst": "error" + }, + "suspicious": { + "noAlert": "off", + "noConsole": "off", + "noExplicitAny": "warn", + "noExtraNonNullAssertion": "error", + "noMisleadingInstantiator": "error", + "noNonNullAssertedOptionalChain": "error", + "noRedeclare": "warn", + "noUnknownAtRules": "warn", + "noUnsafeDeclarationMerging": "error", + "useIterableCallbackReturn": "warn", + "useNamespaceKeyword": "error", + "noArrayIndexKey": "off" + } + } + }, + "javascript": { + "formatter": { + "jsxQuoteStyle": "double", + "quoteProperties": "asNeeded", + "trailingCommas": "all", + "semicolons": "always", + "arrowParentheses": "always", + "bracketSameLine": false, + "quoteStyle": "double", + "attributePosition": "auto", + "bracketSpacing": true + } + }, + "json": { + "formatter": { + "trailingCommas": "none" + } + }, + "css": { + "parser": { + "cssModules": true, + "tailwindDirectives": true + } + }, + "overrides": [ + { + "includes": [".eslintrc.{js,cjs}", "*.config.{js,mjs,ts}"], + "linter": { + "rules": { + "style": { + "noCommonJs": "off" + } + } + } + } + ] +} diff --git a/packages/config/package.json b/packages/config/package.json new file mode 100644 index 00000000..e46807a3 --- /dev/null +++ b/packages/config/package.json @@ -0,0 +1,10 @@ +{ + "name": "@solid-connect/config", + "version": "1.0.0", + "private": true, + "description": "Shared configuration files for Solid Connect monorepo", + "files": [ + "biome", + "typescript" + ] +} diff --git a/packages/config/typescript/base.json b/packages/config/typescript/base.json new file mode 100644 index 00000000..aee68121 --- /dev/null +++ b/packages/config/typescript/base.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "allowJs": true, + "checkJs": false, + "jsx": "preserve", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "isolatedModules": true, + "allowSyntheticDefaultImports": true + }, + "exclude": ["node_modules", ".next", ".output", "dist", "build"] +} diff --git a/packages/config/typescript/nextjs.json b/packages/config/typescript/nextjs.json new file mode 100644 index 00000000..45a5abbf --- /dev/null +++ b/packages/config/typescript/nextjs.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Next.js", + "extends": "./base.json", + "compilerOptions": { + "target": "ES2017", + "lib": ["DOM", "DOM.Iterable", "ES2017"], + "plugins": [ + { + "name": "next" + } + ] + } +} diff --git a/packages/config/typescript/vite.json b/packages/config/typescript/vite.json new file mode 100644 index 00000000..e09908cf --- /dev/null +++ b/packages/config/typescript/vite.json @@ -0,0 +1,10 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Vite + React", + "extends": "./base.json", + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "types": ["vite/client"] + } +}