Может я плохо искал, а может ничего готового действительно нет. Но так или иначе, я пришел к тому что, нужно пилить собственный велосипед, для деплоя front-end приложения на сервер.

Если подкинете название подходящего для этих целей пакета, буду благодарен.

Так как front-end приложение это просто статичные файлы, то для его деплоя, ничего особенного предпринимать нет необходимости. Если разбить процесс деплоя на шаги, получается примерно следующее:

1. Сборка приложения
2. Архивирование сборки
3. Подготовка сервера, создание необходимых папок.
4. Загрузка архива на сервер
5. Распаковка архива на сервере
6. Простановка симлинка
7. Чистка файлов локально

Структура файлов будет следующая.

1
2
3
4
5
6
/root/directory/
/root/directory/releases
/root/directory/releases/201601102323
/root/directory/releases/201601102325
/root/directory/releases/...
/root/directory/current -> /root/directory/releases/201601102325

Реализация

Кому не терпится можно посмотреть gist на github’e.

Начнем с подключения необходимых для работы пакетов.

1
2
3
4
5
var gulp = require('gulp');
var path = require('path');
var GulpSSH = require('gulp-ssh');
var shell = require('gulp-shell');
var moment = require('moment');

Далее настроим подключение к серверу. Подключаемся используя ssh agent, это удобно, особенно когда деплоить могут несколько человек, достаточно дать им доступы к серверу по ssh ключам, нет необходимости хранить пароль в открытом виде.

1
2
3
4
5
6
var config = {
  host: '192.168.0.1',
  port: 22,
  username: 'username',
  agent: process.env.SSH_AUTH_SOCK
};

Заведем переменные, чтобы в будущем быстро переносить деплой файл между проектами.

1
2
3
4
5
6
7
var archiveName = 'deploy.tgz';
var timestamp = moment().format('YYYYMMDDHHmmssSSS');
var buildPath = './dist';
var rootPath = '/home/project/root/directory/';
var releasesPath = rootPath + 'releases/';
var symlinkPath = rootPath + 'current';
var releasePath = releasesPath + timestamp;

Инициализируем gulp-ssh.

1
2
3
4
var gulpSSH = new GulpSSH({
  ignoreErrors: false,
  sshConfig: config
});

Теперь создаем по порядку, все необходимые задачи. Сборка проекта у вас уже должна быть настроена.

Первая задача, архивирование собранного проекта. Задача выполняет после сборки, простую shell команду, для архивирования папки которую указали в переменной buildPath, в архив с именем archiveName.

1
gulp.task('deploy:compress', ['build'], shell.task("tar -czvf ./" + archiveName + " --directory=" + buildPath + " ."));

Следующая задача готовит папки на сервере. На самом деле, предыдущая задача и подготовка папок на сервере, будут выполняться параллельно.

1
2
3
gulp.task('deploy:prepare', function() {
  return gulpSSH.exec("cd " + releasesPath + " && mkdir " + timestamp);
});

Теперь можно загрузить архив сборки, в подготовленную папку. Задача будет выполнена только после полного выполнения предыдущих двух задач.

1
2
3
4
gulp.task('deploy:upload', ['deploy:prepare', 'deploy:compress'], function() {
  return gulp.src(archiveName)
    .pipe(gulpSSH.sftp('write', releasePath + '/' + archiveName))
});

После загрузки, можно приступить к распаковке архива.

1
2
3
gulp.task('deploy:uncompress', ['deploy:upload'], function() {
  return gulpSSH.exec("cd " + releasePath + " && tar -xzvf " + archiveName);
});

Симлинк, для верности будем ставить только после безошибочной распаковки. Удаляем старый, ставим новый.

1
2
3
4
gulp.task('deploy:symlink', ['deploy:uncompress'], function() {
  return gulpSSH.exec("rm " + symlinkPath + " &&" +
    " ln -s " + releasePath + " " + symlinkPath);
});

Ну и последние две задачи, удаление архива локально. И чистка папки со сборкой.

1
gulp.task('deploy:clean', ['deploy:symlink'], shell.task('rm ' + archiveName, {ignoreErrors: true}));
1
2
3
gulp.task('clean:build', ['deploy:symlink'], function () {
  return gulp.start('clean')
});

Ну и задача которая будет выполнять все вышеописанное вместе. Записать её можно было в более короткой форме, т.к. большая часть задач выполняется по цепочке. Но я считаю важно записать именно в таком виде, чтобы было очевидно и понятно, за что именно отвечает эта задача.

1
2
3
4
5
6
7
8
gulp.task('deploy', ['build',
                     'deploy:compress',
                     'deploy:prepare',
                     'deploy:upload',
                     'deploy:uncompress',
                     'deploy:symlink',
                     'deploy:clean',
                     'clean:build']);

Было бы не плохо реализовать

1. При любом не завершении деплоя, удалять архив.
2. Быстрый откат деплоя
3. Удаление старых релизов, оставляя только последние N штук