[탐구] 피어의존성 문제와 symlink 활성화 문제

Dev.Hammy·2023년 11월 1일
0

Etc

목록 보기
13/20

https://github.com/prisma/prisma/issues/6603

syncpack depcheck

pnpm는 공통 종속성을 심볼릭 링크로 공유하여 다운로드와 디스크 상의 종속성 크기를 줄이는 데 도움을 주지만, 공통 종속성이 호환되지 않는 버전을 사용할 때는 이러한 이점을 상실할 수 있습니다. 특히 종속성 범위 대신 종속성의 정확한 버전을 강제하는 것과 같이 더 보수적인 설정을 사용하는 경우에는 이것이 특히 문제가 될 수 있습니다.

이를 해결하기 위해 syncpack를 사용하는 것을 권장합니다. Syncpack는 모든 package.json 파일을 검토하여 공통 종속성을 찾고 모두 동일한 버전으로 유지하도록 보장합니다. 더 미묘한 설정이 필요한 경우에는 다양한 구성 옵션을 제공합니다.

의존성 버전이 자주 동기화되는 것을 보장하려면 package.json의 "postinstall" 스크립트에 syncpack fix-mismatches를 추가하세요.

마지막으로, 시간이 지남에 따라 사용하지 않는 패키지로 인해 몇 가지 쓸모없는 종속성이 남아 있을 가능성이 있습니다. depcheck는 이를 확인하기 위한 훌륭한 도구이며 다양한 구성 옵션도 제공합니다.

훅스크립트

훅 스크립트(hook scripts)는 소프트웨어 개발 및 관리 과정 중에 특정 이벤트나 상황에서 실행되는 사용자 정의 스크립트를 의미합니다.

  • pre-commit Hook: 소스 코드 버전 관리 도구(예: Git)에서 커밋하기 전에 실행되는 스크립트. 이 스크립트는 코드 스타일 체크, 테스트 실행 또는 코드 정적 분석과 같은 작업을 수행하여 커밋하기 전에 코드의 품질을 검사합니다.

  • pre-push Hook: Git에서 푸시하기 전에 실행되는 스크립트. 이 스크립트는 푸시 작업 전에 빌드 및 검증을 수행하고, 무결성을 보장하거나 리모트 저장소에 코드를 업로드하기 전에 추가적인 검사를 수행할 수 있습니다.

  • post-merge Hook: Git에서 브랜치를 병합한 후에 실행되는 스크립트. 이 스크립트는 브랜치를 병합한 후에 필요한 작업을 수행할 수 있으며, 예를 들어 의존성 패키지를 업데이트하거나 데이터베이스를 업데이트하는 데 사용할 수 있습니다.

  • preinstall 및 postinstall Hook: 패키지 관리자(예: pnpm, npm, yarn)에서 패키지를 설치할 때 실행되는 스크립트. 이 스크립트를 사용하여 패키지 설치 전후에 필요한 작업을 수행하거나 설정을 변경할 수 있습니다.

  • pre-build 및 post-build Hook: 빌드 프로세스에서 빌드 전후에 실행되는 스크립트. 이 스크립트를 사용하여 빌드 프로세스를 자동화하거나 빌드 후에 추가 작업을 수행할 수 있습니다.

예시 : 모듈 호이스팅을 사용하는 패키지

이 문서는 bud.js 프로젝트가 pnpm과 호환성을 가질 수 있도록 적용해야 하는 설정과 절차에 대한 내용을 다룹니다. bud.js는 모듈 호이스팅(module hoisting)을 사용하는 패키지이며, pnpm의 기본 동작과 충돌할 수 있습니다.

Installation (설치):

pnpm을 사용하는 경우 --public-hoist-pattern=* 옵션을 설치 명령어에 추가해야 합니다. 이 옵션은 모듈 호이스팅을 활성화시키는 역할을 합니다.

pnpm install --public-hoist-pattern=*

.pnpmfile.cjs compatibility shim:

위에서 언급한 --public-hoist-pattern=* 옵션을 추가하는 것 외에도 .pnpmfile.cjs 파일을 프로젝트 루트 디렉토리에 추가해야 합니다. 이 파일은 pnpm의 특정 훅(hook)을 정의하는 역할을 합니다.

bud.js 모듈은 기본적으로 필요한 의존성을 함께 제공하지만, 내장된 의존성을 사용자 정의 버전으로 오버라이드할 수 있습니다. 그러나 이 접근 방식은 pnpm과 충돌을 일으킬 수 있는데, pnpm은 optional로 표시된 peerDependencies를 설치하지 않기 때문입니다.

.pnpmfile.cjs 파일은 pnpm이 특정 패키지에서 optional로 표시된 peerDependencies를 제거하지 않도록 하기 위한 것으로, 이를 수정합니다.

파일 내에서는 패키지의 package.json 파일에서 의존성(dependencies, devDependencies) 정보를 가져와서 특정 패키지의 peerDependencies 중에서 이미 의존성으로 선언된 것들은 필터링하여 제거합니다.

결과적으로 .pnpmfile.cjs 파일을 통해 pnpm은 optional로 표시된 peerDependencies를 설치하지 않고, 해당 패키지가 제대로 동작할 수 있도록 설정을 조정합니다.

이 설정과 파일은 bud.js와 pnpm을 함께 사용하려는 경우 호환성을 확보하기 위한 작업으로, pnpm이나 다른 패키지 매니저와의 호환성을 위한 일반적인 논의와 작업입니다.

// .pnpmfile.cjs

const {dependencies, devDependencies} = require(`./package.json`)

/**
 * bud.js pnpm compatibility shim
 *
 * pnpm allows customizing the package installation process through special functions called hooks.
 * These hooks can be defined in a .pnpmfile.cjs file, which should be located in the same directory
 * as the lockfile. For example, in a monorepo with a shared lockfile, the .pnpmfile.cjs file should
 * be placed in the root of the monorepo.
 *
 * @see {@link https://pnpm.io/pnpmfile} for more information on pnpmfile
 */
module.exports = {
  hooks: {
    /**
     * This hook removes peerDependencies from @roots/* packages because
     * pnpm does not install peerDependencies marked as optional by default.
     * This behavior differs from npm and yarn, which install peerDependencies
     * even if they are marked as optional.
     */
    readPackage(data) {
      // Skip processing if the package is not a @roots/* package
      if (!data.name.startsWith(`@roots`)) return data

      // Skip processing if the package does not have peerDependencies
      if (!data.peerDependencies) return data

      // Filter out peerDependencies that are already listed as dependencies or devDependencies
      const peerDependencies = Object.entries(data.peerDependencies)
        .filter(
          ([signifier]) =>
            Object.keys(dependencies || {}).includes(signifier) ||
            Object.keys(devDependencies || {}).includes(signifier),
        )
        .reduce(
          (peerDependencies, [signifier, version]) => ({
            ...peerDependencies,
            [signifier]: version,
          }),
          {},
        )

      // Return the package data with the filtered peerDependencies
      // and an empty peerDependenciesMeta object
      return Object.assign(data, {
        peerDependencies,
        peerDependenciesMeta: {},
      })
    },
  },
}

.pnpmfile.cjs

pnpm을 사용하면 특수 기능 (후크)을 통해 설치 프로세스에 직접 연결할 수 있습니다. 후크는 .pnpmfile.cjs라는 파일에서 선언할 수 있습니다.

기본적으로 .pnpmfile.cjs 은 lockfile과 같은 디렉터리에 있어야 합니다. 예를 들어, 공유 lockfile이 있는 워크스페이스에서 .pnpmfile.cjs는 모노레포의 루트에 있어야 합니다.

요약

후크 함수처리사용
hooks.readPackage(pkg, context): pkgpnpm이 의존성 패키지 매니페스트를 파싱한 후에 호출됩니다.의존성의 package.json을 변경할 수 있습니다.
hooks.afterAllResolved(lockfile, context): lockfile의존성이 해결된 후에 호출됩니다.lockfile을 변경할 수 있습니다.

hooks.readPackage(pkg, context): pkg | Promise<pkg>

파싱 후 또는 resolve 전에 의존성의 package.json을 변경할 수 있습니다. 이러한 변형은 파일시스템에 저장되지 않지만, lockfile에서 해결되는 내용과 이에 따라 설치되는 내용에 영향을 줍니다.

수정하려는 의존성을 이미 resolve했다면, pnpm-lock.yaml 을 삭제해야 합니다.

만약 package.json 파일에 변경 내용을 파일 시스템에 저장하려면, pnpm 명령어 중 하나인 pnpm patch를 사용하여 package.json 파일을 패치(patch)해야 합니다. 이렇게 하면 package.json 파일의 내용을 수정할 수 있으며, 예를 들어, 특정 종속성(dependency)의 bin 필드를 제거하는 경우와 같이 필요한 변경 사항을 적용할 수 있습니다.

인수
pkg - 패키지의 매니페스트. 레지스트리 응답 또는 package.json 컨텐츠입니다.
context - 단계에 대한 컨텍스트 객체입니다. #log(msg) 메서드는 단계에 대한 디버그 로그를 사용할 수 있도록 합니다.

function readPackage(pkg, context) {
  // Override the manifest of foo@1.x after downloading it from the registry
  if (pkg.name === 'foo' && pkg.version.startsWith('1.')) {
    // Replace bar@x.x.x with bar@2.0.0
    pkg.dependencies = {
      ...pkg.dependencies,
      bar: '^2.0.0'
    }
    context.log('bar@1 => bar@2 in dependencies of foo')
  }

  // This will change any packages using baz@x.x.x to use baz@1.2.3
  if (pkg.dependencies.baz) {
    pkg.dependencies.baz = '1.2.3';
  }

  return pkg
}

module.exports = {
  hooks: {
    readPackage
  }
}

.pnpmfile.cjs 파일 내에서 사용되는 스크립트로, 패키지의 설치 과정 중에 패키지의 package.json 파일을 수정하고 패키지 의존성(dependencies)을 변경하는 데 사용됩니다. 이 코드는 pnpm의 후크(hook) 중 하나인 readPackage 함수를 정의하고, 이 함수를 .pnpmfile.cjs 파일 내에서 내보내는 역할을 합니다.

코드 내용을 해석하면 다음과 같습니다:

readPackage 함수: 이 함수는 패키지의 package.json 파일을 읽고 수정하는 역할을 합니다. 함수는 두 개의 매개변수를 받습니다.

pkg: 패키지의 package.json 파일 내용을 나타내는 객체입니다.
context: pnpm에서 제공하는 컨텍스트 객체로, 로깅과 같은 작업에 사용됩니다.
조건부 수정:

함수 내에서 두 가지 조건부 수정이 이루어집니다.
첫 번째 수정은 패키지의 이름이 'foo'이고 버전이 '1.'로 시작하는 경우를 대상으로 합니다. 이 경우, pkg.dependencies 객체에 'bar' 패키지를 추가하고 버전을 '^2.0.0'으로 설정합니다. 또한 로깅을 통해 변경 내용을 기록합니다.
두 번째 수정은 패키지의 의존성 중에 'baz' 패키지가 있는 경우를 대상으로 합니다. 이 경우, 'baz' 패키지의 버전을 '1.2.3'으로 고정시킵니다.
module.exports: .pnpmfile.cjs 파일은 pnpm에서 사용되며 hooks 객체를 내보내야 합니다. readPackage 함수를 hooks 객체 내에서 readPackage 키로 정의합니다.

이렇게 설정된 .pnpmfile.cjs 파일은 pnpm 설치 과정에서 해당 패키지의 package.json 파일을 수정하고 패키지 의존성을 변경할 수 있습니다. 이를 통해 패키지 관리자를 사용하는 프로젝트에서 특정 패키지의 동작을 수정하거나 의존성을 업데이트할 수 있습니다.

알려진 제약 사항
readPackage을 통해 의존성의 매니페스트에서 scripts 필드를 제거해도 pnpm이 의존성을 구축하는 것을 방지하지 못합니다. 의존성을 빌드할 때, pnpm은 패키지의 아카이브에서 패키지의 package.json를 읽으며, 이는 후크의 영향을 받지 않습니다. 패키지의 빌드를 무시하려면, pnpm.neverBuiltDependencies 필드를 사용하세요.

hooks.afterAllResolved(lockfile, context): lockfile | Promise<lockfile>

직렬화되기 전에 lockfile 결과를 변경할 수 있습니다.

인수
lockfile - pnpm-lock.yaml로 직렬화되는 lockfile 해결 객체입니다.
context - 단계에 대한 컨텍스트 객체입니다. #log(msg) 메서드는 단계에 대한 디버그 로그를 사용할 수 있도록 합니다.

//.pnpmfile.cjs
function afterAllResolved(lockfile, context) {
  // ...
  return lockfile
}

module.exports = {
  hooks: {
    afterAllResolved
  }
}

이 코드는 pnpm 패키지 관리자의 .pnpmfile.cjs 파일 내에서 사용되는 스크립트로, 패키지의 설치 과정에서 모든 종속성이 해결된 후에 실행되는 후크(hook) 함수를 정의하고 내보냅니다.

여기서 afterAllResolved 함수는 두 개의 매개변수를 받습니다:

lockfile: 패키지의 락(lock) 파일에 대한 정보를 나타내는 객체입니다. 락 파일은 패키지 관리와 종속성 버전 관리를 위해 사용됩니다.
context: pnpm에서 제공하는 컨텍스트 객체로, 로깅 및 다른 작업에 사용될 수 있습니다.
이 함수는 "afterAllResolved" 후크로 정의되며, 모든 종속성이 해결된 후에 실행됩니다. 이 시점에서 패키지 설치 과정이 거의 완료되었을 것입니다.

afterAllResolved 함수 내에서는 lockfile 및 context를 사용하여 추가적인 작업을 수행할 수 있습니다. 후크 내용은 주석 부분인 // ... 부분으로 표시되어 있으며, 실제로 어떤 작업을 수행하는지는 해당 부분에 구체적으로 기술되어 있어야 합니다.

이렇게 정의된 후크 함수는 pnpm 설치 과정의 마지막 단계에서 실행되므로, 패키지 설치 후 추가적인 조작이나 작업이 필요한 경우 사용될 수 있습니다.

package.json

dependenciesMeta

pnpm에서 사용되는 dependenciesMeta 필드와 특히 dependenciesMeta.*.injected 옵션에 대한 설명을 제공합니다.

dependenciesMeta는 패키지의 dependencies, optionalDependencies, 및 devDependencies 내부에 선언된 종속성을 위한 추가적인 메타 정보를 제공합니다.

dependenciesMeta.*.injected 필드는 로컬 종속성에 대한 설정으로, 다음과 같은 작동 방식을 설명합니다:

  • injected가 true로 설정된 경우, 해당 로컬 패키지는 가상 저장소 (예: node_modules/.pnpm)에 하드 링크되며, 가상 저장소에서 모듈 디렉터리로 심볼릭 링크로 연결됩니다.

  • injected가 false로 설정되지 않거나 설정되지 않은 경우, 해당 로컬 패키지는 워크스페이스 내의 위치에서 직접 모듈 디렉터리로 심볼릭 링크로 연결됩니다.

예를 들어, 워크스페이스 내의 card 프로젝트의 package.json에 다음과 같이 종속성을 선언한 경우:

{
  "name": "card",
  "dependencies": {
    "button": "workspace:1.0.0"
  }
}

이렇게 하면 button 패키지가 card의 node_modules 디렉터리에 심볼릭 링크로 생성됩니다.

my-workspace/
  ├── card/
  │     ├── node_modules/
  │     │     ├── button/
  │     │     └── react/ -> ... (심볼릭 링크)
  │     └── package.json
  ├── form/
  │     ├── node_modules/
  │     │     ├── button/
  │     │     └── react/ -> ... (심볼릭 링크)
  │     └── package.json
  ├── button/
  │     ├── node_modules/
  │     │     ├── react/ -> ... (심볼릭 링크)
  │     └── package.json
  ├── pnpm-workspace.yaml
  ├── package.json

그러나 button 패키지가 peerDependencies로 react를 필요로 하는 경우, 버전 충돌 문제가 발생할 수 있습니다. card와 form 프로젝트가 각각 다른 버전의 react를 필요로 하는 경우, 문제가 될 수 있습니다.

injected 필드를 사용하면 각 프로젝트에서 button 패키지를 주입할 수 있습니다. 예를 들어, card 프로젝트의 package.json은 다음과 같이 설정될 수 있습니다:

{
  "name": "card",
  "dependencies": {
    "button": "workspace:1.0.0",
    "react": "16"
  },
  "dependenciesMeta": {
    "button": {
      "injected": true
    }
  }
}

form 프로젝트에서도 button 패키지를 주입할 수 있으며, react 버전을 17로 설정할 수 있습니다.

button 패키지는 각 프로젝트의 종속성으로 하드 링크되며, 각각의 react 버전과 함께 심볼릭 링크됩니다.

마지막으로, 하드 링크된 종속성은 일반적인 종속성과 달리 대상 폴더로 심볼릭 링크되지 않기 때문에 빌드 스크립트를 실행한 후에도 자동으로 업데이트되지 않습니다. 종속성 패키지의 최신 상태를 유지하려면 pnpm i 명령을 다시 실행해야 합니다.

또한, button 패키지는 설치 시에 실행되는 라이프사이클 스크립트를 가져야 하며, 예를 들어 prepare 스크립트를 사용하여 pnpm이 변경 사항을 감지하고 업데이트하도록 해야 합니다.

peerDependenciesMeta

eerDependenciesMeta는 peerDependencies 필드에 나열된 의존성과 관련된 추가 정보를 나열하는 필드입니다.

peerDependenciesMeta.*.optional:
이 필드가 true로 설정된 경우, 해당 peer dependency(동료 종속성)은 패키지 관리자에 의해 선택적으로 표시됩니다. 따라서 이 peer dependency를 생략해도 빌드 과정 중에 오류가 보고되지 않습니다.

예를 들어, 아래와 같이 설정된 경우:

{
    "peerDependencies": {
        "foo": "1"
    },
    "peerDependenciesMeta": {
        "foo": {
            "optional": true
        },
        "bar": {
            "optional": true
        }
    }
}

위의 설정에서 "foo" 및 "bar"는 peerDependencies 필드에 나열되었으며, 두 개의 peer dependency가 선택적으로 표시되도록 지정되었습니다.

"bar"는 peerDependencies에 명시적으로 나열되지 않았지만 peerDependenciesMeta에서 "optional"을 true로 설정했기 때문에, pnpm은 "bar"를 생략해도 문제가 없다고 가정합니다. 따라서 "bar"의 어떤 버전도 사용할 수 있습니다.

"foo" 또한 "optional"로 표시되었지만, peerDependencies에 정확한 버전 명세가 포함되었으므로 선택적으로 생략 가능하지만, 필요한 버전 명세에 맞게 사용해야 합니다.

이를 통해 peerDependenciesMeta를 사용하여 패키지 관리자에게 특정 peer dependency가 선택적임을 알릴 수 있으며, 이를 통해 프로젝트의 빌드 과정 중에 불필요한 오류 메시지를 방지할 수 있습니다.

pnpm.peerDependencyRules.ignoreMissing

pnpm은 이 설정에 나열된 패키지의 peer dependencies가 없을 때도 경고를 표시하지 않습니다.

예를 들어, 다음과 같이 설정하면 pnpm은 react가 필요하지만 설치되지 않은 경우 경고를 표시하지 않습니다.

{
  "pnpm": {
    "peerDependencyRules": {
      "ignoreMissing": ["react"]
    }
  }
}

pnpm.peerDependencyRules.allowedVersions

이 설정을 사용하면 특정 범위의 peer dependencies에 대한 경고를 억제할 수 있습니다.

예를 들어, react@16이 필요하지만 react@17도 작동한다고 알고 있다면 다음과 같이 설정할 수 있습니다.

{
  "pnpm": {
    "peerDependencyRules": {
      "allowedVersions": {
        "react": "17"
      }
    }
  }
}

pnpm.peerDependencyRules.allowAny:

지정된 패턴과 일치하는 어떤 peer dependency도 해당 범위를 무시하고 어떤 버전에서도 해결할 수 있도록 합니다.

예를 들어, @babel/ 패키지나 eslint와 관련된 모든 peer dependency에 대한 버전 불일치 경고를 무시하려면 다음과 같이 설정할 수 있습니다.

{
  "pnpm": {
    "peerDependencyRules": {
      "allowAny": ["@babel/*", "eslint"]
    }
  }
}

pnpm.neverBuiltDependencies

이 설정을 사용하면 명시된 의존성의 "preinstall," "install," 및 "postinstall" 스크립트가 설치 중에 실행되지 않습니다.

예를 들어:

{
  "pnpm": {
    "neverBuiltDependencies": ["fsevents", "level"]
  }
}

pnpm.onlyBuiltDependencies:

명시된 패키지만 설치 중에 스크립트를 실행할 수 있도록 허용하는 목록을 지정합니다.

예를 들어:

{
  "pnpm": {
    "onlyBuiltDependencies": ["fsevents"]
  }
}

pnpm.onlyBuiltDependenciesFile

특정 JSON 파일을 사용하여 설치 중에 스크립트를 실행할 수 있는 패키지를 지정하는 설정입니다. 이를 통해 보안을 강화하거나 특정 의존성만 설치 중에 스크립트를 실행할 수 있습니다.

예를 들어:

{
  "dependencies": {
    "@my-org/policy": "1.0.0"
  },
  "pnpm": {
    "onlyBuiltDependenciesFile": "node_modules/@my-org/policy/onlyBuiltDependencies.json"
  }
}

pnpm.allowedDeprecatedVersions

이 설정을 사용하여 특정 패키지의 사용 가능한 버전에 대한 폐기 경고를 억제할 수 있습니다.

예를 들어:

{
  "pnpm": {
    "allowedDeprecatedVersions": {
      "express": "1",
      "request": "*"
    }
  }
}

pnpm.patchedDependencies

이 설정은 pnpm patch-commit 명령을 실행할 때 자동으로 추가/업데이트됩니다. 패키지의 이름과 정확한 버전을 키로 하며, 값은 패치 파일에 대한 상대 경로입니다.

예를 들어:

{
  "pnpm": {
    "patchedDependencies": {
      "express@4.18.1": "patches/express@4.18.1.patch"
    }
  }
}

pnpm.allowNonAppliedPatches

allowNonAppliedPatches를 true로 설정하면 패치 파일에서 적용되지 않은 패치에 대해 설치가 실패하지 않습니다.

예를 들어:

{
  "pnpm": {
    "patchedDependencies": {
      "express@4.18.1": "patches/express@4.18.1.patch"
    },
    "allowNonAppliedPatches": true
  }
}

.npmrc

pnpm은 커맨드라인, 환경변수, 그리고 .npmrc 파일을 통해 config를 구성할 수 있습니다.

pnpm config 커맨드를 사용하여 사용자 및 전역.npmrc 파일을 수정할 수 있습니다.

4개의 관련된 파일은 다음과 같습니다.

프로젝트별 구성 파일 (/path/to/my/project/.npmrc)
작업 공간별 구성 파일 (pnpm-workspace.yaml 파일이 포함된 디렉토리)
사용자별 구성 파일 (~/.npmrc)
전역 구성 파일 (/etc/npmrc)
모든 .npmrc 파일은 key = value 의 목록을 갖는 INI-formatted 형식의 파일입니다

.npmrc 파일 내의 값은 ${NAME} 구문을 사용하여 환경 변수를 포함할 수 있습니다. 환경 변수는 기본값을 지정하여 설정될 수도 있습니다. ${NAME-fallback}를 사용하면 NAME이 설정되지 않았을 경우 fallback 값을 반환합니다. ${NAME:-fallback}를 사용하면 NAME이 설정되지 않안 경우나 비어있는 문자열인 경우 fallback 값을 반환합니다.

예를 들어, ${NODE_ENV-development}라고 설정하면 NODE_ENV 환경 변수가 설정되어 있지 않으면 기본값으로 development을 사용합니다. 또한, ${DATABASE_URL:-localhost}는 DATABASE_URL 환경 변수가 설정되지 않았거나 비어있을 경우 기본값으로 localhost를 사용합니다.

의존성 호이스팅 설정

hoist

기본값: true
유형: boolean
true로 설정하면 모든 의존성은 node_modules/.pnpm/node_modules로 호이스팅 됩니다. 이는 목록에 없는 의존성이 node_modules 내부의 모든 패키지에 접근할 수 있도록 합니다.

hoist-pattern

기본값: ['*']
유형: String
어떤 패키지를 node_modules/.pnpm/node_modules로 호이스팅 해야 하는지 pnpm에 알립니다. 기본적으로 모든 패키지는 호이스팅됩니다, 하지만 유령 의존성을 갖는 잘못된 패키지를 알고 있다면 이 옵션을 이용해 유령 의존성을 호이스팅할 수 있습니다. (권장)

예를 들어:

hoist-pattern[]=*eslint*
hoist-pattern[]=*babel*

# !를 사용하여 제외 시킬 수도 있다. 
hoist-pattern[]=*types*
hoist-pattern[]=!@types/react

public-hoist-pattern

기본값: ['*eslint*', '*prettier*']
유형: String
hoist-pattern은 가상 저장소 내부의 숨겨진 모듈 디렉토리에 대한 의존성을 호이스트하는 것과 달리, public-hoist-pattern 은 패턴과 일치하는 의존성을 루트 모듈 디렉토리에 호이스트합니다. 루트 모듈 디렉토리에 대한 호이스팅은 애플리케이션 코드가 해결 전략을 부적절하게 수정하더라도 유령 의존성에 액세스할 수 있음을 의미합니다.

이 설정은 의존성을 제대로 해결하지 못하는 일부 결함이 있는 플러그형 도구를 처리할 때 유용합니다.

예를 들어:

public-hoist-pattern[]=*plugin*

# !를 사용하여 제외시킬 수도 있습니다.

public-hoist-pattern[]=*types*
public-hoist-pattern[]=!@types/react

참고: shamefully-hoist 를 true 로 설정하는 것은 public-hoist-pattern 을 *로 설정하는 것과 같습니다.

shamefully-hoist

기본값: false
유형: Boolean
기본적으로 pnpm은 반엄격한 node_modules을 생성합니다. 즉, 의존성은 선언되지 않은 의존성에 대해 액세스 권한을 갖지만 node_modules 외부의 모듈은 액세스할 수 없습니다. 이 레이아웃을 사용하면 생태계의 대부분의 패키지가 문제 없이 작동합니다. 그러나 일부 도구가 호이스트된 의존성이 node_modules의 루트에 있을 때만 작동하는 경우, 이를 true 로 설정하여 호이스트할 수 있습니다.

node_modules 설정

store-dir

기본값:
If the PNPM_HOME env variable is set, then $PNPM_HOME/storeXDG_DATA_HOME env 변수가 설정되면 $XDG_DATA_HOME/pnpm/store
Windows: ~/AppData/Local/pnpm/store
macOS: ~/Library/pnpm/store
Linux: ~/.local/share/pnpm/store
유형: path
모든 패키지가 저장되는 디스크 위치입니다.

저장소는 항상 설치가 발생하는 동일한 디스크에 있어야 합니다. 따라서 디스크당 하나의 저장소가 있도록 합니다. 현재 디스크에 홈 디렉토리가 있으면, 그 안에 저장소가 생성됩니다. 디스크에 홈이 없으면 파일 시스템의 루트에서 저장소가 생성됩니다. 예를 들어, /mnt에 마운트된 파일 시스템에서 설치가 진행 중인 경우, 저장소는 /mnt/.pnpm-store에 생성됩니다. Windows 시스템도 마찬가지입니다.

다른 디스크에서 저장소를 설정할 수 있지만, 이 경우 pnpm은 하드 링크 대신 저장소에서 패키지를 복사합니다. 하드 링크는 동일한 파일 시스템에서만 가능하기 때문입니다.

modules-dir

기본값: node_modules
유형: path
의존성이 설치될 디렉토리 (node_modules 대신).

node-linker

기본값: isolated
유형: isolated, hoisted, pnp
노드 패키지 설치에 사용해야 하는 링커를 정의합니다.

isolated - 의존성은 node_modules/.pnpm의 가상 저장소에서 심볼릭 링크됩니다.
hoisted - 심볼릭 링크가 없는 평탄한 node_modules 이 생성됩니다. npm 또는 Yarn Classic에 의해 생성된 node_modules 와 동일합니다. One of Yarn's libraries is used for hoisting, when this setting is used. 이 설정을 사용해야 하는 정당한 이유:
여러분의 도구가 심볼릭 링크와 잘 작동하지 않습니다. React Native 프로젝트는 호이스트된 node_modules을 사용하는 경우에만 작동합니다.
여러분의 프로젝트가 서버리스 호스팅 제공업체에 배포됩니다. 일부 서버리스 공급자 (예: AWS Lambda)는 심볼릭 링크를 지원하지 않습니다. 이 문제에 대한 대안 솔루션은 배포 전에 애플리케이션을 번들로 묶는 것입니다.
"bundledDependencies"으로 패키지를 게시하려는 경우.
--preserve-symlinks 플래그로 Node.js를 실행하는 경우.
pnp - node_modules 미사용. Plug'n'Play는 Yarn Berry에 의해 사용되는 Node.js를 위한 혁신적인 전략입니다. 링커로 pnp 를 사용할 때 symlink 설정을 false 으로 설정하는 것이 좋습니다.

기본값: true
유형: Boolean
symlink 이 false으로 설정되면 pnpm은 symlink가 없는 가상 저장소 디렉터리를 만듭니다. node-linker=pnp과 함께 사용하면 유용한 설정입니다.

enable-modules-dir

기본값: true
유형: Boolean
false이면 pnpm은 모듈 디렉토리 (node_modules)에 파일을 쓰지 않습니다. 이것은 모듈 디렉토리가 사용자 공간 (FUSE) 의 파일 시스템으로 마운트될 때 유용합니다. FUSE: @pnpm/mount-modules를 사용하여 모듈 디렉토리를 마운트할 수 있는 실험적 CLI가 있습니다.

virtual-store-dir

기본값: node_modules/.pnpm
유형: path
저장소에 대한 링크가 있는 디렉토리입니다. 프로젝트의 모든 직접 및 간접 의존성이 이 디렉토리에 연결됩니다.

이것은 Windows에서 긴 경로 문제를 해결할 수 있는 유용한 설정입니다. 매우 긴 경로에 대한 의존성이 있다면 여러분의 드라이브의 루트에서 가상 저장소를 선택할 수 있습니다 (예: C:\my-project-store).

또는 가상 저장소를 .pnpm 로 설정하고 .gitignore에 추가할 수 있습니다. 이렇게 하면 의존성 경로가 한 디렉토리 더 높기 때문에 스택 트레이스를 더 깔끔하게 만들 수 있습니다.

참고: 가상 저장소는 여러 프로젝트 간에 공유할 수 없습니다. 모든 프로젝트에는 자체 가상 저장소가 있어야 합니다(루트가 공유되는 워크스페이스 제외).

package-import-method

기본값: auto
유형: auto, hardlink, copy, clone, clone-or-copy

"node-linker" 설정을 변경하여 패키지가 node_modules 내에 심볼릭 링크를 사용하지 않도록 설정하려면 이 설정이 아닌 "node-linker" 설정을 변경해야 한다

auto - 저장소에서 패키지 복제를 시도합니다. 복제가 지원되지 않으면 저장소에서 패키지를 하드링크합니다. 복제도, 연결도 불가능하면 복사로 대체합니다.
hardlink - 저장소에서 패키지를 하드링크합니다.
clone-or-copy - 저장소에서 패키지 복제를 시도합니다. 복제가 지원되지 않으면 복사로 대체합니다.
copy - 저장소에서 패키지를 복사합니다.
clone - 저장소에서 패키지를 복제합니다 (copy-on-write 또는 레퍼런스 링크라고도 함).
node_modules에 패키지를 작성할 때 복제가 가장 좋은 방법입니다. 가장 빠르고 안전한 방법입니다. 복제를 사용하면 node_modules에서 파일을 편집할 수 있으며, 이는 중앙 content-addressable 저장소에서 수정되지 않습니다.

불행히도 모든 파일 시스템이 복제를 지원하는 것은 아닙니다. 최고의 pnpm 경험을 위해 CoW(Copy-On-Write) 파일 시스템(예: Linux에서 Ext4 대신 Btrfs)을 사용하는 것이 좋습니다.

Lockfile 설정

lockfile (기본값: true): 이 설정을 false로 설정하면 pnpm은 pnpm-lock.yaml 파일을 읽지 않고 생성하지 않습니다. 즉, lockfile을 사용하지 않을 수 있습니다.

prefer-frozen-lockfile (기본값: true): 이 설정을 true로 설정하면 사용 가능한 pnpm-lock.yaml 파일이 package.json의 dependencies 지시문을 충족하면 headless installation(의존성 해결 단계를 건너뛴 빠른 설치)이 수행됩니다. 즉, lockfile이 이미 존재하고 dependencies가 충족되면 새로운 의존성 해결을 수행하지 않습니다.

lockfile-include-tarball-url (기본값: false): 이 설정을 true로 설정하면 pnpm-lock.yaml의 각 항목에 패키지의 전체 tarball URL을 추가합니다.

git-branch-lockfile (기본값: false): 이 설정을 true로 설정하면 설치 후 생성된 lockfile 이름이 현재 브랜치 이름을 기반으로 설정됩니다. 이렇게 하면 머지 충돌을 완전히 피할 수 있습니다. 예를 들어, 현재 브랜치 이름이 feature-foo라면 생성된 lockfile 이름은 pnpm-lock.feature-foo.yaml이 됩니다. 주로 --merge-git-branch-lockfiles 명령행 인수와 함께 사용되거나 .npmrc 파일에서 merge-git-branch-lockfiles-branch-pattern을 설정하는 데 사용됩니다.

merge-git-branch-lockfiles-branch-pattern (기본값: null): 이 설정은 현재 브랜치 이름을 일치시켜 모든 git 브랜치 lockfile 파일을 병합할지 여부를 결정합니다. 기본적으로 --merge-git-branch-lockfiles 명령행 매개변수를 수동으로 전달해야 합니다. 이 설정을 사용하면 이 프로세스를 자동으로 수행할 수 있습니다. 예를 들어, merge-git-branch-lockfiles-branch-pattern[]에 main과 release* 패턴을 설정하면 이러한 브랜치의 lockfile을 자동으로 병합합니다. 제외 패턴을 사용하여 특정 브랜치를 제외할 수도 있습니다.

Peer Dependency 설정

auto-install-peers (기본값: true): 이 설정을 true로 설정하면 누락된 비-선택적 피어 의존성(peer dependencies)이 자동으로 설치됩니다. 다시 말해, 프로젝트가 필요로 하는 피어 의존성이 없는 경우에도 자동으로 설치됩니다.

dedupe-peer-dependents (기본값: true): 이 설정을 true로 설정하면 피어 의존성을 가진 패키지들은 피어 의존성 해결 이후에 중복 제거(deduplication)됩니다. 예를 들어, 웹팩(webpack) 패키지를 포함하는 두 개의 프로젝트가 있는 경우, 웹팩은 선택적 피어 의존성(optional peer dependencies)으로 esbuild를 가지고 있고, 두 프로젝트 중 하나에는 esbuild가 설치되어 있을 때, pnpm은 웹팩을 중복하여 노드 모듈(node_modules) 디렉토리에 설치합니다. 그러나 이것은 대부분의 개발자들이 기대하는 것이 아니며, pnpm의 dedupe-peer-dependents 설정을 true로 설정하면 웹팩과 같은 패키지가 중복 제거됩니다.

strict-peer-dependencies (기본값: false): 이 설정을 활성화하면 트리(tree)에서 누락된 피어 의존성이나 잘못된 피어 의존성이 있는 경우 명령이 실패합니다. 즉, 피어 의존성이 정확하게 해결되어야 하며, 그렇지 않을 경우 설치가 실패합니다.

resolve-peers-from-workspace-root (기본값: true): 이 설정을 활성화하면 워크스페이스 루트 프로젝트의 종속성(dependencies)을 사용하여 워크스페이스 내의 모든 프로젝트의 피어 의존성을 해결합니다. 이 설정을 사용하면 워크스페이스 루트에만 피어 의존성을 설치할 수 있으며, 모든 프로젝트가 동일한 피어 의존성 버전을 사용함을 보장할 수 있습니다.

빌드 설정

ignore-scripts (기본값: false): 이 설정을 true로 설정하면 프로젝트의 package.json 및 의존성들에 정의된 모든 스크립트를 실행하지 않습니다. 다시 말해, 패키지 설치 시 스크립트를 실행하지 않고 스크립트 단계를 건너뛸 수 있습니다. 이 플래그는 .pnpmfile.cjs 내에 정의된 스크립트의 실행을 방지하지 않습니다.

ignore-dep-scripts (기본값: false): 이 설정을 true로 설정하면 설치된 패키지들의 스크립트를 실행하지 않습니다. 대신 프로젝트의 스크립트는 실행됩니다. 다시 말해, 프로젝트의 스크립트는 실행되지만 의존성 패키지들의 스크립트는 실행되지 않습니다.

child-concurrency (기본값: 5): 이 설정은 동시에 할당할 수 있는 빌드 노드 모듈(child processes)의 최대 수를 나타냅니다. 동시성 수준을 높이거나 낮출 수 있으며 빌드 성능에 영향을 미칩니다.

side-effects-cache (기본값: true): 이 설정을 true로 설정하면 (pre/post)install 후크의 결과를 사용하고 캐시합니다. 이렇게 하면 후크가 성공적으로 실행된 결과를 캐시하여 나중에 동일한 후크 실행을 건너 뛸 수 있습니다.

side-effects-cache-readonly (기본값: false): 이 설정을 true로 설정하면 side effects 캐시가 존재하는 경우에만 사용하고, 새 패키지에 대해 캐시를 생성하지 않습니다. 이를 사용하여 이미 존재하는 캐시를 활용할 수 있습니다.

unsafe-perm (기본값: false IF running as root, ELSE true): 이 설정을 true로 설정하면 패키지 스크립트 실행 시 UID/GID 스위칭을 활성화합니다. 이를 통해 패키지 스크립트가 더 높은 권한을 요구할 때 스크립트가 안전하게 실행됩니다. 반대로 false로 설정하면 root가 아닌 사용자로 설치하는 경우 실패할 수 있습니다.

other 설정

use-running-store-server (기본값: false): 이 설정을 true로 설정하면 스토어 서버로만 설치가 허용됩니다. 스토어 서버가 실행 중이지 않으면 설치가 실패합니다.

save-prefix (기본값: '^', 유형: '^', '', ''): 패키지의 버전을 저장할 때 버전 앞에 붙이는 접두사를 설정합니다. 기본값인 '^'는 마이너 업데이트를 허용합니다. ''로 설정하면 패치 업데이트만 허용합니다. 이 설정은 패키지에 범위가 이미 지정되어 있는 경우에는 무시됩니다.

tag (기본값: latest, 유형: 문자열): 특정 버전을 지정하지 않고 패키지를 설치하는 경우, 해당 설정에 등록된 태그 버전의 패키지를 설치합니다. 또한, pnpm tag 명령을 사용할 때 지정된 태그가 없는 경우 패키지@버전에 추가할 태그를 설정합니다.

global-dir (기본값: 시스템 및 OS에 따라 설정): 글로벌 패키지를 저장할 디렉토리를 사용자 정의 설정할 수 있습니다.

global-bin-dir (기본값: 시스템 및 OS에 따라 설정): 글로벌로 설치된 패키지의 실행 파일(bin)이 저장될 디렉토리를 사용자 정의 설정할 수 있습니다.

state-dir (기본값: 시스템 및 OS에 따라 설정): pnpm이 pnpm-state.json 파일을 생성하는 디렉토리를 사용자 정의 설정할 수 있습니다. 이 파일은 업데이트 확인에 사용됩니다.

cache-dir (기본값: 시스템 및 OS에 따라 설정): 패키지 메타데이터 캐시의 위치를 사용자 정의 설정할 수 있습니다.

use-stderr (기본값: false): 이 설정을 true로 설정하면 모든 출력이 stderr에 작성됩니다.

update-notifier (기본값: true): 이 설정을 false로 설정하면 pnpm 버전이 최신 버전이 아닌 경우 업데이트 알림을 억제합니다.

prefer-symlinked-executables (기본값: true): hoisted 노드 모듈 및 POSIX 시스템에서 node-linker가 설정되어 있는 경우 bin 파일에 대한 심볼릭 링크를 생성합니다. 이 설정은 Windows에서는 무시되며, Windows에서는 커맨드 셰임만 작동합니다.

verify-store-integrity (기본값: true): 스토어에서 파일이 수정되었을 때 해당 파일의 내용을 링크하기 전에 파일 내용을 확인합니다. 이 설정을 false로 설정하면 스토어 내의 파일을 설치하는 동안 확인하지 않습니다.

ignore-compatibility-db (기본값: false): 일부 패키지의 의존성이 자동으로 패치되는 경우가 있습니다. 이 설정을 false로 설정하면 이 작업을 비활성화할 수 있습니다. 패치는 Yarn의 @yarnpkg/extensions 패키지에서 적용됩니다.

resolution-mode (기본값: highest, 유형: highest, time-based, lowest-direct): resolution-mode를 time-based로 설정하면 직접 의존성은 가장 낮은 버전으로 해결되고, subdependencies는 마지막 직접 의존성이 게시되기 전 버전에서 해결됩니다. 이 설정은 웜 캐시에서 빠르게 작동하며, subdependency가 hijacking되는 가능성을 줄입니다.

registry-supports-time-field (기본값: false): 지정한 레지스트리가 "time" 필드를 반환하는 경우 true로 설정합니다. 현재 Verdaccio v5.15.1 이상에서만 지원합니다.

extend-node-path (기본값: true): 이 설정을 false로 설정하면 커맨드 셰임에서 NODE_PATH 환경 변수가 설정되지 않습니다.

deploy-all-files (기본값: false): 패키지를 배포하거나 로컬 패키지를 설치할 때 해당 패키지의 모든 파일이 복사됩니다. 기본적으로 패키지가 package.json의 "files" 필드에 명시된 파일과 디렉토리만 복사됩니다.

dedupe-direct-deps (추가 버전: v8.1.0, 기본값: false): 이 설정을 true로 설정하면 이미 루트 node_modules 디렉터리에 심볼릭 링크가 있는 의존성은 서브프로젝트 node_modules 디렉터리에 심볼릭 링크로 생성되지 않습니다.

Netlify CLI 설치

Netlify
정적 웹 사이트를 배포하고 호스팅하는 클라우드 기반의 서비스입니다.

  • 정적 웹 사이트 호스팅: Netlify는 HTML, CSS, JavaScript 및 이미지와 같은 정적 웹 콘텐츠를 호스팅하는 데 사용됩니다. 이를 통해 웹 사이트를 빠르게 제공하고, 웹 호스팅 비용을 절감할 수 있습니다.

  • 무료 SSL 인증서 제공: Netlify는 모든 사이트에 대해 무료 SSL/TLS 인증서를 제공하여 보안 연결을 제공합니다.

  • 지속적 배포 (Continuous Deployment): GitHub, GitLab, Bitbucket과 통합하여 코드가 업데이트되면 자동으로 웹 사이트를 다시 빌드하고 배포합니다. 이로써 지속적 통합과 지속적 배포 (CI/CD) 프로세스를 구현할 수 있습니다.

  • 프리뷰 빌드: Pull Request를 통해 새로운 기능 또는 변경 사항을 검토할 때, Netlify는 미리보기 URL을 제공하여 변경 사항을 테스트할 수 있는 환경을 제공합니다.

  • 사용자 정의 도메인: Netlify는 사용자 정의 도메인을 지원하므로 웹 사이트에 사용자 고유의 도메인을 연결할 수 있습니다.

  • AWS Lambda 기반 서버리스 기능: Netlify Functions를 사용하여 서버리스 기능을 만들고 호스팅할 수 있습니다. 이를 통해 서버리스 백엔드 API를 생성하고 관리할 수 있습니다.

  • 폼 제출 처리: 웹 폼 제출 데이터를 수집하고 처리할 수 있으며, 이메일로 알림을 보낼 수 있습니다.

  • 스케일링: Netlify는 웹 사이트 트래픽에 따라 자동으로 스케일링되므로 대용량 트래픽을 처리할 수 있습니다.

https://docs.netlify.com/cli/get-started/

# 전역으로 설치
pnpm install netlify-cli -g
# 로컬 CI 환경 고려하여 설치
pnpm install netlify-cli --save-dev

peer dependency가 발견되었다고 한다. pnpm update로 해결이 되지 않으니 상세히 살펴봐야 할 것 같다.

Peer Dependency
프로젝트의 직접 종속성(직접 설치한 패키지)이 특정 패키지 버전을 필요로 할 때 사용됩니다. 이 요구사항은 주로 패키지의 package.json 파일에 명시됩니다.

프로젝트
├── 직접 종속성 A (peer dependency: "B >=1.0.0 <2.0.0")
└── 직접 종속성 B (버전: 1.1.0)

Transitive Dependency
직접 종속성이 필요로 하는 패키지를 설치할 때, 그 패키지가 의존하는 패키지가 자동으로 설치됩니다. 이러한 패키지는 간접 종속성이며 명시적으로 프로젝트에 나열되지 않습니다.

프로젝트
└── 직접 종속성 A
    └── 간접 종속성 C

Transitive Peer Dependency
간접 종속성이 직접 종속성의 "peer dependency"를 충족하기 위해 필요로 하는 패키지를 설치할 때, 해당 패키지가 "peer dependency"의 요구사항을 충족시키는 경우입니다.

프로젝트
├── 직접 종속성 A (peer dependency: "B >=1.0.0 <2.0.0")
└── 간접 종속성 C (B 버전: 1.1.0)

위의 예시에서, "직접 종속성 A"가 "B >=1.0.0 <2.0.0"의 "peer dependency"를 요구하고, "간접 종속성 C"는 B 버전 1.1.0을 설치한 경우입니다. 이로 인해 "간접 종속성 C"는 "직접 종속성 A"의 "peer dependency"를 충족시키므로 프로젝트의 요구사항을 충족시킵니다.

pnpm-lock.yaml 파일에서 @opentelemetry/api와 warning에 출력된 다른 요소들 간의 관계를 살펴본다.

warning 메세지의 scope에 따라 @opentelemetry/api의 버전을 각각 어떻게 요구하고 있는지 살펴보았다.
위 스크린샷에서 @opentelemetry/sdk-trace-nodepeerDependencies (다른 패키지와 상호작용 시 필요한 종속성)의 키 값으로@opentelemetry/api 패키지의 버전 범위를 정의하고 있으며, dependencies(직접 의존하는 종속성)에서 정의한 @opentelemetry/api의 범위와 상이하게 나타나고 있다. 그외 다른 직접종속성들도 괄호 안에 @opentelemetry/api와 그 버전을 명시하는데, 이는 직접종속성의 @opentelemetry/api 버전이다.

대략적인 구조를 살펴본 결과 peerDependencies로 요구하는 @opentelemetry/api 버전은 무시해도 될 것 같다.

pnpm install --legacy-peer-deps
모든 "peer dependencies"를 일반적인 종속성으로 처리하는 것. 개별적인 지정 기능 없음.

pnpm install --ignore=@opentelemetry/api
@opentelemetry/api를 "peer dependency"로 지정한 패키지와 "dependencies"로 지정한 패키지 모두를 무시하게 되므로 사용하지 않음.

package.json에서 pnpm overrides 설정 사용하기

"pnpm": {
  "overrides": {
    "@opentelemetry/api": "원하는 버전"
  }
}

overrides 설정을 사용하여 @opentelemetry/api의 버전을 일반 종속성과 peer dependency 모두에서 동일한 버전으로 강제할 수 있다.

pacage.json 수정

"peerDependencies": {
  "@opentelemetry/api": "원하는 버전"
},
"dependencies": {
  "@opentelemetry/api": "원하는 버전"
}

@opentelemetry/api가 어떤 의존성의 의존성의 의존성 안에 있더라도, 프로젝트 루트의 package.json@opentelemetry/api를 "dependencies" 또는 "peerDependencies"로 명시하면 해당 버전을 사용하게 됨.

package.json 설정을 변경한 후 pnpm install 명령으로 변경된 내용을 적용하였다.

pnpm-lock.yaml 파일에 변경된 내용이 적용되어 있다.

github 계정을 netlify와 연동하였다.

첫 로그인 시 보이는 overview

netlify : command not found

pnpm list 로 확인하면 분명 개발 의존성으로 netlify-cli 가 설치된 것을 확인할 수 있는데 netlify 커맨드를 실행하면 명령을 찾을 수 없다고 나온다.

pnpm의 특성에 기반하여 추측해보기
기본적으로, pnpm은 symlink를 사용하여 프로젝트의 직접적인 의존성만을 모듈 디렉토리의 루트로 추가한다. 설치한 패키지가 symlink와 잘 작동하지 않는 경우에는 .npmrc 파일에서 의존성 호이스팅 설정이나 Node-Modules 설정을 변경하는 방법을 생각해볼 수 있다.

루트 디렉터리의 package.jsonscriptspnpm run <실행하고 싶은 scripts의 명령어 키 값>으로 실행할 수 있는 작업을 정의한 것이다.

  "scripts": {
    "dev": "vite",
    "build": "tsc && vite build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview --port 8080"
  },

... 중략
  "devDependencies": {
    ... 중략
    "@typescript-eslint/eslint-plugin": "^6.9.1",
    "@typescript-eslint/parser": "^6.9.1",
    "@vitejs/plugin-react": "^4.1.0",
    "eslint": "^8.52.0",
    "netlify-cli": "^16.9.3",
    "typescript": "^5.2.2",
    "vite": "^4.5.0"
  },

pnpm run devvite 라는 명령을 실행하는 것이다. 개발 의존성에 설치된 vite 패키지의 명령어임이 자명해보인다. 프로젝트의 직접적인 의존성이므로 symlink를 사용하여 모듈 디렉토리의 루트로 추가했을 것이다. lint 명령어의 eslint 또한 개발의존성에 추가되어 있음을 주목하자.

위 디렉터리 목록에서 이름 옆의 휘어진 화살표 표시는 symlink 표시이다. eslint, vite, netlify-cli는 symlink이고 @eslint, .vite는 일반 디렉터리이다.

eslint.pnpm/eslint@8.52.0/node_modules/eslint와 링크된 symlink이고 @eslint는 eslint 설정과 관련된 별개의 디렉터리였다.

netlify-cli.pnpm/netlify-cli@16.9.3_@types+node@20.8.10/node_modules/netlify-cli에, vite.pnpm/vite@4.5.0_@types_node@20.8.10/node_modules/vite에 링크된 symlink이다.

원본이 있는 위치로 이동해서 명령어와 관련된 단서를 찾아본다.

vite 원본 디렉터리 살펴보기

//.pnpm/vite@4.5.0_@types_node@20.8.10/node_modules/vite/package.json

  "bin": {
    "vite": "bin/vite.js"
  },

package.json 파일의 bin 항목은 Node.js 패키지로 전역으로 설치될 때 실행 가능한 명령어와 해당 명령어 파일 경로를 정의하는 객체이다. 이 항목은 주로 CLI(Command Line Interface) 도구를 개발할 때 사용된다.

//.pnpm/vite@4.5.0_@types_node@20.8.10/node_modules/vite/bin/vite.js

#!/usr/bin/env node
import { performance } from 'node:perf_hooks'

if (!import.meta.url.includes('node_modules')) {
  try {
    // only available as dev dependency
    await import('source-map-support').then((r) => r.default.install())
  } catch (e) {}
}
// 중략

function start() {
  return import('../dist/node/cli.js')
}
// 중략
  • #!/usr/bin/env node: 이 첫 줄은 스크립트가 Node.js 환경에서 실행되어야 함을 지정하는 것. 스크립트를 실행 가능한 바이너리로 만들 때 사용됨.
  • function start() { return import('../dist/node/cli.js') }: start 함수를 정의하며, 이 함수는 ../dist/node/cli.js 모듈을 가져와서 실행함. CLI의 핵심 기능을 포함하고 있음.

netlify 원본 디렉터리 살펴보기

vite가 5개 항목으로 구성됐던 것과는 상반되게 100개 이상의 항목으로 구성되어 있다.

@netlify, netlify-cli는 일반 디렉터리이고, netlify는 symlink이다.

이 symlink는 상위 디렉터리에서 분기한 node_modules/netlify를 링크하고 있다.

netlify symlink 디렉터리

package.json에서 명령어와 관련된 내용은 발견할 수 없었다.

@netlify 디렉터리

여러가지 설정 관련 symlink가 모여있다.

netlify-cli 디렉터리

//package.json
  "bin": {
    "ntl": "./bin/run.mjs",
    "netlify": "./bin/run.mjs"
  }

vite 원본 디렉터리의 package.json에서 "bin/vite.js" 로 명령어 경로를 지정한 것과 달리 ./bin/run.mjs로 명령어 경로를 지정하고 있다. 실험적으로 bin/run.mjs 로 수정해보기로 한다.

// ./bin/run.mjs

#!/usr/bin/env node
import { argv } from 'process'

import updateNotifier from 'update-notifier'

import { createMainCommand } from '../src/commands/index.mjs'
import { error } from '../src/utils/command-helpers.mjs'
import getPackageJson from '../src/utils/get-package-json.mjs'

// 12 hours
const UPDATE_CHECK_INTERVAL = 432e5
const pkg = await getPackageJson()

try {
  updateNotifier({
    pkg,
    updateCheckInterval: UPDATE_CHECK_INTERVAL,
  }).notify()
} catch (error_) {
  error('Error checking for updates:')
  error(error_)
}

const program = createMainCommand()

try {
  await program.parseAsync(argv)
  program.onEnd()
} catch (error_) {
  program.onEnd(error_)
}
  • #!/usr/bin/env node : 스크립트가 Node.js 환경에서 실행되어야 함을 지정. 스크립트를 실행 가능한 바이너리로 만들 때 사용
  • import { createMainCommand } from '../src/commands/index.mjs': CLI의 메인 명령을 생성하는 함수를 가져옴. CLI 도구의 주요 동작과 하위 명령을 설정함.
  • import getPackageJson from '../src/utils/get-package-json.mjs': package.json 파일을 가져오는 함수를 가져옴. 이 함수를 통해 CLI 도구의 패키지 정보를 읽어옴.
  • const pkg = await getPackageJson(): CLI 도구의 package.json 파일을 읽어옴
  • const program = createMainCommand(): CLI의 주요 명령을 생성하여 program 변수에 할당.
  • await program.parseAsync(argv): 명령행 인수(argv)를 파싱하여 CLI 명령을 실행
  • program.onEnd(): CLI 도구의 실행이 종료될 때 필요한 작업을 수행

netlify-cli package.json 수정

//package.json
  "bin": {
    // "./bin/run.mjs"를 수정
    "ntl": "bin/run.mjs",
    "netlify": "bin/run.mjs"
  }

@netlify/config package.json 수정

//../../../@netlify+config@20.9.0/node_modules/@netlify/config/package.json
  "bin": {
    "netlify-config": "bin.js"
  }

@netlify/config bin.js 수정

#!/usr/bin/env node

// This is a workaround for npm issue: https://github.com/npm/cli/issues/2632

// import './lib/bin/main.js'
import 'lib/bin/main.js'

symlink 디렉터리가 링크하고 있는 원본 디렉터리들의 package.json 에서 bin 필드로 명령어 경로를 불러오는 방식을 수정했다.

0개의 댓글