Skip to content

Commit

Permalink
Enforce nested dependency installation through a multi-fronted strategy.
Browse files Browse the repository at this point in the history
- t2 run/push checks for .npmrc and isntructs to the author to run to t2 init to prepare the directory for deployment.
- t2 init now produces an .npmrc file which sets global-style=true
- t2 init will reinstall everything with newly created npmrc config in place

Signed-off-by: Rick Waldron <waldron.rick@gmail.com>
  • Loading branch information
rwaldron committed Jul 12, 2017
1 parent 9251b58 commit 6298b4d
Show file tree
Hide file tree
Showing 11 changed files with 305 additions and 62 deletions.
2 changes: 2 additions & 0 deletions lib/init/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

// System Objects
var path = require('path');

Expand Down
74 changes: 57 additions & 17 deletions lib/init/javascript.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
'use strict';

// System Objects
var path = require('path');
const path = require('path');

// Third Party Dependencies
var fs = require('fs-extra');
var PZ = require('promzard').PromZard;
var NPM = require('npm');
const fs = require('fs-extra');
const PZ = require('promzard').PromZard;
const NPM = require('npm');

// Internal
var log = require('../log');
const log = require('../log');
const glob = require('../tessel/deployment/glob');

var pkg, ctx, options;
var packageJson = path.resolve('./package.json');
var resources = path.resolve(__dirname, './../../', 'resources/javascript');
var exportables = {};
let pkg, ctx, options;
let packageJson = path.resolve('./package.json');
let resources = path.resolve(__dirname, './../../', 'resources/javascript');
let exportables = {};

exportables.meta = {
keywords: ['javascript', 'js']
Expand Down Expand Up @@ -67,7 +70,7 @@ exportables.buildJSON = (npmConfig) => {
}
});

log.info('Created package.json.');
log.info('Created "package.json".');
resolve(data);
});

Expand All @@ -80,14 +83,34 @@ exportables.buildJSON = (npmConfig) => {

// Returns the dependencies of the package.json file
exportables.getDependencies = (pkg) => {
if (!pkg.dependencies) {
return [];
// Let's find the dependencies that were installed
// by the author...
const dependencies = new Set();
const packageFiles = glob.sync('node_modules/*/package.json');
const authorInstalledDependencies = packageFiles.reduce((accum, file) => {
const content = require(path.join(process.cwd(), file));

if (content._requiredBy &&
content._requiredBy.includes('#USER')) {
accum[content.name] = content.version;
}
return accum;
}, {});

if (typeof pkg.dependencies === 'undefined') {
pkg.dependencies = [];
}
var dependencies = [];
for (var mod in pkg.dependencies) {
dependencies.push(`${mod}@${pkg.dependencies[mod]}`);
// pkg.dependencies will contain saved deps
// authorInstalledDependencies will contain author installed
// dependencies that were not necessarily saved.
//
Object.assign(pkg.dependencies, authorInstalledDependencies);

for (const mod in pkg.dependencies) {
dependencies.add(`${mod}@${pkg.dependencies[mod]}`);
}
return dependencies;

return Array.from(dependencies);
};

// Installs npm and dependencies
Expand All @@ -102,6 +125,7 @@ exportables.npmInstall = (dependencies) => {
// load npm to get the npm object
exportables.loadNpm()
.then((npm) => {
npm.registry.log.level = 'silent';
npm.commands.install(dependencies, (error) => {
if (error) {
reject(error);
Expand Down Expand Up @@ -132,6 +156,21 @@ exportables.createSampleProgram = () => {
});
};

exportables.createNpmrc = () => {
const npmrc = '.npmrc';
return new Promise((resolve) => {
fs.exists(npmrc, (exists) => {
if (exists) {
return resolve();
}
fs.copy(path.join(resources, npmrc), npmrc, () => {
log.info('Created ".npmrc".');
resolve();
});
});
});
};

exportables.createTesselinclude = () => {
var tesselinclude = '.tesselinclude';
return new Promise((resolve) => {
Expand All @@ -140,7 +179,7 @@ exportables.createTesselinclude = () => {
return resolve();
}
fs.copy(path.join(resources, tesselinclude), tesselinclude, () => {
log.info('Created .tesselinclude.');
log.info('Created ".tesselinclude".');
resolve();

});
Expand Down Expand Up @@ -210,6 +249,7 @@ exportables.generateProject = (opts) => {
ctx.version = undefined;
return ctx;
})
.then(exportables.createNpmrc)
.then(exportables.loadNpm)
.then(exportables.resolveNpmConfig)
.then(exportables.buildJSON)
Expand Down
40 changes: 25 additions & 15 deletions lib/tessel/deployment/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,25 +410,35 @@ exportables.injectBinaryModules = function(globRoot, tempBundlePath, options) {
};

exportables.preBundle = function(options) {
return options.tessel.fetchNodeProcessVersions()

const npmrc = new Promise((resolve, reject) => {
//
// First we MUST ensure that .npmrc exists:
//
fs.exists(path.join(options.target, '.npmrc'), (exists) => {
if (exists) {
return resolve();
}
const warning = tags.stripIndents `This project is missing an ".npmrc" file!
To prepare your project for deployment, use the command:
t2 init
Once complete, retry:
t2 ${process.argv.slice(2).join(' ')}
`;
reject(warning);
});
});


return npmrc.then(() => options.tessel.fetchNodeProcessVersions())
.then(versions => {
options.tessel.versions = versions;
return exportables.resolveBinaryModules(options);
});

// // We need to find a way to provide the build version directly from the
// // Tessel 2 itself. This approach makes deployment slow with a network
// // connection, or impossible without one.
// return Promise.all([
// updates.requestBuildList(),
// options.tessel.fetchCurrentBuildInfo(),
// options.tessel.fetchNodeProcessVersions(),
// ]).then(responses => {
// options.tessel.versions = Object.assign(responses[2], {
// build: updates.findBuild(responses[0], 'sha', responses[1]),
// });
// return exportables.resolveBinaryModules(options);
// });
};

exportables.tarBundle = function(options) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"minimatch": "^3.0.0",
"node-rsa": "^0.2.26",
"nomnom": "^1.8.1",
"npm": "^2.7.1",
"npm": "^3.10.10",
"npmlog": "^4.1.0",
"osenv": "^0.1.0",
"promzard": "^0.3.0",
Expand Down
25 changes: 25 additions & 0 deletions resources/javascript/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# This file is a special npm configuration file.
#
# From npm docs:
#
# The --global-style argument will cause
# npm to install the package into your
# local node_modules folder with the same
# layout it uses with the global node_modules
# folder. Only your direct dependencies
# will show in node_modules and everything
# they depend on will be flattened in their
# node_modules folders. This obviously will
# eliminate some deduping.
#
# t2-cli gives third party modules control over what
# files and dependencies get deployed to a Tessel 2
# by supporting .tesselignore and .tesselinclude files.
# Ever since npm 3, dependency flattening has taken that
# control away, since the rules in those files are
# resolved relative to the third party module itself.
# Forcing Tessel 2 project dependencies to install as
# described above restores that control, ideally
# producing smaller project bundles.

global-style = true
12 changes: 9 additions & 3 deletions resources/javascript/init-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module.exports = {
fs.readdir(dirname + '/bin', function(er, d) {
// no bins
if (er) return cb()
// just take the first js file we find there, or nada
// just take the first js file we find there, or nada
return cb(null, d.filter(function(f) {
return f.match(/\.js$/)
})[0])
Expand Down Expand Up @@ -96,8 +96,14 @@ module.exports = {
} catch (e) {
return next()
}
if (!p.version) return next()
deps[d] = '~' + p.version
if (!p.version) {
return next();
}
if (p._requiredBy &&
p._requiredBy.includes('#USER')) {
deps[d] = '~' + p.version;
}

return next()
})
})
Expand Down
15 changes: 11 additions & 4 deletions resources/javascript/init-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,13 @@ module.exports = {
} catch (e) {
return next()
}
if (!p.version) return next();
deps[d] = '~' + p.version;
if (!p.version) {
return next();
}
if (p._requiredBy &&
p._requiredBy.includes('#USER')) {
deps[d] = '~' + p.version;
}
return next();
})
})
Expand Down Expand Up @@ -128,7 +133,9 @@ module.exports = {
})
},
"scripts": (function() {
return {'test': 'echo \"Error: no test specified\" && exit 1'};
return {
'test': 'echo \"Error: no test specified\" && exit 1'
};
})(),

"repository": (function() {
Expand Down Expand Up @@ -160,5 +167,5 @@ module.exports = {
"url": config['init.author.url']
} : undefined,
"license": 'MIT',
"readme" : "A bare bones Tessel 2 blinky script."
"readme": "A bare bones Tessel 2 blinky script."
}
2 changes: 1 addition & 1 deletion test/unit/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ exports['Tessel.prototype.deploy'] = {
this.requestBuildList = sandbox.stub(updates, 'requestBuildList').returns(Promise.resolve(tesselBuilds));

this.pWrite = sandbox.stub(Preferences, 'write').returns(Promise.resolve());

this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(true));

deleteTemporaryDeployCode()
.then(done);
Expand Down
33 changes: 33 additions & 0 deletions test/unit/deployment/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exports['Deployment: JavaScript'] = {
this.requestBuildList = sandbox.stub(updates, 'requestBuildList').returns(Promise.resolve(tesselBuilds));

this.pWrite = sandbox.stub(Preferences, 'write').returns(Promise.resolve());
this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(true));

this.spinnerStart = sandbox.stub(log.spinner, 'start');
this.spinnerStop = sandbox.stub(log.spinner, 'stop');
Expand Down Expand Up @@ -2080,6 +2081,10 @@ exports['deploy.sendBundle, error handling'] = {
resolveBinaryModules: function(test) {
test.expect(1);

this.pathResolve.restore();
this.pathResolve = sandbox.stub(path, 'resolve').returns('');
this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(true));

this.findProject = sandbox.stub(deploy, 'findProject', () => Promise.resolve({
pushdir: '',
entryPoint: ''
Expand All @@ -2098,6 +2103,10 @@ exports['deploy.sendBundle, error handling'] = {
tarBundle: function(test) {
test.expect(1);

this.pathResolve.restore();
this.pathResolve = sandbox.stub(path, 'resolve').returns('');
this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(true));

this.findProject = sandbox.stub(deploy, 'findProject', () => Promise.resolve({
pushdir: '',
entryPoint: ''
Expand Down Expand Up @@ -2155,9 +2164,33 @@ exports['deployment.js.preBundle'] = {
done();
},

preBundleChecksForNpmrc(test) {
test.expect(1);

const warning = tags.stripIndents `This project is missing an ".npmrc" file!
To prepare your project for deployment, use the command:
t2 init
Once complete, retry:`;

this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(false));

deployment.js.preBundle({
target: '/',
}).catch(error => {
test.equal(error.startsWith(warning), true);
test.done();
});
},

preBundleReceivesTessel(test) {
test.expect(1);

this.pathResolve.restore();
this.pathResolve = sandbox.stub(path, 'resolve').returns('');
this.exists = sandbox.stub(fs, 'exists', (fpath, callback) => callback(true));

deploy.sendBundle(this.tessel, {
target: '/',
entryPoint: 'foo.js',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6298b4d

Please sign in to comment.