dia
Advanced tools
Comparing version 0.1.2 to 0.2.0
@@ -7,12 +7,12 @@ // | ||
var fs = require('fs') | ||
, colors = require('colors') | ||
, prompt = require('prompt') | ||
, program = require('commander-plus') | ||
, dia = require('./dia'); | ||
var program = require('commander-plus'); | ||
var colors = require('colors'); | ||
var prompt = require('prompt'); | ||
var dia = require('./dia'); | ||
var fs = require('fs'); | ||
colors.setTheme({ | ||
info: 'cyan' | ||
, fail: 'red' | ||
, pass: 'green' | ||
info: 'cyan', | ||
fail: 'red', | ||
pass: 'green' | ||
}); | ||
@@ -51,7 +51,7 @@ | ||
prompt.get([{ | ||
name: 'overwrite' | ||
, pattern: /^[yn]/ | ||
, message: 'Response must be "y" or "n"' | ||
, description: 'Manifest already exists. Replace it? (y/n)' | ||
, required: true | ||
name: 'overwrite', | ||
pattern: /^[yn]/, | ||
message: 'Response must be "y" or "n"', | ||
description: 'Manifest already exists. Replace it? (y/n)', | ||
required: true | ||
}], function(err, result) { | ||
@@ -112,12 +112,12 @@ if (typeof result !== 'undefined' && result.overwrite === 'y') { | ||
var prompts = [{ | ||
name: 'username' | ||
, description: 'Enter your username or email address (Modulus or GitHub)' | ||
, required: true | ||
name: 'username', | ||
description: 'Enter your username or email address (Modulus or GitHub)', | ||
required: true | ||
}, | ||
{ | ||
name: 'password', | ||
description: 'Enter your password (Modulus or GitHub)', | ||
hidden: true, | ||
required: true | ||
} | ||
, { | ||
name: 'password' | ||
, description: 'Enter your password (Modulus or GitHub)' | ||
, hidden: true | ||
, required: true | ||
} | ||
]; | ||
@@ -124,0 +124,0 @@ |
602
lib/dia.js
// | ||
// Dia | ||
// Copyright(c) 2013 Modulus <support@modulus.io> | ||
// Copyright(c) 2014 Modulus <support@modulus.io> | ||
// MIT Licensed | ||
// | ||
var librarian = require('./librarian') | ||
, UserConfig = require('./user-config') | ||
, randpass = require('randpass') | ||
, request = require('request') | ||
, path = require('path') | ||
, util = require('util') | ||
, url = require('url') | ||
, crypto = require('crypto') | ||
, fs = require('fs'); | ||
var UserConfig = require('./user-config'); | ||
var librarian = require('./librarian'); | ||
var randpass = require('randpass'); | ||
var request = require('request'); | ||
var path = require('path'); | ||
var util = require('util'); | ||
var url = require('url'); | ||
var fs = require('fs'); | ||
var dia = exports; | ||
dia.manifest = {}; | ||
@@ -38,219 +38,50 @@ dia.plan = 'test'; | ||
// | ||
// Helper functions for validating manifest properties. | ||
// === | ||
// | ||
var validators = require('./validators')(dia); | ||
// | ||
// Validate a string value - check that the value exists, is a string, | ||
// and is not empty. | ||
// Run the specified tests. | ||
// | ||
var validateString = function(value, name, pass) { | ||
if (typeof value === 'undefined') { | ||
pass = false; | ||
console.log(' check if exists', '[FAIL]'.fail); | ||
return console.log(util.format(' %s is required', name).fail); | ||
} else { | ||
console.log(' check if exists', '[PASS]'.pass); | ||
if (typeof value === 'string' && value.length > 0) { | ||
console.log(util.format(' check %s value', name), '[PASS]'.pass); | ||
} else { | ||
pass = false; | ||
console.log(util.format(' check %s value', name), '[FAIL]'.fail); | ||
console.log(util.format(' %s must be a string and not empty', name).fail); | ||
} | ||
} | ||
}; | ||
dia.test = function () { | ||
var type, filename, options; | ||
var args = Array.prototype.slice.call(arguments); | ||
// | ||
// Validate each property of a manifest. | ||
// | ||
var validateManifestInternal = function(manifest, fn) { | ||
var pass = true; | ||
type = args.shift(); | ||
filename = args.shift(); | ||
console.log('\ntesting manifest id'.info); | ||
validateString(manifest.id, 'id'); | ||
console.log(); | ||
console.log('testing manifest api'.info); | ||
if (typeof manifest.api === 'undefined') { | ||
console.log(' check if exists', '[FAIL]'.fail); | ||
console.log(' api object is required'.fail); | ||
return fn(false); | ||
} else { | ||
console.log(' check if exists', '[PASS]'.pass); | ||
// Parse remaining arguments if present. | ||
if (args.length > 0) { | ||
options = args.shift(); | ||
} | ||
console.log(); | ||
console.log('testing config_vars'.info); | ||
if (typeof manifest.api.config_vars === 'undefined') { | ||
pass = false; | ||
console.log(' check that config_vars exists', '[FAIL]'.fail); | ||
} else { | ||
console.log(' check that config_vars exists', '[PASS]'.pass); | ||
if (args.length > 0) { | ||
dia.plan = args.shift(); | ||
} | ||
if (!(manifest.api.config_vars instanceof Array)) { | ||
pass = false; | ||
console.log(' check that config_vars is an array', '[FAIL]'.fail); | ||
} else { | ||
if (manifest.api.config_vars.length === 0) { | ||
console.log(' check that config_vars contains at lease one config var', '[FAIL]'.fail); | ||
} else { | ||
console.log(' check that config_vars contains at lease one config var', '[PASS]'.pass); | ||
validators.manifest(filename, function (result) { | ||
if (type === 'manifest' || !result || typeof dia.manifest.api.test === 'undefined') { | ||
return; | ||
} | ||
console.log(' check that config_vars is an array', '[PASS]'.pass); | ||
for (var i = 0; i < manifest.api.config_vars.length; i++) { | ||
if (!manifest.id || manifest.api.config_vars[i].indexOf(manifest.id.toUpperCase()) !== 0) { | ||
pass = false; | ||
console.log(' check that config_vars match manifest id', '[FAIL]'.fail); | ||
if (manifest.id) { | ||
// Only show error if the ID exists. | ||
pass = false; | ||
console.log(util.format(' config variables must be prefixed with %s_', manifest.id.toUpperCase()).fail); | ||
} | ||
} else { | ||
console.log(' check that config_vars match manifest id', '[PASS]'.pass); | ||
if (type === 'all') { | ||
validators.provision(function () { | ||
validators.planchange(function () { | ||
validators.deprovision(function () { | ||
validators.sso(options || dia.id); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
if (type === 'provision') { | ||
validators.provision(); | ||
} | ||
} | ||
} | ||
console.log(); | ||
console.log('testing api authentication properties'.info); | ||
validateString(manifest.api.password, 'api password', pass); | ||
validateString(manifest.api.sso_salt, 'api sso salt', pass); | ||
console.log(); | ||
console.log('testing production api properties'.info); | ||
if (typeof manifest.api.production === 'undefined') { | ||
console.log(' check for production api', '[FAIL]'.fail); | ||
console.log(); | ||
return fn(false); | ||
} else { | ||
console.log(' check for production api', '[PASS]'.pass); | ||
} | ||
validateString(manifest.api.production.base_url, 'production base_url', pass); | ||
validateString(manifest.api.production.sso_url, 'production sso_url', pass); | ||
console.log(); | ||
console.log('testing test api properties'.info); | ||
if (typeof manifest.api.test === 'undefined') { | ||
console.log(' check for test api', '[FAIL]'.fail); | ||
console.log(); | ||
return fn(false); | ||
} else { | ||
console.log(' check for test api', '[PASS]'.pass); | ||
} | ||
validateString(manifest.api.test.base_url, 'test base_url', pass); | ||
validateString(manifest.api.test.sso_url, 'test sso_url', pass); | ||
console.log(); | ||
fn(pass); | ||
}; | ||
// | ||
// POST invalid single sign on authentication data and check | ||
// the result. | ||
// | ||
var sendInvalidSso = function(id, fn) { | ||
var opts = { | ||
uri: dia.manifest.api.test.sso_url | ||
, method: 'POST' | ||
, body: { | ||
id: id | ||
, timestamp: parseInt(Date.now() / 1000, 10) | ||
, email: dia.manifest.id + '999' + '@example.com' | ||
, token: 'invalid' | ||
, 'nav-data': '' | ||
} | ||
, json: true | ||
}; | ||
console.log('testing invalid single sign on authentication'.info); | ||
request(opts, function(err, response) { | ||
if (err) { | ||
console.log(' check response code', '[FAIL]'.fail); | ||
console.log(util.format(' error attempting to authenticate single sign on user: %s', err.message).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.statusCode === 403) { | ||
console.log(' check successful single sign on response', '[PASS]'.pass); | ||
} else if (response.statusCode ) { | ||
console.log(' check successful single sign on response', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 403 but got %s', response.statusCode).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
console.log(); | ||
fn(); | ||
}); | ||
}; | ||
// | ||
// POST valid single sign on authentication data and check | ||
// the result. | ||
// | ||
var sendValidSso = function(id) { | ||
var timestamp = parseInt(Date.now() / 1000, 10) | ||
, preToken = id + ':' + dia.manifest.api.sso_salt + ':' + timestamp | ||
, token = crypto.createHash('sha1').update(preToken).digest('hex'); | ||
var opts = { | ||
uri: dia.manifest.api.test.sso_url | ||
, method: 'POST' | ||
, body: { | ||
id: id | ||
, timestamp: timestamp | ||
, email: dia.manifest.id + '999' + '@example.com' | ||
, token: token | ||
, 'nav-data': '' | ||
} | ||
, json: true | ||
}; | ||
console.log('testing valid single sign on authentication'.info); | ||
request(opts, function(err, response) { | ||
if (err) { | ||
console.log(' check response code', '[FAIL]'.fail); | ||
console.log(util.format(' error attempting to authenticate single sign on user: %s', err.message).fail); | ||
return console.log(); | ||
} | ||
if (response.statusCode === 302) { | ||
// Follow the redirect URL and verify that a 200 is returned. | ||
var getUrl = response.headers.location; | ||
if (getUrl.indexOf('http') < 0) { | ||
getUrl = response.request.uri.protocol + '//' + response.request.uri.host + response.headers.location; | ||
if (type === 'planchange') { | ||
dia.id = options; | ||
validators.planchange(); | ||
} | ||
request.get(getUrl, function(err, response) { | ||
if (err) { | ||
console.log(' check successful single sign on response', '[FAIL]'.fail); | ||
console.log(util.format(' unable to GET %s: %s', getUrl, err).fail); | ||
return console.log(); | ||
} | ||
if (response.statusCode !== 200) { | ||
console.log(response.body); | ||
console.log(' check successful single sign on response', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 200 but got %s', response.statusCode).fail); | ||
return console.log(); | ||
} | ||
console.log(' check successful single sign on response', '[PASS]'.pass); | ||
return console.log(); | ||
}); | ||
} else { | ||
console.log(' check successful single sign on response', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 302 but got %s', response.statusCode).fail); | ||
return console.log(); | ||
if (type === 'deprovision') { | ||
dia.id = options; | ||
validators.deprovision(); | ||
} | ||
if (type === 'sso') { | ||
validators.sso(options); | ||
} | ||
} | ||
@@ -261,10 +92,5 @@ }); | ||
// | ||
// Configuration | ||
// === | ||
// | ||
// | ||
// Set a configuration value | ||
// | ||
dia.setConfig = function(key, value) { | ||
dia.setConfig = function (key, value) { | ||
var data = userConfig.data || {}; | ||
@@ -289,3 +115,3 @@ | ||
// | ||
dia.getConfig = function(key) { | ||
dia.getConfig = function (key) { | ||
if (userConfig.data && typeof userConfig.data[key] !== 'undefined') { | ||
@@ -299,277 +125,5 @@ console.log('\n ', userConfig.data[key].info, '\n'); | ||
// | ||
// Add-On test functions. | ||
// === | ||
// Initialize a skeleton manifest to the specified filename/specifier. | ||
// | ||
// | ||
// Begin an add-on test routine. | ||
// | ||
dia.test = function() { | ||
var type, filename, options | ||
, args = Array.prototype.slice.call(arguments); | ||
type = args.shift(); | ||
filename = args.shift(); | ||
// Parse remaining arguments if present. | ||
if (args.length > 0) { | ||
options = args.shift(); | ||
} | ||
if (args.length > 0) { | ||
dia.plan = args.shift(); | ||
} | ||
dia.validateManifest(filename, function(result) { | ||
if (type === 'manifest' || !result || typeof dia.manifest.api.test === 'undefined') return; | ||
if (type === 'all') { | ||
dia.validatePost(function() { | ||
dia.validatePut(function() { | ||
dia.validateDelete(function() { | ||
dia.validateSso(options || dia.id); | ||
}); | ||
}); | ||
}); | ||
} else { | ||
if (type === 'provision') { | ||
dia.validatePost(); | ||
} | ||
if (type === 'planchange') { | ||
dia.id = options; | ||
dia.validatePut(); | ||
} | ||
if (type === 'deprovision') { | ||
dia.id = options; | ||
dia.validateDelete(); | ||
} | ||
if (type === 'sso') { | ||
dia.validateSso(options); | ||
} | ||
} | ||
}); | ||
}; | ||
// | ||
// Validates the manifest for this add-on. | ||
// | ||
dia.validateManifest = function(filename, fn) { | ||
var manifest = filename || ''; | ||
if (manifest.length > 0) { | ||
if (!fs.existsSync(manifest)) { | ||
console.log(' specified manifest does not exist\n'.fail); | ||
} | ||
} else { | ||
manifest = path.resolve('./', 'addon-manifest.json'); | ||
} | ||
fs.readFile(manifest, { encoding: 'utf8' }, function(err, data) { | ||
if (err || !data) { | ||
console.log(util.format('\n failed to open the manifest at %s\n', manifest).fail); | ||
return fn(false); | ||
} | ||
dia.manifest = JSON.parse(data); | ||
validateManifestInternal(dia.manifest, fn); | ||
}); | ||
}; | ||
// | ||
// POST to the API endpoint specified in the manifest to simulate | ||
// a provision call and validate the response. | ||
// | ||
dia.validatePost = function(fn) { | ||
var opts, i; | ||
console.log(util.format('testing POST to %s', dia.manifest.api.test.base_url).info); | ||
opts = { | ||
uri: dia.manifest.api.test.base_url | ||
, method: 'POST' | ||
, auth: { | ||
user: dia.manifest.id | ||
, pass: dia.manifest.api.password | ||
} | ||
, body: { | ||
options: {} | ||
, callback_url: 'http://localhost:8000/callback/999' | ||
, modulus_id: dia.manifest.id + '999' | ||
, email: dia.manifest.id + '999' + '@example.com' | ||
, plan: dia.plan | ||
, region: 'us-east-1' | ||
} | ||
, json: true | ||
}; | ||
request(opts, function(err, response) { | ||
if (err) { | ||
console.log(' check response code', '[FAIL]'.fail); | ||
console.log(util.format(' error attempting to provision: %s', err.message).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.statusCode === 200) { | ||
console.log(' check authentication', '[PASS]'.pass); | ||
} else { | ||
console.log(' check authentication', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 200 but got %s', response.statusCode).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.body) { | ||
if (typeof response.body.id === 'undefined') { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(' id not found in response'.fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
dia.id = response.body.id; | ||
if (!response.body.config) { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(' config vars not found in response'.fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} else { | ||
for (i = 0; i < dia.manifest.api.config_vars.length; i++) { | ||
if (!response.body.config[dia.manifest.api.config_vars[i]]) { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(util.format(' config var %s not found in response', dia.manifest.api.config_vars[i]).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
} | ||
} | ||
console.log(' check response', '[PASS]'.pass); | ||
} else { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(' response body is empty'. fail); | ||
} | ||
console.log(); | ||
fn && fn(); | ||
}); | ||
}; | ||
// | ||
// DELETE to the API endpoint to simulate a deprovision call | ||
// and validate the response. | ||
// | ||
dia.validateDelete = function(fn) { | ||
var opts = { | ||
uri: dia.manifest.api.test.base_url + '/' + dia.id | ||
, method: 'DELETE' | ||
, auth: { | ||
user: dia.manifest.id | ||
, pass: dia.manifest.api.password | ||
} | ||
}; | ||
console.log(util.format('testing DELETE to %s/%s', dia.manifest.api.test.base_url, dia.id).info); | ||
request(opts, function(err, response) { | ||
if (err) { | ||
console.log(' check response code', '[FAIL]'.fail); | ||
console.log(util.format(' error attempting to deprovision: %s', err.message).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.statusCode === 200) { | ||
console.log(' check authentication', '[PASS]'.pass); | ||
} else if (response.statusCode ) { | ||
console.log(' check authentication', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 200 but got %s', response.statusCode).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
console.log(' check response', '[PASS]'.pass); | ||
console.log(); | ||
fn && fn(); | ||
}); | ||
}; | ||
// | ||
// PUT to the API endpoint to simulate a planchange call | ||
// and validate the response. | ||
// | ||
dia.validatePut = function(fn) { | ||
var opts = { | ||
uri: dia.manifest.api.test.base_url + '/' + dia.id | ||
, method: 'PUT' | ||
, auth: { | ||
user: dia.manifest.id | ||
, pass: dia.manifest.api.password | ||
} | ||
, body: { | ||
plan: dia.plan || 'foo' | ||
, modulus_id: dia.manifest.id + '999' | ||
} | ||
, json: true | ||
}; | ||
console.log(util.format('testing PUT to %s/%s', dia.manifest.api.test.base_url, dia.id).info); | ||
request(opts, function(err, response) { | ||
if (err) { | ||
console.log(' check response code', '[FAIL]'.fail); | ||
console.log(util.format(' error attempting to planchange: %s', err.message).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.statusCode === 200) { | ||
console.log(' check authentication', '[PASS]'.pass); | ||
} else if (response.statusCode ) { | ||
console.log(' check authentication', '[FAIL]'.fail); | ||
console.log(util.format(' expected status code 200 but got %s', response.statusCode).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (typeof response.body === 'undefined') { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(' config vars not found in response'.fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} else { | ||
if (typeof response.body.message === 'undefined') { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(' message not found in response'.fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
if (response.body.config) { | ||
for (var i = 0; i < dia.manifest.api.config_vars.length; i++) { | ||
if (!response.body.config[dia.manifest.api.config_vars[i]]) { | ||
console.log(' check response', '[FAIL]'.fail); | ||
console.log(util.format(' config var %s not found in response', dia.manifest.api.config_vars[i]).fail); | ||
console.log(); | ||
return fn ? fn() : void 0; | ||
} | ||
} | ||
} | ||
} | ||
console.log(' check response', '[PASS]'.pass); | ||
console.log(); | ||
fn && fn(); | ||
}); | ||
}; | ||
// | ||
// Simulate single sign on authentication and check the response. | ||
// | ||
dia.validateSso = function(id) { | ||
sendInvalidSso(id, function() { | ||
sendValidSso(id); | ||
}); | ||
}; | ||
// | ||
// Initialize a skeleton manifest to the specified filename. | ||
// | ||
dia.init = function(filename) { | ||
dia.init = function (filename) { | ||
var manifest = { | ||
@@ -599,7 +153,7 @@ id: 'myaddon', | ||
// | ||
dia.authenticate = function(username, password, fn) { | ||
librarian.user.authenticate(username, librarian.util.createHash(password), function(err, user) { | ||
dia.authenticate = function (username, password, fn) { | ||
librarian.user.authenticate(username, librarian.util.createHash(password), function (err, user) { | ||
if (err) { | ||
var token = null | ||
, userData = new Buffer(util.format('%s:%s', username, password), 'ascii').toString('base64'); | ||
var token = null; | ||
var userData = new Buffer(util.format('%s:%s', username, password), 'ascii').toString('base64'); | ||
@@ -615,3 +169,3 @@ var opts = { | ||
request.get(opts, function(error, response, body) { | ||
request.get(opts, function (error, response, body) { | ||
if (!error && response.statusCode === 200) { | ||
@@ -650,6 +204,12 @@ for (var i = 0; i < body.length; i++) { | ||
// | ||
dia.create = function(filename, user) { | ||
if (!userConfig.data) return console.log('\n you must configured the API endpoint\n'.fail); | ||
if (!user.flags.isProvider) return console.log('\n you must opt into the Modulus provider program\n'.fail); | ||
if (!user.flags.isVerified) return console.log('\n you must be verified to create an add-on\n'.fail); | ||
dia.create = function (filename, user) { | ||
if (!userConfig.data) { | ||
return console.log('\n you must configured the API endpoint\n'.fail); | ||
} | ||
if (!user.flags.isProvider) { | ||
return console.log('\n you must opt into the Modulus provider program\n'.fail); | ||
} | ||
if (!user.flags.isVerified) { | ||
return console.log('\n you must be verified to create an add-on\n'.fail); | ||
} | ||
@@ -664,3 +224,3 @@ if (filename.length > 0) { | ||
fs.readFile(filename, { encoding: 'utf8' }, function(err, data) { | ||
fs.readFile(filename, { encoding: 'utf8' }, function (err, data) { | ||
if (err || !data) { | ||
@@ -671,21 +231,19 @@ return console.log(util.format('\n failed to open the manifest at %s\n', filename).fail); | ||
var manifest = JSON.parse(data); | ||
validateManifestInternal(manifest, function(result) { | ||
if (result) { | ||
console.log('creating add-on'.info); | ||
librarian.addOns.create(user.id, user.authToken, manifest, function(err, addon) { | ||
if (err) return console.log('\n failed to create add-on'.fail); | ||
if (addon.err) { | ||
console.log('\n failed to create add-on'.fail); | ||
if (addon.code === 11000) { | ||
return console.log(util.format(' add-on \'%s\' already exists\n', manifest.id).fail); | ||
} | ||
return console.log(util.format(' error code: %s', addon.code).fail); | ||
} | ||
console.log('successfully created add-on.\n'.info); | ||
}); | ||
} else { | ||
console.log(' unable to create add-on because the specified manifest is invalid\n'.fail); | ||
librarian.addOns.create(user.id, user.authToken, manifest, function (err, addon) { | ||
if (err) { | ||
return console.log('\n failed to create add-on'.fail); | ||
} | ||
if (addon.err) { | ||
console.log('\n failed to create add-on'.fail); | ||
if (addon.code === 11000) { | ||
return console.log(util.format(' add-on \'%s\' already exists\n', manifest.id).fail); | ||
} | ||
return console.log(util.format(' error code: %s', addon.code).fail); | ||
} | ||
console.log('successfully created add-on.\n'.info); | ||
}); | ||
}); | ||
}; |
{ | ||
"name": "dia", | ||
"version": "0.1.2", | ||
"version": "0.2.0", | ||
"description": "Modulus add-on test utility.", | ||
@@ -9,4 +9,4 @@ "scripts": { | ||
"repository": { | ||
"type": "GIT", | ||
"url": "https://bitbucket.org/modulus/dia" | ||
"type": "git", | ||
"url": "https://github.com/onmodulus/dia.git" | ||
}, | ||
@@ -13,0 +13,0 @@ "keywords": [ |
@@ -1,2 +0,2 @@ | ||
Dia | ||
Dia [![Build Status](https://travis-ci.org/onmodulus/dia.svg?branch=master)](https://travis-ci.org/onmodulus/dia) | ||
=== | ||
@@ -6,4 +6,2 @@ | ||
[![NPM](https://nodei.co/npm/dia.png?compact=true)](https://nodei.co/npm/dia/) | ||
# Usage | ||
@@ -10,0 +8,0 @@ |
48207
19
1277
72
5