node.js Module Development

3,662 views

Published on

Node.js has given JavaScript a new resurgence as a server-side language. No longer just for image rollovers and AJAX, JS is now available as a platform for creating lightning-fast, lightweight, networked applications. In this session, we will move beyond Node’s base web servers and Twitter applications, and into module development: those small, reusable components that are the foundation for every business application on every platform. Learn how to create a module within Node.js, how to test your module and validate functionality, and how to get your creation distributed into the wild. With this knowledge, you can make the next great Node package and become famous.

Published in: Technology, Art & Photos

node.js Module Development

  1. 1. MODULE DEVELOPMENT
  2. 2. What is node.js? “ Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. ”
  3. 3. ./
  4. 4. sourceControl git  init
  5. 5. javascriptFiles mkdir  ./lib
  6. 6. executableFiles mkdir  ./bin
  7. 7. testFiles mkdir  ./test
  8. 8. documentationFiles mkdir  ./doc mkdir  ./example mkdir  ./man
  9. 9. informationalFiles touch  ./README touch  ./LICENSE touch  ./AUTHOR
  10. 10. ./bin ./doc ./example ./lib ./man ./test singularNouns
  11. 11. packageManagement
  12. 12. Package Manager
  13. 13. github: isaacs/npm install: comes with node
  14. 14. localInstallation npm install <package>
  15. 15. globalInstallation npm install <package> --global -g or --global
  16. 16. dualInstallation npm install <package> --link
  17. 17. dependencyReferences npm install <package> --save[-dev] --save or --save-dev
  18. 18. updateDependencies npm install
  19. 19. packageInitialization npm init
  20. 20. $  npm  init name: (sample-node) Sample version: (0.0.0) 0.1.0 description: This is a sample module entry point: (index.js) _
  21. 21. author:  Jay  Harris license:  (BSD)  BSD About  to  write  to  /Projects/sample-­‐node/package.json: {    "name":  "Sample",    "version":  "0.1.0",    "description":  "This  is  a  sample  module",    "main":  "index.js",    "scripts":  {        "test":  "echo  "Error:  no  test  specified"  &&  exit  1"    },    "author":  "Jay  Harris",    "license":  "BSD" } Is  this  ok?  (yes)  _
  22. 22. ./package.json 1 { "name": "Sample", 2 "version": "0.1.0", 3 "description": "This is a sample module", 4 "main": "index.js", 5 "scripts": { 6 "test": "echo "Error: no tests avail." && exit 1" 7 }, 8 "author": "Jay Harris", 9 "license": "BSD" 10 11 } 12 13 14 15 16
  23. 23. npm init is additive not destructive
  24. 24. moduleCreation
  25. 25. module
  26. 26. module.exports
  27. 27. ./lib/sample.js 1 module.exports.sayHello = function() { return "Hello World!"; 2 3 } 4 5 6 7 8 9 10 11 12 13 14 15 16
  28. 28. var  sample  =  require("./lib/sample.js"); //  Returns  'Hello  World!' sample.sayHello();
  29. 29. ./lib/person.js 1 function Person(first, last) { if (!(this instanceof Person)) { 2 return new Person(first, last); 3 } 4 5 this.firstName = first; 6 this.lastName = last; 7 8 return this; 9 10 } 11 12 module.exports = Person; 13 14 15 16
  30. 30. var person = require("./lib/person.js"); // This will return undefined person.firstName; // This will return 'Jay' var jayHarris = new Person('Jay','Harris'); jayHarris.firstName;
  31. 31. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person(first, last) { // ... } module.exports = Person; // This is a public method; Person.prototype.sayHello = function() { return _join.call(this, "Hello", this.firstName); } // This is a private method var _join(first, second) { return first + ' ' + second; }
  32. 32. var  person  =  require("./lib/person.js"); //  This  will  throw  'Has  No  Method'  error person.sayHello(); //  This  will  return  'Hello  Jay' var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.sayHello();
  33. 33. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 function Person(first, last) { // ... } module.exports = Person; // This is a static method module.exports.sayHello = function() { return "Hello World!"; } // This is a public instance method; Person.prototype.sayHello = function() { return _join.call(this, "Hello", this.firstName); } // This is a private method var _join(first, second) { return first + ' ' + second; }
  34. 34. var  person  =  require("./lib/person.js"); //  This  will  return  'Hello  World!' person.sayHello(); //  This  will  return  'Hello  Jay' var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.sayHello();
  35. 35. eventEmitter
  36. 36. var  EventEmitter  =  require('events').EventEmitter;
  37. 37. EventEmitter.call(this);
  38. 38. var  util  =  require('util'); //  ... util.inherits(MyClass,  EventEmitter);
  39. 39. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var EventEmitter = require('events').EventEmitter , util = require('util'); function Person(first, last) { // ... EventEmitter.call(this); // ... } util.inherits(Person, EventEmitter); Person.prototype.goToBed = function() { this.emit("sleep"); }
  40. 40. var  person  =  require("./person.js"); //  This  will  return  'Hello  Jay' var  jayHarris  =  new  Person('Jay','Harris'); jayHarris.on("sleep",  function()  {        console.log("Goodnight,  Jay"); } jayHarris.goToBed(); //  Output  'Goodnight,  Jay'
  41. 41. testingNode.js
  42. 42. “ TDD is to coding style as yoga is to posture. Even when you're not actively practicing, having done so colors your whole life healthier. ” j. kerr
  43. 43. assertingCorrectness
  44. 44. var  assert  =  require('assert');
  45. 45. assert(value)            .ok(value)            .equal(actual,  expected)            .notEqual(actual,  expected)            .deepEqual(actual,  expected)            .notDeepEqual(actual,  expected)            .strictEqual(actual,  expected)            .notStrictEqual(actual,  expected)            .throws(block,  [error])            .doesNotThrow(block,  [error])            .ifError(value)
  46. 46. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var assert = require('assert'); // Will pass assert.ok(true); // Will throw an exception assert.ok(false);
  47. 47. 1 var assert = require('assert'); 2 3 // Will throw 'false == true' error 4 assert.ok(typeof 'hello' === 'number'); 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  48. 48. $  node  test.js assert.js:104    throw  new  assert.AssertionError({                ^ AssertionError:  false  ==  true        at  Object.<anonymous>  (my-­‐test.js:7:8)        at  Module._compile  (module.js:449:26)        at  Object.Module._extensions..js  (module.js:467:10)        at  Module.load  (module.js:356:32)        at  Function.Module._load  (module.js:312:12)        at  Module.runMain  (module.js:487:10)        at  process.startup.processNextTick.process._tick... $  _
  49. 49. Chai Assertion Library
  50. 50. github: chaijs/chai install: npm install chai
  51. 51. isTrue,                      isFalse, isNull,                      isNotNull, isUndefined,            isDefined, isFunction,              isNotFunction, isArray,                    isNotArray, isBoolean,                isNotBoolean, isNumber,                  isNotNumber, isString,                  isNotString,                                    include,                                    lengthOf,                                    operator,                                    closeTo   isObject,                  isNotObject, typeOf,                      notTypeOf, instanceOf,              notInstanceOf, match,                        notMatch, property,                  notProperty, deepProperty,          notDeepProperty, propertyVal,            propertyNotVal, deepPropertyVal,    deepPropertyNotVal, additional assertions
  52. 52. var  assert  =  require('chai').assert;
  53. 53. 1 var assert = require('chai').assert; 2 3 // Will throw 'expected 'hello' to be a number' 4 assert.isNumber('hello'); 5 6 7 8 9 10 11 12 13 14 15 16 17 18
  54. 54. $  node  test.js expected  'hello'  to  be  a  number $  _
  55. 55. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var chai = require('chai') , assert = chai.assert; chai.Assertion.includeStack = true; // Will throw and display stack assert.isNumber('hello');
  56. 56. expectAssertions
  57. 57. assertSyntax assert.isObject(person); assert.property(person,  "age"); assert.isNumber(person.age); assert.equals(person.age,  34); expectSyntax expect(person).to.be.an('object');        .with.property('age')        .that.is.a('number')        .that.equals(34);
  58. 58. var  expect  =  require('chai').expect;
  59. 59. assertionChains for readability
  60. 60. expect(person).to.exist        .and.be.an('object')        .with.property('age')        .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  61. 61. .to .be .been .is .that .and .have .with syntaxSugar for readability
  62. 62. expect(person).to.exist        .and.be.an('object')        .with.property('age')        .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  63. 63. expect(person).to.exist        .and.be.an('object')        .with.property('age')        .that.is.to.exist        .and.is.a('number')        .and.equals(34);
  64. 64. .property subjectChange from original object
  65. 65. expect(person)    .that.is.an('object')    .with.property('address')        .that.is.an('object')        .with.property('city')            .that.is.a('string')            .and.equals('Detroit')
  66. 66. testingFramework
  67. 67. mocha simple, flexible, fun
  68. 68. mocha github: visionmedia/mocha install: npm install -g mocha
  69. 69. $  npm  install  -­‐g  mocha $  mkdir  test $  mocha        ✔  0  tests  complete  (1ms) $  
  70. 70. var  mocha  =  require('mocha');
  71. 71. bddSyntax describe('Testing  out  this  thing',  function()  {        it('should  do  stuff',  function()  {                //  Assertion  tests        }); });
  72. 72. ./test/my-test.js 1 var assert = require('assert'); 2 3 describe('Assertions', function() { it('should pass on truthiness', function() { 4 assert.ok(true); 5 }); 6 it('should fail on falsiness', function() { 7 assert.ok(false); 8 }); 9 10 }); 11 12 13 14 15 16
  73. 73. $  mocha  -­‐-­‐reporter  spec    Assertions        ✓  should  pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  74. 74. groupedTests
  75. 75. groupSyntax describe('Testing  out  this  thing',  function()  {        describe('with  a  subset  of  this  other  thing',  function()  {                it('should  do  stuff',  function()  {                        //  Assertion  tests                });        }); });
  76. 76. 1 var expect = require('chai').expect; 2 3 describe('Assertions', function() { describe('on equality', function() { 4 it('should pass on truthiness', function() { 5 expect(true).is.true; 6 }); 7 it('should pass on falsiness', function() { 8 expect(false).is.false; 9 }); 10 }); 11 describe('on type', function() { 12 it('should pass on number', function() { 13 expect(5).is.a('number'); 14 }); 15 }); 16 17 }); 18
  77. 77. $  mocha  -­‐-­‐reporter  spec    Assertions        of  equality            ✓  should  pass  on  truthiness              ✓  should  pass  on  falsiness          on  type            ✓  should  pass  on  number      ✔  3  tests  complete  (6ms) $  _
  78. 78. pendingTests
  79. 79. pendingSyntax describe('Testing  out  this  thing',  function()  {        describe('with  a  subset  of  this  other  thing',  function()  {                it('should  do  stuff  someday');        }); });
  80. 80. 1 var assert = require('chai').assert; 2 3 describe('Assertions', function() { describe('of truthiness', function() { 4 it('should pass on truthiness', function() { 5 expect(true).is.true; 6 }); 7 it('should pass on falsiness', function() { 8 expect(false).is.false; 9 }); 10 }); 11 describe('of type', function() { 12 it('should pass on number', function() { 13 expect(5).is.a('number'); 14 }); 15 it('should pass on object'); 16 }); 17 18 });
  81. 81. $  mocha  -­‐-­‐reporter  spec    Assertions        of  truthiness            ✓  should  pass  on  truthiness            ✓  should  pass  on  falsiness        of  type            ✓  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  1  test  pending $  _
  82. 82. skippedTests
  83. 83. skipSyntax describe('Testing  out  this  thing',  function()  {        it.skip('should  be  skipped',  function()  {                //  Assertion  tests        }); }); describe.skip('This  entire  suite  will  be  skipped',  function()  {        it('should  do  stuff',  function()  {                //  Assertion  tests        }); });
  84. 84. 1 describe('Assertions', function() { describe('of truthiness', function() { 2 it('should pass on truthiness', function() { 3 assert.isTrue(true); 4 }); 5 it('should pass on falsiness', function() { 6 assert.isFalse(false); 7 }); 8 }); 9 describe('of type', function() { 10 it.skip('should pass on number', function() { 11 assert.isNumber(5); 12 }); 13 it('should pass on object'); 14 }); 15 16 }); 17 18
  85. 85. $  make  test    Assertions        of  truthiness            ✓  should  pass  on  truthiness              ✓  should  pass  on  falsiness          of  type            -­‐  should  pass  on  number              -­‐  should  pass  on  object    ✔  4  tests  complete  (6ms)    •  2  test  pending $  _
  86. 86. setupTeardown
  87. 87. before(); beforeEach(); after(); afterEach();
  88. 88. setupTeardown describe('Testing  out  this  thing',  function()  {        before(function(){                //  ...        };        describe('with  a  subset  of  that  thing',  function()  {                it('should  do  stuff',  function()  {                        //  Assertion  tests                });                afterEach(function(){                        //  ...                };        }); });
  89. 89. asynchronousTests
  90. 90. asynchronousSyntax it('should  not  error',  function(done)  {        search.find("Apples",  done); });
  91. 91. asynchronousSyntax it('should  not  error',  function(done)  {        search.find("Apples",  done); }); it('should  return  2  items',  function(done)  {        search.find("Apples",  function(err,  res)  {                if  (err)  return  done(err);                res.should.have.length(2);                done();        }); });
  92. 92. All Mocha functions accept this callback
  93. 93. 1 describe('When searching for Apples', function(done) { before(function(done){ 2 items.save(['fiji apples', 3 'empire apples'], done); 4 }); 5 it('should not error', function(done) { 6 search.find("Apples", done); 7 }); 8 it('should return 2 items', function(done) { 9 search.find("Apples", function(err, res) { 10 if (err) return done(err); 11 res.should.have.length(2); 12 done(); 13 }); 14 }); 15 16 }); 17 18
  94. 94. simplifyExecution
  95. 95. $  mocha  <args> should be simplified to $  make  test and $  npm  test
  96. 96. ./makefile 1 # Makefile for sample module 2 3 test: mocha --reporter spec --ui bdd 4 5 6 7 8 .PHONY: test 9 10 11 12 13 14 15 16
  97. 97. ./makefile 1 # Makefile for sample module 2 3 test: mocha 4 --reporter spec 5 --ui bdd 6 7 8 .PHONY: test 9 10 11 12 13 14 15 16
  98. 98. $  make  test    Assertions        ✓  should  pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  99. 99. ./package.json 1 { "name": "sample", 2 "version": "0.1.0", 3 "scripts": { 4 "test": "make test" 5 } 6 7 } 8 9 10 11 12 13 14 15 16
  100. 100. $  npm  test    Assertions        ✓  should  pass  on  truthiness          1)  should  fail  on  falsiness    ✖  1  of  2  tests  failed:    1)  Assertions  should  fail  on  falsiness:              AssertionError:  false  ==  true            at  (stack  trace  omitted  for  brevity) $  _
  101. 101. eliminate global dependency $  npm  install  mocha  -­‐-­‐link
  102. 102. 1 test: @./node_modules/.bin/mocha 2 --reporter spec 3 --ui bdd 4 5 6 .PHONY: test 7 8 9 10 11 12 13 14 15 16 17 18
  103. 103. update dev dependencies $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm  install  chai    -­‐-­‐save-­‐dev
  104. 104. $  npm  install  mocha  -­‐-­‐save-­‐dev $  npm  install  chai    -­‐-­‐save-­‐dev $  git  diff  package.json   diff  -­‐-­‐git  a/package.json  b/package.json index  439cf44..3609bb9  100644 -­‐-­‐-­‐  a/package.json +++  b/package.json @@  -­‐1,5  +1,7  @@  {      "name":  "sample", -­‐    "version":  "0.1.0" +    "version":  "0.1.0", +    "devDependencies":  { +        "mocha":  "~1.9.0", +        "chai":  "~1.6.0" +    }  }
  105. 105. notificationSystems
  106. 106. continuousTesting mocha --watch -w or --watch
  107. 107. $  mocha  -­‐-­‐watch  ✔  5  tests  complete  (22ms)  ^  watching
  108. 108. Growl
  109. 109. mac: apple app store win: growlForWindows.com also: growlNotify
  110. 110. growlNotifications mocha --growl -G or --growl
  111. 111. ⌘S
  112. 112. 1 test: @./node_modules/.bin/mocha 2 --reporter spec 3 --ui bdd 4 5 6 watch: @./node_modules/.bin/mocha 7 --reporter min 8 --ui bdd 9 --growl 10 --watch 11 12 13 .PHONY: test watch 14 15 16 17 18
  113. 113. mockingObjects
  114. 114. JavaScript ships with a mocking framework ...it’s called JavaScript
  115. 115. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var me = {firstName: 'Jay' , lastName: 'Harris' , getFullName: function() { return this.firstName + ' ' + this.lastName; }}; // Returns 'Jay Harris' me.getFullName(); me.getFullName = function() { return 'John Doe'; }; // Returns 'John Doe' me.getFullName();
  116. 116. nock HTTP Mocking Library
  117. 117. nock github: flatiron/nock install: npm install nock
  118. 118. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var http = require('http'); var reqOptions = { host: 'api.twitter.com', path: '/1/statuses/user_timeline.json?' + 'screen_name=jayharris' }; // ... http.request(reqOptions, resCallback).end(); // Returns live Twitter data
  119. 119. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 var nock = require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris') .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end();
  120. 120. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // Returns live Twitter data http.request(reqOptions, resCallback).end(); var nock = require('nock'); var twitter = nock('http://api.twitter.com') .get('/1/statuses/user_timeline.json?'+ 'screen_name=jayharris') .reply(200, "This worked"); // Returns "This worked" http.request(reqOptions, resCallback).end(); // Returns live Twitter data http.request(reqOptions, resCallback).end();
  121. 121. nock nock.recorder.rec(  ); nock.recorder.play(  );
  122. 122. Sinon.js Spies, Stubs, & Mocks
  123. 123. Sinon.js github: cjohansen/Sinon.JS install: npm install sinon
  124. 124. versionTesting
  125. 125. nvm Node Version Manager
  126. 126. nvm github: creationix/nvm github: hakobera/nvmw
  127. 127. $  nvm  list        v0.4.0        v0.6.18        v0.8.21        v0.4.7          v0.8.1        v0.10.0        v0.6.0          v0.8.8 current:     v0.8.1 10  -­‐>  0.10.0  (-­‐>  v0.10.0) 4  -­‐>  0.4.7  (-­‐>  v0.4.7) 6  -­‐>  0.6.18  (-­‐>  v0.6.18) 8  -­‐>  0.8.21  (-­‐>  v0.8.21) default  -­‐>  0.10.0  (-­‐>  v0.10.0) newest  -­‐>  0.10.0  (-­‐>  v0.10.0) $  _
  128. 128. $  node  -­‐-­‐version v0.10.0 $  nvm  install  v0.10.6 Now  using  node  v0.10.6 /Users/jayharris/.nvm/v0.10.6/bin/npm $  node  -­‐-­‐version v0.10.6 $  _
  129. 129. packageDistribution
  130. 130. Package Manager
  131. 131. filePackaging ./.npmignore
  132. 132. userSetup npm adduser
  133. 133. $  npm  adduser Username:  (jayharris)  jayharris Password: Email:  (jay@aranasoft.com)  _
  134. 134. $  npm  whoami jayharris $  _
  135. 135. packagePublish npm publish
  136. 136. {  name:  'morale',    description:  'Async  API  wrapper  for  Morale',    'dist-­‐tags':  {  latest:  '0.2.0'  },    versions:        [  '0.1.0',          '0.1.2',          '0.2.0'  ],    maintainers:  'jayharris  <jay@aranasoft.com>',    time:        {  '0.1.0':  '2012-­‐01-­‐23T03:24:59.824Z',          '0.1.2':  '2012-­‐01-­‐25T23:20:52.927Z',          '0.2.0':  '2012-­‐08-­‐13T16:23:28.488Z'  },    author:  'Arana  Software  <info@aranasoft.com>',    repository:        {  type:  'git',          url:  'git://github.com/aranasoft/morale-­‐node.git'  },    users:  {  fgribreau:  true  },    version:  '0.2.0',
  137. 137. $  npm  publish npm  http  PUT  https://registry.npmjs.org/morale npm  http  409  https://registry.npmjs.org/morale npm  http  GET  https://registry.npmjs.org/morale npm  http  200  https://registry.npmjs.org/morale npm  http  PUT  https://registry.npmjs.org/morale/0.2.1/-­‐tag/latest npm  http  201  https://registry.npmjs.org/morale/0.2.1/-­‐tag/latest npm  http  GET  https://registry.npmjs.org/morale npm  http  200  https://registry.npmjs.org/morale npm  http  PUT  https://registry.npmjs.org/morale/-­‐/ morale-­‐0.2.1.tgz/-­‐rev/25-­‐c9bbf49ea0bd2a750e257153fab5794b npm  http  201  https://registry.npmjs.org/morale/-­‐/ morale-­‐0.2.1.tgz/-­‐rev/25-­‐c9bbf49ea0bd2a750e257153fab5794b +  morale@0.2.1 $  _
  138. 138. {  name:  'morale',    description:  'Async  API  wrapper  for  Morale',    'dist-­‐tags':  {  latest:  '0.2.1'  },    versions:        [  '0.1.0',          '0.1.2',          '0.2.0',          '0.2.1'  ],    maintainers:  'jayharris  <jay@aranasoft.com>',    time:        {  '0.1.0':  '2012-­‐01-­‐23T03:24:59.824Z',          '0.1.2':  '2012-­‐01-­‐25T23:20:52.927Z',          '0.2.0':  '2012-­‐08-­‐13T16:23:28.488Z',          '0.2.1':  '2012-­‐08-­‐30T19:10:20.133Z'  },    author:  'Arana  Software  <info@aranasoft.com>',    repository:        {  type:  'git',          url:  'git://github.com/aranasoft/morale-­‐node.git'  },
  139. 139. privatePackages { "private": "true" }
  140. 140. ./package.json 1 { "name": "Sample", 2 "version": "0.1.0", 3 "description": "This is a sample module", 4 "main": "index.js", 5 "scripts": { 6 "test": "echo "Error: no tests avail." && exit 1" 7 }, 8 "author": "Jay Harris", 9 "license": "BSD", 10 "private": "true" 11 12 } 13 14 15 16
  141. 141. $  npm  publish npm  ERR!  Error:  This  package  has  been  marked  as  private npm  ERR!  Remove  the  'private'  field  from  the  package.json  to   publish  it. $  _
  142. 142. versionLockdown npm shrinkwrap
  143. 143. $  npm  shrinkwrap wrote  npm-­‐shrinkwrap.json $  _
  144. 144. 1 { "name": "morale", 2 "version": "0.2.1", 3 "dependencies": { 4 "underscore": { 5 "version": "1.3.3" 6 }, 7 "mocha": { 8 "version": "1.3.0", 9 "dependencies": { 10 "commander": { 11 "version": "0.6.1" 12 }, 13 "growl": { 14 "version": "1.5.1" 15 }, 16 "jade": { 17 "version": "0.26.3", 18
  145. 145. $  rm  npm-­‐shrinkwrap.json $  npm  update $  npm  test $  npm  shrinkwrap
  146. 146. tellEveryone
  147. 147. Go make some Awesome
  148. 148. jay harris P R E S I D E N T jay@aranasoft.com #nodemoduledev @jayharris

×