代码之家  ›  专栏  ›  技术社区  ›  WHITECOLOR

为嵌套文件夹运行npm安装的最佳方式是什么?

  •  206
  • WHITECOLOR  · 技术社区  · 9 年前

    最正确的安装方式是什么 npm packages 在嵌套子文件夹中?

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

    什么是最好的方式 packages 在里面 /my-sub-module 在以下情况下自动安装 npm install 磨合,磨合 my-app ?

    15 回复  |  直到 9 年前
        1
  •  348
  •   Tunaki user5979544    8 年前

    如果您知道嵌套子磁盘的名称,我更喜欢使用后安装。在里面 package.json :

    "scripts": {
      "postinstall": "cd nested_dir && npm install",
      ...
    }
    
        2
  •  101
  •   beaver mohamed amine hamza    2 年前

    根据@Scott的回答,只要知道子目录名,install|postinstall脚本是最简单的方法。这是我在多个子目录中运行它的方式。例如,假设我们有 api/ , web/ shared/ monorepo根目录中的子项目:

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

    在Windows上,替换 ; 父母之间 && .

    // In monorepo root package.json
    {
    ...
     "scripts": {
        "postinstall": "(cd api && npm install) && (cd web && npm install) && (cd shared && npm install)"
      },
    }
    
        3
  •  49
  •   uɥƃnɐʌuop    4 年前

    用例1 :如果您希望能够在每个子目录(每个package.json所在的位置)中运行npm命令,则需要使用 postinstall .

    正如我经常使用的 npm-run-all 无论如何,我使用它来保持它的美观和简短(postinstall中的部分):

    {
        "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全部运行 作为依赖项,查看demisx的答案(在postinstall中使用子shell)。

    用例2 :如果您将从根目录运行所有npm命令(例如,不会在子目录中使用npm脚本),您可以像安装任何dependency一样简单地安装每个子目录:

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

    在后一种情况下,不要惊讶于你没有发现 node_modules package-lock.json 文件-所有包都将安装在根目录中 节点模块(_M) ,这就是为什么无法从任何子目录运行npm命令(需要依赖项)的原因。

    如果你不确定,用例1总是有效的。

        4
  •  37
  •   Ronin    4 年前

    如果要运行一个命令在嵌套子文件夹中安装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.js 项目结构(包括嵌套组件和 包.json 文件)。

    正如所建议的,您也可以使用bash脚本实现相同的功能。

    编辑:使代码在Windows中运行

        5
  •  28
  •   Jelmer Jellema    6 年前

    仅供参考,以防人们遇到这个问题。您现在可以:

    • 添加程序包。json到子文件夹
    • 将此子文件夹作为参考链接安装到主package.json中:

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

        6
  •  25
  •   catamphetamine    7 年前

    我的解决方案非常相似。 纯节点.js

    以下脚本检查所有子文件夹(递归),只要它们具有 package.json 并运行 npm install 在他们每个人身上。 可以添加例外情况:文件夹不允许 包.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))
    }
    
        7
  •  23
  •   BJ Anderson    3 年前

    接受的答案有效,但您可以使用 --prefix 在选定位置运行npm命令。

    "postinstall": "npm --prefix ./nested_dir install"
    

    --前缀 适用于任何npm命令,而不仅仅是 install .

    您还可以使用

    npm prefix
    

    并将全局安装(-g)文件夹设置为

    npm config set prefix "folder_path"
    

    也许是TMI,但你明白。。。

        8
  •  13
  •   Moha the almighty camel    4 年前

    如果你有 find 实用程序,您可以尝试在应用程序根目录中运行以下命令:
    find . ! -path "*/node_modules/*" -name "package.json" -execdir npm install \;

    基本上,找到所有 package.json 文件并运行 npm install 在该目录中,跳过所有 node_modules 目录。

        9
  •  7
  •   Konstantin Lyakh    3 年前

    编辑 如所述 fgblomqvist公司 在评论中, 净现值 现在支持 workspaces


    有些答案相当陈旧。我想现在我们有一些新的设置选项 monorepos .

    1. 我建议使用 yarn workspaces :

    工作空间是一种设置包架构的新方法,默认情况下从Yarn 1.0开始即可使用。它允许您以只需运行的方式设置多个包 yarn install 一次即可在一次通过中安装所有这些组件。

    1. 如果你愿意或必须留在 净现值 ,我建议你看看 lerna :

    Lerna是一个工具,可以优化使用git和npm管理多包存储库的工作流程。

    莱纳也非常适合纱线工作区- article .我刚刚完成了monorepo项目的设置- example .

    下面是一个配置为使用npm+lerna的多包项目的示例- MDC Web :它们运行 lerna bootstrap 使用package.json postinstall .

        10
  •  3
  •   Ghostrydr    4 年前

    向添加Windows支持 snozza's 回答,以及跳过 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' })
    })
    
        11
  •  2
  •   Braden Rockwell Napier    5 年前

    受这里提供的脚本的启发,我构建了一个可配置的示例:

    • 可以设置为使用 yarn npm
    • 可以设置为基于锁定文件确定要使用的命令,以便如果将其设置为使用 纱线 但目录只有 package-lock.json 它将使用 净现值 对于该目录(默认为true)。
    • 配置日志记录
    • 使用并行运行安装 cp.spawn
    • 可以进行干跑,让你先看看它会做什么
    • 可以作为函数运行,也可以使用envvars自动运行
      • 当作为函数运行时,可选地提供要检查的目录数组
    • 返回一个在完成时解决的承诺
    • 允许根据需要设置最大深度
    • 如果发现文件夹 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 })
    
        12
  •  2
  •   Rostyslav Bornitskyi    4 年前
    find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '{}' && npm install" \;
    
        13
  •  1
  •   Long Nguyen    3 年前

    [对于macOS、Linux用户]:

    我创建了一个bash文件来安装项目和嵌套文件夹中的所有依赖项。

    find . -name node_modules -prune -o -name package.json -execdir npm install \;
    

    解释: 在根目录中,排除 node_modules 文件夹(甚至在嵌套文件夹中),找到包含 package.json 文件,然后运行 npm install 命令

    如果您只想查找指定的文件夹(例如:abc123,def456文件夹),请按如下方式运行:

    find ./abc123/* ./def456/* -name node_modules -prune -o -name package.json -execdir npm install \;
    
        14
  •  1
  •   whtlnv    3 年前

    要运行 npm install 在每个子目录中,您可以执行以下操作:

    "scripts": {
      ...
      "install:all": "for D in */; do npm install --cwd \"${D}\"; done"
    }
    

    哪里

    install:all 只是脚本的名称,您可以随意命名

    D 是当前迭代中目录的名称

    */ 指定要查找子目录的位置。 directory/*/ 将列出其中的所有目录 directory/ directory/*/*/ 将列出中两个级别的所有目录。

    npm install -cwd 在给定文件夹中安装所有依赖项

    您还可以运行几个命令,例如:

    for D in */; do echo \"Installing stuff on ${D}\" && npm install --cwd \"${D}\"; done

    将在每次迭代时打印“Installing stuff on your_subfolder/”。

    这适用于 yarn

        15
  •  0
  •   Ronald Roe    3 年前

    任何可以获取目录列表并运行shell命令的语言都可以为您做到这一点。

    我知道这并不是OP想要的答案,但这是一个永远有效的答案。您需要创建一个子目录名称数组,然后循环它们并运行 npm i ,或您需要运行的任何命令。

    作为参考,我尝试了 npm i **/ ,它刚刚从父目录中的所有子目录安装了模块。这是毫无意义的,但不用说,这不是你需要的解决方案。