UNPKG

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