Skip to content

Commit

Permalink
Merge pull request #29 from ericclemmons/youch
Browse files Browse the repository at this point in the history
Add youch + youch-terminal
  • Loading branch information
ericclemmons authored Mar 8, 2019
2 parents 13825d1 + 2f38cc1 commit eb38222
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 72 deletions.
6 changes: 4 additions & 2 deletions packages/polydev/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"src"
],
"dependencies": {
"body-parser": "^1.18.3",
"ansi-to-html": "^0.6.10",
"chokidar": "^2.0.4",
"debug": "^4.1.1",
"express": "^4.16.4",
Expand All @@ -21,6 +21,8 @@
"opn": "^5.4.0",
"raw-body": "^2.3.3",
"uuid": "^3.3.2",
"wait-on": "^3.2.0"
"wait-on": "^3.2.0",
"youch": "^2.0.10",
"youch-terminal": "^1.0.0"
}
}
11 changes: 11 additions & 0 deletions packages/polydev/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,27 @@ const middleware = require("./middleware")

const { NODE_ENV = "development" } = process.env

const verify = (req, res, buffer, encoding = "utf8") => {
if (buffer && buffer.length) {
req.rawBody = buffer.toString(encoding)
}
}

module.exports.polydev = (options = {}) => {
const { assets = "public", routes = "routes" } = options
const app = express()

// req.body is needed
app.use(express.urlencoded({ extended: true, verify }))
app.use(express.json({ verify }))

app.use(middleware.assets(assets))
app.use(middleware.router(routes))

// TODO Merge 404 & errors together
if (NODE_ENV === "development") {
app.use("/_polydev", middleware.assets(path.resolve(__dirname, "./public")))
app.use(middleware.router(path.resolve(__dirname, "./routes")))
app.use(middleware.notFound)
app.use(middleware.error)
}
Expand Down
91 changes: 66 additions & 25 deletions packages/polydev/src/middleware/error/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,73 @@
const generateId = require("uuid/v1")
const Youch = require("youch")
const forTerminal = require("youch-terminal")

const nonce = generateId()

module.exports = function errorHandler(error, req, res, next) {
const { status = "", statusCode = 500 } = error

res.status(statusCode).send(`
<body class="error">
<link href="https://fonts.googleapis.com/css?family=Quicksand:300,500" rel="stylesheet">
<link href="/_polydev/styles.css" rel="stylesheet">
const youch = new Youch(error, req)

youch.addLink((error) => {
return `
<link href="/_polydev/styles.css" rel="stylesheet">
<div id="splash"></div>
`
})

if (error.code === "MODULE_NOT_FOUND") {
const [, missing] = error.message.match(/'(.*)'/)

youch.addLink(
() => `
<form action="/_polydev/install-module" method="post">
<input name="nonce" type="hidden" value="${nonce}" />
<input name="path" type="hidden" value="${req.path}" />
<input name="module" type="hidden" value="${missing}" />
<h3>
Would you like to install <kbd>${missing}</kbd>?
</h3>
<img src="https://img.shields.io/npm/v/${missing}.svg" />
<img src="https://img.shields.io/npm/dm/${missing}.svg" />
<hr />
<button type="submit">
<code>yarn add ${missing}</code>
</button>
<label>
<input name="dev" type="checkbox" value="true" />
<code>devDependency</code>
</label>
</form>
`
)
}

youch.addLink(({ message }) => {
const url = `https://google.com/search?q=${encodeURIComponent(message)}`

return `<a href="${url}" target="_blank" title="Search Google"><i class="fab fa-google"></i></a>`
})

youch.addLink(({ message }) => {
const url = `https://stackoverflow.com/search?q=${encodeURIComponent(
message
)}`

return `<a href="${url}" target="_blank" title="Search Stack Overflow"><i class="fab fa-stack-overflow"></i></a>`
})

youch.toHTML().then((html) => {
res.status(statusCode).send(html)
})

<section>
<main>
<h1>
<code>${statusCode}</code> ${status}
</h1>
<pre><code>${error.message}</code></pre>
</main>
${
error.stack
? `
<footer>
<pre><code>${error.stack}</code></pre>
</footer>
`
: ""
}
</section>
</body>
`)
youch
.toJSON()
.then(forTerminal)
.then(console.log)
}
5 changes: 0 additions & 5 deletions packages/polydev/src/middleware/notFound/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const bodyParser = require("body-parser")
const express = require("express")
const jetpack = require("fs-jetpack")
const opn = require("opn")
Expand All @@ -9,10 +8,6 @@ const waitOn = require("wait-on")
const nonce = generateId()

module.exports = express()
// req.body is needed
.use(bodyParser.urlencoded({ extended: false }))
.use(bodyParser.json())

// This handler only responds to GET/POST, not HEAD/OPTIONS/etc.
.use(
function onlyGetPost(req, res, next) {
Expand Down
5 changes: 2 additions & 3 deletions packages/polydev/src/middleware/router/handle.development.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ module.exports = function handle(router, file, routes) {
}

const event = {
// TODO Replace with body-parser
body: (await rawBody(req)).toString("utf8"),
body: req.rawBody,
headers: req.headers,
host: req.headers.host,
method: req.method,
Expand All @@ -110,7 +109,7 @@ module.exports = function handle(router, file, routes) {
const handled = handler(req, res, next)

// Automatically bubble up async errors
if (handled.catch) {
if (handled && handled.catch) {
handled.catch(next)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ module.exports = async function handle(router, file, routes) {
const handled = handler(req, res, next)

// Automatically bubble up async errors
if (handled.catch) {
if (handled && handled.catch) {
handled.catch(next)
}
}
Expand Down
71 changes: 45 additions & 26 deletions packages/polydev/src/middleware/router/launcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,62 +21,81 @@ const [, , handlerPath, routesString] = process.argv
// Expected to be JSON.stringify([["GET", "/"]])
const routes = JSON.parse(routesString)

const verify = (req, res, buffer, encoding = "utf8") => {
if (buffer && buffer.length) {
req.rawBody = buffer.toString(encoding)
}
}

// TODO Remove baseUrl unless it's needed in the route
async function startHandler() {
const getLatestHandler = async () => {
// Best way to ensure that HMR doesn't save old copies
delete require.cache[handlerPath]

const exported = require(handlerPath)
const handler = exported ? await (exported.default || exported) : exported

return handler
}

// Next.js returns a Promise for when the server is ready
let handler = await getLatestHandler()
let handler = await getLatestHandler().catch((error) => {
return function invalidHandler(req, res, next) {
next(error)
}
})

// @ts-ignore
if (module.hot) {
let recentlySaved = false

if (typeof handler === "function") {
// @ts-ignore
module.hot.accept(handlerPath, async () => {
if (recentlySaved) {
console.log(`♻️ Restarting ${handlerPath}`)
return process.send("restart")
}
// @ts-ignore
module.hot.accept(handlerPath, async () => {
if (recentlySaved) {
console.log(`♻️ Restarting ${handlerPath}`)
return process.send("restart")
}

handler = await getLatestHandler()
console.log(`🔁 Hot-reloaded ${handlerPath}`)
handler = await getLatestHandler()
console.log(`🔁 Hot-reloaded ${handlerPath}`)

// TODO Send reload signal
// TODO Send reload signal

// Wait for a double-save
recentlySaved = true
// Outside of double-save reload window
setTimeout(() => {
recentlySaved = false
}, 500)
})
}
// Wait for a double-save
recentlySaved = true
// Outside of double-save reload window
setTimeout(() => {
recentlySaved = false
}, 500)
})
}

const url = `http://localhost:${PORT}/`

if (typeof handler === "function") {
const app = express()

// req.body is needed
app.use(express.urlencoded({ extended: true, verify }))
app.use(express.json({ verify }))

routes.forEach(([method, route]) => {
app[method.toLowerCase()].call(
app,
route,
// Make sure we always evaluate at run-time for the latest HMR'd handler
(req, res, next) => {
const handled = handler(req, res, next)

// Automatically bubble up async errors
if (handled.catch) {
handled.catch(next)
}
function handleRoute(req, res, next) {
getLatestHandler()
.then((handler) => {
const handled = handler(req, res, next)

// Automatically bubble up async errors
if (handled && handled.catch) {
handled.catch(next)
}
})
.catch(next)
}
)
})
Expand Down
Loading

0 comments on commit eb38222

Please sign in to comment.