Skip to content

Commit

Permalink
Moved out all remaining parts into files
Browse files Browse the repository at this point in the history
  • Loading branch information
leo committed Jan 15, 2017
1 parent 5697ad9 commit 770f535
Show file tree
Hide file tree
Showing 5 changed files with 274 additions and 252 deletions.
252 changes: 2 additions & 250 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,14 @@
// Native
const path = require('path')
const {parse} = require('url')

// Packages
const {StringDecoder} = require('string_decoder')
const micro = require('micro')
const {red} = require('chalk')
const args = require('args')
const {isBinarySync} = require('istextorbinary')
const filesize = require('filesize')
const mime = require('mime-types')
const compress = require('micro-compress')
const fs = require('fs-promise')
const auth = require('basic-auth')
const stream = require('send')
const pathType = require('path-type')

// Ours
const prepareView = require('./view')
const listening = require('./listening')
const serverHandler = require('./server')

args
.option('port', 'Port to listen on', process.env.PORT)
Expand Down Expand Up @@ -49,246 +39,8 @@ if (flags.ignore && flags.ignore.length > 0) {
ignoredFiles = ignoredFiles.concat(flags.ignore.split(','))
}

const renderDirectory = async dir => {
let files = []
const subPath = path.relative(current, dir)

if (!fs.existsSync(dir)) {
return false
}

try {
files = await fs.readdir(dir)
} catch (err) {
throw err
}

for (const file of files) {
const filePath = path.resolve(dir, file)
const index = files.indexOf(file)
const details = path.parse(filePath)

details.relative = path.join(subPath, details.base)

if (await pathType.dir(filePath)) {
details.base += '/'
} else {
details.ext = details.ext.split('.')[1] || 'txt'

let fileStats

try {
fileStats = await fs.stat(filePath)
} catch (err) {
throw err
}

details.size = filesize(fileStats.size, {round: 0})
}

if (ignoredFiles.indexOf(details.base) > -1) {
delete files[index]
} else {
files[files.indexOf(file)] = details
}
}

const directory = path.join(path.basename(current), subPath, '/')
const pathParts = directory.split('/')

if (dir.indexOf(current + '/') > -1) {
const directoryPath = [...pathParts]
directoryPath.shift()

files.unshift({
base: '..',
relative: path.join(...directoryPath, '..')
})
}

const render = prepareView()

const paths = []
pathParts.pop()

for (const part in pathParts) {
if (!{}.hasOwnProperty.call(pathParts, part)) {
continue
}

let before = 0
const parents = []

while (before <= part) {
parents.push(pathParts[before])
before++
}

parents.shift()

paths.push({
name: pathParts[part],
url: parents.join('/')
})
}

const details = {
port: flags.port,
files,
assetDir: process.env.ASSET_DIR,
directory,
nodeVersion: process.version.split('v')[1],
paths
}

return render(details)
}

const handler = async (req, res) => {
if (flags.auth) {
const credentials = auth(req)

if (!process.env.SERVE_USER || !process.env.SERVE_PASSWORD) {
console.error(red('You are running serve with basic auth but did not set the SERVE_USER and SERVE_PASSWORD environment variables.'))
process.exit(1)
}

if (!credentials || credentials.name !== process.env.SERVE_USER || credentials.pass !== process.env.SERVE_PASSWORD) {
res.statusCode = 401
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"')
return micro.send(res, 401, 'Access Denied')
}
}

const {pathname} = parse(req.url)
let related = path.parse(path.join(current, pathname))

if (related.dir.indexOf(process.env.ASSET_DIR) > -1) {
const relative = path.relative(process.env.ASSET_DIR, pathname)
related = path.parse(path.join(__dirname, '/../assets', relative))
}

related = decodeURIComponent(path.format(related))

let notFoundResponse = 'Not Found'

try {
const custom404Path = path.join(current, '/404.html')
notFoundResponse = await fs.readFile(custom404Path, 'utf-8')
} catch (err) {}

if (!fs.existsSync(related) && flags.single === undefined) {
return micro.send(res, 404, notFoundResponse)
}

// Check if file or directory path
if (await pathType.dir(related)) {
let indexPath = path.join(related, '/index.html')

res.setHeader('Content-Type', mime.contentType(path.extname(indexPath)))

if (!fs.existsSync(indexPath)) {
// Try to render the current directory's content
const renderedDir = await renderDirectory(related)

// If it works, send the directory listing to the user
if (renderedDir) {
return micro.send(res, 200, renderedDir)
}

// And if it doesn't, see if it's a single page application
// If that's not true either, send an error
if (!flags.single) {
return micro.send(res, 404, notFoundResponse)
}

// But IF IT IS true, load the SPA's root index file
indexPath = path.join(current, '/index.html')
}

let indexContent

try {
indexContent = await fs.readFile(indexPath, 'utf8')
} catch (err) {
throw err
}

return micro.send(res, 200, indexContent)
}

if (!fs.existsSync(related) && flags.single) {
const indexPath = path.join(current, '/index.html')
let indexContent

try {
indexContent = await fs.readFile(indexPath, 'utf8')
} catch (err) {
return micro.send(res, 404, notFoundResponse)
}

return micro.send(res, 200, indexContent)
}

let body = 'Not able to load file!'
let stats

try {
body = await fs.readFile(related)
stats = await fs.stat(related)
} catch (err) {
if (err instanceof RangeError) {
return stream(req, related).pipe(res)
}

throw err
}

const binaryStat = isBinarySync(path.parse(related).base, body)
const getETag = s => '"' + s.dev + '-' + s.ino + '-' + s.mtime.getTime() + '"'

let requestDate = req.headers['if-modified-since']
let statusCode = 200

if (requestDate) {
requestDate = new Date(requestDate)

if (requestDate.getTime() === stats.mtime.getTime()) {
statusCode = 304
}
}

const defaultHeaders = {
'Cache-Control': 'public, max-age=' + flags.cache,
Pragma: 'public',
ETag: getETag(stats)
}

// Using same --cors behavior as in http-server:
// https://github.com/indexzero/http-server/blob/fed98f2dbb87f1ea7a793e48a1975c20c9e970fa/lib/http-server.js#L68
if (flags.cors) {
defaultHeaders['Access-Control-Allow-Origin'] = '*'
defaultHeaders['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Range'
}

for (const header in defaultHeaders) {
if (!{}.hasOwnProperty.call(defaultHeaders, header)) {
continue
}

res.setHeader(header, defaultHeaders[header])
}

if (binaryStat) {
res.statusCode = statusCode
res.end(body, 'binary')
} else {
const decoder = new StringDecoder('utf8')
const contentType = mime.contentType(path.extname(related)) || mime.contentType('text')

res.setHeader('Content-Type', contentType)
micro.send(res, statusCode, decoder.write(body))
}
await serverHandler(req, res, flags, current, ignoredFiles)
}

const server = flags.unzipped ? micro(handler) : micro(compress(handler))
Expand Down
105 changes: 105 additions & 0 deletions lib/render.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Native
const path = require('path')

// Packages
const pathType = require('path-type')
const filesize = require('filesize')
const fs = require('fs-promise')

// Ours
const prepareView = require('./view')

module.exports = async (flags, current, dir, ignoredFiles) => {
let files = []
const subPath = path.relative(current, dir)

if (!fs.existsSync(dir)) {
return false
}

try {
files = await fs.readdir(dir)
} catch (err) {
throw err
}

for (const file of files) {
const filePath = path.resolve(dir, file)
const index = files.indexOf(file)
const details = path.parse(filePath)

details.relative = path.join(subPath, details.base)

if (await pathType.dir(filePath)) {
details.base += '/'
} else {
details.ext = details.ext.split('.')[1] || 'txt'

let fileStats

try {
fileStats = await fs.stat(filePath)
} catch (err) {
throw err
}

details.size = filesize(fileStats.size, {round: 0})
}

if (ignoredFiles.indexOf(details.base) > -1) {
delete files[index]
} else {
files[files.indexOf(file)] = details
}
}

const directory = path.join(path.basename(current), subPath, '/')
const pathParts = directory.split('/')

if (dir.indexOf(current + '/') > -1) {
const directoryPath = [...pathParts]
directoryPath.shift()

files.unshift({
base: '..',
relative: path.join(...directoryPath, '..')
})
}

const render = prepareView()

const paths = []
pathParts.pop()

for (const part in pathParts) {
if (!{}.hasOwnProperty.call(pathParts, part)) {
continue
}

let before = 0
const parents = []

while (before <= part) {
parents.push(pathParts[before])
before++
}

parents.shift()

paths.push({
name: pathParts[part],
url: parents.join('/')
})
}

const details = {
port: flags.port,
files,
assetDir: process.env.ASSET_DIR,
directory,
nodeVersion: process.version.split('v')[1],
paths
}

return render(details)
}

0 comments on commit 770f535

Please sign in to comment.