Posted on

WordPress Dev Kit Plugin : 0.0.1

Grunt WordPress Dev Kit

For those of you that have been following along with my exploration of Grunt and automating my plugin workflow automation… I’m sorry.   Quite boring I’m sure but remember this blog is my personal notebook as much as fodder for the 3 people that may be interested in this stuff.

Last night I extended my journey toward less manual updates, each new step adding an option for human error, by creating the first WordPress Development Kit Plugin.  Yup, a plugin for plugin development.  Sort of.  What the new plugin is going to help me with is keeping my Plugin Version Info page updated.   Today it is very rudimentary with a basic list of plugin slugs, the version info, and release dates.  You can see it in action on my Plugin Version Info page. Ultimately the new Plugin companion to the WordPress Development Kit will be extended to get more information from the Grunt process into the public website via the automated toolkit.

My goal is to have ONE PLACE where plugin information is updated, preferably in the readme files.    For now the JSON file that drive Grunt will suffice with future plans to scrape the readme data into the plugins.

What WordPress Dev Kit Plugin Does

The WordPress Dev Kit plugin is very simplistic in its current form.  It reads the plugins.json file from the WP Dev Kit and renders information via a shortcode to a plugin or  post.

Version 0.0.3 of the plugin has the following shortcodes available:
* Actions (default: list)
* o [wpdevkit action='list'] list details about all plugins
*
* Styles (default: formatted)
* o [wpdevkit action='list' style='formatted'] list the details in an HTML formatted layout
* o [wpdevkit action='list' style='raw'] list the details in a print_r raw format
*
* Types (default: basic)
* o [wpdevkit action='list' type='basic'] list basic details = version, updated, directory, wp versions
* o [wpdevkit action='list' type='detailed'] list all details = version, updated, directory, wp versions, description
*
* Slug (default: none = list ALL)
* o [wpdevkit action='list' slug='wordpress-dev-kit-plugin'] list details about a specific plugin

This will list the entire plugin metadata structure in a pre tag as a standard PHP dump format.

WP Dev Kit Plugin Setup

It expects that you have the WordPress Development Kit plugins.json file pushed to a production files directory on your server.   You can set the location of the file to any web-server-readable directory.    The location is specified in the Settings / WP Dev Kit menu in the admin panel.

If you are using the WP Dev Kit Grunt tasks the build:<slug>:production process will move the plugins.json file over to your server along with your production.zip files.

My Process And How This Helps

In case you’re wondering why I would go through the trouble of building a full-fledged plugin like this, here is my typical workflow:

  1. Edit code.
  2. Edit readme to update version, features, etc.
  3. Edit main plugin to update version, etc.
  4. Create zip file.
  5. FTP zip file to server.
  6. If a WordPress Plugin Directory plugin, fetch SVN repo, update trunk, commit, add branch, commit, push.
  7. Login to my website.
  8. Update version info page on website.
  9. Create blog post about new plugin update with features and screen shots if warranted.
  10. If a Store Locator Plus plugin update the HTML that is pushed through the in-product signage app.

Until today nearly every step of that process was manual.  Now with the WP Dev Kit running on my system and the WP Dev Kit Plugin on my public site the process is now:

  1. Edit code.
  2. Edit readme to update version, features, etc.
  3. Edit main plugin to update version, etc.
  4. Edit the WP Dev Kit JSON file.
  5. grunt build:<slug>:production  which automatically
    1. checks that the version in the readme.txt and plugin file match (new quality control test)
    2. creates the zip file
    3. uses SFTP to put the file on my server
    4. updates the WordPress Plugin Directory Listings (fetch, update trunk, commit, add branch, commit, push)
    5. pushes plugin.json which talks to the WP Dev Kit Plugin and keeps the version info page upated
  6. Login to my website.
  7. Create blog post about new plugin update with features and screen shots if warranted.
  8. If a Store Locator Plus plugin update the HTML that is pushed through the in-product signage app.

The magic happens in steps 4 and 5.   It automates many of the steps in the process.  As I refine the WordPress Dev Kit I will be able to eliminate more steps along the way.

Not a bad start.   As each new plugin update happens I will be refining and improving the automation files and plugin to create a better presentation and improve quality control at each step.

Learn about this process via the WordPress Development Kit articles.

Posted on

Preparing A WordPress Plugin for Grunt Automation

Grunt Getting Started Banner

Thanks to my experience at WordCamp Atlanta (#wcatl), I’ve spent the past couple of days learning about Vagrant and how I can use it to build and distribute new VirtualBox systems to my developer-team-in-training.   I will refine that process to get new members of the development team setup with a CSA Standard environment to bring them up to speed with my process flow with less effort.

Today I am starting with another project inspired by my trip to Atlanta, using Grunt to automate my plugin building experience.   In this article I will go through my setup and initial project automation experience as I learn more about what Grunt can do for me,  getting it setup, and how I can use it to automate at least one of  the many steps involved in the final production phase of a WordPress plugin.

My Environment

My WordPress plugin development environment is fully contained within a virtual Linux workstation running on a laptop.   My current setup:

  • CentOS 6.5 with a full GUI desktop
  • Apache 2.x
  • PHP 5.4.23
  • MySQL 5.5.35
  • NetBeans 8.0 RC1
  • SmartGit 3.0.11 (on top of git 1.7.1)
  • WordPress 3.8.1 (soon to be update to latest nightly build for 3.9)
  • Firefox
  • Selenium IDE

My Process

The basic outline of a premium add-on pack production cycle follows a basic routine:

  • Code Cycle
    • Edit code in NetBeans writing directly to the local wp-content/plugins directory.
    • Commit changes via SmartGit to the current development branch.
    • Push changes with SmartGit to BitBucket when ready.
  • Test Cycle
    • sudo mysql < wpreset.sql to reset the WordPress database (blasts all tables)
    • start Firefox and open Selenium IDE
    • run the New WP Install Selenium IDE script
    • run some of the base Store Locator Plus data test scripts
    • for add-on packs run the add-on pack specific test scripts
    • Edit/Commit/Push/Repeat
  • Production Cycle
    • Validate the readme.txt file.
    • Publish a blog post about the update and new features.
    • Edit the CSA Server Update System with new version/date information.
    • Edit the CSA Version Information page.
    • Update the News and Info “signage”.
    • Package the plugin .zip file.
    • Publish the .zip file to the CSA servers.
    • If it is a WordPress Plugin Directory listing, update the svn repo to publish to WordPress.

As you can imagine, there are a lot of steps in the final production cycle that can be automated.    I will also be exploring phpUnit testing for my plugins to provide deeper testing of the plugins that can be automated to be a virtually hands-off test system, but that is a project for later.  For now, I need to learn Grunt and start replacing my useful but less-flexible bash scripts to simplify the final Production Cycle.

Installing Grunt

One of the first things I learned about Grunt is that it runs on node.js and I need the Node Package Manager (npm) to get it working.   On CentOS this is fairly easy.    I open a terminal, execute sudo, and install npm.    It brings the rest of the Node.JS stuff with it as dependencies.   When that is completed you can install grunt-cli and grunt-init via npm. Apparently I am going to want something called “grunt init templates” as part of the Grunt Scaffolding setup, so I will also use git to clone one of the official “bare bones” templates into my Linux user home directory.

NPM is part of the Extra Packages for Enterprise Linux (epel) repository.   You will need to install this before the install npm command will work.   If you are using a stock CentOS 6.5 release you can go to the following URL and click on the package link, open the download with package installer, and the epel yum repository will be installed and activated:

http://mirrors.severcentral.net/fedora/epel/6/i386/repoview/epel-release.html

$ sudo yum install npm
$ sudo npm install -g grunt-cli
$ sudo npm install -g grunt-init
$ git clone https://github.com/gruntjs/grunt-init-gruntfile.git ~/.grunt-init/gruntfile

With my initial install on CentOS 6.5 there were multiple “unmet dependency” errors followed by a “but will load” message. Running the command grunt seems to be pulling up the grunt CLI. For now I am going to assume those errors are not important for getting my first simplified Grunt tasks running.

Grunt CLI Install Warnings
Grunt CLI install warnings.

Adding Grunt To My Plugin Project

For years I’ve been using a series of Bash scripts to help manage my distribution.   One of the scripts that I use in every production cycle is a script named makezip.sh.    This script packages up the plugin subdirectory I specify skipping various files and directories (.git, the assets subdirectory and a few others) and creates a .zip file in a directory “far away from” the running WordPress install.  I can opt to send copies to the live server when they are ready for publication or keep them local for manual distribution and/or testing.   I bring this up because it impacts my first Grunt setup on my Enhanced Results premium add-on for Store Locator Plus.

I already use the assets sub-directory within the wp-content/<plugin> directory to store all of my development and production scripts and related assets.    As such I already have a place, the assets sub-directory within the plugin directory, where I should be able to store my Grunt configuration and script files without impacting the plugin distribution.

[box type=”note” style=”rounded”]My Dev Environment: The ./assets directory under the current plugin directory is NOT distributed during production.[/box]

To get started I go to my plugin sub-directory on my development system and create a grunt folder and the starting assets via the grunt-init template loaded with git clone as noted above.  After getting the template installed I run npm init to fetch the “helpers” for Grunt.  The helpers are node modules, aka Grunt plugins, that will be referenced by the Gruntfile.js execution.

cd ./wp-content/slp-enhanced-results
mkdir assets
cd assets
echo '<!--?php // Silence is golden.' --> index.php
mkdir grunt
cd grunt
echo '<!--?php // Silence is golden.' --> index.php
# get a basic package.json and Grunfile.js in place
grunt-init gruntfile
# set some defaults in package.json
npm init
# installs the modules specified in the package.json
npm install

[box type=”note” style=”rounded”]What are those echo commands? They create an index.php file to prevent browsing of the directories from a web interface. They are there as an extra safety measure in case the assets directory gets published.[/box]

With the gruntfile template I tell it that I am not using the DOM but will want to concatenate and minify files and that I will be using a package.json file at some point.

Grunt Gruntfile Template Setup
Grunt gruntfile template setup for my add-on pack.

Running this command puts the Gruntfile.js and package.json files in my ./assets/grunt folder and gives me access to the basic scripting tools necessary to start a grunt project.

I think I’m ready for some automation!

Gruntfile Defaults

Earlier I ran the the grunt-init gruntfile step to setup a default starter environment for Grunt.   Time to dig into the details of the two install files.   Some basic reading tells me that the package.json file tells Grunt which “helpers” are to available to this project by default including their version numbers:

Grunt “Helpers”

Helpers are officially termed “plugins” in the Grunt world.   I call them helpers at this stage to remind me that they help perform tasks within my project but I’ll still need to guide them as to what to do.

The default “helpers” in package.json:

{
  "engines": {
    "node": ">= 0.10.0"
  },
  "devDependencies": {
    "grunt": "~0.4.2",
    "grunt-contrib-jshint": "~0.7.2",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-contrib-nodeunit": "~0.2.2",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-uglify": "~0.2.7"
  }
}

What are these helpers? The first couple of entries are obvious. The base JavaScript engine is node and the first “helper” is grunt. What are the rest?   They are all plugins from the grunt-contrib library which gives us some hints:

  • grunt-contrib-jshint

    Validate files with JSHint.   JSHint looks for issues in the syntax of JavaScript files.  With Grunt this happens BEFORE they are published if you keep this as a default task.

  • grunt-contrib-watch

    Run tasks whenever watched files change.  This watches files in your projects.  If a file changes, do something.

  • grunt-contrib-nodeunit

    Run Nodeunit unit tests. Allows node unit tests to be added to your project and run during a build cycle with Grunt.

  • grunt-contrib-concat

    Concatenate files.  Grab a list of files and concatenate them.

  • grunt-contrib-uglify

    Minify files with UglifyJS.  Create minimized JavaScript files from your source files.  Speeds up page load times by cutting out all of the non-executable parts of a JavaScript file including white space.

Grunt Commands and Execution

The other file that is created with the default template that I’ve used id the Gruntfile, stored as Gruntfile.js.   That is a pretty good hint that it is a JavaScript file.   Here is what it looks like:

/*global module:false*/
module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    // Metadata.
    pkg: grunt.file.readJSON('package.json'),
    banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
      '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
      '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
      '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
      ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',
    // Task configuration.
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['lib/<%= pkg.name %>.js'],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },
    uglify: {
      options: {
        banner: '<%= banner %>'
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        globals: {}
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib_test: {
        src: ['lib/**/*.js', 'test/**/*.js']
      }
    },
    nodeunit: {
      files: ['test/**/*_test.js']
    },
    watch: {
      gruntfile: {
        files: '<%= jshint.gruntfile.src %>',
        tasks: ['jshint:gruntfile']
      },
      lib_test: {
        files: '<%= jshint.lib_test.src %>',
        tasks: ['jshint:lib_test', 'nodeunit']
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-nodeunit');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-watch');

  // Default task.
  grunt.registerTask('default', ['jshint', 'nodeunit', 'concat', 'uglify']);

};

After sitting in on the Grunt session at WordCamp I know a couple of things about this file. The initConfig section sets the rules for the various “helpers” and way down near the bottom is a Default task. This is what does all of the work when I run Grunt in my project.

What is this going to do?

The default I have now is going to run jshint, which is going to look for any JavaScript files and scan them for syntax issues and other problems like unused variables (I can tell that by looking at the jshint section higher up in the code). It will then run any nodeunit tests by looking in test/**/ for any files ending in _test.js and execute them (I assume). It will concat any files that live in lib/ that end with .js and store them in dist/<pkg.name>.js.   Finally it will uglify… minify… any of the files that are stored in the destination directory defined by the concat section (dist/) and minify them.

Tweaking Gruntfile For Me

Looks like some decent defaults, but for my project I need to change some things.

I won’t run Node unit testing on this project.  In the Gruntfile I remove the nodeunit from the default tasks section and the config section above.   I also remove the package from the json file.

As noted above, the Grunt project directory on my setup is under the assets subdirectory for this plugin.   All of my main project files are up a couple of levels in the plugin parent directory.   Since I want to distribute both the original JS files AND the minified version, I need to change some paths.   I am going to concat and uglify the scripts in the main plugin distribution directories and output the minified versions there.   I need to change any paths in the upper “config part” of the Gruntfile.js:

Update the concat section:

    // Task configuration.
    concat: {
      options: {
        banner: '<%= banner %>',
        stripBanners: true
      },
      dist: {
        src: ['../../js/<%= pkg.name %>.js'],
        dest: '../../js/<%= pkg.name %>.concat.js'
      }
    },

And the JSHint section:

    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: true,
        boss: true,
        eqnull: true,
        globals: {}
      },
      gruntfile: {
        src: 'Gruntfile.js'
      },
      lib_test: {
        src: ['../../js/*.js', '../../js/test/**/*.js']
      }
    },

That will ensure that all JavaScript stuff goes in my standard ./js subdirectory in my plugin. Yes, the users will get those files making for a larger zip file download and more disk space on their server. Disk space is cheap and download speeds are decent in most places. Not too mention zip is pretty darn good at compressing JavaScript files. When my plugin executes it will load the concatenated minified file which gives the full benefits of execution speed and reduced RAM footprints on the server and users browser. This keeps the original source available to my user base so they can read the code and hack functionality if they find the need without wading through obsfucated minified concatenated JavaScript.

I’ve also learned that I need to add more details to the package.json file in order to get the default rules and tools to work.  This includes adding the name, version, and author variables to package.json.   If you do not define all 3, INCLUDING AUTHOR, you will get the following error:

Running "concat:dist" (concat) task
Warning: An error occurred while processing a template (Cannot read property 'name' of undefined). Use --force to continue.

Adding the name, version, and author elements to the default package.json results in:

{
  "name": "slp-enhanced-results",
  "version": "4.1.01",
  "author": "csa",
  "engines": {
    "node": ">= 0.10.0"
  },
  "devDependencies": {
    "grunt": "~0.4.2",
    "grunt-contrib-jshint": "~0.7.2",
    "grunt-contrib-watch": "~0.5.3",
    "grunt-contrib-concat": "~0.3.0",
    "grunt-contrib-uglify": "~0.2.7"
  }
}

Now I an run the grunt command which will execute jshint, concat, and uglify on all my JavaScript files. Currently the Grunt configuration outputs some headers so I know it is thinking about doing something, but I don’t have anything interesting to process yet. But I will soon. That will be content for the next article about automating my WordPress workflow.

First Grunt Run - No Errors
First Grunt Run without errors.