İç içe klasörler için npm yüklemesini çalıştırmanın en iyi yolu?


129

npm packagesİç içe alt klasörlere yüklemenin en doğru yolu nedir ?

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

Gelmiş en iyi yolu nedir packagesiçinde /my-sub-modulene zaman otomatik olarak yüklenecek npm installçalıştırmak my-app?


Bence en aptalca şey, projenizin başında tek bir package.json dosyasına sahip olmak.
Robert Moskal

Bir fikir, bash dosyasını çalıştıran bir npm betiği kullanmak olacaktır.
Davin Tryon

Bu, yerel yolların nasıl çalıştığına dair bir değişiklik ile yapılamaz
mıydı

Yanıtlar:


26

İç içe geçmiş alt klasörlere npm paketlerini yüklemek için tek bir komut çalıştırmak istiyorsanız , kök dizininizde npmve ana aracılığıyla bir komut dosyası çalıştırabilirsiniz package.json. Komut dosyası her alt dizini ziyaret edecek ve çalışacaktır npm install.

Aşağıda .jsistenen sonucu elde edecek bir komut dosyası verilmiştir:

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' })
})

Bunun, özellikle modüler bir proje yapısını (iç içe yerleştirilmiş bileşenler ve dosyalar dahil ) ele alan bir StrongLoop makalesinden alınan bir örnek olduğunu unutmayın .node.jspackage.json

Önerildiği gibi, aynı şeyi bir bash betiği ile de elde edebilirsiniz.

DÜZENLEME: Kodun Windows'ta çalışmasını sağladı


1
Doğru olsa da, makale bağlantısı için teşekkürler.
WHITECOLOR

'Bileşen' tabanlı yapı, bir düğüm uygulaması kurmanın oldukça kullanışlı bir yolu olsa da, uygulamanın ilk aşamalarında ayrı package.json dosyalarını vb. Çıkarmak muhtemelen abartılı olabilir. yasal olarak ayrı modüller / hizmetler istiyorsunuz. Ama evet, gerekli değilse kesinlikle çok karmaşık.
snozza

3
Evet olsa da, bir bash betiği işe yarar, ancak bir DOS kabuğuna sahip Windows ile Unix kabuğuna sahip Linux / Mac arasında maksimum taşınabilirlik için nodejs yöntemini tercih ediyorum.
truthadjustr

270

İç içe geçmiş alt dizinin adlarını biliyorsanız, yükleme sonrası kullanmayı tercih ederim. İçinde package.json:

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

10
peki ya birden çok klasör? "cd nested_dir && npm install && cd .. & cd nested_dir2 && npm install" ??
Emre

1
@Emre evet - işte bu.
Guy

2
@Scott, bir sonraki klasörü "postinstall": "cd nested_dir2 && npm install"her klasör için olduğu gibi package.json içerisine koyamaz mısınız?
Aron

1
@Aron Ya isim ana dizini içinde iki alt dizin istiyorsanız?
Alec

29
@Emre Bu işe yaramalı, alt kabuklar biraz daha temiz olabilir: "(cd nested_dir && npm install); (cd nested_dir2 && npm install); ..."
Alec

49

@ Scott'ın cevabına göre, install | postinstall komut dosyası, alt dizin adları bilindiği sürece en basit yoldur. Birden çok alt dizin için bu şekilde çalıştırıyorum. Örneğin, elimizdeki taklit api/, web/ve shared/bir monorepo kök alt projeleri:

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

1
Mükemmel çözüm. Paylaştığınız için teşekkürler :-)
Rahul Soni

1
Cevap için teşekkürler. Benim için çalışıyor.
AMIC Ming

5
( )Alt kabuklar oluşturmak ve kaçınmak için iyi bir kullanım cd api && npm install && cd ...
Cameron Hudson

4
Seçilen cevap bu olmalı!
tmos

3
npm installEn üst seviyede koşarken şu hatayı alıyorum :"(cd was unexpected at this time."
Bay Polywhirl

22

Benim çözümüm çok benzer. Saf Node.js

Aşağıdaki komut dosyası , her birine sahip oldukları package.jsonve npm installher birinde çalıştıkları sürece tüm alt klasörleri (özyinelemeli olarak) inceler . Buna istisnalar eklenebilir: klasörlere sahip olmamasına izin verilir package.json. Aşağıdaki örnekte böyle bir klasör "paketler" dir. Bir "ön yükleme" komut dosyası olarak çalıştırılabilir.

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
senaryonuz güzel. Bununla birlikte, kişisel amaçlarım için, derinlemesine iç içe geçmiş bir 'npm yüklemesi' elde etmek için ilk 'eğer koşulunu' kaldırmayı tercih ederim!
Guilherme Caraciolo

21

İnsanların bu soruyla karşılaşma ihtimaline karşı referans için. Şimdi yapabilirsin:

  • Bir alt klasöre package.json ekleyin
  • Bu alt klasörü main package.json içinde referans bağlantısı olarak kurun:

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


2
Bağımlılıkların kök klasöre yüklendiğini unutmayın. Bu kalıbı düşünüyorsanız bile, alt dizindeki package.json alt dizininin bağımlılıklarını istediğinizden şüpheleniyorum.
Cody Allan Taylor

Ne demek istiyorsun? Alt klasör paketi için bağımlılıklar alt klasördeki package.json içindedir.
Jelmer Jellema

(npm v6.6.0 & node v8.15.0 kullanarak) - Kendiniz için bir örnek oluşturun. mkdir -p a/b ; cd a ; npm init ; cd b ; npm init ; npm install --save through2 ;Şimdi bekleyin ... bağımlılıkları "b" ye elle yüklediniz, yeni bir projeyi klonladığınızda olan şey bu değil. rm -rf node_modules ; cd .. ; npm install --save ./b. Şimdi node_modules'i listeleyin, ardından b'yi listeleyin.
Cody Allan Taylor

1
Ah, modülleri kastediyorsun. Evet, b için node_modules, / node_modules'e kurulacak. Bu mantıklı, çünkü modülleri "gerçek" bir düğüm modülü olarak değil, ana kodun bir parçası olarak gerektirecek / dahil edeceksiniz. Dolayısıyla, bir "gerekli ('throug2')" bir / düğüm_modülleri içinde2 üzerinden arama yapacaktır.
Jelmer Jellema

Kod üretimi yapmaya çalışıyorum ve kendi node_modules dahil olmak üzere tamamen çalışmaya hazır bir alt klasör paketi istiyorum. Çözümü bulursam, güncelleme yapacağımdan emin olacağım!
ohsully

20

Kullanım Durumu 1 : Her bir alt dizinden (her package.json'un bulunduğu) npm komutlarını çalıştırabilmek istiyorsanız, kullanmanız gerekecektir postinstall.

npm-run-allYine de sık sık kullandığım gibi , onu güzel ve kısa tutmak için kullanıyorum (kurulum sonrası kısım):

{
    "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:*"
}

Bu, hepsini bir kerede veya tek tek kurabileceğim ek bir avantaja sahiptir. Buna ihtiyacınız yoksa veya npm-run-allbağımlılık istemiyorsanız , demisx'in cevabına bakın (kurulum sonrası alt kabukları kullanarak).

Kullanım Durumu 2 : Kök dizinden tüm npm komutlarını çalıştıracaksanız (ve örneğin, alt dizinlerde npm komut dosyalarını kullanmayacaksanız), her bir alt dizini herhangi bir bağımlılık gibi kurabilirsiniz:

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

İkinci durumda, alt dizinlerde herhangi bir dosya node_modulesveya package-lock.jsondosya node_modulesbulmamanıza şaşırmayın - tüm paketler kökte kurulacak , bu nedenle npm komutlarınızı çalıştıramayacaksınız ( alt dizinlerinizden herhangi birinden bağımlılıklar gerektirir).

Emin değilseniz, 1. durumu kullanın her zaman işe yarar.


Her alt modülün kendi kurulum betiğine sahip olması ve sonra hepsini kurulum sonrası çalıştırması güzel. run-pgerekli değil, ama sonra daha ayrıntılı"postinstall": "npm run install:a && npm run install:b"
Qwerty

Evet, &&onsuz kullanabilirsiniz run-p. Ama dediğiniz gibi, bu daha az okunabilir. Diğer bir dezavantaj (bu run-p, yüklemeler paralel çalıştığı için çözülür) biri başarısız olursa, başka hiçbir komut dosyasının etkilenmemesidir
Don Vaughn

3

Snozza'nın cevabına Windows desteği ekleniyor ve node_modulesvarsa klasör atlanıyor .

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' })
})

Elbette yapabilirsin. Çözümümü node_modules klasörünü atlayacak şekilde güncelledim.
Ghostrydr

2

Burada sağlanan komut dosyalarından esinlenerek, yapılandırılabilir bir örnek oluşturdum:

  • kullanmak için ayarlanabilir yarnveyanpm
  • Kilit dosyalarına göre kullanılacak komutu belirleyecek şekilde ayarlanabilir, böylece onu kullanmak üzere ayarlarsanız, yarnancak bir dizinde yalnızca o dizin için package-lock.jsonkullanacağı bir npmdizin bulunur (varsayılan olarak true).
  • günlük kaydını yapılandır
  • kurulumları paralel olarak çalıştırır cp.spawn
  • ilk önce ne yapacağını görmenize izin vermek için kuru çalışmalar yapabilir
  • env değişkenler kullanılarak bir işlev veya otomatik çalıştırma olarak çalıştırılabilir
    • bir işlev olarak çalıştırıldığında, isteğe bağlı olarak kontrol edilecek dizinler dizisi sağlayın
  • tamamlandığında çözülen bir söz verir
  • gerekirse bakmak için maksimum derinliğin ayarlanmasına izin verir
  • ile bir klasör bulursa yinelemeyi durduracağını bilir yarn workspaces(yapılandırılabilir)
  • virgülle ayrılmış env değişkeni kullanarak dizinlerin atlanmasına veya yapılandırmanın eşleşecek dizelerden oluşan bir dizinin veya dosya adını, dosya yolunu ve fs.Dirent nesnesini alan ve bir boole sonucu bekleyen bir işlevi ileterek atlanmasına izin verir.
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;

Ve kullanılıyor:

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

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

1

findSisteminizde yardımcı program varsa , uygulama kök dizininizde aşağıdaki komutu çalıştırmayı deneyebilirsiniz:
find . ! -path "*/node_modules/*" -name "package.json" -execdir npm install \;

Temel olarak, tüm package.jsondosyaları bulun ve npm installtüm node_modulesdizinleri atlayarak o dizinde çalıştırın .


1
Mükemmel cevap. Aşağıdaki ek yolları da atlayabileceğinizi unutmayın:find . ! -path "*/node_modules/*" ! -path "*/additional_path/*" -name "package.json" -execdir npm install \;
Evan Moran
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.