Permalink
Browse files

Initial commit

  • Loading branch information...
0 parents commit 4198c47e745e9f26b26fbb9901d282c245401e21 @ggoodman ggoodman committed Jan 14, 2013
Showing with 407 additions and 0 deletions.
  1. +20 −0 .gitignore
  2. +11 −0 README.md
  3. +4 −0 config.json
  4. +213 −0 index.coffee
  5. +14 −0 middleware/cors.coffee
  6. +17 −0 middleware/json.coffee
  7. +37 −0 package.json
  8. +23 −0 schema/previews/create.json
  9. +40 −0 server.js
  10. +28 −0 views/directory.jade
@@ -0,0 +1,20 @@
+lib-cov
+*.seed
+*.log
+*.csv
+*.dat
+*.out
+*.pid
+*.gz
+
+pids
+logs
+results
+tests
+
+node_modules
+npm-debug.log
+
+.c9revisions
+
+builtAssets
@@ -0,0 +1,11 @@
+# Plunker runner
+
+The server that runs the previewing of saved plunks and plunk previews.
+
+### `POST /` Create a new preview
+
+### `POST /:id` Create a new or update an existing preview
+
+### `GET /:id/:filename?` Read a preview's file
+
+### `GET /plunks/:id/:filename?` Read a plunk's file
@@ -0,0 +1,4 @@
+{
+ "host": "plnkr.co",
+ "port": 8080
+}
@@ -0,0 +1,213 @@
+express = require("express")
+nconf = require("nconf")
+_ = require("underscore")._
+validator = require("json-schema")
+mime = require("mime")
+url = require("url")
+request = require("request")
+path = require("path")
+
+module.exports = app = express.createServer()
+
+runUrl = nconf.get("url:run")
+
+genid = (len = 16, prefix = "", keyspace = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") ->
+ prefix += keyspace.charAt(Math.floor(Math.random() * keyspace.length)) while len-- > 0
+ prefix
+
+
+app.configure ->
+ app.use require("./middleware/cors").middleware()
+ app.use require("./middleware/json").middleware()
+
+ app.set "views", "#{__dirname}/views"
+ app.set "view engine", "jade"
+ app.set "view options", layout: false
+
+LRU = require("lru-cache")
+previews = LRU(512)
+apiUrl = nconf.get("url:api")
+
+coffee = require("coffee-script")
+livescript = require("LiveScript")
+iced = require("iced-coffee-script")
+less = require("less")
+sass = require("sass")
+jade = require("jade")
+markdown = require("marked")
+stylus = require("stylus")
+nib = require("nib")
+
+compilers =
+ sass:
+ match: /\.css$/
+ ext: ['sass']
+ compile: (str, fn) ->
+ try
+ fn(null, sass.render(str))
+ catch err
+ fn(err)
+
+ less:
+ match: /\.css$/
+ ext: ['less']
+ compile: (str, fn) ->
+ try
+ less.render(str, fn)
+ catch err
+ fn(err)
+
+ stylus:
+ match: /\.css/
+ ext: ['styl']
+ compile: (str, fn) ->
+ try
+ stylus(str)
+ .use(nib())
+ .import("nib")
+ .render(fn)
+ catch err
+ fn(err)
+ coffeescript:
+ match: /\.js$/
+ ext: ['coffee']
+ compile: (str, fn) ->
+ try
+ fn(null, coffee.compile(str, bare: true))
+ catch err
+ fn(err)
+
+ livescript:
+ match: /\.js$/
+ ext: ['ls']
+ compile: (str, fn) ->
+ try
+ fn(null, livescript.compile(str))
+ catch err
+ fn(err)
+
+ icedcoffee:
+ match: /\.js$/
+ ext: ['iced']
+ compile: (str, fn) ->
+ try
+ fn(null, iced.compile(str, runtime: "inline"))
+ catch err
+ fn(err)
+
+ jade:
+ match: /\.html$/
+ ext: ['jade']
+ compile: (str, fn) ->
+ render = jade.compile(str, pretty: true)
+ try
+ fn(null, render({}))
+ catch err
+ fn(err)
+
+ markdown:
+ match: /\.html$/
+ ext: ['md',"markdown"]
+ compile: (str, fn) ->
+ try
+ fn(null, markdown(str))
+ catch err
+ fn(err)
+
+renderPlunkFile = (req, res, next) ->
+ # TODO: Determine if a special plunk 'landing' page should be served and serve it
+ plunk = req.plunk
+ filename = req.params[0] or "index.html"
+ file = plunk.files[filename]
+
+ res.header "Cache-Control", "no-cache"
+ res.header "Expires", 0
+
+ if file then res.send(file.content, {"Content-Type": if req.accepts(file.mime) then file.mime else "text/plain"})
+ else if filename
+ base = path.basename(filename, path.extname(filename))
+ type = mime.lookup(filename) or "text/plain"
+
+ for name, compiler of compilers when filename.match(compiler.match)
+ for ext in compiler.ext
+ if found = plunk.files["#{base}.#{ext}"]
+ compiler.compile found.content, (err, compiled) ->
+ if err then next(err)
+ else res.send(compiled, {"Content-Type": if req.accepts(type) then type else "text/plain"})
+ break
+
+ res.send(404) unless found
+
+ else
+ res.local "plunk", plunk
+ res.render "directory"
+
+
+app.get "/plunks/:id/*", (req, res, next) ->
+ req_url = url.parse(req.url)
+ unless req.params[0] or /\/$/.test(req_url.pathname)
+ req_url.pathname += "/"
+ return res.redirect(url.format(req_url), 301)
+
+ request.get "#{apiUrl}/plunks/#{req.params.id}", (err, response, body) ->
+ return res.send(500) if err
+ return res.send(response.statusCode) if response.statusCode >= 400
+
+ try
+ req.plunk = JSON.parse(body)
+ catch e
+ return res.send(500)
+
+ unless req.plunk then res.send(404) # TODO: Better error page
+ else renderPlunkFile(req, res, next)
+
+app.get "/plunks/:id", (req, res) -> res.redirect("/#{req.params.id}/", 301)
+
+app.post "/:id?", (req, res, next) ->
+ json = req.body
+ schema = require("./schema/previews/create")
+ {valid, errors} = validator.validate(json, schema)
+
+ # Despite its awesomeness, validator does not support disallow or additionalProperties; we need to check plunk.files size
+ if json.files and _.isEmpty(json.files)
+ valid = false
+ errors.push
+ attribute: "minProperties"
+ property: "files"
+ message: "A minimum of one file is required"
+
+ unless valid then next(new Error("Invalid json: #{errors}"))
+ else
+ id = req.params.id or genid() # Don't care about id clashes. They are disposable anyway
+ json.id = id
+ json.run_url = "#{runUrl}/#{id}/"
+
+ _.each json.files, (file, filename) ->
+ json.files[filename] =
+ filename: filename
+ content: file.content
+ mime: mime.lookup(filename, "text/plain")
+ run_url: json.run_url + filename
+
+
+ previews.set(id, json)
+
+ status = if req.params.id then 200 else 201
+
+ res.json(json, status)
+
+
+
+app.get "/:id/*", (req, res, next) ->
+ unless req.plunk = previews.get(req.params.id) then res.send(404) # TODO: Better error page
+ else
+ req_url = url.parse(req.url)
+
+ unless req.params[0] or /\/$/.test(req_url.pathname)
+ req_url.pathname += "/"
+ return res.redirect(url.format(req_url), 301)
+
+ renderPlunkFile(req, res, next)
+
+app.get "*", (req, res) ->
+ res.send("Preview does not exist or has expired.", 404)
@@ -0,0 +1,14 @@
+module.exports.middleware = (config = {}) ->
+ (req, res, next) ->
+ # Just send the headers all the time. That way we won't miss the right request ;-)
+ # Other CORS middleware just wouldn't work for me
+ # TODO: Minimize these headers to only those needed at the right time
+
+ res.header("Access-Control-Allow-Origin", req.headers.origin or "*")
+ res.header("Access-Control-Allow-Methods", "OPTIONS,GET,PUT,POST,DELETE")
+ res.header("Access-Control-Allow-Headers", "Authorization, User-Agent, Referer, X-Requested-With, Proxy-Authorization, Proxy-Connection, Accept-Language, Accept-Encoding, Accept-Charset, Connection, Content-Length, Host, Origin, Pragma, Accept-Charset, Cache-Control, Accept, Content-Type")
+ res.header("Access-Control-Expose-Headers", "Link")
+ res.header("Access-Control-Max-Age", "60")
+
+ if "OPTIONS" == req.method then res.send(200)
+ else next()
@@ -0,0 +1,17 @@
+module.exports.middleware = (config = {}) ->
+ (req, res, next) ->
+ if "GET" == req.method or "HEAD" == req.method then return next()
+
+ req.body ||= {}
+
+ buf = '';
+
+ req.setEncoding('utf8');
+ req.on "data", (chunk) -> buf += chunk
+ req.on "end", ->
+ return next() unless buf
+ try
+ req.body = JSON.parse(buf)
+ next()
+ catch err
+ next(err)
@@ -0,0 +1,37 @@
+{
+ "name": "plunker-run",
+ "subdomain": "plunker-run",
+ "domains": [
+ "run.plnkr.co"
+ ],
+ "scripts": {
+ "start": "server.js"
+ },
+ "engines": {
+ "node": "0.8.x"
+ },
+ "version": "0.3.14-9",
+ "private": true,
+ "dependencies": {
+ "coffee-script": "1.x",
+ "express": "2.5.x",
+ "gzippo": "0.1.x",
+ "mime": "*",
+ "underscore": "1.3.x",
+ "nconf": "0.5.x",
+ "json-schema": "https://github.com/kriszyp/json-schema/tarball/master",
+ "lru-cache": "1.1.x",
+ "request": "2.12.0",
+ "sass": "*",
+ "less": "*",
+ "jade": "*",
+ "marked": "*",
+ "LiveScript": "*",
+ "iced-coffee-script": "*",
+ "stylus": "*",
+ "nib": "*"
+ },
+ "bundledDependencies": [
+ "json-schema"
+ ]
+}
@@ -0,0 +1,23 @@
+{
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "files": {
+ "required": true,
+ "type": "object",
+ "additionalProperties": false,
+ "patternProperties": {
+ "^[a-zA-z0-9_-]+(\\.[a-zA-Z0-9]+)?$": {
+ "type": "object",
+ "additionalProperties": false,
+ "properties": {
+ "content": {
+ "type": "string",
+ "required": true
+ }
+ }
+ }
+ }
+ }
+ }
+}
@@ -0,0 +1,40 @@
+// Everything starts better with coffee
+var coffee = require("coffee-script");
+var express = require("express");
+
+
+var nconf = require("nconf").use("memory")
+ .argv()
+ .env()
+ .file({file: "config.json"})
+ .defaults({
+ "PORT": 8080
+ });
+
+if (!nconf.get("host")) {
+ console.error("The host option is required for Plunker to start");
+} else {
+
+ //process.env.NODE_ENV = "production";
+
+ var host = nconf.get("host");
+
+ // Configure global paths
+ if (nconf.get("nosubdomains")) {
+ nconf.set("url:www", "http://" + host);
+ nconf.set("url:raw", "http://" + host + "/raw");
+ nconf.set("url:run", "http://" + host + "/run");
+ nconf.set("url:api", "http://" + host + "/api");
+ nconf.set("url:embed", "http://" + host + "/embed");
+ } else {
+ nconf.set("url:www", "http://" + host);
+ nconf.set("url:raw", "http://raw." + host);
+ nconf.set("url:run", "http://run." + host);
+ nconf.set("url:api", "http://api." + host);
+ nconf.set("url:embed", "http://embed." + host); }
+
+ // Create and start the parent server
+ require("./index").listen(nconf.get("PORT"));
+
+ console.log("Started plunker-www in", nconf.get("NODE_ENV") || "development", "at", nconf.get("host"), "on port", nconf.get("PORT"), "using subdomains:", !nconf.get("nosubdomains"));
+}
Oops, something went wrong.

0 comments on commit 4198c47

Please sign in to comment.