为嵌套文件夹运行 npm install 的最佳方式?
Posted
技术标签:
【中文标题】为嵌套文件夹运行 npm install 的最佳方式?【英文标题】:The best way to run npm install for nested folders? 【发布时间】:2015-10-24 17:58:12 【问题描述】:在嵌套子文件夹中安装npm packages
最正确的方法是什么?
my-app
/my-sub-module
package.json
package.json
当npm install
在my-app
中运行时,自动安装/my-sub-module
中的packages
的最佳方法是什么?
【问题讨论】:
我认为最惯用的做法是在项目的末尾有一个 package.json 文件。 一个想法是使用运行 bash 文件的 npm 脚本。 不能通过修改本地路径的工作方式来完成吗?:***.com/questions/14381898/… 【参考方案1】:如果您知道嵌套子目录的名称,我更喜欢使用安装后。在package.json
:
"scripts":
"postinstall": "cd nested_dir && npm install",
...
【讨论】:
多个文件夹呢? "cd nested_dir && npm install && cd.. & cd nested_dir2 && npm install" ?? @Emre 是的 - 就是这样。 @Scott 你不能把下一个文件夹放在 package.json 里面,就像每个文件夹的"postinstall": "cd nested_dir2 && npm install"
一样?
@Aron 如果您想要名称父目录中的两个子目录怎么办?
@Emre 应该可以,子shell 可能会稍微干净一些:"(cd nested_dir && npm install); (cd nested_dir2 && npm install); ..."【参考方案2】:
根据@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)"
,
在 Windows 上,将括号之间的 ;
替换为 &&
。
// In monorepo root package.json
...
"scripts":
"postinstall": "(cd api && npm install) && (cd web && npm install) && (cd shared && npm install)"
,
【讨论】:
这些 npm 安装是一个接一个还是同时发生? 善用( )
来创建子shell 并避免cd api && npm install && cd ..
。
在顶层运行npm install
时出现此错误:"(cd was unexpected at this time."
在 Windows 上,将括号之间的 ;
替换为 &&
。
我的目录相对于它们的父目录嵌套在相同的深度,所以在我的情况下,我必须这样做:"(cd api && npm install); (cd ../web && npm install);...
包括 ../
以确保它改变目录到右边。正如@CameronHudson 所说,我认为括号会在相同的工作目录上下文中独立运行命令,但由于某种原因,它对我不起作用......【参考方案3】:
用例 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 的答案(在安装后使用子shell)。
用例 2:如果您将从根目录运行所有 npm 命令(例如,不会在子目录中使用 npm 脚本),您可以简单地安装每个子目录,例如你会有任何依赖:
npm install path/to/any/directory/with/a/package-json
在后一种情况下,您在子目录中找不到任何node_modules
或package-lock.json
文件不要感到惊讶——所有软件包都将安装在根目录node_modules
中,这就是为什么您将无法从您的任何子目录运行您的 npm 命令(需要依赖项)。
如果您不确定,用例 1 始终有效。
【讨论】:
最好让每个子模块都有自己的安装脚本,然后在安装后执行它们。run-p
不是必需的,但它更冗长 "postinstall": "npm run install:a && npm run install:b"
是的,您可以使用&&
而不使用run-p
。但正如你所说,这不太可读。另一个缺点(run-p 解决了因为安装并行运行)是如果一个失败,其他脚本不会受到影响
仅供参考,如果您不小心将cd
放入了不包含package.json
的错误路径,它将永远循环安装脚本。因此我使用"install:mymodule": "cd src/mymodule && test -f package.json && npm install"
@DonVaughn 这就是重点:test -f package.json
将使npm install
以error tmp@1.0.0 install:module: cd module && test -f package.json && npm install npm ERR! Exit status 1
退出。如果没有该检查,它将永远循环,而没有任何线索让您找出错误所在。自己试试吧。
@DonVaughn 我想您知道要纠正错误,您需要知道错误在哪里?或者至少有一个错误。这就是我调整的全部目的。没有它,'npm install' 不会向你抛出任何错误。这是我在这个帖子中的最后一条评论,抱歉。【参考方案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
项目结构(包括嵌套组件和 package.json
文件)。
按照建议,您也可以使用 bash 脚本实现相同的目的。
编辑:使代码在 Windows 中工作
【讨论】:
虽然复杂,但感谢文章链接。 虽然基于“组件”的结构是设置节点应用程序的一种非常方便的方法,但在应用程序的早期阶段分解单独的 package.json 文件等可能有点矫枉过正。这个想法倾向于当应用程序增长并且您合法地想要单独的模块/服务时,就会实现。但是,是的,如果没有必要,肯定太复杂了。 虽然可以使用 bash 脚本,但我更喜欢使用 nodejs 的方式来实现它在具有 DOS shell 的 Windows 和具有 Unix shell 的 Linux/Mac 之间的最大可移植性。【参考方案5】:仅供参考,以防人们遇到这个问题。您现在可以:
将 package.json 添加到子文件夹中 将此子文件夹作为参考链接安装在主 package.json 中:npm install --save path/to/my/subfolder
【讨论】:
请注意,依赖项安装在根文件夹中。我怀疑如果你甚至在考虑这种模式,你想要子目录中子目录 package.json 的依赖关系。 什么意思?子文件夹包的依赖项在子文件夹的 package.json 中。 (使用 npm v6.6.0 和节点 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。
啊,你的意思是模块。是的,b 的 node_modules 将安装在 a/node_modules 中。这是有道理的,因为您将需要/包含模块作为主代码的一部分,而不是作为“真正的”节点模块。所以“require('throug2')”会在 a/node_modules 中搜索 through2。
我正在尝试进行代码生成并想要一个完全准备好运行的子文件夹包,包括它自己的 node_modules。如果我找到解决方案,我会确保更新!【参考方案6】:
我的解决方案非常相似。 纯 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))
【讨论】:
你的脚本很好。但是,出于我个人的目的,我更喜欢删除第一个“if 条件”以获得深层嵌套的“npm install”!【参考方案7】:如果您的系统上有find
实用程序,您可以尝试在应用程序根目录中运行以下命令:find . ! -path "*/node_modules/*" -name "package.json" -execdir npm install \;
基本上,找到所有package.json
文件并在该目录中运行npm install
,跳过所有node_modules
目录。
【讨论】:
很好的答案。请注意,您还可以省略其他路径:find . ! -path "*/node_modules/*" ! -path "*/additional_path/*" -name "package.json" -execdir npm install \;
【参考方案8】:
接受的答案有效,但您可以使用 --prefix
在选定位置运行 npm 命令。
"postinstall": "npm --prefix ./nested_dir install"
--prefix
适用于任何 npm 命令,而不仅仅是 install
。
你也可以用
查看当前前缀npm prefix
并将您的全局安装 (-g) 文件夹设置为
npm config set prefix "folder_path"
也许是 TMI,但你明白了……
【讨论】:
【参考方案9】:EDIT 正如 fgblomqvist 在 cmets 中提到的,npm 现在也支持workspaces。
有些答案已经很老了。我认为现在我们有一些新选项可用于设置monorepos。
-
我建议使用yarn workspaces:
工作区是一种设置包架构的新方法,从 Yarn 1.0 开始默认可用。它允许您设置多个包,只需运行一次
yarn install
即可一次安装所有包。
-
如果您喜欢或必须留在 npm,我建议您查看lerna:
Lerna 是一种工具,可优化使用 git 和 npm 管理多包存储库的工作流程。
lerna 也适用于 yarn 工作区 - article。我刚刚建立了一个 monorepo 项目 - example。
这是一个配置为使用 npm + lerna - MDC Web 的多包项目的示例:它们使用 package.json 的 postinstall
运行 lerna bootstrap
。
【讨论】:
请不要使用 lerna,NPM 现在也原生支持“工作区”:docs.npmjs.com/cli/v7/using-npm/workspaces【参考方案10】:将 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】:受此处提供的脚本的启发,我构建了一个可配置的示例:
可以设置为使用yarn
或npm
可以设置为根据锁定文件确定要使用的命令,这样如果您将其设置为使用yarn
,但一个目录只有package-lock.json
,它将对该目录使用npm
(默认为true) .
配置日志记录
使用cp.spawn
并行运行安装
可以进行试运行,让您先看看它会做什么
可以作为函数运行或使用环境变量自动运行
作为函数运行时,可选择提供要检查的目录数组
返回一个完成后解决的承诺
如果需要,允许设置查看的最大深度
知道在找到带有yarn workspaces
的文件夹时停止递归(可配置)
允许使用逗号分隔的 env var 或通过向配置传递要匹配的字符串数组或接收文件名、文件路径和 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】:find . -maxdepth 1 -type d \( ! -name . \) -exec bash -c "cd '' && npm install" \;
【讨论】:
【参考方案13】:要在每个子目录上运行 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
【讨论】:
【参考方案14】:[对于 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 \;
【讨论】:
【参考方案15】:任何可以获取目录列表并运行 shell 命令的语言都可以为您执行此操作。
我知道这不是 OP 想要的确切答案,但它始终有效。您需要创建一个子目录名称数组,然后遍历它们并运行npm i
,或者您需要运行的任何命令。
作为参考,我尝试了npm i **/
,它只是安装了父目录中所有子目录中的模块。这非常不直观,但不用说这不是您需要的解决方案。
【讨论】:
以上是关于为嵌套文件夹运行 npm install 的最佳方式?的主要内容,如果未能解决你的问题,请参考以下文章
npm 错误! cb() 从未调用过!尝试运行 npm install 命令时出错
markdown 在包含package.json文件的每个文件夹中运行npm install
ENOENT:运行 npm install 命令时没有这样的文件或目录
在 src 文件夹上运行 npm install 后,它显示没有找到存储库 [重复]