diff --git a/.gitignore b/.gitignore index e9e966b..151dfd1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store node_modules *.swp +test.js diff --git a/lib/api.js b/lib/api.js new file mode 100644 index 0000000..f83a4d3 --- /dev/null +++ b/lib/api.js @@ -0,0 +1,10 @@ + +module.exports = { + defaults: { + endpoint: 'https://surge.sh' + }, + login: require('./methods/login.js'), + logout: require('./methods/logout.js'), + whoami: require('./methods/whoami.js'), + deploy: require('./methods/deploy.js'), +} diff --git a/lib/methods/deploy.js b/lib/methods/deploy.js new file mode 100644 index 0000000..0069c7c --- /dev/null +++ b/lib/methods/deploy.js @@ -0,0 +1,132 @@ +var projectInfo = require('./util/projectInfo.js') +var domainInfo = require('./util/domainInfo.js') +var surgePkg = require(__dirname + "/../../package.json") +var EventEmitter = require('events').EventEmitter + +var url = require("url") +var request = require("request") +var fsReader = require('fstream-ignore') +var tar = require('tar') +var zlib = require('zlib') +var ignore = require("surge-ignore") +var split = require("split") + +module.exports = function (options) { + + var defaults = this.defaults + var opts = { + project: options.project || false, + token: options.token || false, + domain: options.domain || false, + add: options.add || false, + rem: options.rem || false, + build: options.build || false + } + + var emitter = new EventEmitter(); + + var tick = function tick (d) { + + var payload; + + try { + payload = JSON.parse(d.toString()) + } catch(e) { + return; + } + + if (payload.hasOwnProperty("type") && payload.type === "error") { + emitter.emit('error', payload.error) + } + + if (payload.hasOwnProperty("type") && payload.type === "users") { + // helpers.log("users:".grey, payload.users.join(", ")) + } + + if (payload.hasOwnProperty("type") && payload.type === "subscription") { + // console.log(payload) + } + + if (payload.hasOwnProperty("type") && payload.type === "collect") { + emitter.emit('error', + "Project requires the " + + payload.plan.name + " plan. " + + ("$" + (payload.plan.amount / 100) + "/mo") + " (cancel anytime)." + ) + } + + if (payload.hasOwnProperty("type") && payload.type === "progress") { + emitter.emit('progress', { + file: payload.file, + percent: (payload.written / payload.total) * 100, + written: payload.written, + total: payload.total, + type: payload.id, + payload: payload + }) + } + } + + + projectInfo(opts.project) + .then(function (projInfo) { + + var urlInfo = domainInfo(opts.domain); + + var headers = { + "version" : surgePkg.version, + "file-count": projInfo.fileCount, + "project-size": projInfo.projectSize + } + if (opts.add) headers['add'] = opts.add; + if (opts.rem) headers['rem'] = opts.rem; + if (opts.build) headers['build'] = opts.build; + if (urlInfo.ssl) headers['ssl'] = urlInfo.ssl; + + // create upload + var uri = url.resolve(defaults.endpoint, urlInfo.domain) + var handshake = request.put(uri, { headers: headers }) + + // apply basic auth + handshake.auth("token", opts.token, true) + + // catch errors + handshake.on('error', function (e) { + emitter.emit('error', e) + }) + + // split replies on new line + handshake.pipe(split()) + + // output result + handshake.on("data", tick) + + // done + handshake.on("end", function () { + emitter.emit('end') + }) + + handshake.on("response", function(rsp){ + if (rsp.statusCode == 403) { + emitter.emit('error', 'Aborted - you must be granted access to publish to, ' + urlInfo.domain) + } + }) + + // Read Project + var project = fsReader({ 'path': opts.project, ignoreFiles: [".surgeignore"] }) + + // we always ignore .git directory + project.addIgnoreRules(ignore) + + // chain all this together... + project + .pipe(tar.Pack()) + .pipe(zlib.Gzip()) + .pipe(handshake) + + + }) + + return emitter + +} diff --git a/lib/methods/login.js b/lib/methods/login.js new file mode 100644 index 0000000..de8b17a --- /dev/null +++ b/lib/methods/login.js @@ -0,0 +1,23 @@ +var helpers = require('../middleware/util/helpers.js') +var localCreds = require('../middleware/util/creds.js') + +module.exports = function (email, password, cb) { + var defaults = this.defaults; + var creds = new Promise(function (resolve, reject) { + helpers.fetchToken(defaults.endpoint)(email, password, function(err, obj){ + if (err) reject(err); + else resolve(localCreds(defaults.endpoint).set(obj.email, obj.token)) + }) + }) + + if (typeof cb === 'function') { + creds.then(function (c) { + cb(null, c) + }) + .catch(function (e) { + cb(e, null) + }) + } + + return creds +} diff --git a/lib/methods/logout.js b/lib/methods/logout.js new file mode 100644 index 0000000..ff2a478 --- /dev/null +++ b/lib/methods/logout.js @@ -0,0 +1,5 @@ +var localCreds = require('../middleware/util/creds.js') + +module.exports = function () { + localCreds(this.defaults.endpoint).set(null) +} diff --git a/lib/methods/util/domainInfo.js b/lib/methods/util/domainInfo.js new file mode 100644 index 0000000..c2ff79b --- /dev/null +++ b/lib/methods/util/domainInfo.js @@ -0,0 +1,21 @@ +var url = require('url') +var helpers = require('../../middleware/util/helpers.js') + +module.exports = function (domain) { + + var u = url.parse(domain) + var domainInfo = { + ssl: false, + domain: domain + } + + if (u.protocol !== null) { + if (u.protocol == 'https:') domainInfo.ssl = true + if (u.protocol == 'http:') domainInfo.ssl = false + domainInfo.domain = u.hostname + } + + if (!helpers.validDomain(domainInfo.domain)) domainInfo.domain = false + + return domainInfo +} diff --git a/lib/methods/util/projectInfo.js b/lib/methods/util/projectInfo.js new file mode 100644 index 0000000..1dddabc --- /dev/null +++ b/lib/methods/util/projectInfo.js @@ -0,0 +1,26 @@ +var ignore = require("surge-ignore") +var fsReader = require('fstream-ignore') +var fs = require("fs") + +module.exports = function (path) { + return new Promise(function (resolve, reject) { + var projectSize = 0; + var fileCount = 0 + var project = fsReader({ 'path': path, ignoreFiles: [".surgeignore"] }) + project.addIgnoreRules(ignore) + + project.on("child", function (c) { + fs.lstat(c.path, function(err, stats) { + projectSize += stats.size + if (!stats.isDirectory()) fileCount++ + }) + }).on("close", function() { + resolve({ + projectSize: projectSize, + fileCount: fileCount + }) + }).on("error", function (err) { + reject(err) + }) + }) +} diff --git a/lib/methods/whoami.js b/lib/methods/whoami.js new file mode 100644 index 0000000..5f51316 --- /dev/null +++ b/lib/methods/whoami.js @@ -0,0 +1,5 @@ +var localCreds = require('../middleware/util/creds.js') + +module.exports = function () { + return localCreds(this.defaults.endpoint).get() +}