Manually creating Umbraco packages can be tiresome.

If you’re continuously building upon the same package, doing it manually is wasting time that can be more useful developing new features.

This problem presented itself to me when improving Approve It. In order to create the Umbraco package I need several things:

  • The main assembly
  • The new dashboard section html file
  • The App_Plugins folder which contains every angular controller and views, javascript frameworks and custom css
  • The Package Actions Contrib assembly and its respective actions that enable me to bundle some translations in the package

I decided to go with Grunt and the Grunt Umbraco Package task. A grunt file is a script that typically automates the process of executing some tasks. These scripts can be executed on top of NPM, a package manager for javscript that provides a command line tool to run its packages. To streamline even more the package creation, I installed a Visual Studio extension called NPM Scripts Task Runner, that detects the grunt file and provides a simples UI to handle its tasks.

Step 1 – Install NPM

With NPM installed we are able to execute a Grunt file.

Step 2 – Install NPM Scripts Task Runner

The Visual Studio extension:

NPM Scripts Task Runner Visual Studio extension
NPM Scripts Task Runner Visual Studio extension

Step 3 – Create a Grunt file

And place it in your project:

module.exports = function (grunt) {

    // Setup
    var pkg = grunt.file.readJSON('package.json');
    var projectRoot = 'C:/path/to/project/ApproveIt/';
    var packageNamespace = "Create.Plugin";

    // Grunt Configuration
    grunt.initConfig({
        pkg: pkg,
        clean: {
            files: [
                'bld/App_Plugins',
                'bld/bin',
                'bld/Umbraco'
            ]
        },
        copy: {
            release: {
                files: [
                    {
                        expand: true,
                        cwd: projectRoot + 'bin/',
                        src: [
                            packageNamespace + '.' + pkg.name + '.dll',
                            'PackageActionsContrib.dll'
                        ],
                        dest: 'bld/bin/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'App_Plugins/',
                        src: ['**'],
                        dest: 'bld/App_Plugins/'
                    },
                    {
                        expand: true,
                        cwd: projectRoot + 'Dashboard/Views/dashboard/approveIt/',
                        src: ['approveItdashboardintro.html'],
                        dest: "bld/Umbraco/Views/dashboard/approveIt/"
                    }
                ]
            }
        },
        umbracoPackage: {
            release: {
                src: 'bld/',
                dest: 'bin/umbraco',
                options: {
                    name: pkg.name,
                    version: pkg.version,
                    url: pkg.url,
                    license: pkg.license.name,
                    licenseUrl: pkg.license.url,
                    author: pkg.author.name,
                    authorUrl: pkg.author.url,
                    readme: pkg.readme,
                    outputName: pkg.name + '.v' + pkg.version + '.zip',
                    manifest: 'package.xml'
                }
            }
        }
    });

    // Loading of Grunt Tasks
    grunt.loadNpmTasks('grunt-contrib-clean');
    grunt.loadNpmTasks('grunt-contrib-copy');
    grunt.loadNpmTasks('grunt-umbraco-package');
};

This file has three sections, which I’ll explain in a different order:

  1. Setup
  2. Grunt configuration
  3. Loading of grunt tasks

Loading of Grunt Tasks

Most actions we need a grunt file to execute are simplified by the use of existing Grunt tasks. In my case, I only need 3:

  1. grunt-contrib-clean: deletes files and folders
  2. grunt-contrib-copy: copies files and folders
  3. grunt-umbraco-package: generates an umbraco package

Grunt File Setup

Here I define a set of variables and load an external configuration file that contains project properties I use later on:

{
  "author": {
    "name": "André Santos",
    "url": "http://www.create.pt"
  },
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-clean": "^0.6.0",
    "grunt-contrib-copy": "~0.4.1",
    "grunt-umbraco-package": "1.0.0"
  },
  "license": {
    "name": "MIT",
    "url": "http://opensource.org/licenses/MIT"
  },
  "name": "ApproveIt",
  "readme": "Umbraco Plugin that creates a section that shows all the content that is waiting approval for publishing.",
  "url": "https://our.umbraco.org/projects/backoffice-extensions/approve-it/",
  "version": "1.0.1"
}

Grunt Configuration

This section is where everything comes together. The package.json configuration file is loaded and the three aforementioned grunt tasks are implemented:

Clean

In here, the directories used as the source for the umbraco package creation, are configured so that when we can empty them at will

Copy

This task copies every file needed by the plugin to a centralized location, creating a snapshot of our plugin. We copy full directories and hand picked files as needed.

Umbraco Package

Finally, this task picks up every file we have copied and creates the final Umbraco package. It uses the configuration properties set in package.json and uses a custom package.xml file so that I can include my custom post umbraco package install actions (using Package Actions Contrib):

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<umbPackage>
  <info>
    <package>
      <name><%= name %></name>
      <version><%= version %></version>
      <license url="<%= licenseUrl %>"><%= license %></license>
      <url><%= url %></url>
      <requirements>
        <major>3</major>
        <minor>0</minor>
        <patch>0</patch>
      </requirements>
    </package>
    <author>
      <name><%= author %></name>
      <website><%= authorUrl %></website>
    </author>
    <readme><![CDATA[<%= readme %>]]></readme>
  </info>
  <DocumentTypes />
  <Templates />
  <Stylesheets />
  <Macros />
  <DictionaryItems />
  <Languages />
  <DataTypes />
  <control />
  <Actions>
    <Action runat="install" undo="true" alias="addDashboardSection" dashboardAlias="StartupApproveItDashboardSection">
      <section>
        <areas>
          <area>approveIt</area>
        </areas>
        <tab caption="Get Started">
          <control showOnce="true" addPanel="true" panelCaption="">
            views/dashboard/approveIt/approveItdashboardintro.html
          </control>
        </tab>
      </section>
    </Action>
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="en" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="pt" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="cs" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="da" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="en_us" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="es" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="fr" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="he" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="it" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ja" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ko" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="nl" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="no" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="pl" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ru" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="sv" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="zh" position="end" area="sections" key="approveIt" value="Approve It" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="en" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="pt" position="end" area="general" key="approveitupdatedBy" value="Editador por" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="cs" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="da" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="en_us" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="es" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="fr" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="he" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="it" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ja" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ko" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="nl" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="no" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="pl" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="ru" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="sv" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
    <Action runat="install" undo="true" alias="AddLanguageFileKey" language="zh" position="end" area="general" key="approveitupdatedBy" value="Updated by" />
  </Actions>
  <files>
  	<% files.forEach(function(file) { %>
  	<file>
      <guid><%= file.guid %></guid>
      <orgPath><%= file.dir %></orgPath>
      <orgName><%= file.name %></orgName>
    </file>
	<% }); %>
  </files>
</umbPackage>

This file uses the Grunt templating engine so that, not only can I include the Package Action Contrib custom actions, but also some custom variables, such as:

  • The mandatory list of files that is automatically updated when a new file is added to the project output
  • The package.json variables

That’s it! We can now create new versions of Approve It without having to go into the Umbraco backoffice and doing it manually every time we need a new package version.

You can find the complete source code in my GitHub: https://github.com/ViGiLnT/ApproveIt. You can download the Approve It package here: https://our.umbraco.org/projects/backoffice-extensions/approve-it/.

4 COMMENTS

    • It depends on what you want to debug. If you want to debug the creation of the package, I honestly don’t know. If you want to debug the application you are packaging, you don’t actually need to package it. You just need to have the correct structure for an Umbraco plugin in a local Umbraco installation. You can see a tutorial on how to build an Umbraco plugin here: https://blogit.create.pt/andresantos/2015/11/16/building-an-umbraco-7-backoffice-extension-part-i/. It’s a 3 part post.

      If you have the code for your umbraco plugin running in a local installation of Umbraco you can debug the javascript in your browser developer tools and the server side code (dlls) if you attach the IIS express process to your project in Visual Studio.

LEAVE A REPLY

Please enter your comment!
Please enter your name here