Ejemplo n.º 1
0
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);
            mergeFileLines.Add("src\\system\\assets\\less\\_custom.less", new List<string>());
            mergeFileLines.Add("src\\system\\assets\\less\\_modules.less", new List<string>());
            mergeFileLines.Add("src\\templates\\partials\\module-scripts-header.hbs", new List<string>());
            mergeFileLines.Add("src\\templates\\partials\\module-scripts-footer.hbs", new List<string>());

            StringBuilder publishedFiles = new StringBuilder();
            string cleanup = package.GetValue("cleanup") ?? String.Empty;

            // not using System.IO.Path.GetTempPath() because the paths in our zip are already quite long,
            // so we need a very short temp path for the extract of our zipfile to succeed
            // using drive from tridion cm homedir for temp folder
            tempFolder = ConfigurationSettings.GetTcmHomeDirectory().Substring(0, 3) + "t" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "\\";

            try
            {
                // read values from Component
                var config = GetComponent();
                var fields = new ItemFields(config.Content, config.Schema);
                var favicon = fields.GetMultimediaLink("favicon");
                var version = fields.GetTextValue("version");
                var nodeJs = fields.GetTextValue("nodeJs");

                // set defaults if required
                if (String.IsNullOrEmpty(nodeJs))
                {
                    nodeJs = NodejsDefault;
                }

                PublishJson(String.Format("{{\"version\":{0}}}", JsonEncode(version)), config, GetPublication().RootStructureGroup, "version", "version");

                // create temp folder
                Directory.CreateDirectory(tempFolder);
                Log.Debug("Created " + tempFolder);

                ProcessModules();

                // build html design
                ProcessStartInfo info = new ProcessStartInfo
                    {
                        FileName = "cmd.exe",
                        Arguments = String.Format("/c \"{0}\" start --color=false", nodeJs),
                        WorkingDirectory = tempFolder,
                        CreateNoWindow = true,
                        ErrorDialog = false,
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        StandardErrorEncoding = Encoding.UTF8,
                        StandardOutputEncoding = Encoding.UTF8
                    };
                using (Process cmd = new Process {StartInfo = info})
                {
                    cmd.Start();
                    using (StreamReader reader = cmd.StandardOutput)
                    {
                        string output = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(output))
                        {
                            Log.Info(output);

                            // TODO: check for errors in standard output and throw exception
                        }
                    }
                    using (StreamReader reader = cmd.StandardError)
                    {
                        string error = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(error))
                        {
                            string user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                            Exception ex = new Exception(error);
                            ex.Data.Add("Filename", info.FileName);
                            ex.Data.Add("Arguments", info.Arguments);
                            ex.Data.Add("User", user);

                            if (error.ToLower().Contains("the system cannot find the path specified"))
                            {
                                throw new Exception(String.Format("Node.js not installed or missing from path for user {0}.", user), ex);
                            }
                            else if (error.ToLower().Contains("mkdir") && error.ToLower().Contains("appdata\\roaming\\npm"))
                            {
                                throw new Exception(String.Format("Node.js cannot access %APPDATA% for user {0}.", user), ex);
                            }

                            throw ex;
                        }
                    }
                    cmd.WaitForExit();
                }

                // publish all binaries from dist folder
                string dist = tempFolder + "dist\\";
                if (Directory.Exists(dist))
                {
                    // save favicon to disk (if available)
                    if (favicon != null)
                    {
                        File.WriteAllBytes(dist + "favicon.ico", favicon.BinaryContent.GetByteArray());
                        Log.Debug("Saved " + dist + "favicon.ico");
                    }

                    string[] files = Directory.GetFiles(dist, "*.*", SearchOption.AllDirectories);
                    foreach (var file in files)
                    {
                        string filename = file.Substring(file.LastIndexOf('\\') + 1);
                        string extension = filename.Substring(filename.LastIndexOf('.') + 1);
                        Log.Debug("Found " + file);

                        // determine correct structure group (create if not exists)
                        Publication pub = (Publication)config.ContextRepository;
                        string relativeFolderPath = file.Substring(dist.Length - 1, file.LastIndexOf('\\') + 1 - dist.Length);
                        relativeFolderPath = relativeFolderPath.Replace("system", SystemSgName).Replace('\\', '/');
                        string pubSgWebDavUrl = pub.RootStructureGroup.WebDavUrl;
                        string publishSgWebDavUrl = pubSgWebDavUrl + relativeFolderPath;
                        StructureGroup sg = engine.GetObject(publishSgWebDavUrl) as StructureGroup;
                        if (sg == null)
                        {
                            throw new Exception("Missing Structure Group " + publishSgWebDavUrl);
                        }

                        // add binary to package and publish
                        using (FileStream fs = File.OpenRead(file))
                        {
                            Item binaryItem = Package.CreateStreamItem(GetContentType(extension), fs);
                            var binary = engine.PublishingContext.RenderedItem.AddBinary(binaryItem.GetAsStream(), filename, sg, "dist-" + filename, config, GetMimeType(extension));
                            binaryItem.Properties[Item.ItemPropertyPublishedPath] = binary.Url;
                            package.PushItem(filename, binaryItem);
                            if (publishedFiles.Length > 0)
                            {
                                publishedFiles.Append(",");
                            }
                            publishedFiles.AppendFormat("\"{0}\"", binary.Url);
                            Log.Info("Published " + binary.Url);
                        }
                    }
                }
                else
                {
                    throw new Exception("Grunt build failed, dist folder is missing.");
                }
            }
            finally
            {
                if (String.IsNullOrEmpty(cleanup) || !cleanup.ToLower().Equals("false"))
                {
                    // cleanup workfolder
                    Directory.Delete(tempFolder, true);
                    Log.Debug("Removed " + tempFolder);
                }
                else
                {
                    Log.Debug("Did not cleanup " + tempFolder);
                }
            }
            // output json result
            package.PushItem(Package.OutputName, package.CreateStringItem(ContentType.Text, String.Format(JsonOutputFormat, publishedFiles)));
        }
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);

            bool cleanup;

            // cleanup should be true by default (if not filled in)
            if (!package.TryGetParameter("cleanup", out cleanup, Logger))
            {
                cleanup = true;
            }

            string drive;

            package.TryGetParameter("drive", out drive, Logger);

            List <Binary> binaries = new List <Binary>();

            // Read values from HTML Design Configuration Component (which should be the Component used for this Component Presentation)
            Component inputComponent = GetComponent();

            if (inputComponent.Schema.NamespaceUri != HtmlDesignConfigNamespace || inputComponent.Schema.RootElementName != HtmlDesignConfigRootElementName)
            {
                throw new DxaException(
                          string.Format("Unexpected input Component {0} ('{1}'). Expecting HTML Design Configuration Component.", inputComponent.Id, inputComponent.Title)
                          );
            }
            ItemFields htmlDesignConfigFields = new ItemFields(inputComponent.Content, inputComponent.Schema);
            Component  favIconComponent       = htmlDesignConfigFields.GetMultimediaLink("favicon");
            string     htmlDesignVersion      = htmlDesignConfigFields.GetTextValue("version");

            // Publish version.json file
            IDictionary <string, string> versionData = new Dictionary <string, string> {
                { "version", htmlDesignVersion }
            };
            Binary versionJsonBinary = AddJsonBinary(versionData, inputComponent, Publication.RootStructureGroup, "version", variantId: "version");

            binaries.Add(versionJsonBinary);

            string tempFolder = GetTempFolder(drive);

            Directory.CreateDirectory(tempFolder);
            Logger.Debug("Created temp folder: " + tempFolder);

            try
            {
                // Unzip and merge files
                ProcessModules(tempFolder);

                string distFolder = BuildHtmlDesign(tempFolder);

                // Save favicon to disk (if available)
                if (favIconComponent != null)
                {
                    string favIconFilePath = Path.Combine(distFolder, "favicon.ico");
                    File.WriteAllBytes(favIconFilePath, favIconComponent.BinaryContent.GetByteArray());
                    Logger.Debug("Saved " + favIconFilePath);
                }

                // Publish all files from dist folder
                Publication  pub = (Publication)inputComponent.ContextRepository;
                string       rootStructureGroupWebDavUrl = pub.RootStructureGroup.WebDavUrl;
                RenderedItem renderedItem = engine.PublishingContext.RenderedItem;

                string[] distFiles = Directory.GetFiles(distFolder, "*.*", SearchOption.AllDirectories);
                foreach (string file in distFiles)
                {
                    Logger.Debug("Found " + file);

                    // Map the file path to a Structure Group
                    string relativeFolderPath = file.Substring(distFolder.Length, file.LastIndexOf('\\') - distFolder.Length);
                    Logger.Debug(string.Format("Relative folder path: '{0}'", relativeFolderPath));
                    string         sgWebDavUrl    = rootStructureGroupWebDavUrl + relativeFolderPath.Replace("system", SystemSgName).Replace('\\', '/');
                    StructureGroup structureGroup = engine.GetObject(sgWebDavUrl) as StructureGroup;
                    if (structureGroup == null)
                    {
                        throw new DxaException(string.Format("Cannot publish '{0}' because Structure Group '{1}' does not exist.", file, sgWebDavUrl));
                    }

                    // Add binary to package and publish
                    using (FileStream fs = File.OpenRead(file))
                    {
                        string filename   = Path.GetFileName(file);
                        string extension  = Path.GetExtension(file);
                        string variantId  = string.Format("dist-{0}-{1}", structureGroup.Id.ItemId, filename);
                        Item   binaryItem = Package.CreateStreamItem(GetContentType(extension), fs);
                        Binary binary     = renderedItem.AddBinary(
                            binaryItem.GetAsStream(),
                            filename,
                            structureGroup,
                            variantId,
                            inputComponent,
                            GetMimeType(extension)
                            );
                        binaryItem.Properties[Item.ItemPropertyPublishedPath] = binary.Url;
                        package.PushItem(filename, binaryItem);

                        binaries.Add(binary);
                        Logger.Info(string.Format("Added Binary '{0}' related to Component '{1}' ({2}) with variant ID '{3}'",
                                                  binary.Url, inputComponent.Title, inputComponent.Id, variantId));
                    }
                }
            }
            finally
            {
                if (cleanup)
                {
                    Directory.Delete(tempFolder, true);
                    Logger.Debug("Removed temp folder " + tempFolder);
                }
                else
                {
                    Logger.Debug("Did not cleanup temp folder " + tempFolder);
                }
            }

            OutputSummary("Publish HTML Design", binaries.Select(b => b.Url));
        }
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);

            bool cleanup;
            package.TryGetParameter("cleanup", out cleanup, Logger);

            string drive;
            package.TryGetParameter("drive", out drive, Logger);

            List<Binary> binaries = new List<Binary>();

            // Read values from HTML Design Configuration Component (which should be the Component used for this Component Presentation)
            Component inputComponent = GetComponent();
            if (inputComponent.Schema.NamespaceUri != HtmlDesignConfigNamespace || inputComponent.Schema.RootElementName != HtmlDesignConfigRootElementName)
            {
                throw new DxaException(
                    string.Format("Unexpected input Component {0} ('{1}'). Expecting HTML Design Configuration Component.", inputComponent.Id, inputComponent.Title)
                    );
            }
            ItemFields htmlDesignConfigFields = new ItemFields(inputComponent.Content, inputComponent.Schema);
            Component favIconComponent = htmlDesignConfigFields.GetMultimediaLink("favicon");
            string htmlDesignVersion = htmlDesignConfigFields.GetTextValue("version");

            // Publish version.json file
            IDictionary<string, string> versionData = new Dictionary<string, string> { { "version", htmlDesignVersion } };
            Binary versionJsonBinary = AddJsonBinary(versionData, inputComponent, Publication.RootStructureGroup, "version", variantId: "version");
            binaries.Add(versionJsonBinary);

            string tempFolder = GetTempFolder(drive);
            Directory.CreateDirectory(tempFolder);
            Logger.Debug("Created temp folder: " + tempFolder);

            try
            {
                // Unzip and merge files
                ProcessModules(tempFolder);

                string distFolder = BuildHtmlDesign(tempFolder);

                // Save favicon to disk (if available)
                if (favIconComponent != null)
                {
                    string favIconFilePath = Path.Combine(distFolder, "favicon.ico");
                    File.WriteAllBytes(favIconFilePath, favIconComponent.BinaryContent.GetByteArray());
                    Logger.Debug("Saved " + favIconFilePath);
                }

                // Publish all files from dist folder
                Publication pub = (Publication) inputComponent.ContextRepository;
                string rootStructureGroupWebDavUrl = pub.RootStructureGroup.WebDavUrl;
                RenderedItem renderedItem = engine.PublishingContext.RenderedItem;

                string[] distFiles = Directory.GetFiles(distFolder, "*.*", SearchOption.AllDirectories);
                foreach (string file in distFiles)
                {
                    Logger.Debug("Found " + file);

                    // Map the file path to a Structure Group
                    string relativeFolderPath = file.Substring(distFolder.Length, file.LastIndexOf('\\') - distFolder.Length);
                    Logger.Debug(string.Format("Relative folder path: '{0}'",relativeFolderPath));
                    string sgWebDavUrl = rootStructureGroupWebDavUrl + relativeFolderPath.Replace("system", SystemSgName).Replace('\\', '/');
                    StructureGroup structureGroup = engine.GetObject(sgWebDavUrl) as StructureGroup;
                    if (structureGroup == null)
                    {
                        throw new DxaException(string.Format("Cannot publish '{0}' because Structure Group '{1}' does not exist.", file, sgWebDavUrl));
                    }

                    // Add binary to package and publish
                    using (FileStream fs = File.OpenRead(file))
                    {
                        string filename = Path.GetFileName(file);
                        string extension = Path.GetExtension(file);
                        string variantId =  string.Format("dist-{0}-{1}", structureGroup.Id.ItemId, filename);
                        Item binaryItem = Package.CreateStreamItem(GetContentType(extension), fs);
                        Binary binary = renderedItem.AddBinary(
                            binaryItem.GetAsStream(),
                            filename,
                            structureGroup,
                            variantId,
                            inputComponent,
                            GetMimeType(extension)
                            );
                        binaryItem.Properties[Item.ItemPropertyPublishedPath] = binary.Url;
                        package.PushItem(filename, binaryItem);

                        binaries.Add(binary);
                        Logger.Info(string.Format("Added Binary '{0}' related to Component '{1}' ({2}) with variant ID '{3}'",
                            binary.Url, inputComponent.Title, inputComponent.Id, variantId));
                    }
                }
            }
            finally
            {
                if (cleanup)
                {
                    Directory.Delete(tempFolder, true);
                    Logger.Debug("Removed temp folder " + tempFolder);
                }
                else
                {
                    Logger.Debug("Did not cleanup temp folder " + tempFolder);
                }
            }

            OutputSummary("Publish HTML Design", binaries.Select(b => b.Url));
        }
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);

            _mergeFileLines.Add("src\\system\\assets\\less\\_custom.less", new List<string>());
            _mergeFileLines.Add("src\\system\\assets\\less\\_modules.less", new List<string>());
            _mergeFileLines.Add("src\\templates\\partials\\module-scripts-header.hbs", new List<string>());
            _mergeFileLines.Add("src\\templates\\partials\\module-scripts-footer.hbs", new List<string>());

            StringBuilder publishedFiles = new StringBuilder();
            string drive = package.GetValue("drive") ?? String.Empty;
            string cleanup = package.GetValue("cleanup") ?? String.Empty;

            // not using System.IO.Path.GetTempPath() because the paths in our zip are already quite long,
            // so we need a very short temp path for the extract of our zipfile to succeed
            // using current time and convert to hex (the date won't matter as this folder is cleaned up so time is unique enough)
            int timestamp = Convert.ToInt32(DateTime.Now.ToString("HHmmssfff"));
            if (!String.IsNullOrEmpty(drive) && Char.IsLetter(drive.First()))
            {

                _tempFolder = drive.First() + @":\_" + timestamp.ToString("x");
            }
            else
            {
                // using drive from tridion cm homedir for temp folder
                _tempFolder = ConfigurationSettings.GetTcmHomeDirectory().Substring(0, 3) + "_" + timestamp.ToString("x");
            }

            try
            {
                // read values from Component
                Component config = GetComponent();
                ItemFields fields = new ItemFields(config.Content, config.Schema);
                Component favicon = fields.GetMultimediaLink("favicon");
                string version = fields.GetTextValue("version");

                string url = PublishJson(String.Format("{{\"version\":{0}}}", JsonEncode(version)), config, GetPublication().RootStructureGroup, "version", "version");
                publishedFiles.AppendCommaSeparated(url);
                Logger.Info("Published " + url);

                // create temp folder
                Directory.CreateDirectory(_tempFolder);
                Logger.Debug("Created " + _tempFolder);

                // unzip and merge files
                ProcessModules();

                // build html design
                string user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                ProcessStartInfo info = new ProcessStartInfo
                    {
                        FileName = "cmd.exe",
                        Arguments = "/c npm start --color=false",
                        WorkingDirectory = _tempFolder,
                        CreateNoWindow = true,
                        ErrorDialog = false,
                        UseShellExecute = false,
                        RedirectStandardOutput = true,
                        RedirectStandardError = true,
                        StandardErrorEncoding = Encoding.UTF8,
                        StandardOutputEncoding = Encoding.UTF8
                    };
                using (Process cmd = new Process { StartInfo = info })
                {
                    cmd.Start();
                    using (StreamReader reader = cmd.StandardOutput)
                    {
                        string output = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(output))
                        {
                            Logger.Info(output);

                            // TODO: check for errors in standard output and throw exception
                        }
                    }
                    using (StreamReader reader = cmd.StandardError)
                    {
                        string error = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(error))
                        {
                            Exception ex = new Exception(error);
                            ex.Data.Add("Filename", info.FileName);
                            ex.Data.Add("Arguments", info.Arguments);
                            ex.Data.Add("User", user);

                            // TODO: check for known errors and throw exception with a user friendly message
                            //if (error.ToLower().Contains("something"))
                            //{
                            //    throw new Exception(String.Format("Something went wrong for user {0}.", user), ex);
                            //}

                            throw ex;
                        }
                    }
                    cmd.WaitForExit();
                }

                // publish all binaries from dist folder
                string dist = Path.Combine(_tempFolder, "dist");
                if (Directory.Exists(dist))
                {
                    // save favicon to disk (if available)
                    if (favicon != null)
                    {
                        string ico = Path.Combine(dist, "favicon.ico");
                        File.WriteAllBytes(ico, favicon.BinaryContent.GetByteArray());
                        Logger.Debug("Saved " + ico);
                    }

                    string[] files = Directory.GetFiles(dist, "*.*", SearchOption.AllDirectories);
                    foreach (string file in files)
                    {
                        string filename = Path.GetFileName(file);
                        string extension = Path.GetExtension(file);
                        Logger.Debug("Found " + file);

                        // determine correct structure group
                        Publication pub = (Publication)config.ContextRepository;
                        string relativeFolderPath = file.Substring(dist.Length, file.LastIndexOf('\\') - dist.Length);
                        Logger.Debug("Relative path: " + relativeFolderPath);
                        relativeFolderPath = relativeFolderPath.Replace("system", SystemSgName).Replace('\\', '/');
                        string pubSgWebDavUrl = pub.RootStructureGroup.WebDavUrl;
                        string publishSgWebDavUrl = pubSgWebDavUrl + relativeFolderPath;
                        Logger.Debug("Structure Group WebDAV URL: " + publishSgWebDavUrl);
                        StructureGroup sg = engine.GetObject(publishSgWebDavUrl) as StructureGroup;
                        if (sg == null)
                        {
                            throw new Exception("Missing Structure Group " + publishSgWebDavUrl);
                        }

                        // add binary to package and publish
                        using (FileStream fs = File.OpenRead(file))
                        {
                            Item binaryItem = Package.CreateStreamItem(GetContentType(extension), fs);
                            Binary binary = engine.PublishingContext.RenderedItem.AddBinary(binaryItem.GetAsStream(), filename, sg, "dist-" + filename, config, GetMimeType(extension));
                            binaryItem.Properties[Item.ItemPropertyPublishedPath] = binary.Url;
                            package.PushItem(filename, binaryItem);

                            publishedFiles.AppendCommaSeparated("\"{0}\"", binary.Url);
                            Logger.Info("Published " + binary.Url);
                        }
                    }
                }
                else
                {
                    throw new Exception("Grunt build failed, dist folder is missing.");
                }
            }
            finally
            {
                if (String.IsNullOrEmpty(cleanup) || !cleanup.ToLower().Equals("false"))
                {
                    // cleanup workfolder
                    Directory.Delete(_tempFolder, true);
                    Logger.Debug("Removed " + _tempFolder);
                }
                else
                {
                    Logger.Debug("Did not cleanup " + _tempFolder);
                }
            }

            // output json result
            package.PushItem(Package.OutputName, package.CreateStringItem(ContentType.Text, String.Format(JsonOutputFormat, publishedFiles)));
        }
Ejemplo n.º 5
0
        public override void Transform(Engine engine, Package package)
        {
            Initialize(engine, package);

            _mergeFileLines.Add("src\\system\\assets\\less\\_custom.less", new List <string>());
            _mergeFileLines.Add("src\\system\\assets\\less\\_modules.less", new List <string>());
            _mergeFileLines.Add("src\\templates\\partials\\module-scripts-header.hbs", new List <string>());
            _mergeFileLines.Add("src\\templates\\partials\\module-scripts-footer.hbs", new List <string>());
            _mergeFileLines.Add("src\\templates\\partials\\module-scripts-xpm.hbs", new List <string>());

            StringBuilder publishedFiles = new StringBuilder();
            string        drive          = package.GetValue("drive") ?? String.Empty;
            string        cleanup        = package.GetValue("cleanup") ?? String.Empty;

            // not using System.IO.Path.GetTempPath() because the paths in our zip are already quite long,
            // so we need a very short temp path for the extract of our zipfile to succeed
            // using current time and convert to hex (the date won't matter as this folder is cleaned up so time is unique enough)
            int timestamp = Convert.ToInt32(DateTime.Now.ToString("HHmmssfff"));

            if (!String.IsNullOrEmpty(drive) && Char.IsLetter(drive.First()))
            {
                _tempFolder = drive.First() + @":\_" + timestamp.ToString("x");
            }
            else
            {
                // using drive from tridion cm homedir for temp folder
                _tempFolder = ConfigurationSettings.GetTcmHomeDirectory().Substring(0, 3) + "_" + timestamp.ToString("x");
            }

            try
            {
                // read values from Component
                Component  config  = GetComponent();
                ItemFields fields  = new ItemFields(config.Content, config.Schema);
                Component  favicon = fields.GetMultimediaLink("favicon");
                string     version = fields.GetTextValue("version");

                string url = PublishJson(String.Format("{{\"version\":{0}}}", JsonEncode(version)), config, GetPublication().RootStructureGroup, "version", "version");
                publishedFiles.AppendCommaSeparated(url);
                Logger.Info("Published " + url);

                // create temp folder
                Directory.CreateDirectory(_tempFolder);
                Logger.Debug("Created " + _tempFolder);

                // unzip and merge files
                ProcessModules();

                // build html design
                string           user = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
                ProcessStartInfo info = new ProcessStartInfo
                {
                    FileName               = "cmd.exe",
                    Arguments              = "/c npm start --color=false",
                    WorkingDirectory       = _tempFolder,
                    CreateNoWindow         = true,
                    ErrorDialog            = false,
                    UseShellExecute        = false,
                    RedirectStandardOutput = true,
                    RedirectStandardError  = true,
                    StandardErrorEncoding  = Encoding.UTF8,
                    StandardOutputEncoding = Encoding.UTF8
                };
                using (Process cmd = new Process {
                    StartInfo = info
                })
                {
                    cmd.Start();
                    using (StreamReader reader = cmd.StandardOutput)
                    {
                        string output = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(output))
                        {
                            Logger.Info(output);

                            // TODO: check for errors in standard output and throw exception
                        }
                    }
                    using (StreamReader reader = cmd.StandardError)
                    {
                        string error = reader.ReadToEnd();
                        if (!String.IsNullOrEmpty(error))
                        {
                            Exception ex = new Exception(error);
                            ex.Data.Add("Filename", info.FileName);
                            ex.Data.Add("Arguments", info.Arguments);
                            ex.Data.Add("User", user);

                            // TODO: check for known errors and throw exception with a user friendly message
                            //if (error.ToLower().Contains("something"))
                            //{
                            //    throw new Exception(String.Format("Something went wrong for user {0}.", user), ex);
                            //}

                            throw ex;
                        }
                    }
                    cmd.WaitForExit();
                }

                // publish all binaries from dist folder
                string dist = Path.Combine(_tempFolder, "dist");
                if (Directory.Exists(dist))
                {
                    // save favicon to disk (if available)
                    if (favicon != null)
                    {
                        string ico = Path.Combine(dist, "favicon.ico");
                        File.WriteAllBytes(ico, favicon.BinaryContent.GetByteArray());
                        Logger.Debug("Saved " + ico);
                    }

                    string[] files = Directory.GetFiles(dist, "*.*", SearchOption.AllDirectories);
                    foreach (string file in files)
                    {
                        string filename  = Path.GetFileName(file);
                        string extension = Path.GetExtension(file);
                        Logger.Debug("Found " + file);

                        // determine correct structure group
                        Publication pub = (Publication)config.ContextRepository;
                        string      relativeFolderPath = file.Substring(dist.Length, file.LastIndexOf('\\') - dist.Length);
                        Logger.Debug("Relative path: " + relativeFolderPath);
                        relativeFolderPath = relativeFolderPath.Replace("system", SystemSgName).Replace('\\', '/');
                        string pubSgWebDavUrl     = pub.RootStructureGroup.WebDavUrl;
                        string publishSgWebDavUrl = pubSgWebDavUrl + relativeFolderPath;
                        Logger.Debug("Structure Group WebDAV URL: " + publishSgWebDavUrl);
                        StructureGroup sg = engine.GetObject(publishSgWebDavUrl) as StructureGroup;
                        if (sg == null)
                        {
                            throw new Exception("Missing Structure Group " + publishSgWebDavUrl);
                        }

                        // add binary to package and publish
                        using (FileStream fs = File.OpenRead(file))
                        {
                            Item   binaryItem = Package.CreateStreamItem(GetContentType(extension), fs);
                            Binary binary     = engine.PublishingContext.RenderedItem.AddBinary(binaryItem.GetAsStream(), filename, sg, "dist-" + filename, config, GetMimeType(extension));
                            binaryItem.Properties[Item.ItemPropertyPublishedPath] = binary.Url;
                            package.PushItem(filename, binaryItem);

                            publishedFiles.AppendCommaSeparated("\"{0}\"", binary.Url);
                            Logger.Info("Published " + binary.Url);
                        }
                    }
                }
                else
                {
                    throw new Exception("Grunt build failed, dist folder is missing.");
                }
            }
            finally
            {
                if (String.IsNullOrEmpty(cleanup) || !cleanup.ToLower().Equals("false"))
                {
                    // cleanup workfolder
                    Directory.Delete(_tempFolder, true);
                    Logger.Debug("Removed " + _tempFolder);
                }
                else
                {
                    Logger.Debug("Did not cleanup " + _tempFolder);
                }
            }

            // output json result
            package.PushItem(Package.OutputName, package.CreateStringItem(ContentType.Text, String.Format(JsonOutputFormat, publishedFiles)));
        }