๐Ÿ“š Catalogs & ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ โ€” pnpm ๋งŒ์ด ์ค„ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ๋“ค

2026๋…„ 3์›” 20์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

catalog: ํ”„๋กœํ† ์ฝœ๋กœ ๋ฒ„์ „์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๊ณ , pnpmfile.mjs ๋กœ ์˜์กด์„ฑ์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ ์œผ๋กœ ์ œ์–ดํ•˜๋Š” pnpm ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์™„์ „ ์ •๋ณต

05. Catalogs & ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

๐Ÿ“‹ ๋ชฉ์ฐจ

  1. ์ด์ •๋ฆฌ
  2. ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ
  3. [์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ](#-์˜์ฒ ์ด์˜-ํ‡ด๊ทผ ์ผ๊ธฐ)
  4. ๋” ์•Œ์•„๋ณด๊ธฐ

๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: ์•ฝ 25๋ถ„(์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 12๋ถ„

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„

[Catalogs ๊ธฐ๋ณธ] โ†’ [Named Catalogs] โ†’ [pnpmfile.mjs] โ†’ [dedupe] โ†’ [only-allow]

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • catalog: ํ”„๋กœํ† ์ฝœ๋กœ ๋ชจ๋…ธ๋ ˆํฌ ์˜์กด์„ฑ ๋ฒ„์ „์„ ์ค‘์•™ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค
  • Named Catalogs ๋กœ React 17/18 ๋“ฑ ๋ฒ„์ „ ๋ถ„๊ธฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค
  • pnpmfile.mjs ๋กœ ํŠน์ • ํŒจํ‚ค์ง€์˜ ์˜์กด์„ฑ์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค
  • only-allow ๋กœ ํŒ€ ์ „์ฒด๊ฐ€ pnpm ์„ ์‚ฌ์šฉํ•˜๋„๋ก ๊ฐ•์ œํ•  ์ˆ˜ ์žˆ๋‹ค

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ๋ฐฐ๊ฒฝ ์„ธ๊ณ„๊ด€: '์˜์ˆ˜๋„ค ์ปค๋ฎค๋‹ˆํ‹ฐ'

๋ฒ„์ „ ์ถฉ๋Œ์„ ๊ฑฑ์ •ํ•˜๋Š” ์˜์ฒ ์ด์™€ catalog ๊ธฐ๋Šฅ์„ ์•Œ๋ ค์ฃผ๋Š” ์˜ํ˜ธ
  • ๐Ÿฃ ์˜์ฒ  ( ์‹ ์ž… ): "์˜ํ˜ธ ๋‹˜, ๋ชจ๋…ธ๋ ˆํฌ์—์„œ react ๋ฒ„์ „์ด apps/community ์—์„  18.2.0, apps/admin ์—์„  18.2.1 ๋กœ ๋‹ฌ๋ผ์š”. ๋ˆ„๊ตฐ๊ฐ€ pnpm update ๋ฅผ ํ•œ์ชฝ์—์„œ๋งŒ ํ–ˆ๋‚˜๋ด์š”. ์ด๋Ÿฐ ๊ฒŒ ์Œ“์ด๋ฉด ๋‚˜์ค‘์— ๋ฒ„์ „ ์ถฉ๋Œ์ด ๋‚  ๊ฒƒ ๊ฐ™์•„์„œ ๊ฑฑ์ •์ธ๋ฐ, ๋ชจ๋“  ์•ฑ์—์„œ ๊ฐ™์€ ๋ฒ„์ „์„ ๊ฐ•์ œํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ์žˆ๋‚˜์š”?"
  • ๐Ÿฆ ์˜ํ˜ธ ( ๋ฆฌ๋“œ ): "Catalogs ์“ฐ๋ฉด ๋ผ์š”. pnpm-workspace.yaml ์— ๋ฒ„์ „์„ ํ•œ ๋ฒˆ๋งŒ ์„ ์–ธํ•˜๊ณ , ๊ฐ package.json ์—์„œ 'catalog:' ๋ฅผ ์“ฐ๋ฉด ์ž๋™์œผ๋กœ ์ค‘์•™ ๋ฒ„์ „์„ ์ฐธ์กฐํ•ด์š”. ๋ฒ„์ „ ์˜ฌ๋ฆด ๋•Œ๋„ pnpm-workspace.yaml ํ•œ ์ค„๋งŒ ๋ฐ”๊พธ๋ฉด ์ „์ฒด๊ฐ€ ๋ฐ”๋€Œ๊ณ  pnpm-lock.yaml ์—์„œ ์ถฉ๋Œ๋„ ์ค„์–ด๋“ค์–ด์š”. Catalogs ๋Š” ๋ชจ๋…ธ๋ ˆํฌ๊ฐ€ ์žˆ๋Š” ํŒ€์ด๋ผ๋ฉด ๋ฌด์กฐ๊ฑด ์จ์•ผ ํ•˜๋Š” ๊ธฐ๋Šฅ์ด์—์š”."

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

๋ฒ„์ „ ๋“œ๋ฆฌํ”„ํŠธ ๋ฌธ์ œ

๋ชจ๋…ธ๋ ˆํฌ์—์„œ 10๊ฐœ์˜ ์•ฑ์ด ๋ชจ๋‘ react ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๊ฐ package.json ์— ๋ฒ„์ „ ๋ฒ”์œ„๊ฐ€ 10๋ฒˆ ๋“ฑ์žฅํ•œ๋‹ค.

// apps/community/package.json
{ "react": "^18.2.0" }
 
// apps/admin/package.json
{ "react": "^18.2.1" }   โ† ๋ˆ„๊ตฐ๊ฐ€ ์—…๋ฐ์ดํŠธํ•จ
 
// apps/landing/package.json
{ "react": "^18.1.0" }   โ† ์˜ค๋ž˜๋œ ๋ฒ„์ „

์—…๋ฐ์ดํŠธํ•  ๋•Œ 10๊ฐœ ํŒŒ์ผ์„ ๋ชจ๋‘ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค. ํ•˜๋‚˜๋ผ๋„ ๋น ์ง€๋ฉด ๋ฒ„์ „ ๋“œ๋ฆฌํ”„ํŠธ(drift) ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. PR ๋งˆ๋‹ค package.json ์ˆ˜์ •์ด ๋ฐœ์ƒํ•ด merge conflict ๋„ ์žฆ์•„์ง„๋‹ค.

Catalogs ๊ฐ€ ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•œ๋‹ค.


๐Ÿ“š Catalogs โ€” ๋ฒ„์ „ ๊ด€๋ฆฌ์˜ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›

๊ธฐ๋ณธ Catalog ์„ค์ •

# pnpm-workspace.yaml
# ๐Ÿ’ก ์ด ๊ณต๊ฐ„์ด ๋ชจ๋…ธ๋ ˆํฌ ์ „์ฒด์˜ "๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(Single Source of Truth)"์ด ๋ฉ๋‹ˆ๋‹ค.
packages:
  - 'apps/*'
  - 'packages/*'
 
# ๐Ÿฆ catalog: (๋‹จ์ˆ˜ํ˜•) โ†’ ๋ชจ๋…ธ๋ ˆํฌ ์ „์ฒด์—์„œ ๊ณตํ†ต์œผ๋กœ ์“ธ "๊ธฐ๋ณธ ์นดํƒˆ๋กœ๊ทธ"๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
catalog:
  # ์•„๋ž˜ ์ ํžŒ ํŒจํ‚ค์ง€๋“ค์€ ์ด์ œ๋ถ€ํ„ฐ ์ด ๋ฒ„์ „์œผ๋กœ ์ „์ฒด ํ†ต์ผ๋ฉ๋‹ˆ๋‹ค!
  react: ^18.2.0
  react-dom: ^18.2.0
  next: ^14.2.0
  typescript: ^5.3.3
  "@types/react": ^18.2.0
  "@types/node": ^20.11.0
  tailwindcss: ^3.4.1
  axios: ^1.6.7
  zod: ^3.22.4
  "@tanstack/react-query": ^5.17.15
  prisma: ^5.9.1
  "@prisma/client": ^5.9.1

package.json ์—์„œ catalog: ์ฐธ์กฐ

// apps/community/package.json
{
  "name": "@youngsu/community",
  "dependencies": {
    // ๐Ÿฆ [ํ•ต์‹ฌ] ๋ฒ„์ „์„ ^18.2.0 ์ด๋ผ๊ณ  ์ง์ ‘ ์“ฐ์ง€ ์•Š๊ณ  "catalog:" ๋ผ๊ณ ๋งŒ ์ ์Šต๋‹ˆ๋‹ค.
    // ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด pnpm ์ด ์•Œ์•„์„œ pnpm-workspace.yaml ์— ์„ ์–ธ๋œ ๋ฒ„์ „์„ ๋‹น๊ฒจ์˜ต๋‹ˆ๋‹ค.
    "next": "catalog:",
    "react": "catalog:",
    "react-dom": "catalog:",
    "axios": "catalog:",
    "@tanstack/react-query": "catalog:",
    
    // ๐Ÿ’ก ์šฐ๋ฆฌ ๋ชจ๋…ธ๋ ˆํฌ ๋‚ด๋ถ€์˜ ๋‹ค๋ฅธ ํŒจํ‚ค์ง€๋ฅผ ์“ธ ๋•Œ๋Š” workspace:* ๋ฅผ ์”๋‹ˆ๋‹ค.
    // (์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ = catalog: / ๋‚ด๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ = workspace: ๋กœ ์™„๋ฒฝํ•˜๊ฒŒ ๋ถ„๋ฆฌ!)
    "@youngsu/ui": "workspace:*"
  },
  "devDependencies": {
    "typescript": "catalog:",
    "@types/react": "catalog:",
    "@types/node": "catalog:",
    "tailwindcss": "catalog:"
  }
}
// apps/admin/package.json
{
  "name": "@youngsu/admin",
  "dependencies": {
    "next": "catalog:",     // community ์™€ ์™„์ „ํžˆ ๋™์ผํ•œ ๋ฒ„์ „ ๋ณด์žฅ
    "react": "catalog:",
    "react-dom": "catalog:"
  }
}

๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ โ€” ํ•œ ์ค„๋งŒ ์ˆ˜์ •

# pnpm-workspace.yaml ์—์„œ ํ•œ ์ค„๋งŒ ์ˆ˜์ •
catalog:
  react: ^18.3.0   # โ† ์ด ํ•œ ์ค„์ด ๋ชจ๋“  ์•ฑ์— ์ž๋™ ์ ์šฉ
pnpm install
# โ†’ lock ํŒŒ์ผ ์—…๋ฐ์ดํŠธ, ๋ชจ๋“  ์•ฑ์—์„œ react@18.3.x ์‚ฌ์šฉ

catalog: ํ”„๋กœํ† ์ฝœ ๋ณ€ํ˜•

{
  "dependencies": {
    "react": "catalog:",           // default catalog ์˜ react
    "react": "catalog:default"     // ๋ช…์‹œ์ ์œผ๋กœ default ์ง€์ • (๋™์ผ)
  }
}

publish ์‹œ ์ž๋™ ๋ณ€ํ™˜

// ๋ฐฐํฌ ์ „
{ "react": "catalog:" }
 
// ๋ฐฐํฌ ํ›„ (pnpm publish ๋˜๋Š” pnpm pack)
{ "react": "^18.2.0" }   // ์‹ค์ œ ๋ฒ„์ „์œผ๋กœ ๋ณ€ํ™˜๋จ

๐Ÿท๏ธ Named Catalogs โ€” ๋ฒ„์ „ ๋ถ„๊ธฐ ๊ด€๋ฆฌ

์—ฌ๋Ÿฌ ๋ฒ„์ „์„ ๋™์‹œ์— ๊ด€๋ฆฌํ•ด์•ผ ํ•  ๋•Œ Named Catalogs ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค.

๋‹ค์ค‘ React ๋ฒ„์ „ ๊ด€๋ฆฌ

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'
 
catalog:
  # ๊ธฐ๋ณธ (์ตœ์‹ )
  react: ^18.2.0
  react-dom: ^18.2.0
 
catalogs:
  # ๋ ˆ๊ฑฐ์‹œ ์•ฑ์šฉ (React 17)
  react17:
    react: ^17.0.2
    react-dom: ^17.0.2
    "@types/react": ^17.0.0
 
  # ์ตœ์‹  ์‹คํ—˜์šฉ (React 19)
  react19:
    react: ^19.0.0
    react-dom: ^19.0.0
    "@types/react": ^19.0.0
 
  # ๊ณตํ†ต ๋„๊ตฌ ๋ฒ„์ „ (๋ฒ„์ „ ๋ถ„๋ฆฌ ๊ด€๋ฆฌ)
  dev:
    typescript: ^5.3.3
    eslint: ^8.57.0
    prettier: ^3.2.5
// apps/legacy/package.json (React 17 ๋ ˆ๊ฑฐ์‹œ ์•ฑ)
{
  "dependencies": {
    "react": "catalog:react17",
    "react-dom": "catalog:react17"
  }
}
// apps/community/package.json (๊ธฐ๋ณธ React 18)
{
  "dependencies": {
    "react": "catalog:",          // default catalog
    "react-dom": "catalog:"
  },
  "devDependencies": {
    "typescript": "catalog:dev"   // dev catalog
  }
}

๊ธฐ์กด ๋ชจ๋…ธ๋ ˆํฌ์—์„œ Catalogs ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜

# codemod ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜
pnpm dlx codemod pnpm/catalog
๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ „:
apps/community/package.json: { "react": "^18.2.0" }
apps/admin/package.json:     { "react": "^18.2.0" }

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ›„:
pnpm-workspace.yaml:         catalog: { react: ^18.2.0 }
apps/community/package.json: { "react": "catalog:" }
apps/admin/package.json:     { "react": "catalog:" }

๐ŸŽฃ pnpmfile.mjs โ€” ์˜์กด์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ œ์–ด

pnpmfile.mjs ๋Š” pnpm ์˜ ์„ค์น˜ ํ›… ํŒŒ์ผ์ด๋‹ค. ํŒจํ‚ค์ง€ ์„ค์น˜ ์ค‘์— ์˜์กด์„ฑ์„ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ธฐ๋ณธ ๊ตฌ์กฐ

// .pnpmfile.mjs (๋ชจ๋…ธ๋ ˆํฌ ๋ฃจํŠธ)
// ๐Ÿ’ก ์ด ํŒŒ์ผ์€ pnpm ์ด `pnpm install` ์„ ์‹คํ–‰ํ•  ๋•Œ ์ค‘๊ฐ„์— ๊ฐœ์ž…(๊ฐ€๋กœ์ฑ„๊ธฐ)ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋งˆ๋ฒ•์˜ ํ›…(Hook) ํŒŒ์ผ์ž…๋‹ˆ๋‹ค.
export const hooks = {
  // ๐Ÿฆ [1๋‹จ๊ณ„] readPackage ํ›…: pnpm ์ด ํŠน์ • ํŒจํ‚ค์ง€์˜ package.json ์„ ๋ง‰ ์ฝ์–ด๋“ค์ธ ์ˆœ๊ฐ„์— ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค.
  // ์—ฌ๊ธฐ์„œ ์šฐ๋ฆฌ๊ฐ€ ๊ฐ•์ œ๋กœ package.json ์˜ ๋‚ด์šฉ์„ ์กฐ์ž‘ํ•ด์„œ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!
  readPackage(pkg, context) {
    // pkg.dependencies ๋“ฑ ์ˆ˜์ • ๋กœ์ง...
    return pkg; // ์ˆ˜์ •๋œ ํŒจํ‚ค์ง€ ์ •๋ณด๋ฅผ pnpm ์—๊ฒŒ ๋‹ค์‹œ ๋Œ๋ ค์คŒ
  },
 
  // ๐Ÿฆ [2๋‹จ๊ณ„] afterAllResolved ํ›…: ๋ชจ๋“  ์˜์กด์„ฑ ๊ณ„์‚ฐ์ด ๋๋‚œ ์งํ›„ lock ํŒŒ์ผ์„ ์ตœ์ข… ์ƒ์„ฑํ•˜๊ธฐ ์ „์— ํ˜ธ์ถœ.
  afterAllResolved(lockfile, context) {
    return lockfile;
  }
};

์‹ค์ „ ์ผ€์ด์Šค 1: ๊ตฌํ˜• ํŒจํ‚ค์ง€์˜ ์ž˜๋ชป๋œ ํ”ผ์–ด ์˜์กด์„ฑ ๋ฌด๋ ฅํ™”

// .pnpmfile.mjs
export const hooks = {
  readPackage(pkg, context) {
    // ๐Ÿฃ ์˜์ฒ ์ด ๋ฐœ๊ฒฌํ•œ ๋ฌธ์ œ:
    // 'some-old-package' ๋ผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ "๋‚˜๋Š” ๋ฌด์กฐ๊ฑด react 16 ๋ฒ„์ „์—์„œ๋งŒ ๋Œ์•„!"(peerDependencies) ๋ผ๊ณ  ์šฐ๊ธฐ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
    // ํ•˜์ง€๋งŒ ์šฐ๋ฆฌ๋Š” react 18์„ ์“ฐ๊ณ  ์žˆ์–ด์„œ, ๋ชจ๋‹ˆํ„ฐ์— ๋งค๋ฒˆ ๋ณด๊ธฐ ์‹ซ์€ ๋…ธ๋ž€์ƒ‰ ๊ฒฝ๊ณ (Warning)๊ฐ€ ๋œน๋‹ˆ๋‹ค.
 
    if (pkg.name === 'some-old-package') {
      // ๐Ÿฆ ์˜ํ˜ธ ๋ฆฌ๋“œ์˜ ํ•ด๊ฒฐ์ฑ…:
      // pnpm ์ด ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜๊ธฐ ์ง์ „์—, ์šฐ๋ฆฌ๊ฐ€ ๋ชฐ๋ž˜ package.json ์•ˆ์˜ ์š”๊ตฌ ์กฐ๊ฑด์„ ์ˆ˜์ •ํ•ด๋ฒ„๋ฆฝ๋‹ˆ๋‹ค!
      pkg.peerDependencies = {
        ...pkg.peerDependencies,
        react: '>=16.0.0'   // "16 ์ด์ƒ์ด๋ฉด ๋‹ค ํ˜ธํ™˜๋œ๋‹ค๊ณ  ์ณ๋ผ~" ํ•˜๊ณ  ์กฐ๊ฑด์„ ์™„ํ™”์‹œ์ผฐ์Œ
      };
      // ํ„ฐ๋ฏธ๋„์— ๋กœ๊ทธ๋„ ๋‚จ๊ฒจ์„œ ํ›—๋‚  ๋””๋ฒ„๊น…ํ•˜๊ธฐ ์ข‹๊ฒŒ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
      context.log('some-old-package ์˜ react peerDep ๋ฒ”์œ„๋ฅผ ๊ฐ•์ œ๋กœ ์™„ํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.');
    }
 
    return pkg;
  }
};

์‹ค์ „ ์ผ€์ด์Šค 2: ์ž˜๋ชป๋œ ์˜์กด์„ฑ ๋ฒ„์ „ ๊ต์ฒด

// .pnpmfile.mjs
export const hooks = {
  readPackage(pkg, context) {
    // ๐Ÿฆ ์˜ํ˜ธ ๋ฆฌ๋“œ์˜ ์‹ค์ œ ์‚ฌ๋ก€:
    // ํŠน์ • ํŒจํ‚ค์ง€๊ฐ€ ์ทจ์•ฝํ•œ ๋ฒ„์ „์˜ axios ๋ฅผ deps ๋กœ ๊ณ ์ •ํ•˜๊ณ  ์žˆ์Œ
    // overrides ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ, ์กฐ๊ฑด๋ถ€ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•  ๋•Œ pnpmfile ์ด ์œ ์šฉ
 
    if (pkg.dependencies && pkg.dependencies['axios']) {
      const currentVersion = pkg.dependencies['axios'];
      // 0.x.x ๋ฒ„์ „์€ ๋ชจ๋‘ ์ตœ์‹  ์•ˆ์ „ ๋ฒ„์ „์œผ๋กœ ๊ต์ฒด
      if (currentVersion.startsWith('0.')) {
        pkg.dependencies['axios'] = '^1.6.7';
        context.log(`${pkg.name}: axios ${currentVersion} โ†’ ^1.6.7 ๋กœ ๊ฐ•์ œ ๋ณ€๊ฒฝ`);
      }
    }
 
    return pkg;
  }
};

์‹ค์ „ ์ผ€์ด์Šค 3: ๋ถˆํ•„์š”ํ•œ devDependencies ์ œ๊ฑฐ

// .pnpmfile.mjs
export const hooks = {
  readPackage(pkg, context) {
    // ํŠน์ • ํŒจํ‚ค์ง€์˜ ๋ถˆํ•„์š”ํ•œ ๋นŒ๋“œ ๋„๊ตฌ ์ œ๊ฑฐ (์„ค์น˜ ์†๋„ ํ–ฅ์ƒ)
    if (pkg.name === 'some-heavy-package') {
      delete pkg.devDependencies['webpack'];
      delete pkg.devDependencies['babel-loader'];
    }
 
    return pkg;
  }
};

pnpmfile vs overrides vs patch

๋ฐฉ๋ฒ•์šฉ๋„ํŠน์ง•
overrides๋ฒ„์ „ ๋ฒ”์œ„ ๊ฐ•์ œ์„ ์–ธ์ , ๋‹จ์ˆœ
pnpmfile.mjsํ”„๋กœ๊ทธ๋ž˜๋ฐ์  ์ˆ˜์ •์กฐ๊ฑด๋ถ€ ๋กœ์ง ๊ฐ€๋Šฅ
pnpm patch์ฝ”๋“œ ์ง์ ‘ ์ˆ˜์ •.patch ํŒŒ์ผ๋กœ ์ถ”์ 
# overrides ๋กœ ํ•ด๊ฒฐ ๊ฐ€๋Šฅํ•œ ์ผ€์ด์Šค (๋” ๋‹จ์ˆœ)
# pnpm-workspace.yaml
overrides:
  axios: ^1.6.7
  semver: ^7.5.4

๐Ÿงน pnpm dedupe โ€” ์ค‘๋ณต ์˜์กด์„ฑ ์ •๋ฆฌ

pnpm dedupe ๋Š” pnpm-lock.yaml ์—์„œ ์ค‘๋ณต ์„ค์น˜๋œ ํŒจํ‚ค์ง€๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์นœ๋‹ค.

# ์ค‘๋ณต ํ™•์ธ
pnpm dedupe --check
 
# ์‹ค์ œ ์ค‘๋ณต ์ œ๊ฑฐ (lock ํŒŒ์ผ ์—…๋ฐ์ดํŠธ)
pnpm dedupe

์™œ ์ค‘๋ณต์ด ๋ฐœ์ƒํ•˜๋Š”๊ฐ€:

์ดˆ๊ธฐ ์„ค์น˜ ์‹œ: lodash@4.17.20 (์ตœ์‹ )
3๊ฐœ์›” ํ›„ lodash@4.17.21 ๋ฆด๋ฆฌ์ฆˆ
์ƒˆ ํŒจํ‚ค์ง€ ์„ค์น˜ ์‹œ lodash@4.17.21 ๋„ ํ•จ๊ป˜ ์„ค์น˜

๊ฒฐ๊ณผ:
node_modules/.pnpm/lodash@4.17.20  (๊ธฐ์กด ํŒจํ‚ค์ง€ ์˜์กด์„ฑ)
node_modules/.pnpm/lodash@4.17.21  (์ƒˆ ํŒจํ‚ค์ง€ ์˜์กด์„ฑ)

โ†’ pnpm dedupe ๋กœ 4.17.21 ๋กœ ํ†ต์ผ

๐Ÿ”’ only-allow โ€” ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ๊ฐ•์ œ

ํŒ€์—์„œ npm, yarn, pnpm ํ˜ผ์šฉ์„ ๋ฐฉ์ง€ํ•œ๋‹ค.

// package.json
{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}
# npm install ์‹œ๋„ํ•˜๋ฉด
npm install
 
# ์—๋Ÿฌ:
# Use "pnpm install" for installing dependencies in this project.
# If you don't have pnpm, install it via "npm i -g pnpm".
# For more details, go to https://pnpm.js.org/

๋” ๋‚˜์€ ๋ฐฉ๋ฒ•: packageManager ํ•„๋“œ + Corepack

// package.json
{
  "packageManager": "pnpm@9.15.0"
}
# Corepack ํ™œ์„ฑํ™” ์‹œ ์ž๋™์œผ๋กœ ์ง€์ •๋œ ๋ฒ„์ „๋งŒ ํ—ˆ์šฉ
corepack enable
# ์ดํ›„ yarn install ์‹œ๋„ํ•˜๋ฉด ์ž๋™์œผ๋กœ ์—๋Ÿฌ

๐Ÿ ์ด์ •๋ฆฌ

pnpm ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์š”์•ฝ
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
Catalogs:     pnpm-workspace.yaml ์— ๋ฒ„์ „ ์„ ์–ธ โ†’ package.json ์—์„œ catalog: ์ฐธ์กฐ
Named Cat.:   catalogs: { react17: {...}, react19: {...} } โ†’ catalog:react17 ์ฐธ์กฐ
pnpmfile:     hooks.readPackage ๋กœ ์„ค์น˜ ์ค‘ ์˜์กด์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ˆ˜์ •
dedupe:       pnpm dedupe ๋กœ ์ค‘๋ณต ํŒจํ‚ค์ง€ ๋ฒ„์ „ ํ†ต์ผ
only-allow:   preinstall ์Šคํฌ๋ฆฝํŠธ ๋˜๋Š” packageManager ํ•„๋“œ๋กœ pnpm ๊ฐ•์ œ
โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
๋ฌธ์ œํ•ด๊ฒฐ์ฑ…
๋ชจ๋…ธ๋ ˆํฌ ๋ฒ„์ „ ๋“œ๋ฆฌํ”„ํŠธCatalogs (catalog:)
ํŠน์ • ๊ฐ„์ ‘ ์˜์กด์„ฑ ๋ฒ„์ „ ๊ฐ•์ œoverrides
๋ณต์žกํ•œ ์กฐ๊ฑด๋ถ€ ์˜์กด์„ฑ ์ˆ˜์ •pnpmfile.mjs
ํŒจํ‚ค์ง€ ์ฝ”๋“œ ์ง์ ‘ ์ˆ˜์ •pnpm patch
์ค‘๋ณต ํŒจํ‚ค์ง€ ๋ฒ„์ „ ์ •๋ฆฌpnpm dedupe
ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ € ํ†ต์ผonly-allow ๋˜๋Š” packageManager

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. Catalogs ์—์„œ catalog: (๊ธฐ๋ณธ๊ฐ’) ์™€ catalog:dev (Named) ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š”?

โœ… ์ •๋‹ต: Default catalog ๋Š” ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ(react, next ๋“ฑ) ์˜ ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•˜๊ณ , Named catalog (์˜ˆ: catalog:dev) ๋Š” ๊ฐœ๋ฐœ ๋„๊ตฌ(typescript, eslint ๋“ฑ) ์˜ ๋ฒ„์ „์„ ๋ถ„๋ฆฌ ๊ด€๋ฆฌํ•œ๋‹ค. ์ด๋ ‡๊ฒŒ ๋ถ„๋ฆฌํ•˜๋ฉด ๋Ÿฐํƒ€์ž„ ์˜์กด์„ฑ๊ณผ ๊ฐœ๋ฐœ ๋„๊ตฌ์˜ ๋ฒ„์ „ ์—…๊ทธ๋ ˆ์ด๋“œ ์ฃผ๊ธฐ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ ๋…๋ฆฝ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ์˜๋„๊ฐ€ ๋ช…ํ™•ํ•ด์ง„๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • Default catalog: ๋ชจ๋“  ์•ฑ์ด ๊ณต์œ ํ•˜๋Š” ํ•ต์‹ฌ ์˜์กด์„ฑ ๋ฒ„์ „ (react, next, @tanstack/react-query ๋“ฑ)
  • Named catalog: ํŠน์ • ๋ชฉ์  ๊ทธ๋ฃน (dev ๋„๊ตฌ, react ๋ฒ„์ „๋ณ„ ๋ถ„๊ธฐ, ๋ ˆ๊ฑฐ์‹œ ์•ฑ์šฉ ๋“ฑ)
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "Default = ๊ณตํ†ต, Named = ๋ชฉ์ ๋ณ„ ๋ถ„๋ฆฌ"

Q2. pnpmfile.mjs ์˜ hooks.readPackage ๋Š” ์–ธ์ œ ์‹คํ–‰๋˜๋ฉฐ, overrides ์™€ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ๊ฐ๊ฐ ์„ ํƒํ•ด์•ผ ํ•˜๋Š”๊ฐ€?

โœ… ์ •๋‹ต: hooks.readPackage ๋Š” ๊ฐ ํŒจํ‚ค์ง€์˜ manifest(package.json) ๋ฅผ ํŒŒ์‹ฑํ•œ ์งํ›„ pnpm ์ด ์˜์กด์„ฑ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์ „์— ์‹คํ–‰๋œ๋‹ค. overrides ๋Š” ๋‹จ์ˆœํžˆ ํŠน์ • ํŒจํ‚ค์ง€์˜ ๋ฒ„์ „ ๋ฒ”์œ„๋ฅผ ๊ฐ•์ œํ•  ๋•Œ ์‚ฌ์šฉํ•˜๊ณ , pnpmfile.mjs ๋Š” ์กฐ๊ฑด๋ถ€ ๋กœ์ง(ํŒจํ‚ค์ง€๋ช… ์ฒดํฌ, ๋ฒ„์ „ ํŒจํ„ด ๋งค์นญ) ์ด ํ•„์š”ํ•˜๊ฑฐ๋‚˜ peerDependencies ๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•œ๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • overrides: { axios: "^1.6.7" } โ€” ๋ชจ๋“  axios ๋ฅผ ๊ฐ•์ œ ๋ณ€๊ฒฝ (๋‹จ์ˆœ, ์„ ์–ธ์ )
  • pnpmfile readPackage โ€” "ํŠน์ • ํŒจํ‚ค์ง€์˜ deps ์ค‘ 0.x.x ๋ฒ„์ „๋งŒ" ๊ฐ™์€ ์กฐ๊ฑด๋ถ€ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋‹จ์ˆœ ๋ฒ„์ „ ๊ต์ฒด โ†’ overrides, ๋ณต์žกํ•œ ๋กœ์ง โ†’ pnpmfile"

Q3. ์˜์ฒ ์ด์˜ ํ…Œ์ŠคํŠธ ํƒ€์ž„: ์‹œ๋‹ˆ์–ด ๋ฉด์ ‘ ์งˆ๋ฌธ (์˜์ฒ ์˜ ๋ฉด์ ‘ ๋„์ „)

์˜์ฒ ์ด๊ฐ€ ์ด์ง ๋ฉด์ ‘์—์„œ ๋ฉด์ ‘๊ด€์ด ๋ฌผ์—ˆ๋‹ค.
"pnpm Catalogs ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด pnpm-lock.yaml ์˜ merge conflict ๊ฐ€ ์ค„์–ด๋“œ๋Š” ์ด์œ ๋ฅผ ์„ค๋ช…ํ•ด์ฃผ์„ธ์š”."

โœ… ์ •๋‹ต: Catalogs ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ์•ฑ์˜ package.json ์—์„œ ๋ฒ„์ „ ๋ฒ”์œ„ ๋Œ€์‹  catalog: ์ฐธ์กฐ ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค. ๋ฒ„์ „์„ ์˜ฌ๋ฆด ๋•Œ package.json ์„ ์ˆ˜์ •ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ํ•ด๋‹น ํŒŒ์ผ์—์„œ merge conflict ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฒ„์ „ ๋ณ€๊ฒฝ์€ pnpm-workspace.yaml ํ•œ ๊ณณ์—์„œ๋งŒ ์ผ์–ด๋‚˜๋ฏ€๋กœ conflict ์ง€์ ์ด ๋ถ„์‚ฐ๋˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ’ก ์ƒ์„ธ ํ•ด์„ค:

  • Before(Catalogs ์—†์Œ): react ๋ฒ„์ „ ์—… โ†’ apps/community/package.json, apps/admin/package.json ... 10๊ฐœ ํŒŒ์ผ ์ˆ˜์ • โ†’ PR ์—์„œ 10๊ฐœ ํŒŒ์ผ conflict ๊ฐ€๋Šฅ
  • After(Catalogs ์‚ฌ์šฉ): react ๋ฒ„์ „ ์—… โ†’ pnpm-workspace.yaml 1๊ฐœ ํŒŒ์ผ๋งŒ ์ˆ˜์ • โ†’ conflict ์ง€์  1๊ณณ์œผ๋กœ ์ง‘์ค‘
  • ์ถ”๊ฐ€๋กœ: pnpm-lock.yaml ์˜ ๋ณ€๊ฒฝ ํญ๋„ ์ค„์–ด๋“ค์–ด lock ํŒŒ์ผ conflict ๋„ ์ค„์–ด๋“ฆ
  • ๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: "๋ฒ„์ „์˜ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์› = ๋ฒ„์ „ ์ˆ˜์ • ํŒŒ์ผ์ด 1๊ฐœ = conflict ์ง€์  1๊ฐœ"

๐Ÿฃ ์˜์ฒ ์ด์˜ ํ‡ด๊ทผ ์ผ๊ธฐ

์˜ค๋Š˜ Catalogs ๋ฅผ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ–ˆ๋‹ค. codemod ๋กœ ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•ด๋ดค๋Š”๋ฐ ์ •๋ง ๋งˆ๋ฒ•๊ฐ™์ด package.json ๋“ค์˜ ๋ฒ„์ „์ด catalog: ๋กœ ๋ฐ”๋€Œ์—ˆ๋‹ค.

pnpmfile.mjs ๋Š” ์†”์งํžˆ ์ฒ˜์Œ์—” "์ด๊ฑธ ์–ธ์ œ ์จ?" ์‹ถ์—ˆ๋Š”๋ฐ, ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ์˜ˆ์ „์— ํŒฌํ…€ ์˜์กด์„ฑ ๋•Œ๋ฌธ์— ๊ณ ์ƒํ•˜๋˜ ์ผ€์ด์Šค๋ฅผ ๋ณด์—ฌ์ฃผ์‹œ๋ฉด์„œ ์„ค๋ช…ํ•ด์ฃผ์‹œ๋‹ˆ๊นŒ ์ดํ•ด๊ฐ€ ๋๋‹ค. ๊ตฌํ˜• ํŒจํ‚ค์ง€๊ฐ€ ์ž˜๋ชป๋œ ํ”ผ์–ด ๋ฒ„์ „์„ ์š”๊ตฌํ•  ๋•Œ, overrides ๋กœ ์•ˆ ๋˜๋Š” ๋ณต์žกํ•œ ์ผ€์ด์Šค์—์„œ pnpmfile.mjs ๊ฐ€ ์ง„์งœ ์œ ์šฉํ•˜๊ฒ ๊ตฌ๋‚˜ ์‹ถ์—ˆ๋‹ค.

๊ทธ๋‚˜์ €๋‚˜ Catalogs ๊ฐ€ ์™œ merge conflict ๋ฅผ ์ค„์ด๋Š”์ง€ ๋ฉด์ ‘ ์งˆ๋ฌธ์œผ๋กœ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ƒ๊ฐ์„ ๋ชป ํ–ˆ๋Š”๋ฐ... ์ด๊ฒŒ ์ง„์งœ ์‹œ๋‹ˆ์–ด๊ฐ€ ์•Œ์•„์•ผ ํ•˜๋Š” ์ˆ˜์ค€์ด๊ตฌ๋‚˜. ๋‹จ์ˆœํžˆ "ํŽธํ•˜๋‹ค"๊ฐ€ ์•„๋‹ˆ๋ผ "์™œ ํŽธํ•œ๊ฐ€", "์–ด๋–ค ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ conflict ๋ฅผ ์ค„์ด๋Š”๊ฐ€" ๊นŒ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

๐Ÿ’ก ์˜ค๋Š˜์˜ ๊ตํ›ˆ: "๋ฒ„์ „์˜ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›(Single Source of Truth) ์„ ๋งŒ๋“œ๋Š” ๊ฒƒ โ€” Catalogs ๊ฐ€ ๋ฐ”๋กœ ๊ทธ๊ฒƒ์ด๋‹ค. ์ˆ˜์ •ํ•  ๊ณณ์ด ํ•˜๋‚˜๋ฉด ์‹ค์ˆ˜๋„, ์ถฉ๋Œ๋„ ์ค„์–ด๋“ ๋‹ค."

์˜ค๋Š˜ ์ €๋…์€ ์˜ํ˜ธ ๋ฆฌ๋“œ ๋‹˜์ด ๊ณ ์ƒํ–ˆ๋‹ค๊ณ  ์ˆ˜์œก์„ ์‚ฌ์ฃผ์…จ๋‹ค. ์—ญ์‹œ ๋ฐฐ์šธ ๊ฒŒ ๋งŽ์€ ํŒ€์ด ๋ง›์žˆ๋Š” ๊ฒƒ๋„ ๋งŽ๋‹ค. ๋‚ด์ผ์€ Next.js + Docker + CI/CD ํŽธ์ด๋‹ค. ๋งˆ์ง€๋ง‰ ๋‘ ์ฑ•ํ„ฐ ํŒŒ์ดํŒ…!


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ