简体   繁体   English

如何在Node中同步执行shell条命令?

[英]How to execute shell commands synchronously in Node?

I'm trying to execute some shell commands synchronously to install npm dependencies, build packages and create a database in docker.我正在尝试同步执行一些 shell 命令以安装 npm 依赖项、构建包并在 docker 中创建数据库。

    ['api', 'front-end'].forEach(async (dir) => {
        await new Promise((resolve, reject) => {
          console.log(`Installing npm dependencies for ${dir}`);
          exec('npm install', { cwd: path.join(initDir, 'pushkin', dir) }, (err) => {
            if (err) console.error(`Failed to install npm dependencies for ${dir}: ${err}`);
            if (dir !== 'api' && dir !== 'front-end') return;
          });
          resolve(`${dir} installed...`);
        })
          .then(() => {
            console.log(`Building ${dir}`);
            exec('npm run build', { cwd: path.join(process.cwd(), 'pushkin', dir) }, (err) => {
              if (err) console.error(`Failed to build ${dir}: ${err}`);
              console.log(`${dir} is built`);
            });
          })
          .then(() => {
            shell.exec(startDbCommand);
          })
          .then(() => {
            shell.exec(createDbCommand);
          })
          .then(() => {
            shell.exec(stopDbCommand);
          });
      });

The docker commands are: docker 命令是:

const startDbCommand = 'docker-compose -f pushkin/docker-compose.dev.yml up --no-start && docker-compose -f pushkin/docker-compose.dev.yml start test_db';
const createDbCommand = 'docker-compose -f pushkin/docker-compose.dev.yml exec -T test_db psql -U postgres -c "create database test_db"';
const stopDbCommand = 'docker-compose -f pushkin/docker-compose.dev.yml stop test_db';

When I ran it for the first time, I got this error:当我第一次运行它时,出现了这个错误:

No container found for test_db_1

Failed to build front-end: Error: Command failed: npm run build
sh: react-scripts: command not found

Failed to build api: Error: Command failed: npm run build
sh: babel: command not found

However, after I ran it again for the second time, everything seems to be fine.然而,当我再次运行第二次后,一切似乎都正常了。 Is this a problem about the Promise chain I wrote?这是我写的Promise链的问题吗? Thanks.谢谢。

Two important things are the running commands in order one after the other (I believe that's what you mean by synchronously?) and also bailing when there is a failure.两个重要的事情是一个接一个地按顺序运行命令(我相信这就是你所说的同步的意思?)以及在出现故障时退出。

The project directory loop also looks out of place.项目目录循环看起来也不合适。 At the moment it loops over everything, including the db setup commands.目前它遍历所有内容,包括 db setup 命令。 It looks like you are doing test setup, so I believe the "synchronous" order is:看起来你正在做测试设置,所以我相信“同步”顺序是:

  • npm install / build for api npm 为api npm install / build
  • npm install / build for frontend npm installfrontend安装/ build
  • database setup数据库设置

So first, create a promise out of nodes spawn so you can await it.因此,首先,从生成的节点中创建一个spawn ,这样您就可以await它了。

function runProcessToCompletion(cmd_array, options){
  return new Promise((resolve, reject) => {

    const result = {
      cmd: cmd_array,
      options,
      code: null,
      output: [],
    }

    const proc = spawn(cmd_array[0], cmd_array.slice(1), options)

    proc.on('error', (error) => {
      error.result = result
      reject(error)
    })

    proc.on('close', code => {
      result.code = code
      if (code !== 0) {
        const error = new Error(`PID "${proc.pid}" exited with code "${code}"`)
        error.result = result
        reject(error)
      }
      console.log(`Spawned PID "${proc.pid}" exited with code "${code}"`)
      resolve(result)
    })

    proc.stdout.on('data', (data) => {
      result.output.push(data.toString())
      process.stdout.write(data)
    })

    proc.stderr.on('data', (data) => {
      result.output.push(data.toString())
      process.stderr.write(data)
    })

    if (proc.pid) {
      console.log(`Spawned PID "${proc.pid}" for "${cmd_array.join(' ')}"`)
    }
  })
}

Then you can more easily structure your code as a simple list of commands.然后,您可以更轻松地将代码构造为简单的命令列表。 The benefit of using spawn is you can avoid all the shell-isms.使用spawn的好处是你可以避免所有的 shell-isms。 The downside is you miss out on all the shell-isms.缺点是你错过了所有的 shell-isms。

For example the paths to executables need to be fully defined without a shells PATH例如,需要在没有 shell PATH的情况下完全定义可执行文件的路径

const path = require('path')
const initDir = process.cwd()
const project_dirs = ['api', 'front-end']
const setupDbCommand = ['/usr/local/bin/docker-compose','-f','pushkin/docker-compose.dev.yml','up','--no-start']
const startDbCommand = ['/usr/local/bin/docker-compose','-f','pushkin/docker-compose.dev.yml','start','test_db']
const createDbCommand = ['/usr/local/bin/docker-compose','-f','pushkin/docker-compose.dev.yml','exec','-T','test_db','psql -U postgres -c "create database test_db"']
const stopDbCommand = ['/usr/local/bin/docker-compose','-f','pushkin/docker-compose.dev.yml','stop','test_db']

async function go(){
  for (let dir of project_dirs) {
    const cwd = path.join(initDir, 'pushkin', dir)
    await runProcessToCompletion(['/usr/local/bin/npm','install'], { cwd })
    await runProcessToCompletion(['/usr/local/bin/npm','run','build'], { cwd })
  }
  await runProcessToCompletion(setupDbCommand)
  await runProcessToCompletion(startDbCommand)
  await runProcessToCompletion(createDbCommand)
  await runProcessToCompletion(stopDbCommand)
  return true
}

go().catch(err => {
  console.error(err)
  console.error(err.results)
})

If it's too hard without the shell stuff, you can turn that back on with the spawn options如果没有 shell 东西太难了,你可以用spawn选项重新打开它

{ shell: true }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM