중첩 폴더에 대해 npm 설치를 실행하는 가장 좋은 방법은 무엇입니까?


129

npm packages중첩 된 하위 폴더 에 설치하는 가장 올바른 방법은 무엇입니까 ?

my-app
  /my-sub-module
  package.json
package.json

한하는 가장 좋은 방법은 무엇입니까 packages/my-sub-module경우 자동으로 설치 npm install에서 실행은 my-app?


가장 관용적 인 것은 프로젝트에 하나의 package.json 파일을 갖는 것입니다.
Robert Moskal 2015-08-02

한 가지 아이디어는 bash 파일을 실행하는 npm 스크립트를 사용하는 것입니다.
Davin Tryon

로컬 경로가 작동하는 방식을 수정하면이 작업을 수행 할 수
없습니까

답변:


26

단일 명령을 실행하여 중첩 된 하위 폴더에 npm 패키지를 설치하려면 루트 디렉터리에서 npm및 main package.json을 통해 스크립트를 실행할 수 있습니다 . 스크립트는 모든 하위 디렉토리를 방문하여 npm install.

다음은 .js원하는 결과를 얻을 수 있는 스크립트입니다.

var fs = require('fs')
var resolve = require('path').resolve
var join = require('path').join
var cp = require('child_process')
var os = require('os')

// get library path
var lib = resolve(__dirname, '../lib/')

fs.readdirSync(lib)
  .forEach(function (mod) {
    var modPath = join(lib, mod)
// ensure path has package.json
if (!fs.existsSync(join(modPath, 'package.json'))) return

// npm binary based on OS
var npmCmd = os.platform().startsWith('win') ? 'npm.cmd' : 'npm'

// install folder
cp.spawn(npmCmd, ['i'], { env: process.env, cwd: modPath, stdio: 'inherit' })
})

이것은 모듈 식 프로젝트 구조 (중첩 된 구성 요소 및 파일 포함) 를 구체적으로 다루는 StrongLoop 기사 에서 가져온 예제 입니다.node.jspackage.json

제안 된대로 bash 스크립트로도 동일한 결과를 얻을 수 있습니다.

편집 : Windows에서 코드 작동


1
그러나 기사 링크에 감사드립니다.
WHITECOLOR

'컴포넌트'기반 구조는 노드 앱을 설정하는 데 매우 편리한 방법이지만 별도의 package.json 파일 등을 분리하는 것은 앱의 초기 단계에서 과잉 일 가능성이 높습니다. 앱이 성장하고 합법적으로 별도의 모듈 / 서비스를 원합니다. 그러나 예, 필요하지 않으면 확실히 너무 복잡합니다.
snozza 2015-08-02

3
bash 스크립트는 가능하지만 DOS 쉘이있는 Windows와 Unix 쉘이있는 Linux / Mac 사이의 최대 이식성을 위해 nodejs 방식을 선호합니다.
truthadjustr

270

중첩 된 하위 디렉터리의 이름을 알고 있다면 설치 후 사용하는 것을 선호합니다. 에서 package.json:

"scripts": {
  "postinstall": "cd nested_dir && npm install",
  ...
}

10
여러 폴더는 어떻습니까? "cd nested_dir && npm install && cd .. & cd nested_dir2 && npm install"??
Emre

1
@Emre 예-그게 다입니다.
Guy

2
@Scott은 "postinstall": "cd nested_dir2 && npm install"각 폴더 와 같이 package.json 내부에 다음 폴더를 넣을 수 없습니까?
Aron

1
@Aron 이름 부모 디렉터리 내에 두 개의 하위 디렉터리를 원하는 경우 어떻게합니까?
Alec

29
@Emre 작동해야합니다. 서브 쉘은 약간 더 깔끔 할 수 있습니다. "(cd nested_dir && npm install); (cd nested_dir2 && npm install); ..."
Alec

49

@Scott의 대답에 따르면 install | postinstall 스크립트는 하위 디렉토리 이름을 알고있는 한 가장 간단한 방법입니다. 이것이 여러 하위 디렉토리에 대해 실행하는 방법입니다. 예를 들어, monorepo 루트에 api/, web/shared/하위 프로젝트가 있다고 가정합니다.

// In monorepo root package.json
{
...
 "scripts": {
    "postinstall": "(cd api && npm install); (cd web && npm install); (cd shared && npm install)"
  },
}

1
완벽한 솔루션. 공유 해주셔서 감사합니다 :-)
Rahul Soni

1
답변 해주셔서 감사합니다. 나를 위해 일하고 있습니다.
AMIC MING

5
잘 사용 ( )서브 쉘을 작성하고 방지하는 cd api && npm install && cd ...
Cameron Hudson

4
그게 정답이어야합니다!
tmos

3
npm install최상위 수준에서 실행할 때이 오류가 발생 합니다."(cd was unexpected at this time."
Mr. Polywhirl

22

내 솔루션은 매우 유사합니다. 순수 Node.js

다음 스크립트 는 각 하위 폴더가 package.json있고 실행 npm install되는 한 모든 하위 폴더를 (재귀 적으로) 검사 합니다. 예외를 추가 할 수 있습니다. 폴더에 package.json. 아래 예에서 이러한 폴더 중 하나는 "packages"입니다. "사전 설치"스크립트로 실행할 수 있습니다.

const path = require('path')
const fs = require('fs')
const child_process = require('child_process')

const root = process.cwd()
npm_install_recursive(root)

// Since this script is intended to be run as a "preinstall" command,
// it will do `npm install` automatically inside the root folder in the end.
console.log('===================================================================')
console.log(`Performing "npm install" inside root folder`)
console.log('===================================================================')

// Recurses into a folder
function npm_install_recursive(folder)
{
    const has_package_json = fs.existsSync(path.join(folder, 'package.json'))

    // Abort if there's no `package.json` in this folder and it's not a "packages" folder
    if (!has_package_json && path.basename(folder) !== 'packages')
    {
        return
    }

    // If there is `package.json` in this folder then perform `npm install`.
    //
    // Since this script is intended to be run as a "preinstall" command,
    // skip the root folder, because it will be `npm install`ed in the end.
    // Hence the `folder !== root` condition.
    //
    if (has_package_json && folder !== root)
    {
        console.log('===================================================================')
        console.log(`Performing "npm install" inside ${folder === root ? 'root folder' : './' + path.relative(root, folder)}`)
        console.log('===================================================================')

        npm_install(folder)
    }

    // Recurse into subfolders
    for (let subfolder of subfolders(folder))
    {
        npm_install_recursive(subfolder)
    }
}

// Performs `npm install`
function npm_install(where)
{
    child_process.execSync('npm install', { cwd: where, env: process.env, stdio: 'inherit' })
}

// Lists subfolders in a folder
function subfolders(folder)
{
    return fs.readdirSync(folder)
        .filter(subfolder => fs.statSync(path.join(folder, subfolder)).isDirectory())
        .filter(subfolder => subfolder !== 'node_modules' && subfolder[0] !== '.')
        .map(subfolder => path.join(folder, subfolder))
}

3
귀하의 스크립트가 좋습니다. 그러나 개인적인 목적을 위해 나는 깊은 중첩 된 'npm 설치'를 얻기 위해 첫 번째 'if 조건'을 제거하는 것을 선호합니다!
Guilherme Caraciolo

21

사람들이이 질문을 보게 될 경우를 대비하여 참고 용입니다. 이제 다음을 수행 할 수 있습니다.

  • 하위 폴더에 package.json 추가
  • 이 하위 폴더를 기본 package.json에 참조 링크로 설치합니다.

npm install --save path/to/my/subfolder


2
종속성은 루트 폴더에 설치됩니다. 이 패턴을 고려하고 있다면 하위 디렉터리에있는 package.json 하위 디렉터리의 종속성을 원할 것입니다.
Cody Allan Taylor

무슨 말이야? 하위 폴더 패키지에 대한 종속성은 하위 폴더의 package.json에 있습니다.
Jelmer Jellema

(npm v6.6.0 및 node v8.15.0 사용)-직접 예제를 설정하십시오. mkdir -p a/b ; cd a ; npm init ; cd b ; npm init ; npm install --save through2 ;이제 잠깐 ... "b"에 수동으로 종속성을 설치했습니다. 새 프로젝트를 복제 할 때 발생하는 현상이 아닙니다. rm -rf node_modules ; cd .. ; npm install --save ./b. 이제 node_modules를 나열한 다음 b를 나열하십시오.
Cody Allan Taylor

1
아 당신은 모듈을 의미합니다. 예, b의 node_modules는 a / node_modules에 설치됩니다. 이는 "실제"노드 모듈이 아닌 기본 코드의 일부로 모듈을 요구 / 포함 할 것이기 때문입니다. 따라서 "require ( 'throug2')"는 a / node_modules에서 2를 검색합니다.
Jelmer Jellema

코드 생성을 시도하고 있으며 자체 node_modules를 포함하여 완전히 실행할 준비가 된 하위 폴더 패키지를 원합니다. 해결책을 찾으면 업데이트하겠습니다!
ohsully

20

사용 사례 1 : 각 하위 디렉터리 (각 package.json이있는)에서 npm 명령을 실행하려면 다음을 사용해야합니다.postinstall .

npm-run-all어쨌든 자주 사용 하기 때문에 멋지고 짧게 유지하기 위해 사용합니다 (설치 후 부분).

{
    "install:demo": "cd projects/demo && npm install",
    "install:design": "cd projects/design && npm install",
    "install:utils": "cd projects/utils && npm install",

    "postinstall": "run-p install:*"
}

한 번에 또는 개별적으로 설치할 수있는 추가 이점이 있습니다. 필요하지 않거나 원하지 않는 경우npm-run-all 종속성으로 demisx의 답변을 확인하십시오 (설치 후 하위 셸 사용).

사용 사례 2 : 루트 디렉터리에서 모든 npm 명령을 실행하려는 경우 (예를 들어 하위 디렉터리에서 npm 스크립트를 사용하지 않을 것임) 종속성과 같이 각 하위 디렉터리를 간단히 설치할 수 있습니다.

npm install path/to/any/directory/with/a/package-json

후자의 경우 하위 디렉토리에서 node_modules또는 package-lock.json파일을 찾을 수 없다는 사실에 놀라지 마십시오. 모든 패키지가 root에 설치 node_modules되므로 npm 명령을 실행할 수 없습니다. 종속 항목이 필요합니다).

확실하지 않은 경우 사용 사례 1은 항상 작동합니다.


각 하위 모듈에 자체 설치 스크립트가 있고 설치 후 모두 실행하는 것이 좋습니다. run-p필요하지는 않지만 더 장황합니다"postinstall": "npm run install:a && npm run install:b"
Qwerty

예, 사용할 수 &&없는 run-p. 그러나 당신이 말했듯이 그것은 읽기가 어렵습니다. 또 다른 단점은 (실행-P 해결할 수있는 문제가 설치 횟수가 병렬로 실행되기 때문에 것을) 하나가 실패하면, 다른 스크립트가 영향을받지된다는 점이다
돈 본이

3

snozza의 답변에 Windows 지원을 추가 하고 node_modules폴더가있는 경우 건너 뜁니다 .

var fs = require('fs')
var resolve = require('path').resolve
var join = require('path').join
var cp = require('child_process')

// get library path
var lib = resolve(__dirname, '../lib/')

fs.readdirSync(lib)
  .forEach(function (mod) {
    var modPath = join(lib, mod)
    // ensure path has package.json
    if (!mod === 'node_modules' && !fs.existsSync(join(modPath, 'package.json'))) return

    // Determine OS and set command accordingly
    const cmd = /^win/.test(process.platform) ? 'npm.cmd' : 'npm';

    // install folder
    cp.spawn(cmd, ['i'], { env: process.env, cwd: modPath, stdio: 'inherit' })
})

당신은 확실히 할 수 있습니다. node_modules 폴더를 건너 뛰도록 솔루션을 업데이트했습니다.
Ghostrydr

2

여기에 제공된 스크립트에서 영감을 받아 다음과 같은 구성 가능한 예제를 만들었습니다.

  • 사용하도록 설정 yarn하거나npm
  • 잠금 파일을 기반으로 사용할 명령을 결정하도록 설정할 수 있으므로 사용하도록 설정 yarn했지만 디렉토리에 해당 디렉토리에만 package-lock.json사용할 수 있습니다 npm(기본값은 true).
  • 로깅 구성
  • 다음을 사용하여 병렬로 설치 실행 cp.spawn
  • 먼저 무엇을할지 볼 수 있도록 드라 이런을 할 수 있습니다.
  • 함수로 실행하거나 env vars를 사용하여 자동 실행 가능
    • 함수로 실행할 때 선택적으로 확인할 디렉토리 배열 제공
  • 완료되면 해결되는 promise를 반환합니다.
  • 필요한 경우 최대 깊이를 설정할 수 있습니다.
  • 폴더를 찾으면 반복을 중지하는 것을 알고 있습니다. yarn workspaces(구성 가능) 있습니다.
  • 쉼표로 구분 된 env var를 사용하거나 config에 일치시킬 문자열 배열 또는 파일 이름, 파일 경로 및 fs.Dirent obj를 수신하고 부울 결과를 예상하는 함수를 전달하여 디렉토리를 건너 뛸 수 있습니다.
const path = require('path');
const { promises: fs } = require('fs');
const cp = require('child_process');

// if you want to have it automatically run based upon
// process.cwd()
const AUTO_RUN = Boolean(process.env.RI_AUTO_RUN);

/**
 * Creates a config object from environment variables which can then be
 * overriden if executing via its exported function (config as second arg)
 */
const getConfig = (config = {}) => ({
  // we want to use yarn by default but RI_USE_YARN=false will
  // use npm instead
  useYarn: process.env.RI_USE_YARN !== 'false',
  // should we handle yarn workspaces?  if this is true (default)
  // then we will stop recursing if a package.json has the "workspaces"
  // property and we will allow `yarn` to do its thing.
  yarnWorkspaces: process.env.RI_YARN_WORKSPACES !== 'false',
  // if truthy, will run extra checks to see if there is a package-lock.json
  // or yarn.lock file in a given directory and use that installer if so.
  detectLockFiles: process.env.RI_DETECT_LOCK_FILES !== 'false',
  // what kind of logging should be done on the spawned processes?
  // if this exists and it is not errors it will log everything
  // otherwise it will only log stderr and spawn errors
  log: process.env.RI_LOG || 'errors',
  // max depth to recurse?
  maxDepth: process.env.RI_MAX_DEPTH || Infinity,
  // do not install at the root directory?
  ignoreRoot: Boolean(process.env.RI_IGNORE_ROOT),
  // an array (or comma separated string for env var) of directories
  // to skip while recursing. if array, can pass functions which
  // return a boolean after receiving the dir path and fs.Dirent args
  // @see https://nodejs.org/api/fs.html#fs_class_fs_dirent
  skipDirectories: process.env.RI_SKIP_DIRS
    ? process.env.RI_SKIP_DIRS.split(',').map(str => str.trim())
    : undefined,
  // just run through and log the actions that would be taken?
  dry: Boolean(process.env.RI_DRY_RUN),
  ...config
});

function handleSpawnedProcess(dir, log, proc) {
  return new Promise((resolve, reject) => {
    proc.on('error', error => {
      console.log(`
----------------
  [RI] | [ERROR] | Failed to Spawn Process
  - Path:   ${dir}
  - Reason: ${error.message}
----------------
  `);
      reject(error);
    });

    if (log) {
      proc.stderr.on('data', data => {
        console.error(`[RI] | [${dir}] | ${data}`);
      });
    }

    if (log && log !== 'errors') {
      proc.stdout.on('data', data => {
        console.log(`[RI] | [${dir}] | ${data}`);
      });
    }

    proc.on('close', code => {
      if (log && log !== 'errors') {
        console.log(`
----------------
  [RI] | [COMPLETE] | Spawned Process Closed
  - Path: ${dir}
  - Code: ${code}
----------------
        `);
      }
      if (code === 0) {
        resolve();
      } else {
        reject(
          new Error(
            `[RI] | [ERROR] | [${dir}] | failed to install with exit code ${code}`
          )
        );
      }
    });
  });
}

async function recurseDirectory(rootDir, config) {
  const {
    useYarn,
    yarnWorkspaces,
    detectLockFiles,
    log,
    maxDepth,
    ignoreRoot,
    skipDirectories,
    dry
  } = config;

  const installPromises = [];

  function install(cmd, folder, relativeDir) {
    const proc = cp.spawn(cmd, ['install'], {
      cwd: folder,
      env: process.env
    });
    installPromises.push(handleSpawnedProcess(relativeDir, log, proc));
  }

  function shouldSkipFile(filePath, file) {
    if (!file.isDirectory() || file.name === 'node_modules') {
      return true;
    }
    if (!skipDirectories) {
      return false;
    }
    return skipDirectories.some(check =>
      typeof check === 'function' ? check(filePath, file) : check === file.name
    );
  }

  async function getInstallCommand(folder) {
    let cmd = useYarn ? 'yarn' : 'npm';
    if (detectLockFiles) {
      const [hasYarnLock, hasPackageLock] = await Promise.all([
        fs
          .readFile(path.join(folder, 'yarn.lock'))
          .then(() => true)
          .catch(() => false),
        fs
          .readFile(path.join(folder, 'package-lock.json'))
          .then(() => true)
          .catch(() => false)
      ]);
      if (cmd === 'yarn' && !hasYarnLock && hasPackageLock) {
        cmd = 'npm';
      } else if (cmd === 'npm' && !hasPackageLock && hasYarnLock) {
        cmd = 'yarn';
      }
    }
    return cmd;
  }

  async function installRecursively(folder, depth = 0) {
    if (dry || (log && log !== 'errors')) {
      console.log('[RI] | Check Directory --> ', folder);
    }

    let pkg;

    if (folder !== rootDir || !ignoreRoot) {
      try {
        // Check if package.json exists, if it doesnt this will error and move on
        pkg = JSON.parse(await fs.readFile(path.join(folder, 'package.json')));
        // get the command that we should use.  if lock checking is enabled it will
        // also determine what installer to use based on the available lock files
        const cmd = await getInstallCommand(folder);
        const relativeDir = `${path.basename(rootDir)} -> ./${path.relative(
          rootDir,
          folder
        )}`;
        if (dry || (log && log !== 'errors')) {
          console.log(
            `[RI] | Performing (${cmd} install) at path "${relativeDir}"`
          );
        }
        if (!dry) {
          install(cmd, folder, relativeDir);
        }
      } catch {
        // do nothing when error caught as it simply indicates package.json likely doesnt
        // exist.
      }
    }

    if (
      depth >= maxDepth ||
      (pkg && useYarn && yarnWorkspaces && pkg.workspaces)
    ) {
      // if we have reached maxDepth or if our package.json in the current directory
      // contains yarn workspaces then we use yarn for installing then this is the last
      // directory we will attempt to install.
      return;
    }

    const files = await fs.readdir(folder, { withFileTypes: true });

    return Promise.all(
      files.map(file => {
        const filePath = path.join(folder, file.name);
        return shouldSkipFile(filePath, file)
          ? undefined
          : installRecursively(filePath, depth + 1);
      })
    );
  }

  await installRecursively(rootDir);
  await Promise.all(installPromises);
}

async function startRecursiveInstall(directories, _config) {
  const config = getConfig(_config);
  const promise = Array.isArray(directories)
    ? Promise.all(directories.map(rootDir => recurseDirectory(rootDir, config)))
    : recurseDirectory(directories, config);
  await promise;
}

if (AUTO_RUN) {
  startRecursiveInstall(process.cwd());
}

module.exports = startRecursiveInstall;

그리고 그것을 사용하면 :

const installRecursively = require('./recursive-install');

installRecursively(process.cwd(), { dry: true })

1

find시스템에 유틸리티 가있는 경우 응용 프로그램 루트 디렉터리에서 다음 명령을 실행 해 볼 수 있습니다.
find . ! -path "*/node_modules/*" -name "package.json" -execdir npm install \;

기본적으로 모든 package.json파일을 찾아 npm install해당 디렉토리에서 실행 하고 모든 node_modules디렉토리를 건너 뜁니다 .


1
좋은 대답입니다. 다음을 사용하여 추가 경로를 생략 할 수도 있습니다.find . ! -path "*/node_modules/*" ! -path "*/additional_path/*" -name "package.json" -execdir npm install \;
Evan Moran
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.