Meteor: 调用方法执行 shell(克隆 git 仓库)

6

问题:

在Meteor中,我如何从客户端调用一个方法(传递name),让服务器执行一些shell命令?

这个方法函数基本上是:创建一个目录,然后使用给定的name克隆git repo。

这是非常简单的东西,但Meteor就是不行。我已经困惑了几个小时。在普通的bash或node中都可以正常工作。当前状态:

目录被创建 -> 服务器重新启动 -> Meteor抛出错误,声称该目录已经存在 -> Meteor删除该目录 -> 服务器重新启动

代码:

var cmd, exec, fs;

if (Meteor.isClient) {
  Template.app.events({
    'click button': function() {
        Meteor.call('clone', "NAMEHERE", function(error, result) {
          if (error) {
            console.log(error);
          } else {
            console.log(result);
          }
        });
    }
  });
}

if (Meteor.isServer) {
  fs = Npm.require('fs');
  exec = Npm.require('child_process').exec;
  cmd = Meteor.wrapAsync(exec);
  Meteor.methods({
    'clone': function(name) {
      var dir;
      dir = process.env.PWD + "/projects/" + name;
      cmd("mkdir " + dir + "; git clone git@gitlab.com:username/" + name + ".git " + dir, Meteor.bindEnvironment(function(error, stdout, stderr) {
        if (error) {
          throw new Meteor.Error('error...');
        } else {
          console.log('done');
        }
      }));
      return 'cloning...';
    }
  });
}

更新 1

如果我事先手动创建文件夹,则以下代码将成功克隆存储库:

if (Meteor.isClient) {
  Template.all.events({
    'click button': function() {
      Meteor.call('clone', this.name);
    }
  });
}


if (Meteor.isServer) {
  exec = Npm.require('child_process').exec;
  cmd = Meteor.wrapAsync(exec);
  Meteor.methods({
    'clone': function(name) {
      var dir, res;
      dir = process.env.PWD + "/projects/" + name;
      res = cmd(git clone git@gitlab.com:username/" + name + ".git " + dir);
      return res;
    }
  });
}

然而,如果我在cmd中添加"mkdir "+dir,我仍然有同样的问题:目录被创建 -> 服务器重启 -> Meteor抛出一个错误,声称目录已经存在 -> Meteor删除目录 -> 服务器重启
解决办法:Meteor之所以重新启动是因为其目录中的某些内容发生了变化(projects)。该方法随后在启动时重新运行。这是一个与方法调用不相关的问题。update 1 / @Rebolon的代码是解决方案。

你在问题中嵌入了很多元数据(标题中的“(已解决)”、“解决方案”标题和段落等)。这些元数据已经被StackOverflow的格式处理过了——被采纳的解决方案会显示为绿色,你的问题也清楚地标记为已回答……在问题本身中添加更多数据而不是在评论中添加是没有用的。 - Kyll
@Kyll 我没有添加任何数据。我只是纠正了一些语法并移动了一个标题。 - offthegrass
2个回答

2
你的Meteor.call没问题,问题似乎在服务器端: 你正确使用了wrapAsync来封装exec npm对象,但是你误用了cmd变量:你只需要像这样发送一个参数:
var res = cmd("mkdir " + dir + "; git clone git@gitlab.com:username/" + name + ".git " + dir);
return res;

实际上,你的代码表明你想向客户端返回不同的信息:
  1. 我收到了调用,并且命令正在等待
  2. 命令已经完成,结果是成功或者错误
但实际上这样做行不通,因为使用 wrapAsync cmd(...) 会在返回 "Pending ..." 前等待命令完成。所以客户端只会收到 "Pending ..." 的响应。
你真的需要告诉客户端命令正在等待,然后完成吗?如果是,你可以使用状态集合来存储命令的状态(已接收/等待中/成功/错误...),然后在你的方法中只需更新集合,在客户端订阅此状态集合即可。
你可以查看这个项目,其中我在服务器上使用 exec(带有 retry 包)和一个集合来管理等待状态:https://github.com/MeteorLyon/satis-easy

谢谢。不,我根本不需要通知客户端,所以我删除了那些返回值。上面的代码重新启动服务器-> 抛出错误fatal: destination path... already exists -> 再次重启服务器。我从cmd中删除了mkdir,在调用之前手动创建了目录,这样它就可以正常工作了。如果我想使用fs.mkdir,那么res是否可以调用另一个函数? - offthegrass
明白了:Meteor正在重新启动,因为其目录中的某些内容发生了变化。将“projects”重命名为“.projects”,问题得到解决。感谢您的帮助。 - offthegrass
酷,我认为可以通过res进行多次调用。如果在文件夹的末尾(或开头,我记不清了)添加~,它不会重新启动应用程序。 - Rebolon

0

你的Meteor.call代码不在事件函数块中。像这样的代码应该可以工作:

if (Meteor.isClient) {
  Template.app.events({
    'click button': function() {
        Meteor.call('clone', "NAMEHERE", function(error, result) {
          if (error) {
            return console.log(error);
          } else {
            return console.log(result);
          }
        });
    }
  });
}

我实际上正在使用CoffeeScript,我只是快速地进行了js2.coffee转换,没有正确检查。不过还是谢谢。 - offthegrass
@offthegrass,问题还在吗? - Rebolon
@Rebolon 是的,我仍然有这个问题。 - offthegrass

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接