feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
@@ -1,402 +0,0 @@
|
||||
# monorepo-management — detailed patterns and worked examples
|
||||
|
||||
## pnpm Workspaces
|
||||
|
||||
### Setup
|
||||
|
||||
```yaml
|
||||
# pnpm-workspace.yaml
|
||||
packages:
|
||||
- "apps/*"
|
||||
- "packages/*"
|
||||
- "tools/*"
|
||||
```
|
||||
|
||||
```json
|
||||
// .npmrc
|
||||
# Hoist shared dependencies
|
||||
shamefully-hoist=true
|
||||
|
||||
# Strict peer dependencies
|
||||
auto-install-peers=true
|
||||
strict-peer-dependencies=true
|
||||
|
||||
# Performance
|
||||
store-dir=~/.pnpm-store
|
||||
```
|
||||
|
||||
### Dependency Management
|
||||
|
||||
```bash
|
||||
# Install dependency in specific package
|
||||
pnpm add react --filter @repo/ui
|
||||
pnpm add -D typescript --filter @repo/ui
|
||||
|
||||
# Install workspace dependency
|
||||
pnpm add @repo/ui --filter web
|
||||
|
||||
# Install in all packages
|
||||
pnpm add -D eslint -w
|
||||
|
||||
# Update all dependencies
|
||||
pnpm update -r
|
||||
|
||||
# Remove dependency
|
||||
pnpm remove react --filter @repo/ui
|
||||
```
|
||||
|
||||
### Scripts
|
||||
|
||||
```bash
|
||||
# Run script in specific package
|
||||
pnpm --filter web dev
|
||||
pnpm --filter @repo/ui build
|
||||
|
||||
# Run in all packages
|
||||
pnpm -r build
|
||||
pnpm -r test
|
||||
|
||||
# Run in parallel
|
||||
pnpm -r --parallel dev
|
||||
|
||||
# Filter by pattern
|
||||
pnpm --filter "@repo/*" build
|
||||
pnpm --filter "...web" build # Build web and dependencies
|
||||
```
|
||||
|
||||
## Nx Monorepo
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Create Nx monorepo
|
||||
npx create-nx-workspace@latest my-org
|
||||
|
||||
# Generate applications
|
||||
nx generate @nx/react:app my-app
|
||||
nx generate @nx/next:app my-next-app
|
||||
|
||||
# Generate libraries
|
||||
nx generate @nx/react:lib ui-components
|
||||
nx generate @nx/js:lib utils
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
```json
|
||||
// nx.json
|
||||
{
|
||||
"extends": "nx/presets/npm.json",
|
||||
"$schema": "./node_modules/nx/schemas/nx-schema.json",
|
||||
"targetDefaults": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"inputs": ["production", "^production"],
|
||||
"cache": true
|
||||
},
|
||||
"test": {
|
||||
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
|
||||
"cache": true
|
||||
},
|
||||
"lint": {
|
||||
"inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
|
||||
"cache": true
|
||||
}
|
||||
},
|
||||
"namedInputs": {
|
||||
"default": ["{projectRoot}/**/*", "sharedGlobals"],
|
||||
"production": [
|
||||
"default",
|
||||
"!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)",
|
||||
"!{projectRoot}/tsconfig.spec.json"
|
||||
],
|
||||
"sharedGlobals": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Running Tasks
|
||||
|
||||
```bash
|
||||
# Run task for specific project
|
||||
nx build my-app
|
||||
nx test ui-components
|
||||
nx lint utils
|
||||
|
||||
# Run for affected projects
|
||||
nx affected:build
|
||||
nx affected:test --base=main
|
||||
|
||||
# Visualize dependencies
|
||||
nx graph
|
||||
|
||||
# Run in parallel
|
||||
nx run-many --target=build --all --parallel=3
|
||||
```
|
||||
|
||||
## Shared Configurations
|
||||
|
||||
### TypeScript Configuration
|
||||
|
||||
```json
|
||||
// packages/tsconfig/base.json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"incremental": true,
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
// packages/tsconfig/react.json
|
||||
{
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"]
|
||||
}
|
||||
}
|
||||
|
||||
// apps/web/tsconfig.json
|
||||
{
|
||||
"extends": "@repo/tsconfig/react.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
```
|
||||
|
||||
### ESLint Configuration
|
||||
|
||||
```javascript
|
||||
// packages/config/eslint-preset.js
|
||||
module.exports = {
|
||||
extends: [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:react/recommended",
|
||||
"plugin:react-hooks/recommended",
|
||||
"prettier",
|
||||
],
|
||||
plugins: ["@typescript-eslint", "react", "react-hooks"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
},
|
||||
};
|
||||
|
||||
// apps/web/.eslintrc.js
|
||||
module.exports = {
|
||||
extends: ["@repo/config/eslint-preset"],
|
||||
rules: {
|
||||
// App-specific rules
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
## Code Sharing Patterns
|
||||
|
||||
### Pattern 1: Shared UI Components
|
||||
|
||||
```typescript
|
||||
// packages/ui/src/button.tsx
|
||||
import * as React from 'react';
|
||||
|
||||
export interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary';
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export function Button({ variant = 'primary', children, onClick }: ButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className={`btn btn-${variant}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
// packages/ui/src/index.ts
|
||||
export { Button, type ButtonProps } from './button';
|
||||
export { Input, type InputProps } from './input';
|
||||
|
||||
// apps/web/src/app.tsx
|
||||
import { Button } from '@repo/ui';
|
||||
|
||||
export function App() {
|
||||
return <Button variant="primary">Click me</Button>;
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Shared Utilities
|
||||
|
||||
```typescript
|
||||
// packages/utils/src/string.ts
|
||||
export function capitalize(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export function truncate(str: string, length: number): string {
|
||||
return str.length > length ? str.slice(0, length) + "..." : str;
|
||||
}
|
||||
|
||||
// packages/utils/src/index.ts
|
||||
export * from "./string";
|
||||
export * from "./array";
|
||||
export * from "./date";
|
||||
|
||||
// Usage in apps
|
||||
import { capitalize, truncate } from "@repo/utils";
|
||||
```
|
||||
|
||||
### Pattern 3: Shared Types
|
||||
|
||||
```typescript
|
||||
// packages/types/src/user.ts
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
role: "admin" | "user";
|
||||
}
|
||||
|
||||
export interface CreateUserInput {
|
||||
email: string;
|
||||
name: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// Used in both frontend and backend
|
||||
import type { User, CreateUserInput } from "@repo/types";
|
||||
```
|
||||
|
||||
## Build Optimization
|
||||
|
||||
### Turborepo Caching
|
||||
|
||||
```json
|
||||
// turbo.json
|
||||
{
|
||||
"pipeline": {
|
||||
"build": {
|
||||
// Build depends on dependencies being built first
|
||||
"dependsOn": ["^build"],
|
||||
|
||||
// Cache these outputs
|
||||
"outputs": ["dist/**", ".next/**"],
|
||||
|
||||
// Cache based on these inputs (default: all files)
|
||||
"inputs": ["src/**/*.tsx", "src/**/*.ts", "package.json"]
|
||||
},
|
||||
"test": {
|
||||
// Run tests in parallel, don't depend on build
|
||||
"cache": true,
|
||||
"outputs": ["coverage/**"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Remote Caching
|
||||
|
||||
```bash
|
||||
# Turborepo Remote Cache (Vercel)
|
||||
npx turbo login
|
||||
npx turbo link
|
||||
|
||||
# Custom remote cache
|
||||
# turbo.json
|
||||
{
|
||||
"remoteCache": {
|
||||
"signature": true,
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CI/CD for Monorepos
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci.yml
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # For Nx affected commands
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: 8
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Build
|
||||
run: pnpm turbo run build
|
||||
|
||||
- name: Test
|
||||
run: pnpm turbo run test
|
||||
|
||||
- name: Lint
|
||||
run: pnpm turbo run lint
|
||||
|
||||
- name: Type check
|
||||
run: pnpm turbo run type-check
|
||||
```
|
||||
|
||||
### Deploy Affected Only
|
||||
|
||||
```yaml
|
||||
# Deploy only changed apps
|
||||
- name: Deploy affected apps
|
||||
run: |
|
||||
if pnpm nx affected:apps --base=origin/main --head=HEAD | grep -q "web"; then
|
||||
echo "Deploying web app"
|
||||
pnpm --filter web deploy
|
||||
fi
|
||||
```
|
||||
Reference in New Issue
Block a user