Разработка электроники,

Систем автоматики,

Программного обеспечения

ООО "Антех ПСБ",
Санкт-Петербург

+79811865082

anteh@bk.ru

ООО Антех ПСБ примеры

Заготовка под Sass/SCSS ленивчик. При сохранении .scss файла происходит авто компиляция + авто выгрузка на удаленный web сервер

20.09.2021 Сайт https://anteh.ru

Нужно, чтобы после нажатия на сохранить в любом windows редакторе Sass/SCSS происходила автоматическая компиляция в локальный windows CSS файл, с последующей автоматической передачей CSS файла/файлов на удаленный *nix сервер. Компиляция .sass/.scss чрез dart sass, выгрузка на сервер через ssh.net. Листинг кода консольного приложения в конце статьи C# VS2019 .NET Framewort 4.0. Sass и SCSS могут использоваться как синонимы, это не верно, отличия по ссылке https://tpverstak.ru/sass-scss/ используйте синтаксис SCSS. Предлагаемое решение некая заготовка, из серии с чегобы начать. Описываемая в статье задача слегка упрощённая, в реальности нужно менять некоторые цифры в SCSS файле и видеть изменения сразу на нескольких экранах разных устройств с разным разрешением, ориентацией и пр., + иметь возможность использования своего прекомпилятора для SCSS файла и сопутствующие авто манипуляции с данными и файлами -но это не рассматривается. Вместо SCSS файлов или совместно с SCSS файлами можете компилировать и размещать что угодно и где угодно после самостоятельной доработки VS2019 консольного приложения.

Повторюсь, в статье речь о способе реализации "SCSS ленивчика", только авто компиляция + авто передача на сервер. Задача реализуется из того что есть в свободном/бесплатном доступе, или точнее того что удалось найти и выбрать в качесте оптимального решения. Если Вы работаете под Win, то для реализации достаточно .bat файлов + вспомогательные программы, если под *nix там много портов и скриптовых языков под любые задачи, но время на выбрать и настроить потребуется.

Всю предлагаемую информацию используете как есть, на своё усмотрение, никто за последствия использования предлагаемых решений ответственности не несет.

"SCSS ленивчик" пишется на .NET Framework 4.0 т.к. Renci Ssh.Net работает под .NET Framework 4.0, с .Net Core несовместим. Renci Ssh.Net под MIT лицензией. Библиотека Renci.SshNet.dll .NET Framework 4.0, для реализации загрузки файлов на удаленный сервер, бралась при компиляции в Release проекта VS2019 из https://github.com/sshnet/SSH.NET. Renci Ssh.Net поддерживает много фреймворков, на гитхабе описание есть, решение тех или иных проблем по использованию в сети есть.

Компиляция SCSS в CSS производится через Dart Sass Command-Line Interface по ссылке https://sass-lang.com/documentation/cli/dart-sass исчерпывающей документации достаточно. В dartsass: CSS файлы можно минифицировать, добавить map данные, обрабатывать сразу все SCSS файлы в директории, есть директива для автоматической компиляции SCSS в CSS после сохранения SCSS файла и пр. Всё работает, также проверен вариант Sass/SCSS компилятора на javascript. Лицензия MIT. Dart -язык программирования разработанный гуглом для замены или альтернативы javascript. Что такое Dart здесь https://surf.ru/pochemu-flutter-ispolzuet-dart-a-ne-kotlin-ili-javascript наиболее сжато и понятно.

"Ленивчик" разрабатывается в VS2019 под win консоль, также есть удалённый *nix веб сервер, на который upload.ятся CSS файлы. Соответственно у Вас должен быть SSH клиент, например PuTTY -free SSH and Telnet client и соответствующим образом настроенная SSH служба на серверной стороне. В частности для SSH на серверной стороне нужно настраивать способы аутентификации. Также нужно обратить внимание на антивирусные программы, по началу могут не дружить/запрещать сетевой доступ к серверу.

Первым делом аутентификация. Выясняем какие способы SSH аутентификации настроены на серверной стороне и обязательно проверяем какие из них работают на клиентской стороне посредством любого SSH клиента, например тот же PuTTY. При необходимости на серверной стороне производим дополнительную конфигурацию SSH. Какой способ аутентификации лучше/безопаснее в конкретном случае решайте сами. Повторимся, первым делом получите доступ к веб серверу любым существующим SSH клиентом, чтобы убедится в работоспособности выбранного Вами способа аутентификации.

Renci Ssh.Net поддерживает следующие виды аутентификации: NoneAuthenticationMethod -не использовался в проекте, KeyboardInteractiveAuthenticationMethod -ввод имени пользователя и пароля с клавиатуры, точнее автоматизированный ввод, PasswordAuthenticationMethod -по имени пользователя и паролю, PrivateKeyAuthenticationMethod -по закрытому ключу, точнее по файлу содержащему закрытый ключ в соответствующем формате. Соответственно на серверной стороне SSH должен быть настроен на тот или иной способ аутентификации, или на все сразу. Различные серверные SSH поддерживают гораздо большее количество способов аутоинтификации. Способы аутентификации задаются в массиве AuthenticationMethod[] am = new AuthenticationMethod[] { kauth, pauth, pkauth }; в фигурных скобках в приоритетном порядке указаны предварительно инициализированные способы аутентификации, первым ставим наиболее актуальный, желательно/удобней использовать один способ аутентификации. Пример кода ssh соединения с сервером:

//.net framework 4.0
//подключаем Renci.SshNet.dll из релиз компиляции решения https://github.com/sshnet/SSH.NET 
using Renci.SshNet;
...
private static ConnectionInfo connectionInfo;
private static void HandleKeyEvent(Object sender, Renci.SshNet.Common.AuthenticationPromptEventArgs e)
{
    foreach (Renci.SshNet.Common.AuthenticationPrompt prompt in e.Prompts)
    {
        if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)
        {
            prompt.Response = "пароль";
        }
    }
}
...
    KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod("имяПользователя");
    kauth.AuthenticationPrompt += new EventHandler(HandleKeyEvent);
    PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod("имяПользователя", "пароль");
    //SSH.NET не поддерживает ключевые файлы.ppk Вы должны использовать PuTTYgen для преобразования ключа.ppk в формат OpenSSH
    var pk = new PrivateKeyFile(fullpathtokeyfile);
    PrivateKeyAuthenticationMethod pkauth = new PrivateKeyAuthenticationMethod("имяПользователя", pk);
    AuthenticationMethod[] am = new AuthenticationMethod[] { kauth, pauth, pkauth }; //удобно оставить только один способ аутентификации или первым ставить актуальный
    connectionInfo = new ConnectionInfo("IP.IP.IP.IP", 22, "имяПользователя", am); //22 номер порта
...
using (var sftp = new SftpClient(connectionInfo))
{
    sftp.KeepAliveInterval = TimeSpan.FromSeconds(60);
    try
    {
        sftp.Connect();
        ...
        ...
    }
    catch (Exception ex)
    {
        sftp.Disconnect();        
        Console.WriteLine($"{ex.Message} {ex.InnerException}");
    }
    finally
    {
        sftp.Disconnect();
    }
}

Настройку ssh, openssh и пр. на серверной стороне описывать не будем. Для каждого сайта/клиента обычно создаётся свой пользователь. Если нужно работать сразу с несколькими сайтами/пользователями, соответственно дорабатываем решение.

С запуском компиляции .sсss фалйлов через консоль по коду всё обычно, согласно документации. Параметры к консольной программе или удобства для сторонних пользователей добавляем самостоятельно.

По работе программы. Указываем в SCSSFolder абсолютный полный путь к директории содержащей в том числе .scss файлы, вложенные директории не обрабатываются. После сохранения .scss файла в любом редакторе FileSystemWatcher реагирует на изменения и запускает компиляцию изменившегося .scss файла. Cкомпилированный в .css файл помещаются в CSSFolder, по дороге выполняется минификация, мап данные отключены -или можете скорректировать под себя. Далее FileSystemWatcher следящий за CSSFolder реагирует на изменения и загружает .css на сервер. При необходимости .css файлы можно объединить в один или несколько и по аналогии реализовать автоматические любые действия с любыми файлами, работать сразу с несколькими сайтами и всё что угодно

Код программной заготовки описываемой задачи:

using System;

 

using System.Diagnostics;

using System.IO;

using Renci.SshNet;

 

namespace services2

{

    class Program

    {

        static void ExecuteCommand(string command)

        {

            var processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);

           

            processInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;

            processInfo.CreateNoWindow = true;

            processInfo.UseShellExecute = false;

            processInfo.RedirectStandardError = true;

            processInfo.RedirectStandardOutput = true;

 

            var process = Process.Start(processInfo);

 

            process.OutputDataReceived += (object sender, DataReceivedEventArgs e) =>

            { if (e.Data != null) Console.WriteLine(/*"output>>" + */e.Data); };

            process.BeginOutputReadLine();

 

            process.ErrorDataReceived += (object sender, DataReceivedEventArgs e) =>

            { if (e.Data != null) Console.WriteLine("error>>" + e.Data); };

            process.BeginErrorReadLine();

 

            process.WaitForExit();

            Console.WriteLine("ExitCode: {0}", process.ExitCode);

            process.Close();

        }

 

        //ServerCSSFolder -путь относительно конрневой директории пользователя на сервере

        static string ServerCSSFolder = @"/somesite.ru/CSS/"; //unix стиль сепаратора Path.DirectorySeparatorChar. Директория на удалённом сервере куда копируем CSS

        static string SCSSFolder = @"C:\somedir\somesite.ru\scss"; //windows стиль сепаратора Path.DirectorySeparatorChar. Локальная windows директория содержащая обрабатываемые .scss файлы

        static string CSSFolder = @"C:\somedir\somesite.ru\CSS"; //windows стиль сепаратора. Локальная windows директория содержащая .css после компиляции .scss

        //компиляция всех файлов scss из папки C:\somedir\somesite.ru\scss в папку C:\somedir\somesite.ru\CSS

        //--no-source-map       не генерировать map файл

        //--stop-on-error       остановка обработки всего, после встречи первой ошибки в каком либо scss файле

        //--trace               отображение всех возможных ошибок для отладки

        //--style=compressed    минификация CSS       

        static string srcssass = "--no-source-map --stop-on-error --trace --style=compressed " + SCSSFolder + ":" + CSSFolder;

 

        private static ConnectionInfo connectionInfo;

        private const string ServPassw = "somepass"; //Пароль пользователя удалённого сервера на котором "висит" сайт

        private const string ServUser = "someUserName"; //Имя пользователя удалённого сервера на котором "висит" сайт

        private const string ServIP = "so.me.II.PP"; //Статический IP удалённого сервера или IP локального сервера. Порт указывается в ConnectionInfo

        private static void HandleKeyEvent(Object sender, Renci.SshNet.Common.AuthenticationPromptEventArgs e)

        {

            foreach (Renci.SshNet.Common.AuthenticationPrompt prompt in e.Prompts)

            {

                if (prompt.Request.IndexOf("Password:", StringComparison.InvariantCultureIgnoreCase) != -1)

                {

                    prompt.Response = ServPassw;

                }

            }

        }

 

        static void Main(string[] args)

        {

            #region ServerAutentificationConfig

            //Конфигурация подключения к серверу, аутентификация через имя пользователя и пароль, остальные способы аутентификации приведены в качестве примера и закомментированы

            //KeyboardInteractiveAuthenticationMethod kauth = new KeyboardInteractiveAuthenticationMethod(ServUser);

            //kauth.AuthenticationPrompt += new EventHandler<Renci.SshNet.Common.AuthenticationPromptEventArgs>(HandleKeyEvent);

            PasswordAuthenticationMethod pauth = new PasswordAuthenticationMethod(ServUser, ServPassw);

            //SSH.NET не поддерживает ключевые файлы.ppk.Вы должны использовать PuTTYgen для преобразования ключа.ppk в формат OpenSSH

            //string filenn = AppDomain.CurrentDomain.BaseDirectory + @"prkey";

            //var pk = new PrivateKeyFile(filenn);

            //PrivateKeyAuthenticationMethod pkauth = new PrivateKeyAuthenticationMethod(ServUser, pk);

            //AuthenticationMethod[] am = new AuthenticationMethod[] { kauth, pauth, pkauth };

            //AuthenticationMethod[] am = new AuthenticationMethod[] { pauth, pkauth };

            AuthenticationMethod[] am = new AuthenticationMethod[] { pauth };

            connectionInfo = new ConnectionInfo(ServIP, 46875, ServUser, am);

            #endregion

 

            #region WatcherConfig

            var watcher = new FileSystemWatcher(SCSSFolder); //отслеживание изменений в папке SCSSFolder

            watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.LastAccess | NotifyFilters.CreationTime;

            watcher.Filter = "*.scss"; //watcher.IncludeSubdirectories = true;

            watcher.EnableRaisingEvents = true;

            watcher.Changed += OnChanged;

 

            var CSSwatcher = new FileSystemWatcher(CSSFolder); //отслеживание изменений в папке CSSFolder для передачи на сервер изменившихся файлов

            CSSwatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.LastAccess | NotifyFilters.CreationTime;

            CSSwatcher.Filter = "*.css"; //watcher.IncludeSubdirectories = true;

            CSSwatcher.EnableRaisingEvents = true;

            CSSwatcher.Changed += OnChangedCSSwatcher;

            #endregion

 

            //уходим на ожидание изменений в SCSSFolder и CSSFolder

            Console.WriteLine("Watch folders: " + SCSSFolder);

            Console.ReadLine(); //выход из ожидания по нажатию любой кнопки на активном консольном окне

        }

 

 

        //запуск обработки всех команд из srcssass при изменении какого-либо из *.scss файла

        private static void OnChangedCSSwatcher(object sender, FileSystemEventArgs e)

        {

            FileSystemWatcher sw = (FileSystemWatcher)sender;

            if (e.ChangeType != WatcherChangeTypes.Changed) { return; }

            try

            {

                sw.EnableRaisingEvents = false; //Защита от срабатывания несколько раз

 

                //Выгрузка на сервер

                using (var sftp = new SftpClient(connectionInfo))

                {

                    sftp.KeepAliveInterval = TimeSpan.FromSeconds(60);

                    try

                    {

                        sftp.Connect();

                        sftp.ChangeDirectory(@"/"); //некий обязательный финт

                        //var files = sftp.ListDirectory(@"/somesite.ru/"); //чтение списка всего содержимого корня сайта папки файлы, в том числе . и .. Вложенные элементы не считываются

 

                        var localFile = Path.Combine(e.FullPath); //изменившийся файл .css в CSSFolder                                             

                        var remoteFilePath = Path.Combine(ServerCSSFolder + e.Name); //Полный путь к файлу на удаленном сервере                       

 

                        using (var fs = File.OpenRead(localFile))

                        {

                            sftp.UploadFile(fs, remoteFilePath, true);

                        }

                    }

                    catch (Exception ex)

                    {

                        sftp.Disconnect();

                        Console.WriteLine($"{ex.Message} {ex.InnerException}");

                    }

                    finally

                    {

                        sftp.Disconnect();

                    }

                }

            }

            finally

            {

                sw.EnableRaisingEvents = true; //Защита от срабатывания несколько раз

            }

        }

 

        //запуск очередной обработки всех команд из srcssass при изменении какого-либо из *.scss файла

        private static void OnChanged(object sender, FileSystemEventArgs e)

        {

            if (e.ChangeType != WatcherChangeTypes.Changed) { return; }

            try

            {

                ((FileSystemWatcher)sender).EnableRaisingEvents = false; //Защита от срабатывания несколько раз

 

                //if (OSArchitecture == ProcessorArchitecture.X86) ExecuteCommand("dart32x.exe sass32x.snapshot " + srcssass); //Обработка всех команд из srcssass

                //if ((OSArchitecture == ProcessorArchitecture.IA64) || (OSArchitecture == ProcessorArchitecture.Amd64)) ExecuteCommand("dart64x.exe sass64x.snapshot " + srcssass); //Обработка всех команд из srcssass

                ExecuteCommand("dart64x.exe sass64x.snapshot " + srcssass); //Обработка всех команд из srcssass

                Console.WriteLine($"Changed: {e.FullPath}");

            }

            finally

            {

                ((FileSystemWatcher)sender).EnableRaisingEvents = true; //Защита от срабатывания несколько раз

            }

        }

    }

}

 

 

Copyright ©Новиков Алексей Александрович,

2023 Санкт-Петербург, 197372, ООО "Антех ПСБ",

anteh собака bk.ru