├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── README.md ├── SECURITY.md ├── bin └── harp ├── hooks ├── pre-commit └── pre-push ├── lib ├── default_boilerplate │ ├── 404.jade │ ├── _layout.jade │ ├── index.jade │ └── main.less ├── helpers.js ├── index.js ├── middleware.js ├── skin.js └── templates │ ├── 404.jade │ ├── _layout.jade │ ├── css │ └── harp.css │ ├── error.jade │ ├── index.jade │ └── start.jade ├── package-lock.json ├── package.json └── test ├── 404.html ├── apps ├── app-style-framework │ └── harp.json ├── app-style-implicit │ └── .gitignore ├── app-style-root │ └── _harp.json ├── auth │ └── single │ │ ├── _harp.json │ │ └── index.md ├── basic │ ├── README.md │ ├── harp.json │ └── public │ │ ├── 404.jade │ │ ├── _data.json │ │ ├── _layout.jade │ │ ├── about.jade │ │ ├── articles │ │ ├── _data.json │ │ ├── hello-world.jade │ │ ├── index.jade │ │ ├── with spaces.md │ │ └── you-half-assed-it.jade │ │ ├── basic.html │ │ ├── css │ │ ├── _nav.scss │ │ └── main.scss │ │ ├── current.json.jade │ │ ├── feed.atom.jade │ │ ├── globals.json.jade │ │ ├── index.jade │ │ └── shared │ │ ├── _debug.jade │ │ └── _nav.jade ├── cjs │ ├── framework │ │ ├── harp.json │ │ └── public │ │ │ └── bundle.cjs │ └── root │ │ ├── bundle.cjs │ │ └── index.html ├── compile │ ├── basic │ │ ├── README.md │ │ ├── harp.json │ │ └── public │ │ │ ├── 404.jade │ │ │ ├── _data.json │ │ │ ├── _layout.jade │ │ │ ├── about.jade │ │ │ ├── articles │ │ │ ├── _data.json │ │ │ ├── hello-world.jade │ │ │ ├── index.jade │ │ │ ├── with spaces.md │ │ │ └── you-half-assed-it.jade │ │ │ ├── basic.html │ │ │ ├── css │ │ │ ├── _nav.scss │ │ │ ├── _partials │ │ │ │ ├── _more.scss │ │ │ │ └── some.scss │ │ │ └── main.scss │ │ │ ├── index.jade │ │ │ └── shared │ │ │ ├── _debug.jade │ │ │ └── _nav.jade │ ├── err │ │ ├── a.jade │ │ ├── b.scss │ │ └── npm-debug.log │ └── root │ │ ├── _harp.json │ │ └── www │ │ └── foo ├── envy │ └── harp.json ├── err-invalid-and-valid-stylsheets │ ├── harp.json │ └── public │ │ ├── _layout.jade │ │ ├── css │ │ ├── _nav.styl │ │ ├── main.styl │ │ └── other.styl │ │ ├── index.jade │ │ └── shared │ │ └── _nav.jade ├── err-invalid-config │ ├── harp.json │ └── public │ │ └── index.jade ├── err-invalid-data │ └── public │ │ └── _data.json ├── err-invalid-vars │ ├── harp.json │ └── public │ │ ├── bad.less │ │ ├── index.jade │ │ ├── main.less │ │ └── style.jade ├── err-missing-404 │ ├── harp.json │ └── public │ │ ├── _layout.jade │ │ ├── css │ │ ├── _nav.less │ │ └── main.less │ │ ├── index.jade │ │ └── shared │ │ ├── _debug.jade │ │ └── _nav.jade ├── err-missing-public │ └── harp.json ├── fallbacks │ └── two-hundy │ │ ├── nested │ │ └── app │ │ │ └── 200.jade │ │ ├── plain │ │ └── 200.html │ │ └── processed │ │ └── 200.jade ├── headers │ ├── invalid-coffee.coffee │ ├── invalid-ejs.ejs │ ├── invalid-jade.jade │ ├── invalid-less.less │ ├── invalid-sass.sass │ ├── invalid-scss.scss │ ├── invalid-styl.styl │ ├── valid-coffee.coffee │ ├── valid-css.css │ ├── valid-ejs.ejs │ ├── valid-html.html │ ├── valid-jade.jade │ ├── valid-js.js │ ├── valid-less.less │ ├── valid-markdown.md │ ├── valid-sass.sass │ ├── valid-scss.scss │ └── valid-styl.styl ├── multihost │ ├── app.harp.io │ │ └── index.jade │ ├── app1.subdomain.com │ │ ├── .gitkeep │ │ └── index.jade │ ├── app1.subdomain.net │ │ ├── .gitkeep │ │ └── index.jade │ └── app2.subdomain.io │ │ ├── .gitkeep │ │ └── index.jade ├── plain │ ├── framework-style │ │ ├── harp.json │ │ └── public │ │ │ ├── 404.html │ │ │ ├── hello.txt │ │ │ └── index.html │ └── root-style │ │ ├── 404.html │ │ ├── hello.txt │ │ └── index.html ├── security │ └── _secret.txt └── slash-indifference │ ├── directory │ └── index.html │ └── file.html ├── auth.js ├── basic.js ├── cjs.js ├── compile.js ├── errors.js ├── fallbacks.js ├── headers.js ├── helpers.js ├── invalid.js ├── lib.js ├── plain.js ├── security.js └── slash-indifference.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | hooks 3 | CONTRIBUTING.md 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Thanks for taking the time to help make harp better. Here are some guidelines that will highly increase the chances of your fix or feature request from being accepted. 4 | 5 | ### Bug Fixes 6 | 7 | If you find a bug you would like fixed. Open up a [ticket](https://github.com/sintaxi/harp/issues/new) with a detailed description of the bug and the expected behaviour. If you would like to fix the problem yourself please do the following steps. 8 | 9 | 1. Fork it. 10 | 2. Create a branch (`git checkout -b fix-for-that-thing`) 11 | 3. Commit a failing test (`git commit -am "adds a failing test to demonstrate that thing"`) 12 | 3. Commit a fix that makes the test pass (`git commit -am "fixes that thing"`) 13 | 4. Push to the branch (`git push origin fix-for-that-thing`) 14 | 5. Open a [Pull Request](https://github.com/sintaxi/harp/pulls) 15 | 16 | Please keep your branch up to date by rebasing upstream changes from master. 17 | 18 | ### New Functionality 19 | 20 | If you wish to add new functionality to harp, please provide [@sintaxi](mailto:brock@sintaxi.com) and [@kennethormandy](mailto:kenneth@chloi.io) a harp application that demonstrates deficiency in current design or desired additional behaviour. Its best if you explaining the problem you are trying to solve, not just the solution you want. You may also submit a pull request with the steps above. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Harp 2 | 3 | > zero-configuration web server with built in pre-processing 4 | 5 | ### What is Harp? 6 | 7 | Harp is a static web server that also serves Jade, Markdown, EJS, Less, Stylus, Sass, and CoffeeScript **as** HTML, CSS, and JavaScript without any configuration. It supports the beloved layout/partial paradigm and it has flexible metadata and global objects for traversing the file system and injecting custom data into templates. Optionally, Harp can also compile your project down to static assets for hosting behind any valid HTTP server. 8 | 9 | ### Why? 10 | 11 | Pre-compilers are becoming extremely powerful and shipping front-ends as static assets has many upsides. It's simple, it's easy to maintain, it's low risk, easy to scale, and requires low cognitive overhead. I wanted a lightweight web server that was powerful enough for me to abandon web frameworks for dead simple front-end publishing. 12 | 13 | ### Features 14 | 15 | - easy installation, easy to use 16 | - fast and lightweight 17 | - robust (clean urls, intelligent path redirects) 18 | - built in pre-processing 19 | - first-class layout and partial support 20 | - built in LRU caching in production mode 21 | - can export assets to HTML/CSS/JS 22 | - does not require a build steps or grunt task 23 | - fun to use 24 | 25 | ### Supported Pre-Processors 26 | 27 | - [EJS](https://ejs.co/) 28 | - [Jade](http://jade-lang.com/) 29 | - [Markdown](http://daringfireball.net/projects/markdown/) (Unsanitized) 30 | - [Sass (SCSS)](http://sass-lang.com/) 31 | - [Sass](http://sass-lang.com/) 32 | 33 | ### Resources 34 | 35 | - **Server Documentation** - [harpjs.com/docs/](http://harpjs.com/docs/) 36 | - **Source Code** - [github.com/sintaxi/harp](https://github.com/sintaxi/harp) 37 | 38 | Authored and maintained by [@sintaxi](http://twitter.com/sintaxi). 39 | 40 | --- 41 | 42 | ### Installation 43 | 44 | sudo npm install -g harp 45 | 46 | ### Quick Start 47 | 48 | Start Harp server by pointing oit 49 | 50 | mkdir ./public 51 | harp ./public 52 | 53 | Your Harp application is now running at [http://localhost:9000](http://localhost:9000) 54 | You can now fill your project with ejs, jade, md, sass, scss files to be processed autmatically. 55 | 56 | Compile your project... 57 | 58 | harp ./public ./build 59 | 60 | Yor dist folder is now ready to be published at a static host such as [Surge.sh](https://surge.sh) 61 | 62 | --- 63 | 64 | ## Documentation 65 | 66 | Harp can be used as a library or as a command line utility. 67 | 68 | ### CLI Usage 69 | 70 | ``` 71 | 72 | Harp 〜 Static Web Server v0.46.0 73 | 74 | USAGE 75 | harp serves project locally 76 | harp compiles for static host 77 | 78 | OPTIONS 79 | -p, --port 9000 server port to listen on 80 | -h, --host 0.0.0.0 server host to answer to 81 | -s, --silent false supresses logs 82 | -h, --help outputs this help message 83 | -v, --version outputs version of harp 84 | 85 | ╭───────────────────────────────────────────────────────────────────────────────╮ 86 | │ │ 87 | │ PROCESSING DATA │ 88 | │ .ejs -> .html _data.json - directory data │ 89 | │ .jade -> .html _data.js - dynamic build data │ 90 | │ .md -> .html │ 91 | │ .sass -> .css GENERATION │ 92 | │ .scss -> .css partial("_path/to/partial", { │ 93 | │ .cjs -> .js "title": "Hello World" │ 94 | │ .jsx -> .js }) │ 95 | │ │ 96 | ╰───────────────────────────────────────────────────────────────────────────────╯ 97 | 98 | ``` 99 | 100 | ### Lib Usage 101 | 102 | You may also use harp as a node library for compiling or running as a server. 103 | 104 | Serve up a harp application... 105 | 106 | ```js 107 | var harp = require("harp") 108 | harp.server(projectPath [,args]) 109 | server.listen(port, host) 110 | ``` 111 | 112 | **Or** compile harp application 113 | 114 | ```js 115 | var harp = require("harp") 116 | harp.compile(projectPath [,outputPath] [, callback]) 117 | ``` 118 | 119 | **Or** use as Connect/ExpressJS middleware 120 | 121 | ```js 122 | 123 | ``` 124 | 125 | ```js 126 | // Express 127 | var express = require("express"); 128 | var harp = require("harp"); 129 | var app = express(); 130 | 131 | app.use(express.static(__dirname + "/public")); 132 | app.use(harp.mount(__dirname + "/public")); 133 | 134 | app.listen(port, host) 135 | ``` 136 | 137 | 138 | ## License 139 | 140 | Copyright © 2012–2021 [Chloi Inc](http://chloi.io). All rights reserved. 141 | 142 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 143 | 144 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 145 | 146 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 147 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | ## Vulnerabilities in `harp` 2 | 3 | It is possible that `harp` or its dependent libraries contain vulnerabilities 4 | that would allow triggering unexpected or dangerous behavior with specially 5 | crafted inputs. 6 | 7 | ### What is a vulnerability? 8 | 9 | Since `harp` compiles web projects reading, writing, and deleting files is not 10 | unexpected behavior and therefore is not a vulnerability in `harp`. Please only 11 | submit file system conerns if you believe behaviour exists that is not intended 12 | by the design of the software. 13 | 14 | ### Reporting vulnerabilities 15 | 16 | Please email reports about any security related issues you find to 17 | `brock@sintaxi.com`. Please use a descriptive subject line for your report 18 | email. It's appreceated if you include a patch that fixes the security issue - 19 | though that is not required. 20 | 21 | In addition, please include the following information along with your report: 22 | 23 | * Your name and affiliation (if any). 24 | * A description of the technical details of the vulnerabilities. It is very 25 | important to include details on how how we can reproduce your findings. 26 | * An explanation who can exploit this vulnerability, and what they gain when 27 | doing so -- write an attack scenario. This will help us evaluate your report 28 | quickly, especially if the issue is complex. 29 | * Whether this vulnerability public or known to third parties. If it is, please 30 | provide details. 31 | 32 | If you believe that an existing (public) issue is security-related, please send 33 | an email to `brock@sintaxi.com`. The email should include the issue ID and a 34 | short description of why it should be handled according to this security policy. 35 | 36 | Once an issue is reported, `harp` uses the following process: 37 | 38 | * When a report is received, we will determine its severity. 39 | * Wherever possible, fixes are prepared for the last minor release of the two 40 | latest major releases, as well as the master branch. We will attempt to commit 41 | these fixes as soon as possible, and as close together as possible. 42 | -------------------------------------------------------------------------------- /bin/harp: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var argv = require('minimist')(process.argv.slice(2)) 4 | var pkg = require("../package.json") 5 | var colors = require("@colors/colors") 6 | var path = require("path") 7 | var boxt = require("boxt") 8 | 9 | 10 | var box = (...args) => console.log(boxt(...args).split("\n").join("\n ")); 11 | 12 | var help = function(){ 13 | 14 | var log = function(){ 15 | var arr = Array.prototype.slice.call(arguments); 16 | console.log.apply(this, [" "].concat(arr)) 17 | } 18 | 19 | var box = (...args) => console.log(boxt(...args).split("\n").join("\n ")); 20 | 21 | log() 22 | log("Harp".underline.blue, "〜".grey, pkg.description.brightYellow, "v".grey + pkg.version.grey) 23 | log() 24 | // log("Server | Generator | Bundler".grey) 25 | // log() 26 | log("USAGE".grey) 27 | log(" harp serves project locally") 28 | log(" harp compiles for static host") 29 | log() 30 | log("OPTIONS".grey) 31 | log(" -p, --port " +"9000".blue +" server port to listen on") 32 | log(" -h, --host " +"0.0.0.0".blue +" server host to answer to") 33 | log(" -s, --silent " +"false".grey +" supresses logs") 34 | // log(" -c, --cache " +"false".blue +" server memory cache") 35 | // log(" -t, --pretty " +"false".blue +" server/compile keep whitespace") 36 | log(" -h, --help outputs this help message") 37 | log(" -v, --version outputs version of harp") 38 | box( 39 | `${'PROCESSING'.grey } ${'DATA'.grey } 40 | ${'.ejs -> .html' } ${'_data.json - directory data' } 41 | ${'.jade -> .html' } ${'_data.js - dynamic build data' } 42 | ${'.md -> .html' } 43 | ${'.sass -> .css ' } ${'GENERATION'.grey } 44 | ${'.scss -> .css ' } ${'partial("_path/to/partial", {' } 45 | ${'.cjs -> .js ' } ${' "title": "Hello World"' } 46 | ${'.jsx -> .js ' } ${'})' }` 47 | , { color: 'grey', padding: 2, theme: 'round', align:"left"}); 48 | 49 | } 50 | 51 | 52 | var version = function(){ 53 | console.log("v" + pkg.version) 54 | } 55 | 56 | 57 | /** 58 | * version 59 | */ 60 | 61 | if (argv.version || argv.v) return version() 62 | 63 | 64 | /** 65 | * help 66 | */ 67 | 68 | if ((argv["_"].length === 0) || argv.help || argv.h) return help() 69 | 70 | 71 | 72 | 73 | 74 | var harp = require("../") 75 | 76 | 77 | var duration = function(d){ 78 | if (d[0] < 1){ 79 | var ms = d[1].toString().substring(0,3) 80 | return + ms + "ms" 81 | } else { 82 | var ms = d[1].toString().substring(0,1) 83 | return [d[0], ms].join(".") + "s" 84 | } 85 | } 86 | 87 | /** 88 | * Server 89 | */ 90 | 91 | if (argv["_"].length === 1){ 92 | const serverStart = process.hrtime() 93 | 94 | argv.log = !(argv.silent || argv.s) 95 | var projectPath = path.resolve(process.cwd(), argv["_"][0]) 96 | var host = argv.host || '0.0.0.0' 97 | var port = argv.port || 9000 98 | 99 | var server = harp.server(projectPath, argv) 100 | 101 | server.listen(port, host, function(error){ 102 | if (error) process.exit(1) 103 | 104 | var serverReady = process.hrtime(serverStart) 105 | 106 | box( 107 | `${ 'Harp'.blue } ${ ('v' + pkg.version).grey} 108 | ${ ('http://localhost:' + port + "/").underline.cyan } 109 | ${ duration(serverReady).grey }`, { align: "left", color: "grey", padding: 3 }) 110 | 111 | }) 112 | } 113 | 114 | 115 | /** 116 | * Compile 117 | */ 118 | 119 | if (argv["_"].length === 2){ 120 | argv.log = !(argv.silent || argv.s) 121 | 122 | var projectPath = path.resolve(process.cwd(), argv["_"][0]) 123 | var buildPath = path.resolve(process.cwd(), argv["_"][1]) 124 | 125 | console.log() 126 | console.log(" ", `Harp v${ pkg.version }`.yellow) 127 | console.log(" ", "Generating Site & Bundling Assets...".blue) 128 | if (argv.log) console.log() 129 | 130 | harp.compile(projectPath, buildPath, argv, function(errors, output){ 131 | if(errors) { 132 | console.log(JSON.stringify(errors, null, 2)) 133 | process.exit(1) 134 | } 135 | 136 | if (argv.log) console.log() 137 | console.log(" ", "Done!".green, `(${ duration(output.stats.duration) })`.grey) 138 | console.log() 139 | }) 140 | 141 | } 142 | 143 | -------------------------------------------------------------------------------- /hooks/pre-commit: -------------------------------------------------------------------------------- 1 | 2 | echo "" 3 | echo " #########################################################" 4 | echo " ## ##" 5 | echo " ## Hook (pre-commit) ##" 6 | echo " ## ##" 7 | echo " ## Rebuild package-lock.json.... ##" 8 | echo " ## (use --no-verify to skip) ##" 9 | echo " ## ##" 10 | echo " #########################################################" 11 | 12 | npm install --silent 13 | -------------------------------------------------------------------------------- /hooks/pre-push: -------------------------------------------------------------------------------- 1 | 2 | echo "" 3 | echo " #########################################################" 4 | echo " ## ##" 5 | echo " ## Hook (pre-push) ##" 6 | echo " ## ##" 7 | echo " ## Running local tests prior to push.... ##" 8 | echo " ## (use --no-verify to skip) ##" 9 | echo " ## ##" 10 | echo " #########################################################" 11 | 12 | npm test 13 | -------------------------------------------------------------------------------- /lib/default_boilerplate/404.jade: -------------------------------------------------------------------------------- 1 | h1 404 2 | h3 Whoops. Looks like what you're looking for can't be found. :( 3 | -------------------------------------------------------------------------------- /lib/default_boilerplate/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/main.css") 5 | body 6 | != yield 7 | -------------------------------------------------------------------------------- /lib/default_boilerplate/index.jade: -------------------------------------------------------------------------------- 1 | h1 Welcome to Harp. 2 | h3 This is yours to own. Enjoy. 3 | -------------------------------------------------------------------------------- /lib/default_boilerplate/main.less: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: "Helvetica"; 3 | text-align: center; 4 | padding: 50px 20px 0; 5 | background: #f4f4f7; 6 | color: #444; 7 | margin: 0; 8 | } 9 | 10 | h1{ 11 | font-size: 65px; 12 | line-height: 1.2em; 13 | border-bottom: #CCC solid 1px; 14 | padding: 20px 0; 15 | } 16 | 17 | h3{ 18 | font-weight: 300; 19 | } -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var mime = require('mime-types') 4 | var terraform = require('terraform') 5 | var fse = require('fs-extra') 6 | var envy = require('envy-json') 7 | 8 | 9 | exports.buildFallbackPriorityList = function(filePath){ 10 | var list = [] 11 | var popAndPush = function(arr){ 12 | if (arr.length === 1) return list 13 | arr.pop() 14 | var path200 = arr.join("/") + "/200.html" 15 | var path404 = arr.join("/") + "/404.html" 16 | var list200 = terraform.helpers.buildPriorityList(path200) 17 | var list404 = terraform.helpers.buildPriorityList(path404) 18 | list = list.concat(list200).concat(list404) 19 | return popAndPush(arr) 20 | } 21 | return popAndPush(filePath.split("/")) 22 | } 23 | 24 | 25 | /** 26 | * 27 | * Normalize Url 28 | * 29 | * - removes querystring 30 | * - removes extra slashes 31 | * - changes `/` to `/index.html` 32 | */ 33 | 34 | exports.normalizeUrl = function(url){ 35 | 36 | // take off query string 37 | var base = unescape(url.split('?')[0]) 38 | 39 | /** 40 | * Normalize Path 41 | * 42 | * Note: This converts unix paths to windows path on windows 43 | * (not sure if this is a good thing) 44 | */ 45 | var file_path = path.normalize(base) 46 | 47 | // index.html support 48 | if (path.sep == file_path[file_path.length - 1]) file_path += 'index.html' 49 | 50 | return file_path 51 | } 52 | 53 | 54 | /** 55 | * 56 | * Mime Type 57 | * 58 | * returns type of the file 59 | * 60 | * TODO: reference ext from terraform 61 | */ 62 | 63 | exports.mimeType = function(source){ 64 | var ext = path.extname(source) 65 | 66 | if(['.jade', '.md', '.ejs'].indexOf(ext) !== -1){ 67 | return mime.lookup('html') 68 | }else if(['.less', '.styl', '.scss', '.sass'].indexOf(ext) !== -1){ 69 | return mime.lookup('css') 70 | } else if (['.js', '.coffee'].indexOf(ext) !== -1) { 71 | return mime.lookup('js') 72 | } else { 73 | return mime.lookup(source) 74 | } 75 | 76 | } 77 | 78 | 79 | /** 80 | * 81 | * Walk directory for files 82 | * 83 | * recursive function that returns the directory tree 84 | * http://stackoverflow.com/questions/5827612/node-js-fs-readdir-recursive-directory-search 85 | * 86 | */ 87 | 88 | var walk = function(dir, done) { 89 | var results = [] 90 | 91 | fs.readdir(dir, function(err, list) { 92 | if (err){ 93 | return done(err) 94 | } 95 | var pending = list.length 96 | 97 | if (!pending) return done(null, results); 98 | list.forEach(function(file) { 99 | file = path.resolve(dir, file) 100 | fs.stat(file, function(err, stat) { 101 | if (stat && stat.isDirectory()) { 102 | walk(file, function(err, res) { 103 | results = results.concat(res) 104 | if (!--pending) done(null, results) 105 | }) 106 | } else { 107 | results.push(file) 108 | if (!--pending) done(null, results) 109 | } 110 | }) 111 | }) 112 | }) 113 | 114 | } 115 | 116 | 117 | /** 118 | * 119 | * Fetch all the file paths for a directory. 120 | * returns and array of all the relative paths. 121 | * 122 | */ 123 | 124 | exports.ls = function(dir, callback) { 125 | walk(dir, function(err, results){ 126 | var files = [] 127 | results.map(function(file){ files.push(path.relative(dir, file)) }) 128 | callback(null, files) 129 | }) 130 | } 131 | 132 | 133 | /** 134 | * Setup 135 | * 136 | * This is the style and configuration of a Harp Application. 137 | * returns object with contents of Harp.json and application style 138 | * 139 | * { 140 | * "projectPath" : "/path/to/app", 141 | * "publicPath" : "/path/to/app/public", 142 | * "config" : { ... } 143 | * } 144 | */ 145 | 146 | exports.setup = function(projectPath, env){ 147 | if(!env) env = "development" 148 | 149 | try{ 150 | var configPath = path.join(projectPath, "harp.json") 151 | var contents = fs.readFileSync(configPath).toString() 152 | var publicPath = path.join(projectPath, "public") 153 | }catch(e){ 154 | try{ 155 | var configPath = path.join(projectPath, "_harp.json") 156 | var contents = fs.readFileSync(configPath).toString() 157 | var publicPath = projectPath 158 | }catch(e){ 159 | var contents = "{}" 160 | var publicPath = projectPath 161 | } 162 | } 163 | 164 | // not sure what this does anymore. 165 | if(!contents || contents.replace(/^\s\s*/, '').replace(/\s\s*$/, '') == ''){ 166 | contents = '{}' 167 | } 168 | 169 | // attempt to parse the file 170 | try{ 171 | var cfg = JSON.parse(contents) 172 | }catch(e){ 173 | e.source = "JSON" 174 | e.dest = "CONFIG" 175 | e.message = e.message 176 | e.filename = configPath 177 | e.stack = contents 178 | e.lineno = -1 179 | throw new terraform.helpers.TerraformError(e) 180 | } 181 | 182 | if(!cfg.hasOwnProperty('globals')) cfg['globals'] = {} 183 | 184 | cfg.globals.environment = process.env.NODE_ENV || env 185 | 186 | // replace values that look like environment variables 187 | // e.g. '$foo' -> process.env.foo 188 | cfg = envy(cfg) 189 | 190 | return { 191 | cwd : process.cwd(), 192 | projectPath : projectPath, 193 | publicPath : publicPath, 194 | config : cfg 195 | } 196 | 197 | } 198 | 199 | 200 | /** 201 | * 202 | * Template for outputing Less errors. 203 | * 204 | */ 205 | 206 | exports.cssError = function(error){ 207 | var body = '' + 208 | 209 | 'body{' + 210 | 'margin:0;' + 211 | '}' + 212 | 213 | 'body:before {' + 214 | 'display: block;'+ 215 | 'white-space: pre;' + 216 | 'content: "'+ error.error.source +' -> ' + error.error.dest + ' (' + error.error.message + ') ' + error.error.filename + '";'+ 217 | 'color: #444;'+ 218 | 'background-color: #fefe96;' + 219 | 'padding: 40px 40px;'+ 220 | 'margin: 0;'+ 221 | 'font-family: monospace;'+ 222 | 'font-size: 14px;'+ 223 | '}' 224 | 225 | return body 226 | } 227 | 228 | 229 | /** 230 | * 231 | * Will Collide 232 | * 233 | * Returns true if first path is in the line of fire of the second path. 234 | * ie: if we delete the second path will the first path be affected? 235 | */ 236 | 237 | var willCollide = exports.willCollide = function(projectPath, outputPath){ 238 | var projectPath = path.resolve(projectPath) 239 | var outputPath = path.resolve(outputPath) 240 | var relativePath = path.relative(projectPath, outputPath) 241 | var arr = relativePath.split(path.sep) 242 | var result = true; 243 | 244 | arr.forEach(function(i){ 245 | if(i !== "..") result = false 246 | }) 247 | 248 | /** 249 | * for @kennethormandy ;) 250 | */ 251 | if ([path.sep, "C:\\"].indexOf(outputPath) !== -1) result = true 252 | 253 | /** 254 | * For #400 255 | */ 256 | if (projectPath === outputPath) result = true 257 | 258 | return result 259 | } 260 | 261 | 262 | /** 263 | * 264 | * Will Allow 265 | * 266 | * Returns `true` if we feel projectPath is safe from the output path. 267 | * For this to be the case. The outputPath must live only one directory 268 | * back from the projectPath and the projectPath must live in a directory 269 | * starting with an underscore. 270 | */ 271 | 272 | exports.willAllow = function(projectPath, outputPath){ 273 | var projectPath = path.resolve(projectPath) 274 | var outputPath = path.resolve(outputPath) 275 | var relativePath = path.relative(projectPath, outputPath) 276 | var arr = relativePath.split(path.sep) 277 | 278 | if(willCollide(projectPath, outputPath)){ 279 | if(relativePath === ".."){ 280 | if(projectPath.split(path.sep)[projectPath.split(path.sep).length - 1][0] == "_"){ 281 | return true 282 | }else{ 283 | return false 284 | } 285 | }else{ 286 | return false 287 | } 288 | }else{ 289 | return true 290 | } 291 | } 292 | 293 | 294 | /** 295 | * Prime 296 | * (Disk I/O) 297 | * 298 | * Cleans out a directory but ignores one (optionally). 299 | * 300 | * primePath: Absolute Path 301 | * options: Object 302 | * ignore: Absolute Path || Relative (to delete) 303 | * 304 | * This is a powerful Function so take it seriously. 305 | * 306 | */ 307 | 308 | exports.prime = function(primePath, options, callback){ 309 | 310 | if(!callback){ 311 | callback = options 312 | options = {} 313 | } 314 | 315 | /** 316 | * Options (only one) 317 | */ 318 | var ignorePath = options.ignore 319 | ? path.resolve(primePath, options.ignore) 320 | : null 321 | 322 | // Absolute paths are predictable. 323 | var primePath = path.resolve(primePath) 324 | 325 | fse.mkdirp(primePath, function(){ 326 | fse.readdir(primePath, function(error, contents){ 327 | 328 | /** 329 | * Delete each item in the directory in parallel. Thanks Ry! 330 | */ 331 | 332 | if(contents.length == 0) return callback() 333 | 334 | var total = contents.length 335 | var count = 0 336 | 337 | 338 | contents.forEach(function(i){ 339 | var filePath = path.resolve(primePath, i) 340 | var gitRegExp = new RegExp(/^.git/) 341 | 342 | /** 343 | * We leave `.git`, `.gitignore`, and project path. 344 | */ 345 | if(filePath === ignorePath || i.match(gitRegExp)){ 346 | count++ 347 | if(count == total) callback() 348 | }else{ 349 | fse.remove(filePath, function(err){ 350 | count++ 351 | if(count == total) callback() 352 | }) 353 | } 354 | }) 355 | 356 | }) 357 | }) 358 | 359 | } 360 | 361 | 362 | /** 363 | * Stacktrace 364 | * 365 | * Formats a stactrace 366 | * 367 | * 368 | * This is a powerful Function so take it seriously. 369 | * 370 | */ 371 | 372 | exports.stacktrace = function(str, options){ 373 | var lineno = options.lineno || -1 374 | var context = options.context || 8 375 | var context = context = context / 2 376 | var lines = ('\n' + str).split('\n') 377 | var start = Math.max(lineno - context, 1) 378 | var end = Math.min(lines.length, lineno + context) 379 | 380 | if(lineno === -1) end = lines.length 381 | 382 | var pad = end.toString().length 383 | 384 | var context = lines.slice(start, end).map(function(line, i){ 385 | var curr = i + start 386 | return (curr == lineno ? ' > ' : ' ') 387 | + Array(pad - curr.toString().length + 1).join(' ') 388 | + curr 389 | + '| ' 390 | + line 391 | }).join('\n') 392 | 393 | return context 394 | } 395 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs-extra') 3 | var terraform = require('terraform') 4 | var async = require('async') 5 | var connect = require('connect') 6 | var mime = require('mime-types') 7 | var helpers = require('./helpers') 8 | var middleware = require('./middleware') 9 | var pkg = require('../package.json') 10 | var url = require("url") 11 | var hrn = require("human-readable-numbers") 12 | 13 | 14 | 15 | /** 16 | * Server 17 | * 18 | * Host a single Harp application. 19 | * 20 | */ 21 | 22 | exports.server = function(dirPath, options){ 23 | options = options || {} 24 | 25 | var app = connect() 26 | 27 | if (options.log || options.l){ 28 | app.use(middleware.setupLog) 29 | } 30 | 31 | app.use(middleware.regProjectFinder(dirPath)) 32 | app.use(middleware.setup) 33 | app.use(middleware.basicAuth) 34 | app.use(middleware.underscore) 35 | app.use(middleware.mwl) 36 | app.use(middleware.denySymlink(options)) 37 | app.use(middleware.static) 38 | app.use(middleware.poly) 39 | app.use(middleware.process) 40 | app.use(middleware.fallback2) 41 | return app 42 | } 43 | 44 | 45 | /** 46 | * Multihost 47 | * 48 | * Host multiple Harp applications. 49 | * 50 | */ 51 | 52 | exports.multihost = function(dirPath, options, callback){ 53 | var app = connect() 54 | app.use(middleware.notMultihostURL) 55 | app.use(middleware.index(dirPath)) 56 | app.use(middleware.hostProjectFinder(dirPath)) 57 | app.use(middleware.setup) 58 | app.use(middleware.basicAuth) 59 | app.use(middleware.underscore) 60 | app.use(middleware.mwl) 61 | app.use(middleware.static) 62 | app.use(middleware.poly) 63 | app.use(middleware.process) 64 | app.use(middleware.fallback2) 65 | app.listen(options.port || 9000, callback) 66 | } 67 | 68 | /** 69 | * Mount 70 | * 71 | * Offer the asset pipeline as connect middleware 72 | * 73 | */ 74 | 75 | exports.mount = function(mountPoint, root){ 76 | 77 | if(!root){ 78 | root = mountPoint 79 | mountPoint = null 80 | }else{ 81 | var rx = new RegExp("^" + mountPoint) 82 | } 83 | 84 | var finder = middleware.regProjectFinder(root) 85 | 86 | return function(req, rsp, next){ 87 | 88 | if(rx){ 89 | if(!req.url.match(rx)) return next() 90 | var originalUrl = req.url 91 | req.url = req.url.replace(rx, "/") 92 | } 93 | 94 | finder(req, rsp, function(){ 95 | middleware.setup(req, rsp, function(){ 96 | middleware.static(req, rsp, function(){ 97 | middleware.poly(req, rsp, function(){ 98 | middleware.process(req, rsp, function(){ 99 | if(originalUrl) req.url = originalUrl 100 | next() 101 | }) 102 | }) 103 | }) 104 | }) 105 | }) 106 | } 107 | } 108 | 109 | 110 | /** 111 | * Pipeline 112 | * 113 | * Offer the asset pipeline as connect middleware 114 | * 115 | */ 116 | 117 | exports.pipeline = function(root){ 118 | console.log("Deprecated, please use MOUNT instead, this will be removed in a future version."); 119 | var publicPath = path.resolve(root) 120 | var terra = terraform.root(publicPath) 121 | 122 | return function(req, rsp, next){ 123 | var normalizedPath = helpers.normalizeUrl(req.url) 124 | var priorityList = terraform.helpers.buildPriorityList(normalizedPath) 125 | var sourceFile = terraform.helpers.findFirstFile(publicPath, priorityList) 126 | 127 | if(!sourceFile) return next() 128 | 129 | terra.render(sourceFile, function(error, body){ 130 | if(error) return next(error) 131 | if(!body) return next() // 404 132 | 133 | var outputType = terraform.helpers.outputType(sourceFile) 134 | var mimeType = helpers.mimeType(outputType) 135 | var charset = mime.charsets.lookup(mimeType) 136 | rsp.statusCode = 200 137 | rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : '')) 138 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 139 | rsp.end(body) 140 | }) 141 | 142 | } 143 | 144 | } 145 | 146 | exports.pkg = pkg 147 | 148 | /** 149 | * Export middleware 150 | * 151 | * Make sure middleware is accessible 152 | * when using harp as a library 153 | * 154 | */ 155 | exports.middleware = middleware; 156 | 157 | /** 158 | * Compile 159 | * 160 | * Compiles Single Harp Application. 161 | * 162 | */ 163 | 164 | exports.compile = function(projectPath, outputPath, options, callback){ 165 | if (!callback){ 166 | callback = options 167 | options = { log: false } 168 | } 169 | 170 | const compileStart = process.hrtime() 171 | 172 | var stats = { 173 | types: {} 174 | } 175 | 176 | /** 177 | * Both projectPath and outputPath are optional 178 | */ 179 | 180 | if(!callback && typeof outputPath === "function"){ 181 | callback = outputPath 182 | outputPath = "www" 183 | } 184 | 185 | if(!outputPath){ 186 | outputPath = "www" 187 | } 188 | 189 | 190 | /** 191 | * Setup all the paths and collect all the data 192 | */ 193 | 194 | try{ 195 | outputPath = path.resolve(projectPath, outputPath) 196 | var setup = helpers.setup(projectPath, "production") 197 | var terra = terraform.root(setup.publicPath, setup.config.globals) 198 | }catch(err){ 199 | return callback(err) 200 | } 201 | 202 | 203 | /** 204 | * Protect the user (as much as possible) from compiling up the tree 205 | * resulting in the project deleting its own source code. 206 | */ 207 | 208 | if(!helpers.willAllow(projectPath, outputPath)){ 209 | return callback({ 210 | type: "Invalid Output Path", 211 | message: "Output path cannot be greater then one level up from project path and must be in directory starting with `_` (underscore).", 212 | projectPath: projectPath, 213 | outputPath: outputPath 214 | }) 215 | } 216 | 217 | var toHuman = function(bytes){ 218 | var human = hrn.toHumanString(bytes) 219 | var pattern = /\d+\D$/ 220 | if (pattern.test(human)) return human 221 | return human + "B" 222 | } 223 | 224 | 225 | /** 226 | * Compile and save file 227 | */ 228 | 229 | var compileFile = function(file, done){ 230 | process.nextTick(function () { 231 | terra.render(file, function(error, body){ 232 | if(error) return done(error) 233 | if(!body) return done() 234 | var dest = path.resolve(outputPath, terraform.helpers.outputPath(file)) 235 | fs.mkdirp(path.dirname(dest), function(err){ 236 | var sizeInBytes = body.length 237 | var sizeHuman = toHuman(sizeInBytes) 238 | var sizePadded = sizeHuman.padStart(8, " ") 239 | var filePath = `/${ terraform.helpers.outputPath(file) }` 240 | if (options.log){ 241 | console.log(sizePadded.green, filePath) 242 | } 243 | fs.writeFile(dest, body, done) 244 | }) 245 | }) 246 | }) 247 | } 248 | 249 | 250 | /** 251 | * Copy File 252 | * 253 | * TODO: reference ignore extensions from a terraform helper. 254 | */ 255 | var copyFile = function(file, done){ 256 | var ext = path.extname(file) 257 | if(!terraform.helpers.shouldIgnore(file) && [".jsx", ".jade", ".ejs", ".md", ".styl", ".less", ".scss", ".sass", ".coffee", ".cjs"].indexOf(ext) === -1){ 258 | var localPath = path.resolve(outputPath, file) 259 | fs.mkdirp(path.dirname(localPath), function(err){ 260 | fs.stat(path.resolve(setup.publicPath, file), function(err, stats){ 261 | var sizeInBytes = stats.size 262 | var sizeHuman = toHuman(sizeInBytes) 263 | var sizePadded = sizeHuman.padStart(8, " ") 264 | var filePath = `/${ file }` 265 | if (options.log){ 266 | console.log(sizePadded.grey, filePath) 267 | } 268 | fs.copy(path.resolve(setup.publicPath, file), localPath, done) 269 | }) 270 | }) 271 | }else{ 272 | done() 273 | } 274 | } 275 | 276 | /** 277 | * Scan dir, Compile Less and Jade, Copy the others 278 | */ 279 | 280 | helpers.prime(outputPath, { ignore: projectPath }, function(err){ 281 | if(err) console.log(err) 282 | 283 | helpers.ls(setup.publicPath, function(err, results){ 284 | async.each(results, compileFile, function(err){ 285 | if(err){ 286 | callback(err) 287 | }else{ 288 | async.each(results, copyFile, function(err){ 289 | stats.duration = process.hrtime(compileStart) 290 | 291 | setup.config['harp_version'] = pkg.version 292 | delete setup.config.globals 293 | setup.stats = stats 294 | callback(null, setup) 295 | }) 296 | } 297 | }) 298 | }) 299 | }) 300 | 301 | } 302 | -------------------------------------------------------------------------------- /lib/middleware.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var fs = require('fs') 3 | var helpers = require('./helpers') 4 | var mime = require('mime-types') 5 | var terraform = require('terraform') 6 | var pkg = require('../package.json') 7 | var skin = require('./skin') 8 | var connect = require('connect') 9 | var send = require('send') 10 | var utilsPause = require('pause') 11 | var parse = require('parseurl') 12 | var url = require('url') 13 | var auth = require('basic-auth') 14 | var hrn = require("human-readable-numbers") 15 | var onFinished = require("on-finished") 16 | 17 | var toHuman = function(bytes){ 18 | var human = hrn.toHumanString(bytes) 19 | var pattern = /\d+\D$/ 20 | if (pattern.test(human)) return human 21 | return human + "B" 22 | } 23 | 24 | var measureBody = function(contentLength){ 25 | if (!contentLength) return "" 26 | return `(${ toHuman(contentLength) })` 27 | } 28 | 29 | exports.setupLog = function(req, rsp, next){ 30 | onFinished(rsp, function (err, rsp) { 31 | var contentLength = null 32 | try{ 33 | contentLength = parseInt(rsp._header.split("\n").find(h => h.includes("Content-Length")).split(" ")[1].split("\r")[0]) 34 | }catch(e){} 35 | console.log(new Date(), req.method.green, rsp.statusCode, req.url, measureBody(contentLength).grey) 36 | }) 37 | 38 | // var start = process.hrtime() 39 | // var _end = rsp.end 40 | 41 | // rsp.end = function(body){ 42 | // var stop = process.hrtime(start) 43 | // console.log(new Date(), this.req.method.green, this.statusCode, this.req.url, measureBody(body).grey) 44 | // return _end.call(this, body) 45 | // } 46 | 47 | return next() 48 | } 49 | 50 | exports.notMultihostURL = function(req, rsp, next){ 51 | var host = req.headers.host 52 | var hostname = host.split(':')[0] 53 | var arr = hostname.split(".") 54 | var port = host.split(':')[1] ? ':' + host.split(':')[1] : '' 55 | 56 | if(hostname == "127.0.0.1" || hostname == "localhost"){ 57 | rsp.statusCode = 307 58 | rsp.setHeader('Location', 'http://lvh.me' + port) 59 | rsp.end("redirecting you to http://lvh.me" + port) 60 | }else if(arr.length == 4){ 61 | arr.pop() 62 | arr.push('io') 63 | var link = 'http://' + arr.join('.') + port 64 | var body = "Local server does not support history. Perhaps you are looking for " + link + "." 65 | rsp.statusCode = 307 66 | rsp.end(body) 67 | }else if(arr.length > 4){ 68 | arr.shift() 69 | var link = 'http://' + arr.join('.') + port 70 | rsp.statusCode = 307 71 | rsp.setHeader('Location', link) 72 | rsp.end("redirecting you to " + link) 73 | }else{ 74 | next() 75 | } 76 | } 77 | 78 | var reservedDomains = ["harp.io", "harpdev.io", "harpapp.io"]; 79 | exports.index = function(dirPath){ 80 | return function(req, rsp, next){ 81 | var host = req.headers.host; 82 | var hostname = host.split(':')[0]; 83 | var arr = hostname.split("."); 84 | var port = host.split(':')[1] ? ':' + host.split(':')[1] : ''; 85 | var poly = terraform.root(__dirname + "/templates"); 86 | 87 | if(arr.length == 2){ 88 | fs.readdir(dirPath, function(err, files){ 89 | var projects = []; 90 | 91 | files.forEach(function(file){ 92 | var local = file.split('.'); 93 | 94 | var appPart = local.join("_"); 95 | 96 | if (local.length > 2) { 97 | var domain = local.slice(Math.max(local.length - 2, 1)).join("."); 98 | if (reservedDomains.indexOf(domain) != -1) { 99 | appPart = local[0]; 100 | } 101 | } 102 | 103 | // DOT files are ignored. 104 | if (file[0] !== ".") { 105 | projects.push({ 106 | "name" : file, 107 | "localUrl" : 'http://' + appPart + "." + host, 108 | "localPath" : path.resolve(dirPath, file) 109 | }); 110 | } 111 | }); 112 | 113 | poly.render("index.jade", { pkg: pkg, projects: projects, layout: "_layout.jade" }, function(error, body){ 114 | rsp.end(body) 115 | }); 116 | }) 117 | } else { 118 | next(); 119 | } 120 | } 121 | } 122 | 123 | exports.hostProjectFinder = function(dirPath){ 124 | return function(req, rsp, next){ 125 | var host = req.headers.host; 126 | var hostname = host.split(':')[0]; 127 | var matches = []; 128 | 129 | fs.readdir(dirPath, function(err, files){ 130 | var appPart = hostname.split(".")[0]; 131 | files.forEach(function(file){ 132 | var fp = file.split('.'); 133 | var filePart; 134 | // Check against Reserved Domains first. 135 | if (fp.length > 2) { 136 | var domain = fp.slice(Math.max(fp.length - 2, 1)).join("."); 137 | if (reservedDomains.indexOf(domain) != -1) { 138 | fp = fp.slice(0, Math.max(fp.length - 2)) 139 | } 140 | } 141 | 142 | filePart = fp.join("_"); 143 | if (appPart == filePart) { 144 | matches.push(file); 145 | } 146 | }); 147 | 148 | if(matches.length > 0){ 149 | req.projectPath = path.resolve(dirPath, matches[0]); 150 | next(); 151 | } else { 152 | rsp.end("Cannot find project") 153 | } 154 | 155 | }); 156 | } 157 | } 158 | 159 | exports.regProjectFinder = function(projectPath){ 160 | return function(req, rsp, next){ 161 | req.projectPath = projectPath 162 | next() 163 | } 164 | } 165 | 166 | 167 | exports.buildFallbackPriorityList = function(filePath){ 168 | var list = [] 169 | var popAndPush = function(arr){ 170 | if (arr.length === 1) return list 171 | arr.pop() 172 | var path200 = arr.join("/") + "/200.html" 173 | var path404 = arr.join("/") + "/404.html" 174 | var list200 = terraform.helpers.buildPriorityList(path200) 175 | var list404 = terraform.helpers.buildPriorityList(path404) 176 | list = list.concat(list200).concat(list404) 177 | return popAndPush(arr) 178 | } 179 | return popAndPush(filePath.split("/")) 180 | } 181 | 182 | 183 | /** 184 | * Fallback2 185 | * 186 | * This is a reimplementation of Fallback (see below) with the goal of supporting nested 200 & 404 pages. 187 | * 188 | * 1. make a Priority List of wanted files 189 | * 2. make list of actual files. 190 | * 3. find by priority and compile/serve that file. 191 | * 5. default 404 192 | * 193 | * It is broken into two public functions `fallback`, and `notFound` 194 | * 195 | */ 196 | 197 | var fallbackCascade = function(req, rsp, next){ 198 | var pathn = parse(req).pathname; 199 | var priorityList = helpers.buildFallbackPriorityList(pathn) 200 | 201 | helpers.ls(req.setup.publicPath, function(error, fileList){ 202 | if (error) return next() 203 | 204 | var fallbackFile = priorityList.find(function(wantedFallbackFile){ 205 | return (fileList.indexOf(wantedFallbackFile) !== -1) 206 | }) 207 | 208 | if(!fallbackFile) return next() 209 | 210 | var statusCode = fallbackFile.indexOf("200") !== -1 ? 200 : 404 211 | 212 | 213 | 214 | req.poly.render(fallbackFile, function(error, body){ 215 | if(error){ 216 | rsp.statusCode = 404; 217 | rsp.end("There is an error in your " + fallbackFile + " file") 218 | }else{ 219 | if(!body) return next() 220 | var type = helpers.mimeType("html") 221 | var charset = mime.charsets.lookup(type) 222 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); 223 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 224 | rsp.statusCode = statusCode 225 | rsp.end(body) 226 | } 227 | }) 228 | 229 | // fs.readFile(path.resolve(req.setup.publicPath, fallbackFile), function(err, contents){ 230 | // if(contents){ 231 | // var body = contents.toString() 232 | // var type = helpers.mimeType("html") 233 | // var charset = mime.charsets.lookup(type) 234 | // rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')) 235 | // rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 236 | // rsp.statusCode = statusCode 237 | // rsp.end(body) 238 | // }else{ 239 | // return next() 240 | // } 241 | // }) 242 | 243 | }) 244 | 245 | } 246 | 247 | var fallback = exports.fallback2 = function(req, rsp, next){ 248 | skin(req, rsp, [fallbackCascade, custom200static, custom404static, default404], next) 249 | } 250 | 251 | // var notFound = exports.notFound = function(req, rsp, next){ 252 | // skin(req, rsp, [custom404static, custom404dynamic, default404], next) 253 | // } 254 | 255 | 256 | 257 | /** 258 | * Fallback 259 | * 260 | * This is the logic behind rendering fallback files. 261 | * 262 | * 1. return static 200.html file 263 | * 2. compile and return 200.xxx 264 | * 3. return static 404.html file 265 | * 4. compile and return 404.xxx file 266 | * 5. default 404 267 | * 268 | * It is broken into two public functions `fallback`, and `notFound` 269 | * 270 | */ 271 | 272 | var fallback = exports.fallback = function(req, rsp, next){ 273 | skin(req, rsp, [custom200static, custom200dynamic, notFound], next) 274 | } 275 | 276 | var notFound = exports.notFound = function(req, rsp, next){ 277 | skin(req, rsp, [custom404static, custom404dynamic, default404], next) 278 | } 279 | 280 | 281 | /** 282 | * Custom 200 283 | * 284 | * 1. return static 200.html file 285 | * 2. compile and return 200.xxx file 286 | * 287 | */ 288 | 289 | var custom200static = function(req, rsp, next){ 290 | fs.readFile(path.resolve(req.setup.publicPath, "200.html"), function(err, contents){ 291 | if(contents){ 292 | var body = contents.toString() 293 | var type = helpers.mimeType("html") 294 | var charset = mime.charsets.lookup(type) 295 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')) 296 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 297 | rsp.statusCode = 200 298 | rsp.end(body) 299 | }else{ 300 | next() 301 | } 302 | }) 303 | } 304 | 305 | /** 306 | * Custom 200 (jade, md, ejs) 307 | * 308 | * 1. return static 200.html file 309 | * 2. compile and return 404.xxx file 310 | * 311 | */ 312 | 313 | var custom200dynamic = function(req, rsp, next){ 314 | skin(req, rsp, [poly], function(){ 315 | var priorityList = terraform.helpers.buildPriorityList("200.html") 316 | var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList) 317 | if(!sourceFile) return next() 318 | 319 | req.poly.render(sourceFile, function(error, body){ 320 | if(error){ 321 | // TODO: make this better 322 | rsp.statusCode = 404; 323 | rsp.end("There is an error in your " + sourceFile + " file") 324 | }else{ 325 | if(!body) return next() 326 | var type = helpers.mimeType("html") 327 | var charset = mime.charsets.lookup(type) 328 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); 329 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 330 | rsp.statusCode = 200; 331 | rsp.end(body) 332 | } 333 | }) 334 | }) 335 | } 336 | 337 | 338 | /** 339 | * Custom 404 (html) 340 | * 341 | * 1. return static 404.html file 342 | * 2. compile and return 404.xxx file 343 | * 344 | * TODO: cache readFile IO 345 | * 346 | */ 347 | 348 | var custom404static = function(req, rsp, next){ 349 | fs.readFile(path.resolve(req.setup.publicPath, "404.html"), function(err, contents){ 350 | if(contents){ 351 | var body = contents.toString() 352 | var type = helpers.mimeType("html") 353 | var charset = mime.charsets.lookup(type) 354 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')) 355 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 356 | rsp.statusCode = 404 357 | rsp.end(body) 358 | }else{ 359 | next() 360 | } 361 | }) 362 | } 363 | 364 | 365 | /** 366 | * Custom 404 (jade, md, ejs) 367 | * 368 | * 1. return static 404.html file 369 | * 2. compile and return 404.xxx file 370 | * 371 | */ 372 | 373 | var custom404dynamic = function(req, rsp, next){ 374 | skin(req, rsp, [poly], function(){ 375 | var priorityList = terraform.helpers.buildPriorityList("404.html") 376 | var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList) 377 | if(!sourceFile) return next() 378 | 379 | req.poly.render(sourceFile, function(error, body){ 380 | if(error){ 381 | // TODO: make this better 382 | rsp.statusCode = 404; 383 | rsp.end("There is an error in your " + sourceFile + " file") 384 | }else{ 385 | if(!body) return next() 386 | var type = helpers.mimeType("html") 387 | var charset = mime.charsets.lookup(type) 388 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); 389 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 390 | rsp.statusCode = 404; 391 | rsp.end(body) 392 | } 393 | }) 394 | }) 395 | } 396 | 397 | 398 | /** 399 | * Default 404 400 | * 401 | * No 200 nor 404 files were found. 402 | * 403 | */ 404 | 405 | var default404 = function(req, rsp, next){ 406 | var locals = { 407 | project: req.headers.host, 408 | name: "Page Not Found", 409 | pkg: pkg 410 | } 411 | terraform.root(__dirname + "/templates").render("404.jade", locals, function(err, body){ 412 | var type = helpers.mimeType("html") 413 | var charset = mime.charsets.lookup(type) 414 | rsp.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); 415 | rsp.statusCode = 404 416 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 417 | rsp.end(body) 418 | }) 419 | } 420 | 421 | 422 | /** 423 | * Underscore 424 | * 425 | * Returns 404 if path contains beginning underscore 426 | * 427 | */ 428 | exports.underscore = function(req, rsp, next){ 429 | if(terraform.helpers.shouldIgnore(req.url)){ 430 | notFound(req, rsp, next) 431 | }else{ 432 | next() 433 | } 434 | } 435 | 436 | /** 437 | * Modern Web Language 438 | * 439 | * Returns 404 if file is a precompiled 440 | * 441 | */ 442 | exports.mwl = function(req, rsp, next){ 443 | var ext = path.extname(req.url).replace(/^\./, '') 444 | req.originalExt = ext 445 | 446 | // This prevents the source files from being served, but also 447 | // has to factor in that in this brave new world, sometimes 448 | // `.html` (Handlebars, others), `.css` (PostCSS), and 449 | // `.js` (Browserify) are actually being used to specify 450 | // source files 451 | 452 | //if (['js'].indexOf(ext) === -1) { 453 | if (terraform.helpers.processors["html"].indexOf(ext) !== -1 || terraform.helpers.processors["css"].indexOf(ext) !== -1 || terraform.helpers.processors["js"].indexOf(ext) !== -1) { 454 | notFound(req, rsp, next) 455 | } else { 456 | next() 457 | } 458 | //} else { 459 | //next() 460 | //} 461 | } 462 | 463 | /** 464 | * Static 465 | * 466 | * Serves up static page (if it exists). 467 | * 468 | */ 469 | exports.static = function(req, res, next) { 470 | var options = {} 471 | var redirect = true 472 | 473 | if ('GET' != req.method && 'HEAD' != req.method) return next() 474 | //if (['js'].indexOf(path.extname(req.url).replace(/^\./, '')) !== -1) return next() 475 | 476 | var pathn = parse(req).pathname; 477 | var pause = utilsPause(req); 478 | 479 | function resume() { 480 | next(); 481 | pause.resume(); 482 | } 483 | 484 | function directory() { 485 | if (!redirect) return resume(); 486 | var pathname = url.parse(req.originalUrl).pathname; 487 | res.statusCode = 301; 488 | res.setHeader('Location', pathname + '/'); 489 | res.end('Redirecting to ' + pathname + '/'); 490 | } 491 | 492 | function error(err) { 493 | if (404 == err.status){ 494 | // look for implicit `*.html` if we get a 404 495 | return path.extname(err.path) === '' 496 | ? serve(pathn + ".html") 497 | : resume() 498 | } 499 | next(err); 500 | } 501 | 502 | var serve = function(pathn){ 503 | send(req, pathn, { 504 | maxage: options.maxAge || 0, 505 | root: req.setup.publicPath, 506 | hidden: options.hidden 507 | }) 508 | .on('error', error) 509 | .on('directory', directory) 510 | .pipe(res) 511 | } 512 | serve(pathn) 513 | } 514 | 515 | /** 516 | * Opens the (optional) harp.json file and sets the config settings. 517 | */ 518 | 519 | exports.setup = function(req, rsp, next){ 520 | if(req.hasOwnProperty('setup')) return next() 521 | 522 | try{ 523 | req.setup = helpers.setup(req.projectPath) 524 | }catch(error){ 525 | error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno }) 526 | 527 | var locals = { 528 | project: req.headers.host, 529 | error: error, 530 | pkg: pkg 531 | } 532 | 533 | return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){ 534 | rsp.statusCode = 500 535 | rsp.end(body) 536 | }) 537 | } 538 | 539 | next() 540 | } 541 | 542 | /** 543 | * Basic Auth 544 | */ 545 | 546 | exports.basicAuth = function(req, res, next){ 547 | // default empty 548 | var creds = [] 549 | 550 | // allow array 551 | if(req.setup.config.hasOwnProperty("basicAuth") && req.setup.config["basicAuth"] instanceof Array) 552 | creds = req.setup.config["basicAuth"] 553 | 554 | // allow string 555 | if(req.setup.config.hasOwnProperty("basicAuth") && typeof req.setup.config["basicAuth"] === 'string') 556 | creds = [req.setup.config["basicAuth"]] 557 | 558 | // move on if no creds 559 | if(creds.length === 0) return next() 560 | 561 | function check (name, pass) { 562 | return creds.some(function(cred){ 563 | return cred === name + ":" + pass 564 | }) 565 | } 566 | var credentials = auth(req) 567 | if (!credentials || !check(credentials.name, credentials.pass)) { 568 | res.statusCode = 401 569 | res.setHeader('WWW-Authenticate', 'Basic realm="example"') 570 | res.end('Access denied') 571 | } else { 572 | res.end('Access granted') 573 | } 574 | 575 | 576 | } 577 | 578 | /** 579 | * Sets up the poly object 580 | */ 581 | 582 | var poly = exports.poly = function(req, rsp, next){ 583 | if(req.hasOwnProperty("poly")) return next() 584 | 585 | try{ 586 | req.poly = terraform.root(req.setup.publicPath, req.setup.config.globals) 587 | }catch(error){ 588 | error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno }) 589 | var locals = { 590 | project: req.headers.host, 591 | error: error, 592 | pkg: pkg 593 | } 594 | return terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){ 595 | rsp.statusCode = 500 596 | rsp.end(body) 597 | }) 598 | } 599 | next() 600 | } 601 | 602 | 603 | /** 604 | * Deny Symlink 605 | */ 606 | 607 | exports.denySymlink = function(options){ 608 | options = options || {} 609 | return function(req, rsp, next){ 610 | if (!options.hasOwnProperty("deny-symlinks")) return next() 611 | if (options["deny-symlinks"] === false) return next() 612 | var sourceFilePath = path.join(req.setup.publicPath, helpers.normalizeUrl(req.url)) 613 | fs.lstat(sourceFilePath, function(err, stats) { 614 | if (!stats.isSymbolicLink()) return next() 615 | var body = "403 Forbidden" 616 | rsp.statusCode = 403 617 | rsp.end(body) 618 | }) 619 | } 620 | } 621 | 622 | 623 | /** 624 | * Asset Pipeline 625 | */ 626 | 627 | exports.process = function(req, rsp, next){ 628 | var normalizedPath = helpers.normalizeUrl(req.url) 629 | var priorityList = terraform.helpers.buildPriorityList(normalizedPath) 630 | var sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, priorityList) 631 | 632 | 633 | /** 634 | * We GTFO if we don't have a source file. 635 | */ 636 | 637 | if(!sourceFile){ 638 | if (path.basename(normalizedPath) === "index.html") { 639 | var pathAr = normalizedPath.split(path.sep); pathAr.pop() // Pop index.html off the list 640 | var prospectCleanPath = pathAr.join("/") 641 | var prospectNormalizedPath = helpers.normalizeUrl(prospectCleanPath) 642 | var prospectPriorityList = terraform.helpers.buildPriorityList(prospectNormalizedPath) 643 | prospectPriorityList.push(path.basename(prospectNormalizedPath + ".html")) 644 | 645 | sourceFile = terraform.helpers.findFirstFile(req.setup.publicPath, prospectPriorityList) 646 | 647 | if (!sourceFile) { 648 | return next() 649 | } else { 650 | // 301 redirect 651 | rsp.statusCode = 301 652 | rsp.setHeader('Location', prospectCleanPath) 653 | rsp.end('Redirecting to ' + prospectCleanPath) 654 | } 655 | 656 | } else { 657 | return next() 658 | } 659 | } else { 660 | 661 | /** 662 | * Now we let terraform handle the asset pipeline. 663 | */ 664 | 665 | 666 | req.poly.render(sourceFile, function(error, body){ 667 | if(error){ 668 | error.stack = helpers.stacktrace(error.stack, { lineno: error.lineno }) 669 | 670 | var locals = { 671 | project: req.headers.host, 672 | error: error, 673 | pkg: pkg 674 | } 675 | if(terraform.helpers.outputType(sourceFile) == 'css'){ 676 | var outputType = terraform.helpers.outputType(sourceFile) 677 | var mimeType = helpers.mimeType(outputType) 678 | var charset = mime.charsets.lookup(mimeType) 679 | var body = helpers.cssError(locals) 680 | rsp.statusCode = 200 681 | rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : '')) 682 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 683 | rsp.end(body) 684 | }else{ 685 | 686 | // Make the paths relative but keep the root dir. 687 | // TODO: move to helper. 688 | // 689 | // var loc = req.projectPath.split(path.sep); loc.pop() 690 | // var loc = loc.join(path.sep) + path.sep 691 | // if(error.filename) error.filename = error.filename.replace(loc, "") 692 | 693 | terraform.root(__dirname + "/templates").render("error.jade", locals, function(err, body){ 694 | var mimeType = helpers.mimeType('html') 695 | var charset = mime.charsets.lookup(mimeType) 696 | rsp.statusCode = 500 697 | rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : '')) 698 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 699 | rsp.end(body) 700 | }) 701 | } 702 | }else{ 703 | // 404 704 | if(!body) return next() 705 | 706 | var outputType = terraform.helpers.outputType(sourceFile) 707 | var mimeType = helpers.mimeType(outputType) 708 | var charset = mime.charsets.lookup(mimeType) 709 | rsp.statusCode = 200 710 | rsp.setHeader('Content-Type', mimeType + (charset ? '; charset=' + charset : '')) 711 | rsp.setHeader('Content-Length', Buffer.byteLength(body, charset)); 712 | rsp.end(body); 713 | } 714 | }) 715 | } 716 | 717 | 718 | 719 | 720 | 721 | 722 | 723 | 724 | 725 | 726 | } 727 | -------------------------------------------------------------------------------- /lib/skin.js: -------------------------------------------------------------------------------- 1 | module.exports = function(req, rsp, stack, callback){ 2 | var that = this 3 | var index = 0 4 | 5 | function next(err){ 6 | var layer = stack[index++] 7 | if(!layer) return callback(req, rsp, next) 8 | layer.call(that, req, rsp, next) 9 | } 10 | 11 | next() 12 | } -------------------------------------------------------------------------------- /lib/templates/404.jade: -------------------------------------------------------------------------------- 1 | .center 2 | h1 404 3 | h2 Page Not Found 4 | p We recommend you add a 404.jade file to your project. -------------------------------------------------------------------------------- /lib/templates/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html(lang="en") 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible", content="IE=edge,chrome=1") 6 | //- title #{ pkg.name } #{ pkg.version } 7 | title Harp 8 | meta(name="handheldfriendly", content="true") 9 | meta(name="mobileoptimized", content="320") 10 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 11 | meta(http-equiv="cleartype", content="on") 12 | style. 13 | body { 14 | font-family: Verdana; 15 | padding:10px 10px; 16 | } 17 | 18 | h1, h2, h3, h4, h5, h6{ 19 | font-weight: 300; 20 | line-height: 1.5em; 21 | } 22 | 23 | h3 { 24 | font-weight: 500; 25 | font-size: 1em; 26 | } 27 | 28 | p { 29 | font-size: 1em; 30 | line-height: 1.5em; 31 | } 32 | 33 | header{ 34 | padding:20px 0px; 35 | } 36 | 37 | header h1{ 38 | margin:0; 39 | } 40 | 41 | header h3{ 42 | margin:0; 43 | } 44 | 45 | .stacktrace{ 46 | padding:0px; 47 | margin-bottom:20px; 48 | margin-top:40px; 49 | border:2px solid #a44; 50 | background:#a44; 51 | } 52 | 53 | .stacktrace h3{ 54 | padding:22px 0px 20px 10px; 55 | color: #fff; 56 | margin:0px; 57 | font-size:0.75em; 58 | line-height:1em; 59 | font-weight:600; 60 | overflow:auto; 61 | } 62 | 63 | pre { 64 | padding: 20px 0px; 65 | margin:0; 66 | background:#f8f8f8; 67 | width:100%; 68 | overflow:auto; 69 | } 70 | 71 | code{ 72 | line-height: 1.5em; 73 | font-size: 1em; 74 | } 75 | 76 | footer{ 77 | background: #f8f8f8; 78 | padding:20px 0px 20px 20px; 79 | border-top:2px solid #eee; 80 | } 81 | 82 | .version{ 83 | background: #6b919e; 84 | padding: 2px 6px; 85 | border-radius: 3px; 86 | color:#FFF; 87 | font-weight: 300; 88 | } 89 | 90 | ul.projects { 91 | padding: 0; 92 | } 93 | 94 | ul.projects li{ 95 | list-style-type:none; 96 | margin-bottom:10px; 97 | } 98 | 99 | ul.projects li a{ 100 | background: #eee; 101 | padding:25px 0px 25px 20px; 102 | border-left: 4px solid #6b919e; 103 | display:block; 104 | border-radius: 5px; 105 | text-decoration:none; 106 | } 107 | 108 | ul.projects li a h2{ 109 | font-weight:bold; 110 | color: #6b919e; 111 | margin:0px; 112 | } 113 | 114 | ul.projects li a code{ 115 | margin:0px; 116 | color:#666; 117 | text-decoration:none; 118 | } 119 | 120 | .center { 121 | text-align:center; 122 | padding-top: 100px; 123 | padding-bottom: 100px; 124 | } 125 | 126 | body 127 | != yield 128 | footer 129 | p Harp v#{ pkg.version }   130 | a(href="http://harpjs.com/docs/", target="_blank") Documentation 131 | -------------------------------------------------------------------------------- /lib/templates/css/harp.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/lib/templates/css/harp.css -------------------------------------------------------------------------------- /lib/templates/error.jade: -------------------------------------------------------------------------------- 1 | header 2 | h1 #{ error.name } - #{ error.message } 3 | if error.stack 4 | .stacktrace 5 | h3 #{ error.filename } 6 | pre.stack: code= error.stack 7 | 8 | -------------------------------------------------------------------------------- /lib/templates/index.jade: -------------------------------------------------------------------------------- 1 | header 2 | h1 Welcome to Harp 3 | h3 Your local Development Server is Running! 4 | if projects == "" 5 | p You have no apps yet, but you are ready to create one! 6 | p Inside your apps directory, run harp init myproject and then harp server myproject. 7 | else 8 | h2 Your apps: 9 | ul.projects 10 | for project in projects 11 | li 12 | a(href="#{ project.localUrl }") 13 | h2.project-name= project.name 14 | code.project-path= project.localPath 15 | -------------------------------------------------------------------------------- /lib/templates/start.jade: -------------------------------------------------------------------------------- 1 | h1 Hurray, you have a Harp App! 2 | h3 1) Now add a public/index.jade file to your project and refresh your browser. 3 | pre 4 | code 5 | | #{ projectName }/ 6 | | | 7 | | +- public/ 8 | | | 9 | | +- index.jade <-- add this file! 10 | 11 | h5 Error Message: 12 | pre 13 | code= error.output -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harp", 3 | "version": "0.46.1", 4 | "description": "Static Web Server/Generator/Bundler", 5 | "author": "Brock Whitten ", 6 | "contributors": [ 7 | "Brock Whitten ", 8 | "Rob Ellis ", 9 | "Jorge Pedret ", 10 | "Michael Brooks ", 11 | "Tommy-Carlos Williams ", 12 | "Darryl Pogue ", 13 | "Boris Mann ", 14 | "Kenneth Ormandy ", 15 | "Keith Yao ", 16 | "Eric Drechsel ", 17 | "Andrew Hobden ", 18 | "Max Melentiev ", 19 | "Remy Sharp ", 20 | "Zeke Sikelianos ", 21 | "Marc Knaup ", 22 | "Jurgen Van de Moere ", 23 | "Marco Emrich ", 24 | "Roger K ", 25 | "Claus Colloseus ", 26 | "mannyluvstacos ", 27 | "Marcos Lopez C " 28 | ], 29 | "keywords": [ 30 | "static web server", 31 | "static site generator", 32 | "sass", 33 | "markdown", 34 | "jade", 35 | "ejs", 36 | "cjs", 37 | "jsx" 38 | ], 39 | "homepage": "http://harpjs.com", 40 | "bugs": "http://github.com/sintaxi/harp/issues", 41 | "main": "./lib/index.js", 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/sintaxi/harp.git" 45 | }, 46 | "dependencies": { 47 | "@colors/colors": "1.5.0", 48 | "async": "0.2.9", 49 | "basic-auth": "2.0.1", 50 | "boxt": "1.1.2", 51 | "connect": "3.7.0", 52 | "envy-json": "0.2.1", 53 | "fs-extra": "10.1.0", 54 | "human-readable-numbers": "0.9.5", 55 | "mime-types": "2.1.35", 56 | "minimist": "1.2.6", 57 | "on-finished": "^2.4.1", 58 | "parseurl": "1.3.3", 59 | "pause": "0.1.0", 60 | "send": "0.18.0", 61 | "terraform": "1.22.1" 62 | }, 63 | "devDependencies": { 64 | "axios": "0.27.2", 65 | "mocha": "10.0.0", 66 | "should": "3.3.2" 67 | }, 68 | "scripts": { 69 | "prepare": "git config core.hooksPath ./hooks", 70 | "test": "mocha --reporter spec -t 4000", 71 | "test:fallbacks": "mocha test/fallbacks.js -t 3000", 72 | "test:helpers": "mocha test/helpers.js -t 3000" 73 | }, 74 | "license": "MIT", 75 | "preferGlobal": true, 76 | "readmeFilename": "README.md", 77 | "bin": "./bin/harp", 78 | "engines": { 79 | "node": ">=6.x" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /test/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 404 4 | 5 | 6 |

404

7 |

page not found

8 | 9 | -------------------------------------------------------------------------------- /test/apps/app-style-framework/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "foo": "bar" 4 | } 5 | } -------------------------------------------------------------------------------- /test/apps/app-style-implicit/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/app-style-implicit/.gitignore -------------------------------------------------------------------------------- /test/apps/app-style-root/_harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "foo": "bar" 4 | } 5 | } -------------------------------------------------------------------------------- /test/apps/auth/single/_harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "basicAuth": ["foo:bar"] 3 | } -------------------------------------------------------------------------------- /test/apps/auth/single/index.md: -------------------------------------------------------------------------------- 1 | # Hello World 2 | 3 | This is a protected page -------------------------------------------------------------------------------- /test/apps/basic/README.md: -------------------------------------------------------------------------------- 1 | # Example Harp App 2 | ================== 3 | 4 | This app exposes a lot of the functionality that Harp offers. Browse the code and take a look. Questions may be asked in the #harp IRC channel on irc.freenode.net. 5 | -------------------------------------------------------------------------------- /test/apps/basic/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "brand" : "Kitchen Sink", 4 | "name" : "Brock Whitten", 5 | "email" : "brock@chloi.io", 6 | "uri" : "http://example.com" 7 | } 8 | } -------------------------------------------------------------------------------- /test/apps/basic/public/404.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | style. 5 | body{ 6 | text-align:center; 7 | font-family: "verdana"; 8 | } 9 | body 10 | h1 404 11 | h2 Custom, Page Not Found 12 | -------------------------------------------------------------------------------- /test/apps/basic/public/_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "404":{ 3 | "layout": false 4 | }, 5 | "feed":{ 6 | "layout": false 7 | }, 8 | "globals":{ 9 | "layout": false 10 | }, 11 | "current":{ 12 | "layout": false 13 | }, 14 | "about": { 15 | "name": "Brock" 16 | } 17 | } -------------------------------------------------------------------------------- /test/apps/basic/public/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/css/main.css") 5 | link(rel="alternate", type="application/atom+xml", title="#{ brand }", href="#{ uri }/feed.atom") 6 | script. 7 | var showDebug = function(){ 8 | document.getElementById('debug').style.display = 'block'; 9 | return false; 10 | } 11 | body 12 | #header 13 | h1= brand 14 | include shared/_nav 15 | != yield 16 | a(href="#", onclick="showDebug()") open debug panel 17 | .well#debug(style="display:none;") 18 | != partial("shared/_debug") 19 | 20 | -------------------------------------------------------------------------------- /test/apps/basic/public/about.jade: -------------------------------------------------------------------------------- 1 | h2 About #{ name } 2 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 3 | -------------------------------------------------------------------------------- /test/apps/basic/public/articles/_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-world" : { 3 | "title" : "Hello World", 4 | "date" : "May 2, 2012" 5 | }, 6 | "you-half-assed-it" : { 7 | "title" : "You half assed it. That is why your PhoneGap application sucks.", 8 | "date" : "June 6, 2012" 9 | } 10 | } -------------------------------------------------------------------------------- /test/apps/basic/public/articles/hello-world.jade: -------------------------------------------------------------------------------- 1 | h2 2 | a(href="/articles/hello-world") Hello World 3 | h3= date 4 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 5 | 6 | //!= partial("../shared/_debug") -------------------------------------------------------------------------------- /test/apps/basic/public/articles/index.jade: -------------------------------------------------------------------------------- 1 | h2 Articles 2 | each article, slug in public.articles._data 3 | hr 4 | != partial(slug, article) -------------------------------------------------------------------------------- /test/apps/basic/public/articles/with spaces.md: -------------------------------------------------------------------------------- 1 | foo article with spaces -------------------------------------------------------------------------------- /test/apps/basic/public/articles/you-half-assed-it.jade: -------------------------------------------------------------------------------- 1 | h2 2 | a(href="/articles/you-half-assed-it") You Half assed it 3 | h3= date 4 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 5 | 6 | //!= partial("../shared/_debug") 7 | -------------------------------------------------------------------------------- /test/apps/basic/public/basic.html: -------------------------------------------------------------------------------- 1 |

Basic HTML Page

-------------------------------------------------------------------------------- /test/apps/basic/public/css/_nav.scss: -------------------------------------------------------------------------------- 1 | #header { 2 | 3 | h1 { 4 | font-size: 26px; 5 | font-weight: bold; 6 | } 7 | 8 | ul{ 9 | list-style-type: none; 10 | padding:0; 11 | li{ 12 | display: inline-block; 13 | margin-right: 20px; 14 | a { 15 | padding: 10px 5px; 16 | } 17 | } 18 | li.current a{ 19 | background: pink; 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/apps/basic/public/css/main.scss: -------------------------------------------------------------------------------- 1 | @import "_nav"; 2 | 3 | body{ 4 | font-family: "Verdana" 5 | } 6 | 7 | .well{ 8 | background: #eee; 9 | } -------------------------------------------------------------------------------- /test/apps/basic/public/current.json.jade: -------------------------------------------------------------------------------- 1 | != JSON.stringify(current) -------------------------------------------------------------------------------- /test/apps/basic/public/feed.atom.jade: -------------------------------------------------------------------------------- 1 | 2 | feed(xmlns='http://www.w3.org/2005/Atom') 3 | title= brand 4 | link(href="#{ uri }/articles.atom", rel="self", type="application/atom+xml") 5 | link(href="#{ uri }", rel="alternate", type="text/html") 6 | author 7 | name= name 8 | email= email 9 | id= uri + "/" 10 | each article, slug in public.articles._data 11 | entry 12 | title= article.title 13 | id= uri + "/articles/" + slug 14 | updated= article.date 15 | link(href="#{ uri }/articles/#{ slug }", rel="alternate", type="text/html") -------------------------------------------------------------------------------- /test/apps/basic/public/globals.json.jade: -------------------------------------------------------------------------------- 1 | != JSON.stringify(locals) -------------------------------------------------------------------------------- /test/apps/basic/public/index.jade: -------------------------------------------------------------------------------- 1 | h2 Home 2 | p Welcome to Harp. This basic app is a domonstration of how harp works. Poke around and enjoy. 3 | -------------------------------------------------------------------------------- /test/apps/basic/public/shared/_debug.jade: -------------------------------------------------------------------------------- 1 | h4 globals 2 | pre= JSON.stringify(public, null, 2) 3 | 4 | h4 current 5 | pre= JSON.stringify(current, null, 2) -------------------------------------------------------------------------------- /test/apps/basic/public/shared/_nav.jade: -------------------------------------------------------------------------------- 1 | ul 2 | li(class="#{ current.path[0] == 'index' ? 'current' : '' }") 3 | a(href="/") Home 4 | li(class="#{ current.path[0] == 'articles' ? 'current' : '' }") 5 | a(href="/articles") Articles 6 | li(class="#{ current.path[0] == 'about' ? 'current' : '' }") 7 | a(href="/about") About 8 | li 9 | a(href="/feed.atom") RSS 10 | li= current.source -------------------------------------------------------------------------------- /test/apps/cjs/framework/harp.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/cjs/framework/harp.json -------------------------------------------------------------------------------- /test/apps/cjs/framework/public/bundle.cjs: -------------------------------------------------------------------------------- 1 | 2 | console.log("hello world") 3 | -------------------------------------------------------------------------------- /test/apps/cjs/root/bundle.cjs: -------------------------------------------------------------------------------- 1 | 2 | var div = document.createElement("h1") 3 | div.appendChild(document.createTextNode("hello world")) 4 | document.body.appendChild(div) 5 | -------------------------------------------------------------------------------- /test/apps/cjs/root/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/apps/compile/basic/README.md: -------------------------------------------------------------------------------- 1 | # Example Harp App 2 | ================== 3 | 4 | This app exposes a lot of the functionality that Harp offers. Browse the code and take a look. Questions may be asked in the #harp IRC channel on irc.freenode.net. 5 | -------------------------------------------------------------------------------- /test/apps/compile/basic/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "brand" : "Kitchen Sink", 4 | "name" : "Brock Whitten", 5 | "email" : "brock@chloi.io", 6 | "uri" : "http://example.com" 7 | } 8 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/404.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | style. 5 | body{ 6 | text-align:center; 7 | font-family: "verdana"; 8 | } 9 | body 10 | h1 404 11 | h2 Custom, Page Not Found 12 | -------------------------------------------------------------------------------- /test/apps/compile/basic/public/_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "404":{ 3 | "layout": false 4 | }, 5 | "feed":{ 6 | "layout": false 7 | }, 8 | "globals":{ 9 | "layout": false 10 | }, 11 | "current":{ 12 | "layout": false 13 | }, 14 | "about": { 15 | "name": "Brock" 16 | } 17 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/css/main.css") 5 | link(rel="alternate", type="application/atom+xml", title="#{ brand }", href="#{ uri }/feed.atom") 6 | script. 7 | var showDebug = function(){ 8 | document.getElementById('debug').style.display = 'block'; 9 | return false; 10 | } 11 | body 12 | #header 13 | h1= brand 14 | include shared/_nav 15 | != yield 16 | a(href="#", onclick="showDebug()") open debug panel 17 | .well#debug(style="display:none;") 18 | != partial("shared/_debug") 19 | 20 | -------------------------------------------------------------------------------- /test/apps/compile/basic/public/about.jade: -------------------------------------------------------------------------------- 1 | h2 About #{ name } 2 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 3 | -------------------------------------------------------------------------------- /test/apps/compile/basic/public/articles/_data.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello-world" : { 3 | "title" : "Hello World", 4 | "date" : "May 2, 2012" 5 | }, 6 | "you-half-assed-it" : { 7 | "title" : "You half assed it. That is why your PhoneGap application sucks.", 8 | "date" : "June 6, 2012" 9 | } 10 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/articles/hello-world.jade: -------------------------------------------------------------------------------- 1 | h2 2 | a(href="/articles/hello-world") Hello World 3 | h3= date 4 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 5 | 6 | //!= partial("../shared/_debug") -------------------------------------------------------------------------------- /test/apps/compile/basic/public/articles/index.jade: -------------------------------------------------------------------------------- 1 | h2 Articles 2 | each article, slug in public.articles._data 3 | hr 4 | != partial(slug, article) -------------------------------------------------------------------------------- /test/apps/compile/basic/public/articles/with spaces.md: -------------------------------------------------------------------------------- 1 | foo article with spaces -------------------------------------------------------------------------------- /test/apps/compile/basic/public/articles/you-half-assed-it.jade: -------------------------------------------------------------------------------- 1 | h2 2 | a(href="/articles/you-half-assed-it") You Half assed it 3 | h3= date 4 | p Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 5 | 6 | //!= partial("../shared/_debug") 7 | -------------------------------------------------------------------------------- /test/apps/compile/basic/public/basic.html: -------------------------------------------------------------------------------- 1 |

Basic HTML Page

-------------------------------------------------------------------------------- /test/apps/compile/basic/public/css/_nav.scss: -------------------------------------------------------------------------------- 1 | #header { 2 | 3 | h1 { 4 | font-size: 26px; 5 | font-weight: bold; 6 | } 7 | 8 | ul{ 9 | list-style-type: none; 10 | padding:0; 11 | li{ 12 | display: inline-block; 13 | margin-right: 20px; 14 | a { 15 | padding: 10px 5px; 16 | } 17 | } 18 | li.current a{ 19 | background: pink; 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/css/_partials/_more.scss: -------------------------------------------------------------------------------- 1 | #header { 2 | 3 | h1 { 4 | font-size: 26px; 5 | font-weight: bold; 6 | } 7 | 8 | ul{ 9 | list-style-type: none; 10 | padding:0; 11 | li{ 12 | display: inline-block; 13 | margin-right: 20px; 14 | a { 15 | padding: 10px 5px; 16 | } 17 | } 18 | li.current a{ 19 | background: pink; 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/css/_partials/some.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: red; 3 | &:before { 4 | contents: "Chloi Inc." 5 | } 6 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/css/main.scss: -------------------------------------------------------------------------------- 1 | @import "_nav"; 2 | 3 | body{ 4 | font-family: "Verdana" 5 | } 6 | 7 | .well{ 8 | background: #eee; 9 | } -------------------------------------------------------------------------------- /test/apps/compile/basic/public/index.jade: -------------------------------------------------------------------------------- 1 | h2 Home 2 | p Welcome to Harp. This basic app is a domonstration of how harp works. Poke around and enjoy. 3 | -------------------------------------------------------------------------------- /test/apps/compile/basic/public/shared/_debug.jade: -------------------------------------------------------------------------------- 1 | h4 globals 2 | pre= JSON.stringify(locals, null, 2) 3 | 4 | h4 current 5 | pre= JSON.stringify(current, null, 2) -------------------------------------------------------------------------------- /test/apps/compile/basic/public/shared/_nav.jade: -------------------------------------------------------------------------------- 1 | ul 2 | li(class="#{ current.path[0] == 'index' ? 'current' : '' }") 3 | a(href="/") Home 4 | li(class="#{ current.path[0] == 'articles' ? 'current' : '' }") 5 | a(href="/articles") Articles 6 | li(class="#{ current.path[0] == 'about' ? 'current' : '' }") 7 | a(href="/about") About 8 | li 9 | a(href="/feed.atom") RSS -------------------------------------------------------------------------------- /test/apps/compile/err/a.jade: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title Hi 4 | link(href="b.css" rel="stylesheet" type="text/css") 5 | body 6 | h1 Hello -------------------------------------------------------------------------------- /test/apps/compile/err/b.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: $indigo 3 | } -------------------------------------------------------------------------------- /test/apps/compile/err/npm-debug.log: -------------------------------------------------------------------------------- 1 | 0 info it worked if it ends with ok 2 | 1 verbose cli [ 'node', '/usr/local/bin/npm', 'test' ] 3 | 2 info using npm@1.3.8 4 | 3 info using node@v0.10.18 5 | 4 verbose node symlink /usr/bin/node 6 | 5 error Error: ENOENT, open '/Users/kennethormandy/Sites/kennethormandy/harp/test/apps/compile/err/package.json' 7 | 6 error If you need help, you may report this log at: 8 | 6 error 9 | 6 error or email it to: 10 | 6 error 11 | 7 error System Darwin 12.5.0 12 | 8 error command "node" "/usr/local/bin/npm" "test" 13 | 9 error cwd /Users/kennethormandy/Sites/kennethormandy/harp/test/apps/compile/err 14 | 10 error node -v v0.10.18 15 | 11 error npm -v 1.3.8 16 | 12 error path /Users/kennethormandy/Sites/kennethormandy/harp/test/apps/compile/err/package.json 17 | 13 error code ENOENT 18 | 14 error errno 34 19 | 15 verbose exit [ 34, true ] 20 | -------------------------------------------------------------------------------- /test/apps/compile/root/_harp.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/apps/compile/root/www/foo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/compile/root/www/foo -------------------------------------------------------------------------------- /test/apps/envy/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": "bar", 3 | "basicAuth": "$HARP_BASIC_AUTH", 4 | "optionalThing": "$OPTIONAL_THING" 5 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "brand" : "Kitchen Sink", 4 | "name" : "Brock Whitten", 5 | "email" : "brock@chloi.io", 6 | "uri" : "http://example.com" 7 | } 8 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/css/main.css") 5 | link(rel="stylesheet", href="css/other.css") 6 | body 7 | #header 8 | h1 Two Stylsheets Test 9 | include shared/_nav 10 | != yield -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/css/_nav.styl: -------------------------------------------------------------------------------- 1 | #header 2 | h1 3 | font-size: 26px 4 | font-weight: bold 5 | 6 | ul 7 | list-style-type: none; 8 | padding:0; 9 | li 10 | display: inline-block 11 | margin-right: 20px 12 | a 13 | padding: 10px 5px 14 | li.current 15 | a 16 | background: pink -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/css/main.styl: -------------------------------------------------------------------------------- 1 | @import "_nav" 2 | 3 | html 4 | background: lighten(pink, 50%) 5 | 6 | body 7 | font-family: "Verdana" 8 | background: #CCC 9 | display: inline-block 10 | text-align: center 11 | width: 50% 12 | float: right 13 | 14 | p 15 | text-align: left 16 | 17 | .well 18 | background: #eee -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/css/other.styl: -------------------------------------------------------------------------------- 1 | header 2 | font-family: "Georgia" 3 | background: cornflowerblue 4 | 5 | // This is where the error is 6 | div -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/index.jade: -------------------------------------------------------------------------------- 1 | h2 Test 2 | p 3 | 4 | p In this app, two stylesheets are included in _layout.jade. One compiled successfully and the other did not. 5 | p The styling for this error needs to have !important on everything so it doesn’t get overwritten accidentally by the app’s styles. -------------------------------------------------------------------------------- /test/apps/err-invalid-and-valid-stylsheets/public/shared/_nav.jade: -------------------------------------------------------------------------------- 1 | ul 2 | li(class="#{ current.path[0] == 'index' ? 'current' : '' }") 3 | a(href="/") Home 4 | li(class="#{ current.path[0] == 'articles' ? 'current' : '' }") 5 | a(href="/articles") Articles 6 | li(class="#{ current.path[0] == 'about' ? 'current' : '' }") 7 | a(href="/about") About 8 | li 9 | a(href="/feed.atom") RSS -------------------------------------------------------------------------------- /test/apps/err-invalid-config/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "name" : "missing comma" 4 | "email" : "brock@chloi.io" 5 | } 6 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-config/public/index.jade: -------------------------------------------------------------------------------- 1 | h2 Home 2 | p Welcome to Harp. This app has an invalid JSON file. 3 | -------------------------------------------------------------------------------- /test/apps/err-invalid-data/public/_data.json: -------------------------------------------------------------------------------- 1 | { 2 | invalid: json 3 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-vars/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "name" : "Brock Whitten", 4 | "email" : "brock@chloi.io" 5 | } 6 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-vars/public/bad.less: -------------------------------------------------------------------------------- 1 | body{ 2 | background: @green; 3 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-vars/public/index.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/main.css", type="text/css") 5 | body 6 | h2 Home 7 | p Welcome to Harp. This app has jade errors and css errors. 8 | p= foo -------------------------------------------------------------------------------- /test/apps/err-invalid-vars/public/main.less: -------------------------------------------------------------------------------- 1 | body{ 2 | background:pink; 3 | } -------------------------------------------------------------------------------- /test/apps/err-invalid-vars/public/style.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/bad.css", type="text/css") 5 | body 6 | h2 Home 7 | p This should have a stylesheet error -------------------------------------------------------------------------------- /test/apps/err-missing-404/harp.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "brand" : "Kitchen Sink", 4 | "name" : "Brock Whitten", 5 | "email" : "brock@chloi.io", 6 | "uri" : "http://example.com" 7 | } 8 | } -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/_layout.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | html 3 | head 4 | link(rel="stylesheet", href="/css/main.css") 5 | link(rel="alternate", type="application/atom+xml", title="#{ globals.brand }", href="#{ globals.uri }/feed.atom") 6 | script. 7 | var showDebug = function(){ 8 | document.getElementById('debug').style.display = 'block'; 9 | return false; 10 | } 11 | body 12 | #header 13 | h1= globals.brand 14 | include shared/_nav 15 | != yield 16 | a(href="#", onclick="showDebug()") open debug panel 17 | .well#debug(style="display:none;") 18 | != partial("shared/_debug") 19 | 20 | -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/css/_nav.less: -------------------------------------------------------------------------------- 1 | #header { 2 | 3 | h1 { 4 | font-size: 26px; 5 | font-weight: bold; 6 | } 7 | 8 | ul{ 9 | list-style-type: none; 10 | padding:0; 11 | li{ 12 | display: inline-block; 13 | margin-right: 20px; 14 | a { 15 | padding: 10px 5px; 16 | } 17 | } 18 | li.current a{ 19 | background: pink; 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/css/main.less: -------------------------------------------------------------------------------- 1 | @import "_nav"; 2 | 3 | body{ 4 | font-family: "Verdana" 5 | } 6 | 7 | .well{ 8 | background: #eee; 9 | } -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/index.jade: -------------------------------------------------------------------------------- 1 | h2 Home 2 | p Welcome to Harp. This basic app is a domonstration of how harp works. Poke around and enjoy. 3 | -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/shared/_debug.jade: -------------------------------------------------------------------------------- 1 | h4 globals 2 | pre= JSON.stringify(globals, null, 2) 3 | 4 | h4 current 5 | pre= JSON.stringify(current, null, 2) -------------------------------------------------------------------------------- /test/apps/err-missing-404/public/shared/_nav.jade: -------------------------------------------------------------------------------- 1 | ul 2 | li(class="#{ current.path[0] == 'index' ? 'current' : '' }") 3 | a(href="/") Home 4 | li(class="#{ current.path[0] == 'articles' ? 'current' : '' }") 5 | a(href="/articles") Articles 6 | li(class="#{ current.path[0] == 'about' ? 'current' : '' }") 7 | a(href="/about") About 8 | li 9 | a(href="/feed.atom") RSS -------------------------------------------------------------------------------- /test/apps/err-missing-public/harp.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/err-missing-public/harp.json -------------------------------------------------------------------------------- /test/apps/fallbacks/two-hundy/nested/app/200.jade: -------------------------------------------------------------------------------- 1 | h1 This is a 200.jade file -------------------------------------------------------------------------------- /test/apps/fallbacks/two-hundy/plain/200.html: -------------------------------------------------------------------------------- 1 |

This is a 200.html file

-------------------------------------------------------------------------------- /test/apps/fallbacks/two-hundy/processed/200.jade: -------------------------------------------------------------------------------- 1 | h1 This is a 200.jade file -------------------------------------------------------------------------------- /test/apps/headers/invalid-coffee.coffee: -------------------------------------------------------------------------------- 1 | foo( -------------------------------------------------------------------------------- /test/apps/headers/invalid-ejs.ejs: -------------------------------------------------------------------------------- 1 |

Hello EJS

2 | <%- foo %> -------------------------------------------------------------------------------- /test/apps/headers/invalid-jade.jade: -------------------------------------------------------------------------------- 1 | h1 Hello Jade 2 | if foo() -------------------------------------------------------------------------------- /test/apps/headers/invalid-less.less: -------------------------------------------------------------------------------- 1 | @foo -------------------------------------------------------------------------------- /test/apps/headers/invalid-sass.sass: -------------------------------------------------------------------------------- 1 | $foo -------------------------------------------------------------------------------- /test/apps/headers/invalid-scss.scss: -------------------------------------------------------------------------------- 1 | $foo -------------------------------------------------------------------------------- /test/apps/headers/invalid-styl.styl: -------------------------------------------------------------------------------- 1 | @foo -------------------------------------------------------------------------------- /test/apps/headers/valid-coffee.coffee: -------------------------------------------------------------------------------- 1 | () -> alert("hello world") -------------------------------------------------------------------------------- /test/apps/headers/valid-css.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background: #FFFFFF; 4 | } 5 | -------------------------------------------------------------------------------- /test/apps/headers/valid-ejs.ejs: -------------------------------------------------------------------------------- 1 |

Hello World

-------------------------------------------------------------------------------- /test/apps/headers/valid-html.html: -------------------------------------------------------------------------------- 1 |

Hello World

-------------------------------------------------------------------------------- /test/apps/headers/valid-jade.jade: -------------------------------------------------------------------------------- 1 | h1 Hello World -------------------------------------------------------------------------------- /test/apps/headers/valid-js.js: -------------------------------------------------------------------------------- 1 | alert("hello world") -------------------------------------------------------------------------------- /test/apps/headers/valid-less.less: -------------------------------------------------------------------------------- 1 | body { 2 | background: pink; 3 | } -------------------------------------------------------------------------------- /test/apps/headers/valid-markdown.md: -------------------------------------------------------------------------------- 1 | # Hello World -------------------------------------------------------------------------------- /test/apps/headers/valid-sass.sass: -------------------------------------------------------------------------------- 1 | body 2 | background: pink 3 | -------------------------------------------------------------------------------- /test/apps/headers/valid-scss.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background: pink; 3 | } -------------------------------------------------------------------------------- /test/apps/headers/valid-styl.styl: -------------------------------------------------------------------------------- 1 | body 2 | background: pink; -------------------------------------------------------------------------------- /test/apps/multihost/app.harp.io/index.jade: -------------------------------------------------------------------------------- 1 | h1 app.harp.io -------------------------------------------------------------------------------- /test/apps/multihost/app1.subdomain.com/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/multihost/app1.subdomain.com/.gitkeep -------------------------------------------------------------------------------- /test/apps/multihost/app1.subdomain.com/index.jade: -------------------------------------------------------------------------------- 1 | h1 app1.subdomain.com -------------------------------------------------------------------------------- /test/apps/multihost/app1.subdomain.net/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/multihost/app1.subdomain.net/.gitkeep -------------------------------------------------------------------------------- /test/apps/multihost/app1.subdomain.net/index.jade: -------------------------------------------------------------------------------- 1 | h1 app1.subdomain.net -------------------------------------------------------------------------------- /test/apps/multihost/app2.subdomain.io/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/multihost/app2.subdomain.io/.gitkeep -------------------------------------------------------------------------------- /test/apps/multihost/app2.subdomain.io/index.jade: -------------------------------------------------------------------------------- 1 | h1 app2.subdomain.io -------------------------------------------------------------------------------- /test/apps/plain/framework-style/harp.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sintaxi/harp/31e013f16004915383eb5d08e0e06b368e5dcb2b/test/apps/plain/framework-style/harp.json -------------------------------------------------------------------------------- /test/apps/plain/framework-style/public/404.html: -------------------------------------------------------------------------------- 1 |

404 in HTML

-------------------------------------------------------------------------------- /test/apps/plain/framework-style/public/hello.txt: -------------------------------------------------------------------------------- 1 | text files are wonderful -------------------------------------------------------------------------------- /test/apps/plain/framework-style/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Foobar

6 | 7 | -------------------------------------------------------------------------------- /test/apps/plain/root-style/404.html: -------------------------------------------------------------------------------- 1 |

404 in HTML

-------------------------------------------------------------------------------- /test/apps/plain/root-style/hello.txt: -------------------------------------------------------------------------------- 1 | text files are wonderful -------------------------------------------------------------------------------- /test/apps/plain/root-style/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Foobar

6 | 7 | -------------------------------------------------------------------------------- /test/apps/security/_secret.txt: -------------------------------------------------------------------------------- 1 | secret text -------------------------------------------------------------------------------- /test/apps/slash-indifference/directory/index.html: -------------------------------------------------------------------------------- 1 |

file in directory contents

-------------------------------------------------------------------------------- /test/apps/slash-indifference/file.html: -------------------------------------------------------------------------------- 1 |

file contents

-------------------------------------------------------------------------------- /test/auth.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require('path') 4 | var harp = require("../") 5 | 6 | describe("basicAuth", function(){ 7 | 8 | describe("single", function(done){ 9 | var projectPath = path.join(__dirname, "apps/auth/single") 10 | var server; 11 | 12 | before(function(done){ 13 | server = harp.server(projectPath).listen(8310, done) 14 | }) 15 | 16 | it("should be a protected page", function(done){ 17 | axios.get('http://localhost:8310/').catch(function(error) { 18 | error.response.status.should.eql(401) 19 | done() 20 | }) 21 | }) 22 | 23 | it("should fetch protected resource with correct creds", function(done){ 24 | axios({ 25 | method: "GET", 26 | url: 'http://localhost:8310/', 27 | auth: { 28 | username: 'foo', 29 | password: 'bar' 30 | } 31 | }).then(function(response){ 32 | response.status.should.eql(200) 33 | done() 34 | }) 35 | }) 36 | 37 | after(function(done){ 38 | server.close(done) 39 | }) 40 | }) 41 | 42 | }) -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("basic", function(){ 9 | var projectPath = path.join(__dirname, "apps/basic") 10 | var outputPath = path.join(__dirname, "out/basic") 11 | var config; 12 | var server; 13 | 14 | before(function(done){ 15 | harp.compile(projectPath, outputPath, function(errors, output){ 16 | config = output.config 17 | server = harp.server(projectPath).listen(8100, done) 18 | }) 19 | }) 20 | 21 | it("should have harp version in config", function(done){ 22 | config.should.have.property("harp_version") 23 | done() 24 | }) 25 | 26 | it("should have global vars", function(done){ 27 | var staticGlobals = require(path.join(outputPath, "globals.json")) 28 | staticGlobals.should.have.property("environment", "production") 29 | staticGlobals.should.have.property("public") 30 | axios.get('http://localhost:8100/globals.json').then(function(r){ 31 | r.status.should.eql(200) 32 | r.data.should.have.property("environment", "development") 33 | r.data.should.have.property("public") 34 | done() 35 | }) 36 | }) 37 | 38 | it("should have current vars", function(done){ 39 | var staticCurrent = require(path.join(outputPath, "current.json")) 40 | staticCurrent.should.have.property("path") 41 | staticCurrent.should.have.property("source", "current.json") 42 | axios.get('http://localhost:8100/current.json').then(function(r){ 43 | r.status.should.eql(200) 44 | r.data.should.have.property("path") 45 | r.data.should.have.property("source", "current.json") 46 | done() 47 | }) 48 | }) 49 | 50 | it("should have index file that uses the layout", function(done){ 51 | fs.readFile(path.join(outputPath, "index.html"), function(err, contents){ 52 | contents.toString().should.include("Kitchen Sink") 53 | contents.toString().should.include("Home") 54 | axios.get('http://localhost:8100/').then(function(r){ 55 | r.status.should.eql(200) 56 | r.data.should.include("Kitchen Sink") 57 | r.data.should.include("Home") 58 | done() 59 | }) 60 | }) 61 | }) 62 | 63 | it("should have custom 404 page that does not use layout", function(done){ 64 | fs.readFile(path.join(outputPath, "404.html"), function(err, contents){ 65 | contents.toString().should.not.include("Kitchen Sink") 66 | contents.toString().should.include("Custom, Page Not Found") 67 | axios.get('http://localhost:8100/some/missing/path').catch(function(e){ 68 | e.response.status.should.eql(404) 69 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 70 | e.response.data.should.not.include("Kitchen Sink") 71 | e.response.data.should.include("Custom, Page Not Found") 72 | e.response.data.should.eql(contents.toString()) 73 | done() 74 | }) 75 | }) 76 | }) 77 | 78 | it("should return CSS file", function(done){ 79 | fs.readFile(path.join(outputPath, "css", "main.css"), function(err, contents){ 80 | contents.toString().should.include("background") 81 | axios.get('http://localhost:8100/css/main.css').then(function(r){ 82 | r.status.should.eql(200) 83 | r.data.should.include("background") 84 | r.data.should.eql(contents.toString()) 85 | done() 86 | }) 87 | }) 88 | }) 89 | 90 | it("should return proper mime type on 404 page", function(done){ 91 | axios.get('http://localhost:8100/some/missing/path.css').catch(function(e){ 92 | e.response.status.should.eql(404) 93 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 94 | done() 95 | }) 96 | }) 97 | 98 | it("should render HTML page without requiring extension", function(done){ 99 | fs.readFile(path.join(outputPath, "basic.html"), function(err, contents){ 100 | contents.toString().should.not.include("Kitchen Sink") 101 | contents.toString().should.include("

Basic HTML Page

") 102 | axios.get('http://localhost:8100/basic').then(function(r){ 103 | r.status.should.eql(200) 104 | r.data.should.not.include("Kitchen Sink") 105 | r.data.should.include("

Basic HTML Page

") 106 | r.data.should.eql(contents.toString()) 107 | done() 108 | }) 109 | }) 110 | }) 111 | 112 | it("should not return file starting with underscore", function(done){ 113 | axios.get('http://localhost:8100/shared/_nav.jade').catch(function(e){ 114 | e.response.status.should.eql(404) 115 | done() 116 | }) 117 | }) 118 | 119 | it("should render HTML page with spaces in the file name", function(done){ 120 | axios.get('http://localhost:8100/articles/with%20spaces').then(function(r){ 121 | r.status.should.eql(200) 122 | r.data.should.include("foo article") 123 | done() 124 | }) 125 | }) 126 | 127 | after(function(done){ 128 | exec("rm -rf " + outputPath, function(){ 129 | server.close(done) 130 | }) 131 | }) 132 | 133 | }) 134 | -------------------------------------------------------------------------------- /test/cjs.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var path = require("path") 3 | var fs = require("fs") 4 | var exec = require("child_process").exec 5 | var harp = require("../"); 6 | 7 | describe("cjs", function(){ 8 | 9 | describe("basic root project", function(){ 10 | var projectPath = path.join(__dirname, "apps/cjs/root") 11 | var outputPath = path.join(__dirname, "out/cjs-root") 12 | 13 | it("should compile", function(done){ 14 | harp.compile(projectPath, outputPath, function(error){ 15 | should.not.exist(error) 16 | done() 17 | }) 18 | }) 19 | 20 | it("compile should not include bundle.cjs", function(done) { 21 | 22 | var rsp = fs.existsSync(path.join(outputPath, "/bundle.cjs")) 23 | rsp.should.be.false 24 | 25 | var rsp = fs.existsSync(path.join(outputPath, "/bundle.js")) 26 | rsp.should.be.true 27 | 28 | done() 29 | 30 | }) 31 | 32 | after(function(done){ 33 | exec("rm -rf " + outputPath, function() { 34 | done(); 35 | }) 36 | }) 37 | 38 | }) 39 | 40 | describe("basic framework project", function(){ 41 | var projectPath = path.join(__dirname, "apps/cjs/framework") 42 | var outputPath = path.join(__dirname, "out/cjs-framework") 43 | 44 | it("should compile", function(done){ 45 | harp.compile(projectPath, outputPath, function(error){ 46 | should.not.exist(error) 47 | done() 48 | }) 49 | }) 50 | 51 | it("compile should not include bundle.cjs", function(done) { 52 | 53 | var rsp = fs.existsSync(path.join(outputPath, "/bundle.cjs")) 54 | rsp.should.be.false 55 | 56 | var rsp = fs.existsSync(path.join(outputPath, "/bundle.js")) 57 | rsp.should.be.true 58 | 59 | done() 60 | 61 | }) 62 | 63 | after(function(done){ 64 | exec("rm -rf " + outputPath, function() { 65 | done(); 66 | }) 67 | }) 68 | 69 | }) 70 | 71 | 72 | }) 73 | -------------------------------------------------------------------------------- /test/compile.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var path = require("path") 3 | var fs = require("fs") 4 | var exec = require("child_process").exec 5 | var harp = require("../"); 6 | 7 | describe("compile", function(){ 8 | 9 | describe("basic app", function(){ 10 | var projectPath = path.join(__dirname, "apps/compile/basic") 11 | var outputPath = path.join(__dirname, "out/compile-basic") 12 | 13 | it("should compile", function(done){ 14 | harp.compile(projectPath, outputPath, function(error){ 15 | should.not.exist(error) 16 | done() 17 | }) 18 | }) 19 | 20 | it("compile should not include folders named with underscores", function(done) { 21 | var cssOutputPath = path.join(outputPath, "/css") 22 | 23 | var rsp = fs.existsSync(path.join(cssOutputPath, "/_partials/some.css")) 24 | rsp.should.be.false 25 | 26 | var rsp = fs.existsSync(path.join(cssOutputPath, "/_partials/_more.css")) 27 | rsp.should.be.false 28 | 29 | done() 30 | 31 | }) 32 | 33 | it("compile should not include files named with underscores", function(done) { 34 | var cssOutputPath = path.join(outputPath, "/css") 35 | 36 | var rsp = fs.existsSync(path.join(cssOutputPath, "/one/two/three/_four.css")) 37 | rsp.should.be.false 38 | 39 | var rsp = fs.existsSync(path.join(cssOutputPath, "/one/two/three/_five.css")) 40 | rsp.should.be.false 41 | 42 | var rsp = fs.existsSync(path.join(cssOutputPath, "/_nav.css")) 43 | rsp.should.be.false 44 | 45 | done() 46 | }) 47 | 48 | after(function(done){ 49 | exec("rm -rf " + outputPath, function() { 50 | done(); 51 | }) 52 | }) 53 | 54 | }) 55 | 56 | describe("root app with .git dir", function(){ 57 | var projectPath = path.join(__dirname, "apps","compile","root") 58 | var outputPath = path.join(__dirname, "out","compile-root") 59 | var gitPath = path.join(projectPath, ".git") 60 | 61 | // Make at runtime since git refuses to store .git dirs 62 | if (!fs.existsSync(gitPath)) { 63 | fs.mkdirSync(gitPath); 64 | fs.openSync(path.join(gitPath, "foo"), 'a') 65 | } 66 | 67 | 68 | it("should compile", function(done){ 69 | harp.compile(projectPath, outputPath, function(error){ 70 | should.not.exist(error) 71 | done() 72 | }) 73 | }) 74 | 75 | it("should not include .git in output", function(done) { 76 | var rsp = fs.existsSync(path.join(projectPath, ".git", "foo")) 77 | rsp.should.be.true 78 | 79 | var rsp = fs.existsSync(path.join(outputPath, ".git")) 80 | rsp.should.be.false 81 | 82 | done() 83 | }) 84 | 85 | after(function(done){ 86 | exec("rm -rf " + outputPath + " " + gitPath, function() { 87 | done(); 88 | }) 89 | }) 90 | 91 | }) 92 | 93 | describe("root app with output dir containing .git in project dir", function(){ 94 | var projectPath = path.join(__dirname, "apps","compile","root") 95 | var outputPath = path.join(projectPath, "out") 96 | var gitPath = path.join(outputPath, ".git") 97 | 98 | // Making this at runtime since git refuses to store .git dirs 99 | if (!fs.existsSync(gitPath)) { 100 | fs.mkdirSync(outputPath); 101 | fs.mkdirSync(gitPath); 102 | fs.openSync(path.join(gitPath, "foo"), 'a') 103 | } 104 | 105 | 106 | it("should compile", function(done){ 107 | harp.compile(projectPath, outputPath, function(error){ 108 | should.not.exist(error) 109 | done() 110 | }) 111 | }) 112 | 113 | it("should not include a copy of the output subpath in output", function(done) { 114 | var rsp = fs.existsSync(path.join(outputPath, "out")) 115 | rsp.should.be.false 116 | 117 | done(); 118 | }) 119 | 120 | after(function(done){ 121 | exec("rm -rf " + outputPath, function() { 122 | done(); 123 | }) 124 | }) 125 | }) 126 | 127 | }) 128 | -------------------------------------------------------------------------------- /test/errors.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("errors", function(){ 9 | 10 | describe("err-invalid-config", function(){ 11 | var projectPath = path.join(__dirname, "apps/err-invalid-config") 12 | var outputPath = path.join(__dirname, "out/err-invalid-config") 13 | var port = 8111 14 | var server; 15 | 16 | before(function(done){ 17 | server = harp.server(projectPath).listen(port, done) 18 | }) 19 | 20 | it("should get error message for invalid harp.json", function(done){ 21 | axios.get('http://localhost:'+ port +'/').catch(function(e){ 22 | e.response.status.should.eql(500) 23 | e.response.data.should.include(harp.pkg.version) 24 | harp.compile(projectPath, outputPath, function(error){ 25 | should.exist(error) 26 | error.should.have.property("source") 27 | error.should.have.property("dest") 28 | error.should.have.property("filename") 29 | error.should.have.property("message") 30 | error.should.have.property("stack") 31 | error.should.have.property("lineno") 32 | done() 33 | }) 34 | }) 35 | }) 36 | 37 | after(function(done){ 38 | server.close(done) 39 | }) 40 | }) 41 | 42 | describe("err-invalid-data", function(){ 43 | var projectPath = path.join(__dirname, "apps/err-invalid-data") 44 | var outputPath = path.join(__dirname, "out/err-invalid-data") 45 | var port = 8112 46 | var server; 47 | 48 | before(function(done){ 49 | server = harp.server(projectPath).listen(port, done) 50 | }) 51 | 52 | it("should get error message for invalid _data.json", function(done){ 53 | axios.get('http://localhost:'+ port +'/').catch(function(e){ 54 | e.response.status.should.eql(500) 55 | e.response.data.should.include(harp.pkg.version) 56 | harp.compile(projectPath, outputPath, function(error){ 57 | should.exist(error) 58 | error.should.have.property("source") 59 | error.should.have.property("dest") 60 | error.should.have.property("filename") 61 | error.should.have.property("message") 62 | error.should.have.property("stack") 63 | error.should.have.property("lineno") 64 | done() 65 | }) 66 | }) 67 | }) 68 | 69 | after(function(done){ 70 | server.close(done) 71 | }) 72 | }) 73 | 74 | describe("err-missing-public", function(){ 75 | var projectPath = path.join(__dirname, "apps/err-missing-public") 76 | var outputPath = path.join(__dirname, "out/err-missing-public") 77 | var port = 8113 78 | var server; 79 | 80 | before(function(done){ 81 | server = harp.server(projectPath).listen(port, done) 82 | }) 83 | 84 | it("should get error message for invalid _data.json", function(done){ 85 | axios.get('http://localhost:'+ port +'/').catch(function(e){ 86 | e.response.status.should.eql(500) 87 | e.response.data.should.include(harp.pkg.version) 88 | harp.compile(projectPath, outputPath, function(error){ 89 | should.exist(error) 90 | error.should.have.property("source") 91 | error.should.have.property("dest") 92 | error.should.have.property("filename") 93 | error.should.have.property("message") 94 | error.should.have.property("stack") 95 | error.should.have.property("lineno") 96 | done() 97 | }) 98 | }) 99 | }) 100 | 101 | after(function(done){ 102 | server.close(done) 103 | }) 104 | }) 105 | 106 | describe("err-missing-public", function(){ 107 | var projectPath = path.join(__dirname, "apps/err-missing-404") 108 | var outputPath = path.join(__dirname, "out/err-missing-404") 109 | var port = 8114 110 | var server; 111 | 112 | before(function(done){ 113 | server = harp.server(projectPath).listen(port, done) 114 | }) 115 | 116 | it("should return proper mime type on 404 page", function(done){ 117 | axios.get('http://localhost:'+ port +'/some/missing/path.css').catch(function(e){ 118 | e.response.status.should.eql(404) 119 | e.response.data.should.include(harp.pkg.version) 120 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 121 | done() 122 | }) 123 | }) 124 | 125 | after(function(done){ 126 | server.close(done) 127 | }) 128 | }) 129 | 130 | after(function(done){ 131 | exec("rm -rf " + path.join(__dirname, "out"), function(){ 132 | done() 133 | }) 134 | }) 135 | 136 | }) 137 | -------------------------------------------------------------------------------- /test/fallbacks.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("fallbacks", function(){ 9 | 10 | describe("plain 200 file", function(){ 11 | var projectPath = path.join(__dirname, "apps/fallbacks/two-hundy/plain") 12 | var outputPath = path.join(__dirname, "out/fallbacks-two-hundy-plain") 13 | var port = 8115 14 | var server; 15 | 16 | before(function(done){ 17 | harp.compile(projectPath, outputPath, function(errors, output){ 18 | server = harp.server(projectPath).listen(port, done) 19 | }) 20 | }) 21 | 22 | it("should return proper mime type on 200 page", function(done){ 23 | axios.get('http://localhost:'+ port +'/some/fallback/path').then(function(r){ 24 | r.status.should.eql(200) 25 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 26 | done() 27 | }) 28 | }) 29 | 30 | it("should have custom 200 page", function(done){ 31 | fs.readFile(path.join(outputPath, "200.html"), function(err, contents){ 32 | should.not.exist(err) 33 | axios.get('http://localhost:'+ port +'/some/missing/path').then(function(r){ 34 | r.status.should.eql(200) 35 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 36 | r.data.should.eql(contents.toString()) 37 | done() 38 | }) 39 | }) 40 | }) 41 | 42 | after(function(done){ 43 | server.close(done) 44 | }) 45 | 46 | }) 47 | 48 | describe("processed 200 file", function(){ 49 | var projectPath = path.join(__dirname, "apps/fallbacks/two-hundy/processed") 50 | var outputPath = path.join(__dirname, "out/fallbacks-two-hundy-processed") 51 | var port = 8116 52 | var server; 53 | 54 | before(function(done){ 55 | harp.compile(projectPath, outputPath, function(errors, output){ 56 | server = harp.server(projectPath).listen(port, done) 57 | }) 58 | }) 59 | 60 | it("should return proper mime type on 200 page", function(done){ 61 | axios.get('http://localhost:'+ port +'/some/fallback/path').then(function(r){ 62 | r.status.should.eql(200) 63 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 64 | done() 65 | }) 66 | }) 67 | 68 | it("should have custom 200 page", function(done){ 69 | fs.readFile(path.join(outputPath, "200.html"), function(err, contents){ 70 | should.not.exist(err) 71 | axios.get('http://localhost:'+ port +'/some/missing/path').then(function(r){ 72 | r.status.should.eql(200) 73 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 74 | r.data.should.eql(contents.toString()) 75 | done() 76 | }) 77 | }) 78 | }) 79 | 80 | after(function(done){ 81 | server.close(done) 82 | }) 83 | }) 84 | 85 | 86 | describe("nested 200 file", function(){ 87 | var projectPath = path.join(__dirname, "apps/fallbacks/two-hundy/nested") 88 | var outputPath = path.join(__dirname, "out/fallbacks-two-hundy-nested") 89 | var port = 8117 90 | var server; 91 | 92 | before(function(done){ 93 | harp.compile(projectPath, outputPath, function(errors, output){ 94 | server = harp.server(projectPath).listen(port, done) 95 | }) 96 | }) 97 | 98 | it("should return 404 on missing path", function(done){ 99 | axios.get('http://localhost:'+ port +'/some/fallback/path').catch(function(e){ 100 | e.response.status.should.eql(404) 101 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 102 | done() 103 | }) 104 | }) 105 | 106 | it("should have fallback 404 page", function(done){ 107 | axios.get('http://localhost:'+ port +'/some/missing/path').catch(function(e){ 108 | e.response.status.should.eql(404) 109 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 110 | done() 111 | }) 112 | }) 113 | 114 | it("should return custom 200 page on nested path", function(done){ 115 | fs.readFile(path.join(outputPath, "app/200.html"), function(err, contents){ 116 | should.not.exist(err) 117 | axios.get('http://localhost:'+ port +'/app/missing/path').then(function(r){ 118 | r.status.should.eql(200) 119 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 120 | r.data.should.eql(contents.toString()) 121 | done() 122 | }) 123 | }) 124 | }) 125 | 126 | it("should return 200 on /app/", function(done){ 127 | fs.readFile(path.join(outputPath, "app/200.html"), function(err, contents){ 128 | should.not.exist(err) 129 | axios.get('http://localhost:'+ port +'/app/').then(function(r){ 130 | r.status.should.eql(200) 131 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 132 | r.data.should.eql(contents.toString()) 133 | done() 134 | }) 135 | }) 136 | }) 137 | 138 | it("should return 301 on /app to redirect to /app/", function(done){ 139 | axios.get('http://localhost:'+ port +'/app', { maxRedirects: 0 }).catch(function(e){ 140 | e.response.status.should.eql(301) 141 | done() 142 | }) 143 | }) 144 | 145 | after(function(done){ 146 | server.close(done) 147 | }) 148 | 149 | }) 150 | 151 | 152 | after(function(done){ 153 | exec("rm -rf " + path.join(__dirname, "out"), function(){ 154 | done() 155 | }) 156 | }) 157 | 158 | }) -------------------------------------------------------------------------------- /test/headers.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require('path') 4 | var harp = require('../') 5 | 6 | 7 | 8 | describe("headers", function(){ 9 | var projectPath = path.join(__dirname, "apps/headers") 10 | var port = 8901 11 | var server; 12 | 13 | before(function(done){ 14 | server = harp.server(projectPath).listen(port, done) 15 | }) 16 | 17 | // static 18 | 19 | it("should be correct with a valid CSS file", function(done){ 20 | axios.get("http://localhost:" + port + "/valid-css.css").then(function(r){ 21 | r.status.should.eql(200) 22 | r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 23 | r.headers.should.have.property("content-length") 24 | done() 25 | }) 26 | }) 27 | 28 | it("should be correct with a valid HTML file", function(done){ 29 | axios.get("http://localhost:" + port + "/valid-html.html").then(function(r){ 30 | r.status.should.eql(200) 31 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 32 | r.headers.should.have.property("content-length") 33 | done() 34 | }) 35 | }) 36 | 37 | it("should be correct with a valid JS file", function(done){ 38 | axios.get("http://localhost:" + port + "/valid-js.js").then(function(r){ 39 | r.status.should.eql(200) 40 | r.headers.should.have.property("content-type", "application/javascript; charset=UTF-8") 41 | r.headers.should.have.property("content-length") 42 | done() 43 | }) 44 | }) 45 | 46 | // valid 47 | 48 | it("should be correct with a valid Jade file", function(done){ 49 | axios.get("http://localhost:" + port + "/valid-jade.html").then(function(r){ 50 | r.status.should.eql(200) 51 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 52 | r.headers.should.have.property("content-length") 53 | done() 54 | }) 55 | }) 56 | 57 | it("should be correct with a valid EJS file", function(done){ 58 | axios.get("http://localhost:" + port + "/valid-ejs.html").then(function(r){ 59 | r.status.should.eql(200) 60 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 61 | r.headers.should.have.property("content-length") 62 | done() 63 | }) 64 | }) 65 | 66 | it("should be correct with a valid Markdown file", function(done){ 67 | axios.get("http://localhost:" + port + "/valid-markdown.html").then(function(r){ 68 | r.status.should.eql(200) 69 | r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 70 | r.headers.should.have.property("content-length") 71 | done() 72 | }) 73 | }) 74 | 75 | // it("should be correct with a valid CoffeeScript file", function(done){ 76 | // request("http://localhost:" + port + "/valid-coffee.js", function(e, r, b){ 77 | // r.statusCode.should.eql(200) 78 | // r.headers.should.have.property("content-type", "application/javascript; charset=UTF-8") 79 | // r.headers.should.have.property("content-length") 80 | // done() 81 | // }) 82 | // }) 83 | 84 | // it("should be correct with a valid LESS file", function(done){ 85 | // request("http://localhost:" + port + "/valid-less.css", function(e, r, b){ 86 | // r.statusCode.should.eql(200) 87 | // r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 88 | // r.headers.should.have.property("content-length") 89 | // done() 90 | // }) 91 | // }) 92 | 93 | // it("should be correct with a valid Stylus file", function(done){ 94 | // request("http://localhost:" + port + "/valid-styl.css", function(e, r, b){ 95 | // r.statusCode.should.eql(200) 96 | // r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 97 | // r.headers.should.have.property("content-length") 98 | // done() 99 | // }) 100 | // }) 101 | 102 | it("should be correct with a valid SCSS file", function(done){ 103 | axios.get("http://localhost:" + port + "/valid-scss.css").then(function(r){ 104 | r.status.should.eql(200) 105 | r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 106 | r.headers.should.have.property("content-length") 107 | done() 108 | }) 109 | }) 110 | 111 | it("should be correct with a valid Sass file", function(done){ 112 | axios.get("http://localhost:" + port + "/valid-sass.css").then(function(r){ 113 | r.status.should.eql(200) 114 | r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 115 | r.headers.should.have.property("content-length") 116 | done() 117 | }) 118 | }) 119 | 120 | // invalid 121 | 122 | it("should be correct with an invalid EJS file", function(done){ 123 | axios.get("http://localhost:" + port + "/invalid-ejs.html").catch(function(e){ 124 | e.response.status.should.eql(500) 125 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 126 | e.response.headers.should.have.property("content-length") 127 | done() 128 | }) 129 | }) 130 | 131 | it("should be correct with an invalid Jade file", function(done){ 132 | axios.get("http://localhost:" + port + "/invalid-jade.html").catch(function(e){ 133 | e.response.status.should.eql(500) 134 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 135 | e.response.headers.should.have.property("content-length") 136 | done() 137 | }) 138 | }) 139 | 140 | // it("should be correct with an invalid LESS file", function(done){ 141 | // request("http://localhost:" + port + "/invalid-less.css", function(e, r, b){ 142 | // r.statusCode.should.eql(200) 143 | // r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 144 | // r.headers.should.have.property("content-length") 145 | // done() 146 | // }) 147 | // }) 148 | 149 | // it("should be correct with an invalid Stylus file", function(done){ 150 | // request("http://localhost:" + port + "/invalid-styl.css", function(e, r, b){ 151 | // r.statusCode.should.eql(200) 152 | // r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 153 | // r.headers.should.have.property("content-length") 154 | // done() 155 | // }) 156 | // }) 157 | 158 | it("should be correct with an invalid SCSS file", function(done){ 159 | axios.get("http://localhost:" + port + "/invalid-scss.css").then(function(r){ 160 | r.status.should.eql(200) 161 | r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 162 | r.headers.should.have.property("content-length") 163 | done() 164 | }) 165 | }) 166 | 167 | it("should be correct with an invalid Sass file", function(done){ 168 | axios.get("http://localhost:" + port + "/invalid-sass.css").then(function(r){ 169 | r.status.should.eql(200) 170 | r.headers.should.have.property("content-type", "text/css; charset=UTF-8") 171 | r.headers.should.have.property("content-length") 172 | done() 173 | }) 174 | }) 175 | 176 | // TODO: This should change to javascript error file. 177 | // it("should be correct with an invalid CoffeeScript file", function(done){ 178 | // request("http://localhost:" + port + "/invalid-coffee.js", function(e, r, b){ 179 | // r.statusCode.should.eql(500) 180 | // r.headers.should.have.property("content-type", "text/html; charset=UTF-8") 181 | // r.headers.should.have.property("content-length") 182 | // done() 183 | // }) 184 | // }) 185 | 186 | // direct 187 | 188 | it("should be correct when Jade file requested", function(done){ 189 | axios.get("http://localhost:" + port + "/valid-jade.jade").catch(function(e){ 190 | e.response.status.should.eql(404) 191 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 192 | e.response.headers.should.have.property("content-length") 193 | done() 194 | }) 195 | }) 196 | 197 | it("should be correct when EJS file requested", function(done){ 198 | axios.get("http://localhost:" + port + "/valid-ejs.ejs").catch(function(e){ 199 | e.response.status.should.eql(404) 200 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 201 | e.response.headers.should.have.property("content-length") 202 | done() 203 | }) 204 | }) 205 | 206 | it("should be correct when Markdown file requested", function(done){ 207 | axios.get("http://localhost:" + port + "/valid-markdown.md").catch(function(e){ 208 | e.response.status.should.eql(404) 209 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 210 | e.response.headers.should.have.property("content-length") 211 | done() 212 | }) 213 | }) 214 | 215 | it("should be correct when CoffeeScript file requested", function(done){ 216 | axios.get("http://localhost:" + port + "/valid-coffee.coffee").catch(function(e){ 217 | e.response.status.should.eql(404) 218 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 219 | e.response.headers.should.have.property("content-length") 220 | done() 221 | }) 222 | }) 223 | 224 | it("should be correct when LESS file requested", function(done){ 225 | axios.get("http://localhost:" + port + "/valid-less.less").catch(function(e){ 226 | e.response.status.should.eql(404) 227 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 228 | e.response.headers.should.have.property("content-length") 229 | done() 230 | }) 231 | }) 232 | 233 | it("should be correct when Stylus file requested", function(done){ 234 | axios.get("http://localhost:" + port + "/valid-styl.styl").catch(function(e){ 235 | e.response.status.should.eql(404) 236 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 237 | e.response.headers.should.have.property("content-length") 238 | done() 239 | }) 240 | }) 241 | 242 | it("should be correct when SCSS file requested", function(done){ 243 | axios.get("http://localhost:" + port + "/valid-scss.scss").catch(function(e){ 244 | e.response.status.should.eql(404) 245 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 246 | e.response.headers.should.have.property("content-length") 247 | done() 248 | }) 249 | }) 250 | 251 | // missing pages 252 | 253 | it("should be correct when missing css file", function(done){ 254 | axios.get("http://localhost:" + port + "/missing.css").catch(function(e){ 255 | e.response.status.should.eql(404) 256 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 257 | e.response.headers.should.have.property("content-length") 258 | done() 259 | }) 260 | }) 261 | 262 | it("should be correct when missing html file", function(done){ 263 | axios.get("http://localhost:" + port + "/missing.html").catch(function(e){ 264 | e.response.status.should.eql(404) 265 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 266 | e.response.headers.should.have.property("content-length") 267 | done() 268 | }) 269 | }) 270 | 271 | it("should be correct when missing js file", function(done){ 272 | axios.get("http://localhost:" + port + "/missing.js").catch(function(e){ 273 | e.response.status.should.eql(404) 274 | e.response.headers.should.have.property("content-type", "text/html; charset=UTF-8") 275 | e.response.headers.should.have.property("content-length") 276 | done() 277 | }) 278 | }) 279 | 280 | after(function(done){ 281 | server.close(done) 282 | }) 283 | 284 | }) 285 | -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var helpers = require("../lib/helpers") 3 | var fse = require("fs-extra") 4 | var path = require("path") 5 | 6 | describe("helpers", function(){ 7 | 8 | describe("buildFallbackPriorityList", function(){ 9 | it("should build priority list", function(done){ 10 | var list = helpers.buildFallbackPriorityList("/foo/bar/baz") 11 | list.should.eql([ 12 | 'foo/bar/200.jade', 13 | 'foo/bar/200.ejs', 14 | 'foo/bar/200.md', 15 | 'foo/bar/200.html.jade', 16 | 'foo/bar/200.html.ejs', 17 | 'foo/bar/200.html.md', 18 | 'foo/bar/404.jade', 19 | 'foo/bar/404.ejs', 20 | 'foo/bar/404.md', 21 | 'foo/bar/404.html.jade', 22 | 'foo/bar/404.html.ejs', 23 | 'foo/bar/404.html.md', 24 | 'foo/200.jade', 25 | 'foo/200.ejs', 26 | 'foo/200.md', 27 | 'foo/200.html.jade', 28 | 'foo/200.html.ejs', 29 | 'foo/200.html.md', 30 | 'foo/404.jade', 31 | 'foo/404.ejs', 32 | 'foo/404.md', 33 | 'foo/404.html.jade', 34 | 'foo/404.html.ejs', 35 | 'foo/404.html.md', 36 | "200.jade", 37 | "200.ejs", 38 | "200.md", 39 | "200.html.jade", 40 | "200.html.ejs", 41 | "200.html.md", 42 | "404.jade", 43 | "404.ejs", 44 | "404.md", 45 | "404.html.jade", 46 | "404.html.ejs", 47 | "404.html.md" 48 | ]) 49 | return done() 50 | }) 51 | }) 52 | 53 | describe("willCollide(projectPath, outputPath)", function(){ 54 | it("should collide if output path is /", function(done){ 55 | helpers.willCollide("/", "/").should.be.true 56 | helpers.willCollide("/foo/bar/myproject", "/").should.be.true 57 | helpers.willCollide("/foo/bar/myproject/", "/").should.be.true 58 | done() 59 | }) 60 | 61 | it("should collide if output path is the root of the source directory", function(done) { 62 | helpers.willCollide("/foo/bar/myproject", "/foo/bar/myproject").should.be.true 63 | helpers.willCollide("./", "./").should.be.true 64 | done() 65 | }) 66 | 67 | it("should not collide if output path is /output", function(done){ 68 | helpers.willCollide("/foo/bar/myproject", "/output").should.be.false 69 | helpers.willCollide("/foo/bar/myproject", "/output/").should.be.false 70 | helpers.willCollide("/foo/bar/myproject/", "/output/").should.be.false 71 | helpers.willCollide("/foo/bar/myproject/", "/output").should.be.false 72 | done() 73 | }) 74 | 75 | it("should not collide if output path is in projectPath", function(done){ 76 | helpers.willCollide("/foo/bar/myproject", "/foo/bar/myproject/www").should.be.false 77 | helpers.willCollide("/foo/bar/myproject/", "/foo/bar/myproject/www").should.be.false 78 | helpers.willCollide("/foo/bar/myproject", "/foo/bar/myproject/www/").should.be.false 79 | helpers.willCollide("/foo/bar/myproject/", "/foo/bar/myproject/www/").should.be.false 80 | done() 81 | }) 82 | 83 | it("should not collide if project path is one back and begins with underscore", function(done){ 84 | helpers.willCollide("/foo/bar/myproject", "/foo/bar").should.be.true 85 | helpers.willCollide("/foo/bar/myproject/", "/foo/bar/").should.be.true 86 | helpers.willCollide("/foo/bar/myproject", "/foo/bar/").should.be.true 87 | helpers.willCollide("/foo/bar/myproject/", "/foo/bar").should.be.true 88 | done() 89 | }) 90 | }) 91 | 92 | describe("willAllow(projectPath, outputPath)", function(){ 93 | it("should not allow project to compile one directory back if source not starting with underscore", function(done){ 94 | helpers.willAllow("/foo/bar/myproject", "/foo/bar").should.be.false 95 | helpers.willAllow("/foo/bar/myproject/", "/foo/bar/").should.be.false 96 | helpers.willAllow("/foo/bar/myproject", "/foo/bar/").should.be.false 97 | helpers.willAllow("/foo/bar/myproject/", "/foo/bar").should.be.false 98 | done() 99 | }) 100 | 101 | it("should not allow project to compile into the source directory when no name is specified", function(done) { 102 | helpers.willAllow("/foo/bar/myproject", "/foo/bar/myproject").should.be.false 103 | helpers.willAllow("./", "./").should.be.false 104 | done() 105 | }) 106 | 107 | it("should allow project to compile one directory back if source directory starts with underscore", function(done){ 108 | helpers.willAllow("/foo/bar/_myproject", "/foo/bar").should.be.true 109 | helpers.willAllow("/foo/bar/_myproject/", "/foo/bar/").should.be.true 110 | helpers.willAllow("/foo/bar/_myproject", "/foo/bar/").should.be.true 111 | helpers.willAllow("/foo/bar/_myproject/", "/foo/bar").should.be.true 112 | done() 113 | }) 114 | 115 | it("should not allow project to compile one directory back if source directory starts with underscore", function(done){ 116 | helpers.willAllow("/foo/bar/_myproject", "/foo").should.be.false 117 | helpers.willAllow("/foo/_bar/myproject", "/foo").should.be.false 118 | helpers.willAllow("/foo/_bar/_myproject", "/foo").should.be.false 119 | done() 120 | }) 121 | }) 122 | 123 | describe("setup(projectPath)", function(){ 124 | 125 | it("should detect framework style", function(done){ 126 | var cfg = helpers.setup(path.join(__dirname, "apps", "app-style-framework")) 127 | cfg.should.have.property("config") 128 | cfg.should.have.property("projectPath") 129 | cfg.should.have.property("publicPath") 130 | done() 131 | }) 132 | 133 | it("should detect root style", function(done){ 134 | var cfg = helpers.setup(path.join(__dirname, "apps", "app-style-root")) 135 | cfg.should.have.property("config") 136 | cfg.should.have.property("projectPath") 137 | cfg.should.have.property("publicPath") 138 | cfg.publicPath.should.eql(cfg.projectPath) 139 | done() 140 | }) 141 | 142 | it("should default to root style", function(done){ 143 | var cfg = helpers.setup(path.join(__dirname, "apps", "app-style-implicit")) 144 | cfg.should.have.property("config") 145 | cfg.should.have.property("projectPath") 146 | cfg.should.have.property("publicPath") 147 | cfg.publicPath.should.eql(cfg.projectPath) 148 | done() 149 | }) 150 | 151 | it("should replace values like $foo with process.env.foo", function(done){ 152 | process.env.HARP_BASIC_AUTH = "jabberwocky:skrillex" 153 | var cfg = helpers.setup(path.join(__dirname, "apps", "envy")) 154 | cfg.should.have.property("config") 155 | cfg.should.have.property("projectPath") 156 | cfg.should.have.property("publicPath") 157 | cfg.config.should.have.property("basicAuth", "jabberwocky:skrillex") 158 | cfg.config.should.not.have.property("optionalThing") 159 | done() 160 | }) 161 | 162 | }) 163 | 164 | describe("prime(outputPath)", function(){ 165 | before(function(done){ 166 | fse.mkdirp(path.join(__dirname, "temp"), function(){ 167 | fse.mkdirSync(path.join(__dirname, "temp", "myproj")) 168 | fse.mkdirSync(path.join(__dirname, "temp", "foo")) 169 | fse.writeFileSync(path.join(__dirname, "temp", "bar"), "hello bar") 170 | done() 171 | }) 172 | }) 173 | 174 | it("should only remove directories that do not begin with underscore", function(done){ 175 | helpers.prime(path.join(__dirname, "temp"), { ignore: "myproj" }, function(error){ 176 | fse.existsSync(path.join(__dirname, "temp", "myproj")).should.be.true 177 | fse.existsSync(path.join(__dirname, "temp", "foo")).should.be.false 178 | fse.existsSync(path.join(__dirname, "temp", "bar")).should.be.false 179 | done() 180 | }) 181 | }) 182 | 183 | after(function(done){ 184 | fse.remove(path.join(__dirname, "temp"), done) 185 | }) 186 | }) 187 | 188 | }) 189 | -------------------------------------------------------------------------------- /test/invalid.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require('path') 4 | var harp = require('../') 5 | 6 | 7 | 8 | describe("headers", function(){ 9 | var projectPath = path.join(__dirname, "apps/err-invalid-source-files") 10 | var port = 8801 11 | var server; 12 | 13 | before(function(done){ 14 | server = harp.server(projectPath).listen(port, done) 15 | }) 16 | 17 | it("should return correct mime type for css files", function(done){ 18 | axios.get("http://localhost:" + port + "/invalid-jade.html").catch(function(e){ 19 | e.response.status.should.eql(500) 20 | e.response.data.should.include(harp.pkg.version) 21 | done() 22 | }) 23 | }) 24 | 25 | after(function(done){ 26 | server.close(done) 27 | }) 28 | 29 | }) -------------------------------------------------------------------------------- /test/lib.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var harp = require('../lib'); 3 | var middleware = require('../lib/middleware'); 4 | 5 | describe("harp as a library", function() { 6 | 7 | it("should expose a mount function", function() { 8 | should(harp.mount).be.type('function'); 9 | }); 10 | 11 | it("should expose the middleware", function() { 12 | should(harp.middleware).be.equal(middleware); 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /test/plain.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("plain", function(){ 9 | var output = path.join(__dirname, "out/plain") 10 | 11 | describe("framework-style", function(){ 12 | var projectPath = path.join(__dirname, "apps/plain/framework-style") 13 | var outputPath = path.join(__dirname, "out/plain/framework-style") 14 | var config; 15 | var server; 16 | 17 | before(function(done){ 18 | harp.compile(projectPath, outputPath, function(errors, output){ 19 | config = output.config 20 | server = harp.server(projectPath).listen(8102, done) 21 | }) 22 | }) 23 | 24 | it("should have harp version in config", function(done){ 25 | config.should.have.property("harp_version") 26 | done() 27 | }) 28 | 29 | it("should serve index file", function(done){ 30 | fs.readFile(path.join(outputPath, "index.html"), function(err, contents){ 31 | axios.get('http://localhost:8102/').then(function(r){ 32 | r.status.should.eql(200) 33 | r.data.should.eql(contents.toString()) 34 | done() 35 | }) 36 | }) 37 | }) 38 | 39 | it("should serve text file", function(done){ 40 | fs.readFile(path.join(outputPath, "hello.txt"), function(err, contents){ 41 | contents.toString().should.eql("text files are wonderful") 42 | axios.get('http://localhost:8102/hello.txt').then(function(r){ 43 | r.status.should.eql(200) 44 | r.data.should.eql(contents.toString()) 45 | done() 46 | }) 47 | }) 48 | }) 49 | 50 | it("should have custom 404 page that is raw HTML", function(done){ 51 | fs.readFile(path.join(outputPath, "404.html"), function(err, contents){ 52 | axios.get('http://localhost:8102/404.html').then(function(r){ 53 | r.status.should.eql(200) 54 | r.data.should.eql(contents.toString()) 55 | axios.get('http://localhost:8102/missing/path').catch(function(e){ 56 | e.response.status.should.eql(404) 57 | e.response.data.should.eql(contents.toString()) 58 | done() 59 | }) 60 | }) 61 | }) 62 | }) 63 | 64 | after(function(done){ 65 | server.close(done) 66 | }) 67 | 68 | }) 69 | 70 | describe("root-style", function(){ 71 | var projectPath = path.join(__dirname, "apps/plain/root-style") 72 | var outputPath = path.join(__dirname, "out/plain/root-style") 73 | var config; 74 | var server; 75 | 76 | before(function(done){ 77 | harp.compile(projectPath, outputPath, function(errors, output){ 78 | config = output.config 79 | server = harp.server(projectPath).listen(8103, done) 80 | }) 81 | }) 82 | 83 | it("should have harp version in config", function(done){ 84 | config.should.have.property("harp_version") 85 | done() 86 | }) 87 | 88 | it("should serve index file", function(done){ 89 | fs.readFile(path.join(outputPath, "index.html"), function(err, contents){ 90 | axios.get('http://localhost:8103/').then(function(r){ 91 | r.status.should.eql(200) 92 | r.data.should.eql(contents.toString()) 93 | done() 94 | }) 95 | }) 96 | }) 97 | 98 | it("should serve text file", function(done){ 99 | fs.readFile(path.join(outputPath, "hello.txt"), function(err, contents){ 100 | contents.toString().should.eql("text files are wonderful") 101 | axios.get('http://localhost:8103/hello.txt').then(function(r){ 102 | r.status.should.eql(200) 103 | r.data.should.eql(contents.toString()) 104 | done() 105 | }) 106 | }) 107 | }) 108 | 109 | it("should have custom 404 page that is raw HTML", function(done){ 110 | fs.readFile(path.join(outputPath, "404.html"), function(err, contents){ 111 | axios.get('http://localhost:8103/404.html').then(function(r){ 112 | r.status.should.eql(200) 113 | r.data.should.eql(contents.toString()) 114 | axios.get('http://localhost:8103/missing/path').catch(function(e){ 115 | e.response.status.should.eql(404) 116 | e.response.data.should.eql(contents.toString()) 117 | done() 118 | }) 119 | }) 120 | }) 121 | }) 122 | 123 | after(function(done){ 124 | server.close(done) 125 | }) 126 | 127 | }) 128 | 129 | after(function(done){ 130 | exec("rm -rf " + output, function(){ 131 | done() 132 | }) 133 | }) 134 | 135 | }) 136 | -------------------------------------------------------------------------------- /test/security.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("security", function(){ 9 | var projectPath = path.join(__dirname, "apps/security") 10 | var outputPath = path.join(__dirname, "out/security") 11 | var config; 12 | var server; 13 | 14 | before(function(done){ 15 | harp.compile(projectPath, outputPath, function(errors, output){ 16 | config = output 17 | server = harp.server(projectPath).listen(8101, done) 18 | }) 19 | }) 20 | 21 | it("should not serve file starting with underscore", function(done){ 22 | axios.get('http://localhost:8101/_secret.txt').catch(function(e){ 23 | e.response.status.should.eql(404) 24 | done() 25 | }) 26 | }) 27 | 28 | it("should not serve file starting with encoded underscore", function(done){ 29 | axios.get('http://localhost:8101/%5fsecret.txt').catch(function(e){ 30 | e.response.status.should.eql(404) 31 | done() 32 | }) 33 | }) 34 | 35 | after(function(done){ 36 | exec("rm -rf " + outputPath, function(){ 37 | server.close(done) 38 | }) 39 | }) 40 | 41 | }) 42 | -------------------------------------------------------------------------------- /test/slash-indifference.js: -------------------------------------------------------------------------------- 1 | var should = require("should") 2 | var axios = require('axios') 3 | var path = require("path") 4 | var fs = require("fs") 5 | var exec = require("child_process").exec 6 | var harp = require("../") 7 | 8 | describe("slash-indifference", function(){ 9 | var projectPath = path.join(__dirname, "apps/slash-indifference") 10 | var server; 11 | 12 | before(function(done){ 13 | server = harp.server(projectPath).listen(8119, done) 14 | }) 15 | 16 | describe("file", function(){ 17 | it("should get 200 without slash", function(done){ 18 | axios.get('http://localhost:8119/file', { maxRedirects: 0 }).then(function(r){ 19 | r.status.should.eql(200) 20 | r.data.should.eql("

file contents

") 21 | done() 22 | }) 23 | }) 24 | 25 | it("should get redirected when slash present", function(done){ 26 | axios.get('http://localhost:8119/file/', { maxRedirects: 0 }).catch(function(e){ 27 | e.response.status.should.eql(301) 28 | e.response.headers["location"].should.eql("/file") 29 | done() 30 | }) 31 | }) 32 | }) 33 | 34 | describe("directory", function(){ 35 | it("should get 200 with slash", function(done){ 36 | axios.get('http://localhost:8119/directory/', { maxRedirects: 0 }).then(function(r){ 37 | r.status.should.eql(200) 38 | r.data.should.eql("

file in directory contents

") 39 | done() 40 | }) 41 | }) 42 | 43 | it("should get redirected when slash absent", function(done){ 44 | axios.get('http://localhost:8119/directory', { maxRedirects: 0 }).catch(function(e){ 45 | e.response.status.should.eql(301) 46 | e.response.headers["location"].should.eql("/directory/") 47 | done() 48 | }) 49 | }) 50 | }) 51 | 52 | after(function(done){ 53 | server.close(done) 54 | }) 55 | 56 | }) 57 | --------------------------------------------------------------------------------