From 4ab7d60ca428f0c8278820bee973a8153ec52fb3 Mon Sep 17 00:00:00 2001 From: Rick Waldron Date: Tue, 27 Oct 2015 14:11:53 -0400 Subject: [PATCH] --slim by default. Fixes gh-412, Fixes gh-418 --- bin/tessel-2.js | 28 ++- lib/tessel/deploy.js | 82 ++++--- package.json | 4 +- test/unit/deploy.js | 260 ++++++++++++++++++++--- test/unit/fixtures/bundling/package.json | 6 + test/unit/fixtures/init/index.js | 8 + test/unit/fixtures/init/package.json | 6 + 7 files changed, 326 insertions(+), 68 deletions(-) create mode 100644 test/unit/fixtures/bundling/package.json create mode 100644 test/unit/fixtures/init/index.js create mode 100644 test/unit/fixtures/init/package.json diff --git a/bin/tessel-2.js b/bin/tessel-2.js index 824060c3..247ad336 100755 --- a/bin/tessel-2.js +++ b/bin/tessel-2.js @@ -69,7 +69,7 @@ makeCommand('restart') }) .option('entryPoint', { position: 1, - help: 'The entry point file to deploy to Tessel' + help: 'The program entry point file to deploy to Tessel.', }) .option('type', { default: 'ram', @@ -85,7 +85,7 @@ makeCommand('run') .option('entryPoint', { position: 1, required: true, - help: 'The entry point file to deploy to Tessel' + help: 'The program entry point file to deploy to Tessel.' }) .option('single', { flag: true, @@ -99,10 +99,17 @@ makeCommand('run') }) .option('slim', { flag: true, - help: 'Bundle only the required modules' + default: true, + help: 'Deploy a single "bundle" file that contains that contains only the required files, excluding any files matched by non-negated rules in .tesselignore. Program is run from "slimPath" file.' }) .option('slimPath', { - default: 'build.js' + default: '__tessel_program__.js', + help: 'Specify the name of the --slim bundle file.' + }) + .option('full', { + flag: true, + default: false, + help: 'Deploy all files in project including those not used by the program, excluding any files matched by non-negated rules in .tesselignore. Program is run from specified "entryPoint" file.' }) .help('Deploy a script to Tessel and run it with Node'); @@ -114,7 +121,7 @@ makeCommand('push') .option('entryPoint', { position: 1, required: true, - help: 'The entry point file to deploy to Tessel' + help: 'The program entry point file to deploy to Tessel.' }) .option('single', { flag: true, @@ -128,10 +135,17 @@ makeCommand('push') }) .option('slim', { flag: true, - help: 'Bundle only the required modules' + default: true, + help: 'Push a single "bundle" file that contains that contains only the required files, excluding any files matched by non-negated rules in .tesselignore. Program is run from "slimPath" file.' }) .option('slimPath', { - default: 'build.js' + default: '__tessel_program__.js', + help: 'Specify the name of the --slim bundle file.' + }) + .option('full', { + flag: true, + default: false, + help: 'Push all files in project including those not used by the program, excluding any files matched by non-negated rules in .tesselignore. Program is run from specified "entryPoint" file.' }) .help('Pushes the file/dir to Flash memory to be run anytime the Tessel is powered, runs the file immediately once the file is copied over'); diff --git a/lib/tessel/deploy.js b/lib/tessel/deploy.js index 36217f5f..fcddc2bc 100644 --- a/lib/tessel/deploy.js +++ b/lib/tessel/deploy.js @@ -5,6 +5,8 @@ var fs = require('fs'); var path = require('path'); var tar = require('tar'); var Ignore = require('fstream-ignore'); +var Reader = require('fstream').Reader; +var fsTemp = require('fs-temp'); var browserify = require('browserify'); var uglify = require('uglify-js'); var glob = require('glob'); @@ -275,7 +277,7 @@ actions.sendBundle = function(tessel, filepath, opts) { } // Log write - logs.info('Writing %s to %s on %s (%d kB)...', project.entryPoint, memtype, tessel.name, bundle.length / 1000); + logs.info('Writing project to %s on %s (%d kB)...', memtype, tessel.name, bundle.length / 1000); tessel.receive(remoteProcess).then(function() { logs.info('Deployed.'); @@ -296,14 +298,22 @@ actions.sendBundle = function(tessel, filepath, opts) { actions.tarBundle = function(opts) { var target = opts.target || process.cwd(); - var pack = tar.Pack(); + var relative = path.relative(process.cwd(), target); + var packer = tar.Pack({ + noProprietary: true + }); var buffers = []; + if (opts.full) { + opts.slim = false; + } + if (opts.slim) { logs.info('Generating slim build.'); return new Promise(function(resolve, reject) { actions.glob(target + '/**/*.tesselignore', { - dot: true + dot: true, + mark: true, }, function(error, ignoreFiles) { if (error) { return reject(error); @@ -311,8 +321,8 @@ actions.tarBundle = function(opts) { var rules = ignoreFiles.reduce(function(rules, ignoreFile) { var dirname = path.dirname(ignoreFile); - var patterns = fs.readFileSync(ignoreFile, 'utf8').trim().split('\n').map(function(pattern) { - return path.join(dirname, pattern).replace(process.cwd() + path.sep, ''); + var patterns = fs.readFileSync(ignoreFile, 'utf8').trim().split(/\r?\n/).map(function(pattern) { + return path.relative(target, path.join(dirname, pattern)); }); return rules.concat(patterns); @@ -329,9 +339,9 @@ actions.tarBundle = function(opts) { return rule; }); - var exclusions = '{' + rules.join(',') + '}'; - - var b = actions.browserify(opts.resolvedEntryPoint, { + var fileCount = 0; + var entry = path.join(relative, opts.resolvedEntryPoint); + var bify = actions.browserify(entry, { builtins: false, commondir: false, browserField: false, @@ -339,32 +349,47 @@ actions.tarBundle = function(opts) { ignoreMissing: true }); - actions.glob.sync(exclusions).forEach(function(file) { - b.exclude(file); + rules.forEach(function(rule) { + actions.glob.sync(rule, { + cwd: relative || target + }).forEach(function(file) { + bify.exclude(file); + }); }); - b.bundle(function(error, results) { + bify.on('file', function() { + fileCount++; + }); + + bify.bundle(function(error, results) { if (error) { return reject(error); } else { - fs.writeFileSync(opts.slimPath, actions.compress(results)); - var fstream = new Ignore({ - path: target, - }); + // If there is only one file in this project, then there is + // no reason to use the code generated by browserify, because + // it will always have the module loading boilerplate included. + if (opts.single || fileCount === 1) { + results = fs.readFileSync(entry); + } - fstream.addIgnoreRules(['*', '!' + opts.slimPath]); + var projectDirectory = fsTemp.mkdirSync(); + var projectBundle = path.join(projectDirectory, opts.slimPath); - fstream.basename = ''; - pack._noProprietary = true; + fs.writeFileSync(projectBundle, actions.compress(results)); - fstream.on('entry', function(entry) { - entry.root = { - path: entry.path - }; + var fstream = new Reader({ + path: projectDirectory, + type: 'Directory', }); - fstream.pipe(pack) + fstream + .on('entry', function(entry) { + entry.root = { + path: entry.path + }; + }) + .pipe(packer) .on('data', function(chunk) { buffers.push(chunk); }) @@ -372,7 +397,8 @@ actions.tarBundle = function(opts) { reject(data); }) .on('end', function() { - fs.unlinkSync(opts.slimPath); + fs.unlinkSync(projectBundle); + fs.rmdirSync(projectDirectory); resolve(Buffer.concat(buffers)); }); } @@ -383,6 +409,7 @@ actions.tarBundle = function(opts) { return new Promise(function(resolve, reject) { var fstream = new Ignore({ + basename: '', path: target, ignoreFiles: ['.tesselignore'] }); @@ -391,9 +418,6 @@ actions.tarBundle = function(opts) { fstream.addIgnoreRules(['*', '!' + opts.resolvedEntryPoint]); } - fstream.basename = ''; - pack._noProprietary = true; - // This ensures that the remote root directory // is the same level as the directory containing // our program entry-point files. @@ -404,7 +428,7 @@ actions.tarBundle = function(opts) { }); // Send the ignore-filtered file stream into the tar packer - fstream.pipe(pack) + fstream.pipe(packer) .on('data', function(chunk) { buffers.push(chunk); }) @@ -419,7 +443,7 @@ actions.tarBundle = function(opts) { }; actions.runScript = function(t, filepath, entryPoint, opts) { - logs.info('Running %s...', entryPoint); + logs.info('Running %s...', opts.slim ? 'bundled project' : entryPoint); return new Promise(function(resolve) { return t.connection.exec(commands.runScript(filepath, opts.slim ? opts.slimPath : entryPoint)) .then(function(remoteProcess) { diff --git a/package.json b/package.json index 560e5bb8..80f6965e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,8 @@ "debug": "^2.2.0", "envfile": "^1.0.0", "fs-extra": "^0.18.0", + "fs-temp": "^1.0.0", + "fstream": "^1.0.8", "fstream-ignore": "^1.0.2", "glob": "^5.0.15", "inquirer": "^0.8.5", @@ -67,9 +69,7 @@ "ssh2": "^0.4.2", "stream-to-buffer": "^0.1.0", "tar": "^2.1.1", - "tar-stream": "^1.2.1", "uglify-js": "^2.5.0", - "uglifyify": "^3.0.1", "url-join": "0.0.1", "usb": "1.1.0", "usb-daemon-parser": "0.0.1" diff --git a/test/unit/deploy.js b/test/unit/deploy.js index 669b5ee0..d7254275 100644 --- a/test/unit/deploy.js +++ b/test/unit/deploy.js @@ -9,6 +9,8 @@ var mkdirp = require('mkdirp'); var path = require('path'); var rimraf = require('rimraf'); var Ignore = require('fstream-ignore'); +var Reader = require('fstream').Reader; +var fsTemp = require('fs-temp'); var browserify = require('browserify'); var uglify = require('uglify-js'); var meminfo = fs.readFileSync('test/unit/fixtures/proc-meminfo', 'utf8'); @@ -327,10 +329,31 @@ exports['Tessel.prototype.deployScript'] = { exports['tarBundle'] = { setUp: function(done) { + this.writeFileSync = sandbox.spy(fs, 'writeFileSync'); + this.rmdirSync = sandbox.spy(fs, 'rmdirSync'); + this.unlinkSync = sandbox.spy(fs, 'unlinkSync'); + + this.exclude = sandbox.spy(browserify.prototype, 'exclude'); + this.mkdirSync = sandbox.spy(fsTemp, 'mkdirSync'); this.addIgnoreRules = sandbox.spy(Ignore.prototype, 'addIgnoreRules'); + this.minify = sandbox.spy(uglify, 'minify'); + + this.browserify = sandbox.spy(deploy, 'browserify'); + this.compress = sandbox.spy(deploy, 'compress'); + this.logsWarn = sandbox.stub(logs, 'warn', function() {}); this.logsInfo = sandbox.stub(logs, 'info', function() {}); + this.glob = sandbox.stub(deploy, 'glob', function(pattern, options, callback) { + process.nextTick(function() { + callback(null, []); + }); + }); + + this.globSync = sandbox.stub(deploy.glob, 'sync', function() { + return []; + }); + done(); }, @@ -339,29 +362,123 @@ exports['tarBundle'] = { done(); }, - project: function(test) { - test.expect(2); + full: function(test) { + test.expect(11); var target = 'test/unit/fixtures/bundling'; deploy.tarBundle({ - target: target + target: target, + full: true, }).then(function(bundle) { + test.equal(this.glob.callCount, 0); + test.equal(this.globSync.callCount, 0); test.equal(this.addIgnoreRules.callCount, 0); - test.equal(bundle.length, 4608); + test.equal(this.browserify.callCount, 0); + test.equal(this.exclude.callCount, 0); + test.equal(this.compress.callCount, 0); + test.equal(this.minify.callCount, 0); + test.equal(this.writeFileSync.callCount, 0); + test.equal(this.unlinkSync.callCount, 0); + test.equal(this.rmdirSync.callCount, 0); + + test.equal(bundle.length, 5632); test.done(); }.bind(this)); }, slim: function(test) { - test.expect(21); + test.expect(13); + + var entryPoint = 'index.js'; + var target = 'test/unit/fixtures/bundling'; + var slimPath = '__tessel_program__.js'; + + // this.join.reset(); + deploy.tarBundle({ + target: target, + resolvedEntryPoint: entryPoint, + slimPath: slimPath, + slim: true, + }).then(function(bundle) { + test.equal(this.glob.callCount, 1); + test.equal(this.browserify.callCount, 1); + test.equal(this.compress.callCount, 1); + test.equal(this.minify.callCount, 1); + test.equal(this.mkdirSync.callCount, 1); + test.equal(this.writeFileSync.callCount, 1); + + test.equal( + this.writeFileSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); + test.equal(this.writeFileSync.lastCall.args[1], 'console.log("testing deploy");'); + + test.equal(this.unlinkSync.callCount, 1); + test.equal( + this.unlinkSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); + + test.equal(this.rmdirSync.callCount, 1); + test.equal(this.rmdirSync.lastCall.args[0], this.mkdirSync.lastCall.returnValue); + + test.equal(bundle.length, 2048); + test.done(); + }.bind(this)); + }, + + slimRequireOnlyTesselLikeInit: function(test) { + test.expect(13); + + var entryPoint = 'index.js'; + var target = 'test/unit/fixtures/init'; + var slimPath = '__tessel_program__.js'; + + deploy.tarBundle({ + target: target, + resolvedEntryPoint: entryPoint, + slimPath: slimPath, + slim: true, + }).then(function(bundle) { + test.equal(this.glob.callCount, 1); + test.equal(this.browserify.callCount, 1); + test.equal(this.compress.callCount, 1); + test.equal(this.minify.callCount, 1); + + test.equal(this.mkdirSync.callCount, 1); + test.equal(this.writeFileSync.callCount, 1); + + test.equal( + this.writeFileSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); + test.equal(this.writeFileSync.lastCall.args[1], 'var tessel=require("tessel");tessel.led[2].on(),setInterval(function(){tessel.led[2].toggle(),tessel.led[3].toggle()},100);'); + + test.equal(this.unlinkSync.callCount, 1); + test.equal( + this.unlinkSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); + + test.equal(this.rmdirSync.callCount, 1); + test.equal(this.rmdirSync.lastCall.args[0], this.mkdirSync.lastCall.returnValue); + + test.equal(bundle.length, 2048); + test.done(); + }.bind(this)); + }, + + slimRespectTesselIgnore: function(test) { + test.expect(22); var target = 'test/unit/fixtures/slim'; var entryPoint = 'index.js'; var tesselignore = path.join(target, '.tesselignore'); var fileToIgnore = path.join(target, 'mock-foo.js'); - var slimPath = path.join(target, 'bundle.js'); + var slimPath = '__tessel_program__.js'; + this.glob.restore(); this.glob = sandbox.stub(deploy, 'glob', function(pattern, options, callback) { test.equal(options.dot, true); process.nextTick(function() { @@ -371,28 +488,19 @@ exports['tarBundle'] = { // This is necessary because the path in which the tests are being run might // not be the same path that this operation occurs within. + this.globSync.restore(); this.globSync = sandbox.stub(deploy.glob, 'sync', function() { return [fileToIgnore]; }); - this.exclude = sandbox.spy(browserify.prototype, 'exclude'); - this.browserify = sandbox.spy(deploy, 'browserify'); - - this.minify = sandbox.spy(uglify, 'minify'); - this.compress = sandbox.spy(deploy, 'compress'); - - this.writeFileSync = sandbox.spy(fs, 'writeFileSync'); - this.unlinkSync = sandbox.spy(fs, 'unlinkSync'); - deploy.tarBundle({ target: target, - resolvedEntryPoint: path.join(target, entryPoint), + resolvedEntryPoint: entryPoint, slimPath: slimPath, slim: true, }).then(function() { test.equal(this.glob.callCount, 1); test.equal(this.globSync.callCount, 1); - test.equal(this.browserify.callCount, 1); test.equal(this.browserify.lastCall.args[0], path.join(target, entryPoint)); @@ -423,55 +531,147 @@ exports['tarBundle'] = { // mutate the options reference. No need to keep track of that. test.equal(this.minify.lastCall.args[1].fromString, true); - // Creates up bundle.js + test.equal(this.mkdirSync.callCount, 1); test.equal(this.writeFileSync.callCount, 1); - test.equal(this.writeFileSync.lastCall.args[0], slimPath); + + test.equal( + this.writeFileSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); test.equal(this.writeFileSync.lastCall.args[1], minified); - test.equal(this.addIgnoreRules.callCount, 1); - test.deepEqual(this.addIgnoreRules.lastCall.args[0], ['*', '!' + slimPath]); + test.equal(this.unlinkSync.callCount, 1); + test.equal( + this.unlinkSync.lastCall.args[0], + path.join(this.mkdirSync.lastCall.returnValue, slimPath) + ); + + test.equal(this.rmdirSync.callCount, 1); + test.equal(this.rmdirSync.lastCall.args[0], this.mkdirSync.lastCall.returnValue); + + + test.done(); + }.bind(this)); + }, + + slimSingle: function(test) { + test.expect(7); + + var target = 'test/unit/fixtures/bundling'; + var entryPoint = 'index.js'; + var slimPath = '__tessel_program__.js'; + + deploy.tarBundle({ + target: target, + entryPoint: entryPoint, + resolvedEntryPoint: entryPoint, + single: true, + slim: true, + slimPath: slimPath, + }).then(function(bundle) { + test.equal(this.glob.callCount, 1); + test.equal(this.browserify.callCount, 1); + test.equal(this.compress.callCount, 1); + test.equal(this.minify.callCount, 1); + test.equal(this.writeFileSync.callCount, 1); + test.equal(this.unlinkSync.callCount, 1); + + + test.equal(bundle.length, 2048); + test.done(); + }.bind(this)); + }, + + slimSingleNested: function(test) { + test.expect(7); + + var target = 'test/unit/fixtures/bundling'; + var entryPoint = 'another.js'; + var slimPath = '__tessel_program__.js'; + + deploy.tarBundle({ + target: target, + entryPoint: entryPoint, + resolvedEntryPoint: path.join('nested', entryPoint), + single: true, + slim: true, + slimPath: slimPath, - // Cleaned up bundle.js + }).then(function(bundle) { + test.equal(this.glob.callCount, 1); + test.equal(this.browserify.callCount, 1); + test.equal(this.compress.callCount, 1); + test.equal(this.minify.callCount, 1); + test.equal(this.writeFileSync.callCount, 1); test.equal(this.unlinkSync.callCount, 1); - test.equal(this.unlinkSync.lastCall.args[0], slimPath); + test.equal(bundle.length, 2048); test.done(); }.bind(this)); }, single: function(test) { - test.expect(3); + test.expect(11); var target = 'test/unit/fixtures/bundling'; var entryPoint = 'index.js'; + var slimPath = '__tessel_program__.js'; deploy.tarBundle({ target: target, entryPoint: entryPoint, resolvedEntryPoint: entryPoint, - single: true + single: true, + full: true, + slimPath: slimPath, }).then(function(bundle) { + test.equal(this.glob.callCount, 0); + test.equal(this.globSync.callCount, 0); + test.equal(this.browserify.callCount, 0); + test.equal(this.exclude.callCount, 0); + test.equal(this.compress.callCount, 0); + test.equal(this.minify.callCount, 0); + test.equal(this.writeFileSync.callCount, 0); + test.equal(this.unlinkSync.callCount, 0); + test.equal(this.addIgnoreRules.callCount, 1); - test.deepEqual(this.addIgnoreRules.lastCall.args[0], ['*', '!index.js']); + test.deepEqual( + this.addIgnoreRules.lastCall.args[0], ['*', '!index.js'] + ); test.equal(bundle.length, 2048); test.done(); }.bind(this)); }, singleNested: function(test) { - test.expect(3); + test.expect(11); var target = 'test/unit/fixtures/bundling'; var entryPoint = 'another.js'; + var slimPath = path.join(target, '__tessel_program__.js'); deploy.tarBundle({ target: target, entryPoint: entryPoint, - resolvedEntryPoint: 'nested/' + entryPoint, - single: true + resolvedEntryPoint: path.join('nested', entryPoint), + single: true, + full: true, + slimPath: slimPath, + }).then(function(bundle) { + test.equal(this.glob.callCount, 0); + test.equal(this.globSync.callCount, 0); + test.equal(this.browserify.callCount, 0); + test.equal(this.exclude.callCount, 0); + test.equal(this.compress.callCount, 0); + test.equal(this.minify.callCount, 0); + test.equal(this.writeFileSync.callCount, 0); + test.equal(this.unlinkSync.callCount, 0); + test.equal(this.addIgnoreRules.callCount, 1); - test.deepEqual(this.addIgnoreRules.lastCall.args[0], ['*', '!nested/another.js']); + test.deepEqual( + this.addIgnoreRules.lastCall.args[0], ['*', '!nested/another.js'] + ); test.equal(bundle.length, 2560); test.done(); }.bind(this)); diff --git a/test/unit/fixtures/bundling/package.json b/test/unit/fixtures/bundling/package.json new file mode 100644 index 00000000..db2cad43 --- /dev/null +++ b/test/unit/fixtures/bundling/package.json @@ -0,0 +1,6 @@ +{ + "name": "bundling", + "version": "0.0.1", + "description": "bundling", + "main": "./index.js" +} diff --git a/test/unit/fixtures/init/index.js b/test/unit/fixtures/init/index.js new file mode 100644 index 00000000..ada18566 --- /dev/null +++ b/test/unit/fixtures/init/index.js @@ -0,0 +1,8 @@ +var tessel = require('tessel'); + +tessel.led[2].on(); + +setInterval(function() { + tessel.led[2].toggle(); + tessel.led[3].toggle(); +}, 100); diff --git a/test/unit/fixtures/init/package.json b/test/unit/fixtures/init/package.json new file mode 100644 index 00000000..970b83be --- /dev/null +++ b/test/unit/fixtures/init/package.json @@ -0,0 +1,6 @@ +{ + "name": "blink", + "version": "0.0.1", + "description": "blink", + "main": "./index.js" +}