node package manager
Share your code. npm Orgs help your team discover, share, and reuse code. Create a free org »

grunt-force-developer

grunt-force-developer

A grunt task for salesforce and force.com development. Designed to help force.com developers to work using the benefits of grunt and a folder structure when developing.

Getting Started

First - learn about Grunt Second - read the overview below Third - watch this short video demoing grunt-force-developer. Finally - Download via NPM and have a play.

npm install grunt-force-developer --save-dev

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

grunt.loadNpmTasks('grunt-force-developer');

Overview

Using grunt and the grunt-force-developer tasks, developers for salesforce & force.com can:

  • Manage their projects / packages in any folder structure they like.
  • Integrate the full suite of grunt tasks into their deployment process.
  • Ensure only new & modified code is published as part of each deployment / build. This enables a developer to code using any IDE, pushing changes via grunt.

To use grunt-force-developer as quickly as possible, we recommend starting with the Gruntfile.js in examples.

Folder Structures

Traditional Folder Structure

Traditionally, when a developer is developing for salesforce / force.com, they are constrained by the mandated package structure. This structure is extremely limiting and, as the size of projects / packages grow, raplidly becomes unwieldy.

package.xml
== classes
    -- PaymentController.cls
    -- PaymentController.cls-meta.xml
    -- UserManagement.cls
    -- UserManagement.cls-meta.xml
== pages
    -- Payment.page
    -- Payment.page-meta.xml
    -- UserManagement.page
    -- UserManagement.page-meta.xml
== objects
    -- Payment__c.object

grunt-force-developer Folder Structure

Using grunt-force-developer, a developer can adopt a fully dynamic file structure that operates independent of the prescribed salesforce package structure. The below example is a snippet from a developer managing their package in structure with little constraints, appropriate for their project.

== .metadata
    -- Payment.page-meta.xml
    -- PaymentController.cls-meta.xml
    -- UserManagement.page-meta.xml
    -- UserManagementController.cls-meta.xml
== Admin
   == Users
      -- UserManagementController.cls
      -- UserManagement.page
== Payments
    -- PaymentController.cls
    -- Payment.page
    -- Payment__c.object

Usage

In your project's Gruntfile, add a section named force to the data object passed into grunt.initConfig().

var credentials = {
  consumerKey: '3MVG98SW_UPr.JFjzEoUdZZczc4pPByJsJh_3hvL_dxAMPsA8DpjdYBXepSg3GztwV.PPEG2Q6YaK1l.11111',
  consumerSecret: '1233452345246423523',
  username: 'james.kent@gruntdemo.vertic.com.au',
  password: 'qwerty123',
  token: 'O7uccvnguEXqLnOBiTLC1234'
};
 
// Project configuration. 
grunt.initConfig({
  force: {
    createPackage: {
      options: {
        action: 'package'
      },
    },
    deployPackage: {
      options: {
        action: 'deploy',
        consumerKey: credentials.consumerKey,
        consumerSecret: credentials.consumerSecret,
        username: credentials.username,
        password: credentials.password,
        token: credentials.token
      },
    }
  }
});

Options

options.action

Type: String Default value: 'deploy'

This option drives the behaviour of the plugin. There are 3 available modes:

  • reset = Deletes the 'package' directory and clears any files hashes. Ensures the next 'package' action will package all project files.
  • package = Copies all supported new & modified project files into the standard salesforce package structure.
  • deploy = Deploys the code to salesforce using nforce (Currently not working -- [grunt-ant-sfdc](https://github.com/kevinohara80/grunt-ant-sfdc] as an alternative).
  • commit = Updates the the files hashes cache, ensuring the next deploy call will only package changed, undeployed project files.

options.environment

Type: String Default value: 'production'

Values can be 'production' or 'sandbox'. Maps to nforce createConnection.

options.fileChangeHashFile

Type: String Default value: '.force-developer.filehash.json'

Persists the file hashes to determine modified and new files.

options.metadataSourceDirectory

Type: String Default value: 'app-metadata'

The folder used to store all '-meta.xml' files for the project. A corresponding file is required for all pages, components, trigger and classes. If the projectBaseDirectory isn't altered, the default location is ./project/app-metadata.

options.pollInterval

Type: Integer Default value: 500

Sets the polling interval when deploying a package.

options.projectBaseDirectory

Type: String Default value: 'project'

Used to determine the root of the project folder.

options.outputDirectory

Type: String Default value: '.package'

The folder used when the files are copied from the project folder into a salesforce package-compliant folder structure.

options.outputPackageZip

Type: String Default value: './.package/package.zip'

The location where the zipped package is to be stored.

options.apiVersion

Type: Integer Default value: 34

The api version to be used by the task. Used when creating on-demand meta-xml files.

Recommended Gruntfile.js

In this example, the default task is configured to package the new and modified project files and upload these using ant. ant has been used due to problems getting nforce-metadata to deploy zip files -- this will be addressed.

This script enables a developer to work using any folder structure and uploading changes by executing grunt. Once the nforce issue is addressed, this script would be executed grunt default-nforce.

Install the appropriate dependancies required for this Gruntfile by executing:

npm install grunt-force-developer grunt-ant-sfdc grunt-contrib-compress --save-dev

Please ensure ant is installed and available as part of the environment path.

Gruntfile.js:

'use strict';
module.exports = function(grunt) {
 
  // TODO: Update the credentials. 
  var credentials = {
    // Not required for ant deployment 
    consumerKey: '3MVG98SW_UPr.JFjzEoUdZZczc4pPByJsJh_3hvL_dxAMPsA8DpjdYBXepSg3GztwV.PPEG2Q6YaK1l.11111', 
    // Not required for ant deployment 
    consumerSecret: '1233452345246423523',
    
    username: 'james.kent@gruntdemo.vertic.com.au',
    password: 'qwerty123',
    token: 'O7uccvnguEXqLnOBiTLC1234'
  };
 
// Project configuration. 
  grunt.initConfig({
 
    // Configuration to be run (and then tested). 
    force: {
      resetCache: {
        options: {
          action: 'reset'
        },
      },
      createPackage: {
        options: {
          action: 'package'
        },
      },
      deployPackage: {
        options: {
          action: 'deploy',
          consumerKey: credentials.consumerKey,
          consumerSecret: credentials.consumerSecret,
          username: credentials.username,
          password: credentials.password,
          token: credentials.token
        },
      },
      commitCache: {
        options: {
          action: 'commit'
        },
      },
    },
 
    compress: {
      packageZip: {
        options: {
          archive: './.package/package.zip'
        },
        files: [
          {cwd: './.package/src/', expand: true, src: ['**']} // includes files in path and its subdirs 
        ]
      }
    },
 
    antdeploy: {
      options: {
        root: './.package/src/', // note trailing slash is important 
        apiVersion: '32.0',
        existingPackage: true
      },
      deployPackage: {
        options: {
          user: credentials.username,
          pass: credentials.password,
          token: credentials.token
        }
      }
    }
 
  });
 
  // These plugins provide necessary tasks. 
  grunt.loadNpmTasks('grunt-ant-sfdc');
  grunt.loadNpmTasks('grunt-contrib-compress');
  grunt.loadNpmTasks('grunt-force-developer');
 
  grunt.registerTask('reset', ['force:resetCache']);
  grunt.registerTask('default', ['force:createPackage', 'compress:packageZip', 'antdeploy:deployPackage']);
  grunt.registerTask('default-nforce', ['force:createPackage', 'compress:packageZip', 'force:deployPackage']);
 
};

Contributing

In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using Grunt.

Acknowledgements

  • Kevin O'Hara for his exceptional nodejs/salesforce work ... and especially his nforce libraries.

Release History

  • 0.1.10
    • Changed search for meta-xml files to search all project directories before generating metadata.
  • 0.1.9
    • Fundamental change - Altered logic so a deploy action does not update the changed file cache. This means that once a deploy is successful, you must make a second call to update the changed file cache so they aren't included in any subsequent deploy calls.
  • 0.1.8
    • Added apiVersion to options.
    • Added support for dynamically creating -meta.xml files for classes and pages.