Monday, September 1, 2014

Postinstall NPM Scripts with Output in the Working Directory


After last night's efforts on eee-polymer-tests, I can wipe my current Polymer (JS) testing install (NPM packages, tests, setup, and Karma config), install, generate new test setup, and run passing tests:
# Delete existing test install...
$ rm -rf karma.conf.js node_modules test

# Install eee-polymer-tests and dependencies..
$ npm install
karma-jasmine@0.2.2 node_modules/karma-jasmine
karma@0.12.23 node_modules/karma
karma-chrome-launcher@0.1.4 node_modules/karma-chrome-launcher
eee-polymer-tests@0.0.1 node_modules/eee-polymer-tests

# Generate new setup and sample test...
$ ./node_modules/eee-polymer-tests/generator.js click-sink

# Run tests...
$ karma start
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 37.0.2062 (Linux)]: Connected on socket ra4z9IfQR4BW8onHkNhy with id 82158706
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0.046 secs / 0.041 secs)
That is going to be a huge help as I work through the remainder of Patterns in Polymer, adding tests to any book code that do not already have them. But there are still a couple of outstanding issues that I would like to knock off first. One of them is more an annoyance, but it will have to be dealt with: the small sample test tends to be so small that the Karma tests don't fully initialize Polymer before running, causing errors. Another problem is that I have not really accounted for the pre-existence of any of the generated files. The last problem is that I never seem to remember to run the generator manually.

I think I will work the problems in reverse order, since the last two are somewhat related by virtue of being NPM code. I can solve the last problem by making the script part of the install, which is done with one of the package lifecycle hooks in NPM. In this case, I make my eee-polymer-tests/generator.js script a post-install script:
{
  "name": "eee-polymer-tests",
  "version": "0.0.1",
  "description": "Sane Polymer test setup (including generator)",
  "scripts": {
    "postinstall" : "./generator.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  // ...
}
When I install, this script is run, but it fails.

It fails because it expects STDIN input to describe the element name:
var element = process.argv[2];
But there is no STDIN as part of a lifecycle script. I need a way to block for input. For that, I make use of the experimental Readline. Readline needs access to both input and output in order to read input and prompt for it:
var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout
});
Using a readline instance is easy, thanks to the question() method:
rl.question("What is the name of the Polymer element being tested? ", function(answer) {
  element = answer.replace(/[<>]*/g, '');

  console.log("Generating test setup for: ", element);
  generate();
  console.log("Done!");

  rl.close();
});
Once I have my answer, I assign it to a global element variable for use in the templates being generated. After my generator does its thing, I tell the user that everything is ready and close the readline object.

And that works great. My generator is run after install and all of the code is generated correctly. Except...

It installs the generated code in the node_modules/eee-polymer-tests subdirectory instead of in the directory from which I called npm install. The whole point of this testing setup is to generate sane defaults and structure for my Polymer elements. So I need Jasmine tests and Karma configuration directly inside my Polymer elements—not inside the node_modules directory.

I am unsure if there is a proper way to get this working, but I solve it by checking the process.cwd() and then changing directories if this is being run from node_modules/eee-polymer-tests:
if (/node_modules\/eee-polymer-tests/.test(process.cwd())) {
  // Need to run the generator from the element package, not from the
  // installed node module
  process.chdir('../..');
}
And that does the trick. Now I can go from a clean Polymer install to passing tests with a single command:
$ rm -rf karma.conf.js node_modules test

$ npm install
> eee-polymer-tests@0.0.1 postinstall /home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/eee-polymer-tests
> ./generator.js

What is the name of the Polymer element being tested? click-sink
Generating test setup for:  click-sink
Done!

karma-jasmine@0.2.2 node_modules/karma-jasmine
karma@0.12.23 node_modules/karma
karma-chrome-launcher@0.1.4 node_modules/karma-chrome-launcher
eee-polymer-tests@0.0.1 node_modules/eee-polymer-tests

$ karma start --single-run
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 37.0.2062 (Linux)]: Connected on socket SOOfrLj5phVcYX11idm1 with id 77662837
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0.06 secs / 0.054 secs)
I still need to decide how to deal with pre-existing files of the same name as those being generated. I thought I had a good idea how to do that, but got mired in details tonight. Hopefully approaching it fresh will resolve things tomorrow. Then I need to see if I can resolve the spurious first run test failure in the generated code. That will no doubt be annoying and tedious since it does not happen all the time. If I am very lucky, I will have a brain wave that makes me understand why that happens and how to fix it. But I leave both tasks for another night.


Day #170

2 comments:

  1. Thanks for posting this. It helped me resolve some of the issues I was having getting my postinstall script to run correctly, too.

    ReplyDelete
  2. I simply wanted to thank you so much again. I am not sure the things that I might have gone through without the type of hints revealed by you regarding that situation.
    Surya Informatics

    ReplyDelete