:sparkles:🎨 Add project zip download and remove lib/app folder

This commit is contained in:
brunosimon
2017-07-30 16:53:55 +02:00
parent 55c6204188
commit 46b9dac6f0
14 changed files with 1496 additions and 513 deletions
+1 -1
View File
@@ -2,5 +2,5 @@
'use strict' 'use strict'
const App = require('../lib/app') const App = require('../lib')
const app = new App() const app = new App()
-62
View File
@@ -1,62 +0,0 @@
'use strict'
// Dependencies
const slug = require('slug')
const Files = require('./files.js')
class Project
{
constructor(_config, _options)
{
this.config = _config
this.setName(_options.name)
this.setSocket()
this.files = new Files(this.config, { projectSocket: this.projectSocket })
this.date = new Date()
}
setName(_name)
{
this.name = _name
this.slug = slug(this.name, { lower: true })
}
setSocket()
{
// Save original socket and create a channel for this specific project
this.projectSocket = this.config.socket.of('/project/' + this.slug)
// Connection event
this.projectSocket.on('connection', (socket) =>
{
this.projectSocket.emit('update_project', this.describe())
// Debug
if(this.config.debug >= 1)
{
console.log('project > socket'.green.bold + ' - ' + 'connection'.cyan + ' - ' + socket.id.cyan)
}
})
}
describe()
{
// Set up
const result = {}
result.name = this.name
result.files = this.files.describe()
result.date = this.date
return result
}
destructor()
{
this.projectSocket.emit('destruct')
}
}
module.exports = Project
-224
View File
@@ -1,224 +0,0 @@
'use strict'
class EventEmitter
{
/**
* Constructor
*/
constructor()
{
this.callbacks = {}
this.callbacks.base = {}
}
/**
* On
*/
on(_names, callback)
{
const that = this
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong names')
return false
}
if(typeof callback === 'undefined')
{
console.warn('wrong callback')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach(function(_name)
{
// Resolve name
const name = that.resolveName(_name)
// Create namespace if not exist
if(!(that.callbacks[ name.namespace ] instanceof Object))
that.callbacks[ name.namespace ] = {}
// Create callback if not exist
if(!(that.callbacks[ name.namespace ][ name.value ] instanceof Array))
that.callbacks[ name.namespace ][ name.value ] = []
// Add callback
that.callbacks[ name.namespace ][ name.value ].push(callback)
})
return this
}
/**
* Off
*/
off(_names)
{
const that = this
// Errors
if(typeof _names === 'undefined' || _names === '')
{
console.warn('wrong name')
return false
}
// Resolve names
const names = this.resolveNames(_names)
// Each name
names.forEach(function(_name)
{
// Resolve name
const name = that.resolveName(_name)
// Remove namespace
if(name.namespace !== 'base' && name.value === '')
{
delete that.callbacks[ name.namespace ]
}
// Remove specific callback in namespace
else
{
// Default
if(name.namespace === 'base')
{
// Try to remove from each namespace
for(const namespace in that.callbacks)
{
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
{
delete that.callbacks[ namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(that.callbacks[ namespace ]).length === 0)
delete that.callbacks[ namespace ]
}
}
}
// Specified namespace
else if(that.callbacks[ name.namespace ] instanceof Object && that.callbacks[ name.namespace ][ name.value ] instanceof Array)
{
delete that.callbacks[ name.namespace ][ name.value ]
// Remove namespace if empty
if(Object.keys(that.callbacks[ name.namespace ]).length === 0)
delete that.callbacks[ name.namespace ]
}
}
})
return this
}
/**
* Trigger
*/
trigger(_name, _args)
{
// Errors
if(typeof _name === 'undefined' || _name === '')
{
console.warn('wrong name')
return false
}
const that = this
let finalResult = null
let result = null
// Default args
const args = !(_args instanceof Array) ? [] : _args
// Resolve names (should on have one event)
let name = this.resolveNames(_name)
// Resolve name
name = this.resolveName(name[ 0 ])
// Default namespace
if(name.namespace === 'base')
{
// Try to find callback in each namespace
for(const namespace in that.callbacks)
{
if(that.callbacks[ namespace ] instanceof Object && that.callbacks[ namespace ][ name.value ] instanceof Array)
{
that.callbacks[ namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(that, args)
if(typeof finalResult === 'undefined')
{
finalResult = result
}
})
}
}
}
// Specified namespace
else if(this.callbacks[ name.namespace ] instanceof Object)
{
if(name.value === '')
{
console.warn('wrong name')
return this
}
that.callbacks[ name.namespace ][ name.value ].forEach(function(callback)
{
result = callback.apply(that, args)
if(typeof finalResult === 'undefined')
finalResult = result
})
}
return finalResult
}
/**
* Resolve names
*/
resolveNames(_names)
{
let names = _names
names = names.replace(/[^a-zA-Z0-9 ,\/.]/g, '')
names = names.replace(/[,\/]+/g, ' ')
names = names.split(' ')
return names
}
/**
* Resolve name
*/
resolveName(name)
{
const newName = {}
const parts = name.split('.')
newName.original = name
newName.value = parts[ 0 ]
newName.namespace = 'base' // Base namespace
// Specified namespace
if(parts.length > 1 && parts[ 1 ] !== '')
{
newName.namespace = parts[ 1 ]
}
return newName
}
}
module.exports = EventEmitter
View File
View File
View File
+6 -3
View File
@@ -11,7 +11,7 @@ const colors = require('colors')
const opener = require('opener') const opener = require('opener')
const fs = require('fs') const fs = require('fs')
const path = require('path') const path = require('path')
const packageJson = require('../../package.json') const packageJson = require('../package.json')
/** /**
* App class * App class
@@ -186,9 +186,9 @@ class App
// Start project // Start project
socket.on('start_project', (data) => socket.on('start_project', (data) =>
{ {
project = this.projects.createProject(data.name) project = this.projects.createProject(data)
this.site.createProject(project.slug, data.path) this.site.createProjectRoute(project)
// Debug // Debug
if(this.config.debug >= 1) if(this.config.debug >= 1)
@@ -214,6 +214,7 @@ class App
socket.on('update_file', (data) => socket.on('update_file', (data) =>
{ {
project.files.createVersion(data.path, data.content) project.files.createVersion(data.path, data.content)
project.zipNeedsUpdate = true
// Debug // Debug
if(this.config.debug >= 1) if(this.config.debug >= 1)
@@ -230,6 +231,7 @@ class App
socket.on('create_file', (data) => socket.on('create_file', (data) =>
{ {
project.files.create(data.path, data.content) project.files.create(data.path, data.content)
project.zipNeedsUpdate = true
// Debug // Debug
if(this.config.debug >= 1) if(this.config.debug >= 1)
@@ -246,6 +248,7 @@ class App
socket.on('delete_file', (data) => socket.on('delete_file', (data) =>
{ {
project.files.delete(data.path) project.files.delete(data.path)
project.zipNeedsUpdate = true
// Debug // Debug
if(this.config.debug >= 1) if(this.config.debug >= 1)
View File
+112
View File
@@ -0,0 +1,112 @@
'use strict'
// Dependencies
const slug = require('slug')
const Files = require('./files.js')
const AdmZip = require('adm-zip')
const glob = require('glob')
const fs = require('fs')
const path = require('path')
class Project
{
constructor(_config, _options)
{
this.config = _config
this.zipNeedsUpdate = true
this.name = _options.name
this.path = _options.path
this.excludeRegex = new RegExp(_options.excludeRegex)
this.slug = slug(this.name, { lower: true })
this.setSocket()
this.files = new Files(this.config, { projectSocket: this.projectSocket })
this.date = new Date()
}
setSocket()
{
// Save original socket and create a channel for this specific project
this.projectSocket = this.config.socket.of('/project/' + this.slug)
// Connection event
this.projectSocket.on('connection', (socket) =>
{
this.projectSocket.emit('update_project', this.describe())
// Debug
if(this.config.debug >= 1)
{
console.log('project > socket'.green.bold + ' - ' + 'connection'.cyan + ' - ' + socket.id.cyan)
}
})
}
getZipBuffer()
{
// Already zipped and don't need update
if(!this.zipNeedsUpdate)
{
// Debug
if(this.config.debug >= 1)
{
console.log('project > zip'.green.bold + ' - ' + 'from cache'.cyan)
}
return this.zipBuffer
}
// Search files with glob
const files = glob.sync(this.path + '/**', { dot: true })
// Create a zip file
const zip = new AdmZip()
// Add files to zip
for(const _file of files)
{
const stats = fs.lstatSync(_file)
// Ignore if folder or exluded
if(stats.isFile() && !_file.match(this.excludeRegex))
{
const basename = path.basename(_file)
const relativePath = _file.replace(basename, '').replace(this.path, '')
zip.addLocalFile(_file, relativePath)
}
}
// Create and return zip buffer
this.zipBuffer = zip.toBuffer()
this.zipNeedsUpdate = false
// Debug
if(this.config.debug >= 1)
{
console.log('project > zip'.green.bold + ' - ' + 'create'.cyan)
}
return this.zipBuffer
}
describe()
{
// Set up
const result = {}
result.name = this.name
result.files = this.files.describe()
result.date = this.date
return result
}
destructor()
{
this.projectSocket.emit('destruct')
}
}
module.exports = Project
+2 -2
View File
@@ -29,10 +29,10 @@ class Projects
}) })
} }
createProject(_name) createProject(_options)
{ {
// Create project // Create project
const project = new Project(this.config, { name: _name }) const project = new Project(this.config, _options)
let sameNameProject = this.all[project.slug] let sameNameProject = this.all[project.slug]
// Try to found same name project // Try to found same name project
+23 -4
View File
@@ -101,14 +101,33 @@ class Site
/** /**
* Create project * Create project
*/ */
createProject(_slug, _path) createProjectRoute(_project)
{ {
this.express.get('/' + _slug, (request, response) => // Project route
this.express.get('/' + _project.slug, (request, response) =>
{ {
response.send('<a href="/' + _slug + '/files/empty.txt" download>download</a>') response.send(`
<a href="/${_project.slug}/download" download>download project</a>
<a href="/${_project.slug}/files/empty.txt" download>download file</a>
`)
}) })
this.express.use('/' + _slug + '/files', express.static(_path)) // Project download route
this.express.get('/' + _project.slug + '/download', (request, response) =>
{
// Get zip buffer
const zipBuffer = _project.getZipBuffer()
// Send
response.writeHead(200, {
'Content-Type': 'application/zip',
'Content-Length': zipBuffer.length
})
response.write(zipBuffer, 'binary')
response.end(null, 'binary')
})
this.express.use('/' + _project.slug + '/files', express.static(_project.path))
} }
} }
+13 -9
View File
@@ -19,6 +19,7 @@ class Watcher
this.config = _config this.config = _config
this.path = process.cwd() this.path = process.cwd()
this.setExcludeRegex()
this.setSocket() this.setSocket()
this.watch() this.watch()
} }
@@ -34,7 +35,7 @@ class Watcher
// Connect event // Connect event
this.socket.on('connect', () => this.socket.on('connect', () =>
{ {
this.socket.emit('start_project', { name: this.config.name, path: this.path }) this.socket.emit('start_project', { name: this.config.name, path: this.path, excludeRegex: '' + this.excludeRegex })
// Debug // Debug
if(this.config.debug) if(this.config.debug)
@@ -54,13 +55,9 @@ class Watcher
}) })
} }
/** setExcludeRegex()
* Watch
* Listen to modifications on files and folders
*/
watch()
{ {
// Create ignored regex // Exclude files regex
const regexs = [] const regexs = []
const Minimatch = require('minimatch').Minimatch const Minimatch = require('minimatch').Minimatch
@@ -77,14 +74,21 @@ class Watcher
regexs.push(regex) regexs.push(regex)
} }
const regex = new RegExp(regexs.join('|')) this.excludeRegex = new RegExp(regexs.join('|'))
}
/**
* Watch
* Listen to modifications on files and folders
*/
watch()
{
// Set up // Set up
this.watcher = chokidar.watch( this.watcher = chokidar.watch(
this.path, this.path,
{ {
// ignored: /[\/\\]\./, // ignored: /[\/\\]\./,
ignored: regex, ignored: this.excludeRegex,
ignoreInitial: !this.config.initialSend ignoreInitial: !this.config.initialSend
} }
) )
+1336 -207
View File
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -7,7 +7,7 @@
}, },
"main": "bin/index.js", "main": "bin/index.js",
"scripts": { "scripts": {
"demo-folder": "cd test/demo-folder && node ../../bin 'Demo folder' --debug 1 -i", "demo-folder": "cd test/demo-folder && node ../../bin 'Demo folder'",
"test-project": "node ./bin --debug 1 --test" "test-project": "node ./bin --debug 1 --test"
}, },
"repository": { "repository": {
@@ -21,10 +21,12 @@
}, },
"homepage": "https://github.com/brunosimon/keppler", "homepage": "https://github.com/brunosimon/keppler",
"dependencies": { "dependencies": {
"adm-zip": "^0.4.7",
"chokidar": "^1.5.1", "chokidar": "^1.5.1",
"colors": "^1.1.2", "colors": "^1.1.2",
"diff": "^2.2.3", "diff": "^2.2.3",
"express": "^4.13.4", "express": "^4.13.4",
"glob": "^7.1.2",
"helmet": "^2.1.0", "helmet": "^2.1.0",
"ip": "^1.1.3", "ip": "^1.1.3",
"mime": "^1.3.4", "mime": "^1.3.4",