helpers.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. var fs = require('fs')
  2. var path = require('path')
  3. var mime = require('mime')
  4. var terraform = require('candlewax')
  5. var fse = require('fs-extra')
  6. var envy = require('envy-json')
  7. /**
  8. *
  9. * Normalize Url
  10. *
  11. * - removes querystring
  12. * - removes extra slashes
  13. * - changes `/` to `/index.html`
  14. */
  15. exports.normalizeUrl = function(url){
  16. // take off query string
  17. var base = unescape(url.split('?')[0])
  18. /**
  19. * Normalize Path
  20. *
  21. * Note: This converts unix paths to windows path on windows
  22. * (not sure if this is a good thing)
  23. */
  24. var file_path = path.normalize(base)
  25. // index.html support
  26. if (path.sep == file_path[file_path.length - 1]) file_path += 'index.html'
  27. return file_path
  28. }
  29. /**
  30. *
  31. * Mime Type
  32. *
  33. * returns type of the file
  34. *
  35. * TODO: reference ext from terraform
  36. */
  37. exports.mimeType = function(source){
  38. var ext = path.extname(source)
  39. if(['.jade', '.md', '.ejs'].indexOf(ext) !== -1){
  40. return mime.lookup('html')
  41. }else if(['.less', '.styl', '.scss', '.sass'].indexOf(ext) !== -1){
  42. return mime.lookup('css')
  43. } else if (['.js', '.coffee'].indexOf(ext) !== -1) {
  44. return mime.lookup('js')
  45. } else {
  46. return mime.lookup(source)
  47. }
  48. }
  49. /**
  50. *
  51. * Walk directory for files
  52. *
  53. * recursive function that returns the directory tree
  54. * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search
  55. *
  56. */
  57. var walk = function(dir, done) {
  58. var results = []
  59. fs.readdir(dir, function(err, list) {
  60. if (err){
  61. return done(err)
  62. }
  63. var pending = list.length
  64. if (!pending) return done(null, results);
  65. list.forEach(function(file) {
  66. file = path.resolve(dir, file)
  67. fs.stat(file, function(err, stat) {
  68. if (stat && stat.isDirectory()) {
  69. walk(file, function(err, res) {
  70. results = results.concat(res)
  71. if (!--pending) done(null, results)
  72. })
  73. } else {
  74. results.push(file)
  75. if (!--pending) done(null, results)
  76. }
  77. })
  78. })
  79. })
  80. }
  81. /**
  82. *
  83. * Fetch all the file paths for a directory.
  84. * returns and array of all the relative paths.
  85. *
  86. */
  87. exports.ls = function(dir, callback) {
  88. walk(dir, function(err, results){
  89. var files = []
  90. results.map(function(file){ files.push(path.relative(dir, file)) })
  91. callback(null, files)
  92. })
  93. }
  94. /**
  95. * Setup
  96. *
  97. * This is the style and configuration of a Harp Application.
  98. * returns object with contents of Harp.json and application style
  99. *
  100. * {
  101. * "projectPath" : "/path/to/app",
  102. * "publicPath" : "/path/to/app/public",
  103. * "config" : { ... }
  104. * }
  105. */
  106. exports.setup = function(projectPath, env){
  107. if(!env) env = "development"
  108. try{
  109. var configPath = path.join(projectPath, "harp.json")
  110. var contents = fs.readFileSync(configPath).toString()
  111. var publicPath = path.join(projectPath, "public")
  112. }catch(e){
  113. try{
  114. var configPath = path.join(projectPath, "_harp.json")
  115. var contents = fs.readFileSync(configPath).toString()
  116. var publicPath = projectPath
  117. }catch(e){
  118. var contents = "{}"
  119. var publicPath = projectPath
  120. }
  121. }
  122. // not sure what this does anymore.
  123. if(!contents || contents.replace(/^\s\s*/, '').replace(/\s\s*$/, '') == ''){
  124. contents = '{}'
  125. }
  126. // attempt to parse the file
  127. try{
  128. var cfg = JSON.parse(contents)
  129. }catch(e){
  130. e.source = "JSON"
  131. e.dest = "CONFIG"
  132. e.message = e.message
  133. e.filename = configPath
  134. e.stack = contents
  135. e.lineno = -1
  136. throw new terraform.helpers.TerraformError(e)
  137. }
  138. if(!cfg.hasOwnProperty('globals')) cfg['globals'] = {}
  139. cfg.globals.environment = process.env.NODE_ENV || env
  140. // replace values that look like environment variables
  141. // e.g. '$foo' -> process.env.foo
  142. cfg = envy(cfg)
  143. return {
  144. projectPath : projectPath,
  145. publicPath : publicPath,
  146. config : cfg
  147. }
  148. }
  149. /**
  150. *
  151. * Template for outputing Less errors.
  152. *
  153. */
  154. exports.cssError = function(error){
  155. var body = '' +
  156. 'body{' +
  157. 'margin:0;' +
  158. '}' +
  159. 'body:before {' +
  160. 'display: block;'+
  161. 'white-space: pre;' +
  162. 'content: "'+ error.error.source +' -> ' + error.error.dest + ' (' + error.error.message + ') ' + error.error.filename + '";'+
  163. 'color: #444;'+
  164. 'background-color: #fefe96;' +
  165. 'padding: 40px 40px;'+
  166. 'margin: 0;'+
  167. 'font-family: monospace;'+
  168. 'font-size: 14px;'+
  169. '}'
  170. return body
  171. }
  172. /**
  173. *
  174. * Will Collide
  175. *
  176. * Returns true if first path is in the line of fire of the second path.
  177. * ie: if we delete the second path will the first path be affected?
  178. */
  179. var willCollide = exports.willCollide = function(projectPath, outputPath){
  180. var projectPath = path.resolve(projectPath)
  181. var outputPath = path.resolve(outputPath)
  182. var relativePath = path.relative(projectPath, outputPath)
  183. var arr = relativePath.split(path.sep)
  184. var result = true;
  185. arr.forEach(function(i){
  186. if(i !== "..") result = false
  187. })
  188. /**
  189. * for @kennethormandy ;)
  190. */
  191. if ([path.sep, "C:\\"].indexOf(outputPath) !== -1) result = true
  192. /**
  193. * For #400
  194. */
  195. if (projectPath === outputPath) result = true
  196. return result
  197. }
  198. /**
  199. *
  200. * Will Allow
  201. *
  202. * Returns `true` if we feel projectPath is safe from the output path.
  203. * For this to be the case. The outputPath must live only one directory
  204. * back from the projectPath and the projectPath must live in a directory
  205. * starting with an underscore.
  206. */
  207. exports.willAllow = function(projectPath, outputPath){
  208. var projectPath = path.resolve(projectPath)
  209. var outputPath = path.resolve(outputPath)
  210. var relativePath = path.relative(projectPath, outputPath)
  211. var arr = relativePath.split(path.sep)
  212. if(willCollide(projectPath, outputPath)){
  213. if(relativePath === ".."){
  214. if(projectPath.split(path.sep)[projectPath.split(path.sep).length - 1][0] == "_"){
  215. return true
  216. }else{
  217. return false
  218. }
  219. }else{
  220. return false
  221. }
  222. }else{
  223. return true
  224. }
  225. }
  226. /**
  227. * Prime
  228. * (Disk I/O)
  229. *
  230. * Cleans out a directory but ignores one (optionally).
  231. *
  232. * primePath: Absolute Path
  233. * options: Object
  234. * ignore: Absolute Path || Relative (to delete)
  235. *
  236. * This is a powerful Function so take it seriously.
  237. *
  238. */
  239. exports.prime = function(primePath, options, callback){
  240. if(!callback){
  241. callback = options
  242. options = {}
  243. }
  244. /**
  245. * Options (only one)
  246. */
  247. var ignorePath = options.ignore
  248. ? path.resolve(primePath, options.ignore)
  249. : null
  250. // Absolute paths are predictable.
  251. var primePath = path.resolve(primePath)
  252. fse.mkdirp(primePath, function(){
  253. fse.readdir(primePath, function(error, contents){
  254. /**
  255. * Delete each item in the directory in parallel. Thanks Ry!
  256. */
  257. if(contents.length == 0) return callback()
  258. var total = contents.length
  259. var count = 0
  260. contents.forEach(function(i){
  261. var filePath = path.resolve(primePath, i)
  262. var gitRegExp = new RegExp(/^.git/)
  263. /**
  264. * We leave `.git`, `.gitignore`, and project path.
  265. */
  266. if(filePath === ignorePath || i.match(gitRegExp)){
  267. count++
  268. if(count == total) callback()
  269. }else{
  270. fse.remove(filePath, function(err){
  271. count++
  272. if(count == total) callback()
  273. })
  274. }
  275. })
  276. })
  277. })
  278. }
  279. /**
  280. * Stacktrace
  281. *
  282. * Formats a stactrace
  283. *
  284. *
  285. * This is a powerful Function so take it seriously.
  286. *
  287. */
  288. exports.stacktrace = function(str, options){
  289. var lineno = options.lineno || -1
  290. var context = options.context || 8
  291. var context = context = context / 2
  292. var lines = ('\n' + str).split('\n')
  293. var start = Math.max(lineno - context, 1)
  294. var end = Math.min(lines.length, lineno + context)
  295. if(lineno === -1) end = lines.length
  296. var pad = end.toString().length
  297. var context = lines.slice(start, end).map(function(line, i){
  298. var curr = i + start
  299. return (curr == lineno ? ' > ' : ' ')
  300. + Array(pad - curr.toString().length + 1).join(' ')
  301. + curr
  302. + '| '
  303. + line
  304. }).join('\n')
  305. return context
  306. }