Comparing version 6.5.5 to 6.5.6
166
bin/serve.js
#!/usr/bin/env node | ||
// Native | ||
const path = require('path') | ||
const https = require('https') | ||
const path = require('path'); | ||
const https = require('https'); | ||
// Packages | ||
const micro = require('micro') | ||
const args = require('args') | ||
const compress = require('micro-compress') | ||
const detect = require('detect-port') | ||
const { coroutine } = require('bluebird') | ||
const checkForUpdate = require('update-check') | ||
const { red, bold } = require('chalk') | ||
const nodeVersion = require('node-version') | ||
const cert = require('openssl-self-signed-certificate') | ||
const boxen = require('boxen') | ||
const micro = require('micro'); | ||
const args = require('args'); | ||
const compress = require('micro-compress'); | ||
const detect = require('detect-port'); | ||
const {coroutine} = require('bluebird'); | ||
const checkForUpdate = require('update-check'); | ||
const {red, bold} = require('chalk'); | ||
const nodeVersion = require('node-version'); | ||
const cert = require('openssl-self-signed-certificate'); | ||
const boxen = require('boxen'); | ||
// Utilities | ||
const pkg = require('../package') | ||
const listening = require('../lib/listening') | ||
const serverHandler = require('../lib/server') | ||
const { options, minimist } = require('../lib/options') | ||
const pkg = require('../package'); | ||
const listening = require('../lib/listening'); | ||
const serverHandler = require('../lib/server'); | ||
const {options, minimist} = require('../lib/options'); | ||
// Throw an error if node version is too low | ||
if (nodeVersion.major < 6) { | ||
console.error( | ||
`${red( | ||
'Error!' | ||
)} Serve requires at least version 6 of Node. Please upgrade!` | ||
) | ||
process.exit(1) | ||
console.error( | ||
`${red( | ||
'Error!' | ||
)} Serve requires at least version 6 of Node. Please upgrade!` | ||
); | ||
process.exit(1); | ||
} | ||
// Register the list of options | ||
args.options(options) | ||
args.options(options); | ||
// And initialize `args` | ||
const flags = args.parse(process.argv, { minimist }) | ||
const flags = args.parse(process.argv, {minimist}); | ||
// Figure out the content directory | ||
const [directory] = args.sub | ||
const [directory] = args.sub; | ||
// Don't log anything to the console if silent mode is enabled | ||
if (flags.silent) { | ||
console.log = () => {} | ||
console.log = () => {}; | ||
} | ||
process.env.ASSET_DIR = Math.random() | ||
.toString(36) | ||
.substr(2, 10) | ||
.toString(36) | ||
.substr(2, 10); | ||
let current = process.cwd() | ||
let current = process.cwd(); | ||
if (directory) { | ||
current = path.resolve(process.cwd(), directory) | ||
current = path.resolve(process.cwd(), directory); | ||
} | ||
let ignoredFiles = ['.DS_Store', '.git/'] | ||
let ignoredFiles = ['.DS_Store', '.git/']; | ||
if (flags.ignore && flags.ignore.length > 0) { | ||
ignoredFiles = ignoredFiles.concat(flags.ignore.split(',')) | ||
ignoredFiles = ignoredFiles.concat(flags.ignore.split(',')); | ||
} | ||
const handler = coroutine(function*(req, res) { | ||
yield serverHandler(req, res, flags, current, ignoredFiles) | ||
}) | ||
const handler = coroutine(function *run(req, res) { | ||
yield serverHandler(req, res, flags, current, ignoredFiles); | ||
}); | ||
const httpsOpts = { | ||
key: cert.key, | ||
cert: cert.cert, | ||
passphrase: cert.passphrase | ||
} | ||
key: cert.key, | ||
cert: cert.cert, | ||
passphrase: cert.passphrase | ||
}; | ||
const microHttps = fn => | ||
https.createServer(httpsOpts, (req, res) => micro.run(req, res, fn)) | ||
https.createServer(httpsOpts, (req, res) => micro.run(req, res, fn)); | ||
const server = flags.ssl | ||
? microHttps(flags.unzipped ? handler : compress(handler)) | ||
: micro(flags.unzipped ? handler : compress(handler)) | ||
? microHttps(flags.unzipped ? handler : compress(handler)) | ||
: micro(flags.unzipped ? handler : compress(handler)); | ||
let { port } = flags | ||
let {port} = flags; | ||
detect(port).then(async open => { | ||
const { NODE_ENV } = process.env | ||
const {NODE_ENV} = process.env; | ||
if (NODE_ENV !== 'production') { | ||
const update = await checkForUpdate(pkg) | ||
if (NODE_ENV !== 'production') { | ||
const update = await checkForUpdate(pkg); | ||
if (update) { | ||
const message = `${bold( | ||
'UPDATE AVAILABLE:' | ||
)} The latest version of \`serve\` is ${update.latest}` | ||
console.log( | ||
boxen(message, { | ||
padding: 1, | ||
borderColor: 'green', | ||
margin: 1 | ||
}) | ||
) | ||
} | ||
} | ||
if (update) { | ||
const message = `${bold( | ||
'UPDATE AVAILABLE:' | ||
)} The latest version of \`serve\` is ${update.latest}`; | ||
let inUse = open !== port | ||
console.log( | ||
boxen(message, { | ||
padding: 1, | ||
borderColor: 'green', | ||
margin: 1 | ||
}) | ||
); | ||
} | ||
} | ||
if (inUse) { | ||
port = open | ||
let inUse = open !== port; | ||
inUse = { | ||
old: flags.port, | ||
open | ||
} | ||
} | ||
if (inUse) { | ||
port = open; | ||
const listenArgs = [ | ||
server, | ||
current, | ||
inUse, | ||
flags.clipless !== true, | ||
flags.open, | ||
flags.ssl, | ||
flags.local | ||
] | ||
inUse = { | ||
old: flags.port, | ||
open | ||
}; | ||
} | ||
if (flags.local) { | ||
server.listen(port, 'localhost', listening.bind(this, ...listenArgs)) | ||
} else { | ||
server.listen(port, listening.bind(this, ...listenArgs)) | ||
} | ||
}) | ||
const listenArgs = [ | ||
server, | ||
current, | ||
inUse, | ||
flags.clipless !== true, | ||
flags.open, | ||
flags.ssl, | ||
flags.local | ||
]; | ||
if (flags.local) { | ||
server.listen(port, 'localhost', listening.bind(this, ...listenArgs)); | ||
} else { | ||
server.listen(port, listening.bind(this, ...listenArgs)); | ||
} | ||
}); |
// Native | ||
const { spawn } = require('child_process') | ||
const path = require('path') | ||
const {spawn} = require('child_process'); | ||
const path = require('path'); | ||
// Packages | ||
const dargs = require('dargs') | ||
const dargs = require('dargs'); | ||
module.exports = (directory = process.cwd(), options = {}) => { | ||
const scriptPath = path.join(__dirname, '..', 'bin', 'serve.js') | ||
const aliases = { cors: 'C' } | ||
const scriptPath = path.join(__dirname, '..', 'bin', 'serve.js'); | ||
const aliases = {cors: 'C'}; | ||
options._ = [directory] // Let dargs handle the directory argument | ||
options._ = [directory]; // Let dargs handle the directory argument | ||
// The CLI only understands comma-separated values for ignored files | ||
// So we join the string array with commas | ||
if (options.ignore) { | ||
options.ignore = options.ignore.join(',') | ||
} | ||
// The CLI only understands comma-separated values for ignored files | ||
// So we join the string array with commas | ||
if (options.ignore) { | ||
options.ignore = options.ignore.join(','); | ||
} | ||
const args = [scriptPath, ...dargs(options, { aliases })] | ||
const args = [scriptPath, ...dargs(options, {aliases})]; | ||
const cli = spawn('node', args, { | ||
stdio: 'inherit' | ||
}) | ||
const cli = spawn('node', args, { | ||
stdio: 'inherit' | ||
}); | ||
return { | ||
stop() { | ||
cli.kill() | ||
} | ||
} | ||
} | ||
return { | ||
stop() { | ||
cli.kill(); | ||
} | ||
}; | ||
}; |
// Native | ||
const { basename } = require('path') | ||
const {basename} = require('path'); | ||
// Packages | ||
const { write: copy } = require('clipboardy') | ||
const ip = require('ip') | ||
const pathType = require('path-type') | ||
const chalk = require('chalk') | ||
const boxen = require('boxen') | ||
const { coroutine } = require('bluebird') | ||
const opn = require('opn') | ||
const {write: copy} = require('clipboardy'); | ||
const ip = require('ip'); | ||
const pathType = require('path-type'); | ||
const chalk = require('chalk'); | ||
const boxen = require('boxen'); | ||
const {coroutine} = require('bluebird'); | ||
const opn = require('opn'); | ||
module.exports = coroutine(function*( | ||
server, | ||
current, | ||
inUse, | ||
clipboard, | ||
open, | ||
ssl, | ||
isLocal | ||
module.exports = coroutine(function *firework( | ||
server, | ||
current, | ||
inUse, | ||
clipboard, | ||
open, | ||
ssl, | ||
isLocal | ||
) { | ||
const details = server.address() | ||
const { isTTY } = process.stdout | ||
const details = server.address(); | ||
const {isTTY} = process.stdout; | ||
const shutdown = () => { | ||
server.close() | ||
const shutdown = () => { | ||
server.close(); | ||
process.exit(0); | ||
}; | ||
// eslint-disable-next-line unicorn/no-process-exit | ||
process.exit(0) | ||
} | ||
process.on('SIGINT', shutdown); | ||
process.on('SIGTERM', shutdown); | ||
process.on('SIGINT', shutdown) | ||
process.on('SIGTERM', shutdown) | ||
let isDir; | ||
let isDir | ||
try { | ||
isDir = yield pathType.dir(current); | ||
} catch (err) { | ||
isDir = false; | ||
} | ||
try { | ||
isDir = yield pathType.dir(current) | ||
} catch (err) { | ||
isDir = false | ||
} | ||
if (!isDir) { | ||
const base = basename(current); | ||
if (!isDir) { | ||
const base = basename(current) | ||
console.error( | ||
chalk.red(`Specified directory ${chalk.bold(`"${base}"`)} doesn't exist!`) | ||
); | ||
console.error( | ||
chalk.red(`Specified directory ${chalk.bold(`"${base}"`)} doesn't exist!`) | ||
) | ||
process.exit(1); | ||
} | ||
// eslint-disable-next-line unicorn/no-process-exit | ||
process.exit(1) | ||
} | ||
if (process.env.NODE_ENV !== 'production') { | ||
let message = chalk.green('Serving!'); | ||
if (process.env.NODE_ENV !== 'production') { | ||
let message = chalk.green('Serving!') | ||
if (inUse) { | ||
message += ` ${chalk.red(`(on port ${inUse.open}, because ${inUse.old} is already in use)`)}`; | ||
} | ||
if (inUse) { | ||
message += | ||
' ' + | ||
chalk.red( | ||
`(on port ${inUse.open}, because ${inUse.old} is already in use)` | ||
) | ||
} | ||
message += '\n\n'; | ||
message += '\n\n' | ||
const localURL = `http${ssl ? 's' : ''}://localhost:${details.port}`; | ||
message += `- ${chalk.bold('Local: ')} ${localURL}`; | ||
const localURL = `http${ssl ? 's' : ''}://localhost:${details.port}` | ||
message += `- ${chalk.bold('Local: ')} ${localURL}` | ||
if (!isLocal) { | ||
try { | ||
const ipAddress = ip.address(); | ||
const url = `http${ssl ? 's' : ''}://${ipAddress}:${details.port}`; | ||
if (!isLocal) { | ||
try { | ||
const ipAddress = ip.address() | ||
const url = `http${ssl ? 's' : ''}://${ipAddress}:${details.port}` | ||
message += `\n- ${chalk.bold('On Your Network: ')} ${url}`; | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
message += `\n- ${chalk.bold('On Your Network: ')} ${url}` | ||
} catch (err) {} | ||
} | ||
if (isTTY && clipboard) { | ||
try { | ||
yield copy(localURL); | ||
message += `\n\n${chalk.grey('Copied local address to clipboard!')}`; | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
if (isTTY && clipboard) { | ||
try { | ||
yield copy(localURL) | ||
message += `\n\n${chalk.grey('Copied local address to clipboard!')}` | ||
} catch (err) {} | ||
} | ||
if (isTTY && open) { | ||
try { | ||
opn(localURL); | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
} | ||
if (isTTY && open) { | ||
try { | ||
opn(localURL) | ||
} catch (err) {} | ||
} | ||
if (!isTTY) { | ||
console.log(`serve: Running on port ${details.port}`); | ||
return; | ||
} | ||
if (!isTTY) { | ||
console.log(`serve: Running on port ${details.port}`) | ||
return | ||
} | ||
console.log( | ||
boxen(message, { | ||
padding: 1, | ||
borderColor: 'green', | ||
margin: 1 | ||
}) | ||
) | ||
} | ||
}) | ||
console.log( | ||
boxen(message, { | ||
padding: 1, | ||
borderColor: 'green', | ||
margin: 1 | ||
}) | ||
); | ||
} | ||
}); |
@@ -1,90 +0,90 @@ | ||
const envPort = process.env.PORT | ||
const defaultPort = isNaN(envPort) ? 5000 : parseInt(envPort, 10) | ||
const envPort = process.env.PORT; | ||
const defaultPort = isNaN(envPort) ? 5000 : parseInt(envPort, 10); | ||
exports.options = [ | ||
{ | ||
name: 'port', | ||
description: 'Port to listen on', | ||
defaultValue: defaultPort | ||
}, | ||
{ | ||
name: 'cache', | ||
description: 'Time in milliseconds for caching files in the browser' | ||
}, | ||
{ | ||
name: 'single', | ||
description: 'Serve single page applications (sets `-c` to 1 day)' | ||
}, | ||
{ | ||
name: 'unzipped', | ||
description: 'Disable GZIP compression' | ||
}, | ||
{ | ||
name: 'ignore', | ||
description: 'Files and directories to ignore' | ||
}, | ||
{ | ||
name: 'auth', | ||
description: 'Serve behind basic auth' | ||
}, | ||
{ | ||
name: 'cors', | ||
description: 'Setup * CORS headers to allow requests from any origin', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'silent', | ||
description: `Don't log anything to the console` | ||
}, | ||
{ | ||
name: ['n', 'clipless'], | ||
description: `Don't copy address to clipboard`, | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'open', | ||
description: 'Open local address in browser', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'treeless', | ||
description: `Don't display statics tree`, | ||
defaultValue: false | ||
}, | ||
{ | ||
name: ['T', 'ssl'], | ||
description: 'Serve content using SSL', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'local', | ||
description: 'Serve content only on localhost', | ||
defaultValue: false | ||
} | ||
] | ||
{ | ||
name: 'port', | ||
description: 'Port to listen on', | ||
defaultValue: defaultPort | ||
}, | ||
{ | ||
name: 'cache', | ||
description: 'Time in milliseconds for caching files in the browser' | ||
}, | ||
{ | ||
name: 'single', | ||
description: 'Serve single page applications (sets `-c` to 1 day)' | ||
}, | ||
{ | ||
name: 'unzipped', | ||
description: 'Disable GZIP compression' | ||
}, | ||
{ | ||
name: 'ignore', | ||
description: 'Files and directories to ignore' | ||
}, | ||
{ | ||
name: 'auth', | ||
description: 'Serve behind basic auth' | ||
}, | ||
{ | ||
name: 'cors', | ||
description: 'Setup * CORS headers to allow requests from any origin', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'silent', | ||
description: `Don't log anything to the console` | ||
}, | ||
{ | ||
name: ['n', 'clipless'], | ||
description: `Don't copy address to clipboard`, | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'open', | ||
description: 'Open local address in browser', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'treeless', | ||
description: `Don't display statics tree`, | ||
defaultValue: false | ||
}, | ||
{ | ||
name: ['T', 'ssl'], | ||
description: 'Serve content using SSL', | ||
defaultValue: false | ||
}, | ||
{ | ||
name: 'local', | ||
description: 'Serve content only on localhost', | ||
defaultValue: false | ||
} | ||
]; | ||
exports.minimist = { | ||
alias: { | ||
a: 'auth', | ||
C: 'cors', | ||
S: 'silent', | ||
s: 'single', | ||
u: 'unzipped', | ||
n: 'clipless', | ||
o: 'open', | ||
t: 'treeless', | ||
l: 'local' | ||
}, | ||
boolean: [ | ||
'auth', | ||
'cors', | ||
'silent', | ||
'single', | ||
'unzipped', | ||
'clipless', | ||
'open', | ||
'treeless', | ||
'ssl', | ||
'local' | ||
] | ||
} | ||
'alias': { | ||
a: 'auth', | ||
C: 'cors', | ||
S: 'silent', | ||
s: 'single', | ||
u: 'unzipped', | ||
n: 'clipless', | ||
o: 'open', | ||
t: 'treeless', | ||
l: 'local' | ||
}, | ||
'boolean': [ | ||
'auth', | ||
'cors', | ||
'silent', | ||
'single', | ||
'unzipped', | ||
'clipless', | ||
'open', | ||
'treeless', | ||
'ssl', | ||
'local' | ||
] | ||
}; |
// Native | ||
const path = require('path') | ||
const path = require('path'); | ||
// Packages | ||
const pathType = require('path-type') | ||
const filesize = require('filesize') | ||
const fs = require('fs-extra') | ||
const { coroutine } = require('bluebird') | ||
const pathType = require('path-type'); | ||
const filesize = require('filesize'); | ||
const fs = require('fs-extra'); | ||
const {coroutine} = require('bluebird'); | ||
// Utilities | ||
const prepareView = require('./view') | ||
const prepareView = require('./view'); | ||
module.exports = coroutine(function*(port, current, dir, ignoredFiles) { | ||
let files = [] | ||
const subPath = path.relative(current, dir) | ||
module.exports = coroutine(function *renderer(port, current, dir, ignoredFiles) { | ||
let files = []; | ||
const subPath = path.relative(current, dir); | ||
if (!fs.existsSync(dir)) { | ||
return false | ||
} | ||
if (!fs.existsSync(dir)) { | ||
return false; | ||
} | ||
try { | ||
files = yield fs.readdir(dir) | ||
} catch (err) { | ||
throw err | ||
} | ||
try { | ||
files = yield 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) | ||
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) | ||
details.relative = path.join(subPath, details.base); | ||
if (yield pathType.dir(filePath)) { | ||
details.base += '/' | ||
} else { | ||
details.ext = details.ext.split('.')[1] || 'txt' | ||
if (yield pathType.dir(filePath)) { | ||
details.base += '/'; | ||
} else { | ||
details.ext = details.ext.split('.')[1] || 'txt'; | ||
let fileStats | ||
let fileStats; | ||
try { | ||
fileStats = yield fs.stat(filePath) | ||
} catch (err) { | ||
throw err | ||
} | ||
try { | ||
fileStats = yield fs.stat(filePath); | ||
} catch (err) { | ||
throw err; | ||
} | ||
details.size = filesize(fileStats.size, { round: 0 }) | ||
} | ||
details.size = filesize(fileStats.size, {round: 0}); | ||
} | ||
details.title = details.base | ||
details.title = details.base; | ||
if (ignoredFiles.indexOf(details.base) > -1) { | ||
delete files[index] | ||
} else { | ||
files[files.indexOf(file)] = details | ||
} | ||
} | ||
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(path.sep) | ||
const directory = path.join(path.basename(current), subPath, '/'); | ||
const pathParts = directory.split(path.sep); | ||
// Sort to list directories first, then sort alphabetically | ||
files = files.sort((a, b) => { | ||
const aIsDir = a.base.endsWith('/') | ||
const bIsDir = b.base.endsWith('/') | ||
if (aIsDir && !bIsDir) return -1 | ||
if (bIsDir && !aIsDir) return 1 | ||
if (a.base > b.base) return 1 | ||
if (a.base < b.base) return -1 | ||
return 0 | ||
}) | ||
// Sort to list directories first, then sort alphabetically | ||
files = files.sort((a, b) => { | ||
const aIsDir = a.base.endsWith('/'); | ||
const bIsDir = b.base.endsWith('/'); | ||
if (aIsDir && !bIsDir) { | ||
return -1; | ||
} | ||
if (bIsDir && !aIsDir) { | ||
return 1; | ||
} | ||
if (a.base > b.base) { | ||
return 1; | ||
} | ||
if (a.base < b.base) { | ||
return -1; | ||
} | ||
return 0; | ||
}); | ||
// Add parent directory to the head of the sorted files array | ||
if (dir.indexOf(current + '/') > -1) { | ||
const directoryPath = [...pathParts] | ||
directoryPath.shift() | ||
// Add parent directory to the head of the sorted files array | ||
if (dir.indexOf(`${current}/`) > -1) { | ||
const directoryPath = [...pathParts]; | ||
directoryPath.shift(); | ||
files.unshift({ | ||
base: '..', | ||
relative: path.join(...directoryPath, '..'), | ||
title: path.join(...pathParts.slice(0, -2), '/') | ||
}) | ||
} | ||
files.unshift({ | ||
base: '..', | ||
relative: path.join(...directoryPath, '..'), | ||
title: path.join(...pathParts.slice(0, -2), '/') | ||
}); | ||
} | ||
const render = prepareView() | ||
const render = prepareView(); | ||
const paths = [] | ||
pathParts.pop() | ||
const paths = []; | ||
pathParts.pop(); | ||
for (const part in pathParts) { | ||
if (!{}.hasOwnProperty.call(pathParts, part)) { | ||
continue | ||
} | ||
for (const part in pathParts) { | ||
if (!{}.hasOwnProperty.call(pathParts, part)) { | ||
continue; | ||
} | ||
let before = 0 | ||
const parents = [] | ||
let before = 0; | ||
const parents = []; | ||
while (before <= part) { | ||
parents.push(pathParts[before]) | ||
before++ | ||
} | ||
while (before <= part) { | ||
parents.push(pathParts[before]); | ||
before++; | ||
} | ||
parents.shift() | ||
parents.shift(); | ||
paths.push({ | ||
name: pathParts[part], | ||
url: parents.join('/') | ||
}) | ||
} | ||
paths.push({ | ||
name: pathParts[part], | ||
url: parents.join('/') | ||
}); | ||
} | ||
const details = { | ||
port, | ||
files, | ||
assetDir: process.env.ASSET_DIR, | ||
directory, | ||
nodeVersion: process.version.split('v')[1], | ||
paths | ||
} | ||
const details = { | ||
port, | ||
files, | ||
assetDir: process.env.ASSET_DIR, | ||
directory, | ||
nodeVersion: process.version.split('v')[1], | ||
paths | ||
}; | ||
return render(details) | ||
}) | ||
return render(details); | ||
}); |
// Native | ||
const path = require('path') | ||
const { parse, format } = require('url') | ||
const path = require('path'); | ||
const {parse, format} = require('url'); | ||
// Packages | ||
const micro = require('micro') | ||
const auth = require('basic-auth') | ||
const { red } = require('chalk') | ||
const fs = require('fs-extra') | ||
const pathType = require('path-type') | ||
const mime = require('mime-types') | ||
const stream = require('send') | ||
const { coroutine } = require('bluebird') | ||
const isPathInside = require('path-is-inside') | ||
const micro = require('micro'); | ||
const auth = require('basic-auth'); | ||
const {red} = require('chalk'); | ||
const fs = require('fs-extra'); | ||
const pathType = require('path-type'); | ||
const mime = require('mime-types'); | ||
const stream = require('send'); | ||
const {coroutine} = require('bluebird'); | ||
const isPathInside = require('path-is-inside'); | ||
// Utilities | ||
const renderDirectory = require('./render') | ||
const renderDirectory = require('./render'); | ||
module.exports = coroutine(function*(req, res, flags, current, ignoredFiles) { | ||
const headers = {} | ||
module.exports = coroutine(function *server(req, res, flags, current, ignoredFiles) { | ||
const headers = {}; | ||
if (flags.cors) { | ||
headers['Access-Control-Allow-Origin'] = '*' | ||
headers['Access-Control-Allow-Headers'] = | ||
'Origin, X-Requested-With, Content-Type, Accept, Range' | ||
} | ||
if (flags.cors) { | ||
headers['Access-Control-Allow-Origin'] = '*'; | ||
headers['Access-Control-Allow-Headers'] = | ||
'Origin, X-Requested-With, Content-Type, Accept, Range'; | ||
} | ||
for (const header in headers) { | ||
if (!{}.hasOwnProperty.call(headers, header)) { | ||
continue | ||
} | ||
for (const header in headers) { | ||
if (!{}.hasOwnProperty.call(headers, header)) { | ||
continue; | ||
} | ||
res.setHeader(header, headers[header]) | ||
} | ||
res.setHeader(header, headers[header]); | ||
} | ||
if (flags.auth) { | ||
const credentials = auth(req) | ||
if (flags.auth) { | ||
const credentials = auth(req); | ||
if (!process.env.SERVE_USER || !process.env.SERVE_PASSWORD) { | ||
const error = | ||
if (!process.env.SERVE_USER || !process.env.SERVE_PASSWORD) { | ||
const error = | ||
'The environment variables "SERVE_USER" ' + | ||
'and/or "SERVE_PASSWORD" are missing!' | ||
console.error(red(error)) | ||
'and/or "SERVE_PASSWORD" are missing!'; | ||
console.error(red(error)); | ||
// eslint-disable-next-line unicorn/no-process-exit | ||
process.exit(1) | ||
} | ||
process.exit(1); | ||
} | ||
if ( | ||
!credentials || | ||
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') | ||
} | ||
} | ||
) { | ||
res.statusCode = 401; | ||
res.setHeader('WWW-Authenticate', 'Basic realm="User Visible Realm"'); | ||
return micro.send(res, 401, 'Access Denied'); | ||
} | ||
} | ||
const { pathname } = parse(req.url) | ||
const { ASSET_DIR } = process.env | ||
const {pathname} = parse(req.url); | ||
const {ASSET_DIR} = process.env; | ||
let related = path.parse(path.join(current, pathname)) | ||
let assetRequest = false | ||
let related = path.parse(path.join(current, pathname)); | ||
let assetRequest = false; | ||
if (path.basename(related.dir) === ASSET_DIR) { | ||
assetRequest = true | ||
related = decodeURIComponent(path.join(__dirname, '/../assets/styles.css')) | ||
} else { | ||
related = decodeURIComponent(path.format(related)) | ||
if (path.basename(related.dir) === ASSET_DIR) { | ||
assetRequest = true; | ||
related = decodeURIComponent(path.join(__dirname, '/../assets/styles.css')); | ||
} else { | ||
related = decodeURIComponent(path.format(related)); | ||
const relatedResolved = path.resolve(related) | ||
const relatedCurrent = path.resolve(current) | ||
const relatedResolved = path.resolve(related); | ||
const relatedCurrent = path.resolve(current); | ||
const isSame = relatedResolved === relatedCurrent | ||
const isSame = relatedResolved === relatedCurrent; | ||
if (!isSame && !isPathInside(relatedResolved, relatedCurrent)) { | ||
return micro.send(res, 400, 'Bad Request') | ||
} | ||
} | ||
if (!isSame && !isPathInside(relatedResolved, relatedCurrent)) { | ||
return micro.send(res, 400, 'Bad Request'); | ||
} | ||
} | ||
let notFoundResponse = 'Not Found' | ||
let notFoundResponse = 'Not Found'; | ||
try { | ||
const custom404Path = path.join(current, '/404.html') | ||
notFoundResponse = yield fs.readFile(custom404Path, 'utf-8') | ||
} catch (err) {} | ||
try { | ||
const custom404Path = path.join(current, '/404.html'); | ||
notFoundResponse = yield fs.readFile(custom404Path, 'utf-8'); | ||
// Don't allow rendering ignored files | ||
const ignored = !ignoredFiles.every(item => { | ||
return !decodeURIComponent(pathname).includes(item) | ||
}) | ||
// We shouldn't do anything here, because it is okay if | ||
// the error template doesn't exist. | ||
if (ignored || (!assetRequest && related.indexOf(current) !== 0)) { | ||
return micro.send(res, 404, notFoundResponse) | ||
} | ||
// eslint-disable-next-line no-empty | ||
} catch (err) {} | ||
const relatedExists = yield fs.exists(related) | ||
// Don't allow rendering ignored files | ||
const ignored = !ignoredFiles.every(item => !decodeURIComponent(pathname).includes(item)); | ||
if (!relatedExists && !flags.single) { | ||
return micro.send(res, 404, notFoundResponse) | ||
} | ||
if (ignored || (!assetRequest && related.indexOf(current) !== 0)) { | ||
return micro.send(res, 404, notFoundResponse); | ||
} | ||
const streamOptions = {} | ||
const cacheValue = parseInt(flags.cache, 10) | ||
const relatedExists = yield fs.exists(related); | ||
if (cacheValue > 0) { | ||
streamOptions.maxAge = flags.cache | ||
} else if (cacheValue === 0) { | ||
// Disable the cache control by `send`, as there's no support for `no-cache`. | ||
// Set header manually. | ||
streamOptions.cacheControl = false | ||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate') | ||
} else if (flags.single) { | ||
// Cache assets of single page applications for a day. | ||
// Later in the code, we'll define that `index.html` never | ||
// gets cached! | ||
streamOptions.maxAge = 86400000 | ||
} | ||
if (!relatedExists && !flags.single) { | ||
return micro.send(res, 404, notFoundResponse); | ||
} | ||
// Check if directory | ||
if (relatedExists && (yield pathType.dir(related))) { | ||
// Normalize path to trailing slash | ||
// Otherwise problems like #70 will occur | ||
const url = parse(req.url) | ||
const streamOptions = {}; | ||
const cacheValue = parseInt(flags.cache, 10); | ||
if (url.pathname.substr(-1) !== '/') { | ||
url.pathname += '/' | ||
const newPath = format(url) | ||
if (cacheValue > 0) { | ||
streamOptions.maxAge = flags.cache; | ||
} else if (cacheValue === 0) { | ||
// Disable the cache control by `send`, as there's no support for `no-cache`. | ||
// Set header manually. | ||
streamOptions.cacheControl = false; | ||
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); | ||
} else if (flags.single) { | ||
// Cache assets of single page applications for a day. | ||
// Later in the code, we'll define that `index.html` never | ||
// gets cached! | ||
streamOptions.maxAge = 86400000; | ||
} | ||
res.writeHead(302, { | ||
Location: newPath | ||
}) | ||
// Check if directory | ||
if (relatedExists && (yield pathType.dir(related))) { | ||
// Normalize path to trailing slash | ||
// Otherwise problems like #70 will occur | ||
const url = parse(req.url); | ||
res.end() | ||
return | ||
} | ||
if (url.pathname.substr(-1) !== '/') { | ||
url.pathname += '/'; | ||
const newPath = format(url); | ||
let indexPath = path.join(related, '/index.html') | ||
res.setHeader('Content-Type', mime.contentType(path.extname(indexPath))) | ||
res.writeHead(302, { | ||
Location: newPath | ||
}); | ||
if (!(yield fs.exists(indexPath))) { | ||
// Try to render the current directory's content | ||
const port = flags.port || req.socket.localPort | ||
const renderedDir = yield renderDirectory( | ||
port, | ||
current, | ||
related, | ||
ignoredFiles | ||
) | ||
res.end(); | ||
return; | ||
} | ||
// If it works, send the directory listing to the user | ||
// If is treeless, stepover | ||
if (renderedDir && !flags.treeless) { | ||
return micro.send(res, 200, renderedDir) | ||
} | ||
let indexPath = path.join(related, '/index.html'); | ||
res.setHeader('Content-Type', mime.contentType(path.extname(indexPath))); | ||
// 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) | ||
} | ||
if (!(yield fs.exists(indexPath))) { | ||
// Try to render the current directory's content | ||
const port = flags.port || req.socket.localPort; | ||
const renderedDir = yield renderDirectory( | ||
port, | ||
current, | ||
related, | ||
ignoredFiles | ||
); | ||
// But IF IT IS true, then this is a hit to a directory with no `index.html` | ||
// Load the SPA's root index file | ||
indexPath = path.join(current, '/index.html') | ||
} | ||
// If it works, send the directory listing to the user | ||
// If is treeless, stepover | ||
if (renderedDir && !flags.treeless) { | ||
return micro.send(res, 200, renderedDir); | ||
} | ||
if (flags.single && indexPath === path.join(current, '/index.html')) { | ||
// Don't cache the `index.html` file for single page applications | ||
streamOptions.maxAge = 0 | ||
} | ||
// 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); | ||
} | ||
return stream(req, indexPath, streamOptions).pipe(res) | ||
} | ||
// But IF IT IS true, then this is a hit to a directory with no `index.html` | ||
// Load the SPA's root index file | ||
indexPath = path.join(current, '/index.html'); | ||
} | ||
// Serve `index.html` for single page applications if: | ||
// - The path does not match any file or directory OR | ||
// - The path matches exactly `/index.html` | ||
if ( | ||
flags.single && | ||
if (flags.single && indexPath === path.join(current, '/index.html')) { | ||
// Don't cache the `index.html` file for single page applications | ||
streamOptions.maxAge = 0; | ||
} | ||
return stream(req, indexPath, streamOptions).pipe(res); | ||
} | ||
// Serve `index.html` for single page applications if: | ||
// - The path does not match any file or directory OR | ||
// - The path matches exactly `/index.html` | ||
if ( | ||
flags.single && | ||
(!relatedExists || related === path.join(current, '/index.html')) | ||
) { | ||
// Don't cache the `index.html` file for single page applications | ||
streamOptions.maxAge = 0 | ||
) { | ||
// Don't cache the `index.html` file for single page applications | ||
streamOptions.maxAge = 0; | ||
// Load `index.html` and send it to the client | ||
const indexPath = path.join(current, '/index.html') | ||
return stream(req, indexPath, streamOptions).pipe(res) | ||
} | ||
// Load `index.html` and send it to the client | ||
const indexPath = path.join(current, '/index.html'); | ||
return stream(req, indexPath, streamOptions).pipe(res); | ||
} | ||
// TODO remove the change below when https://github.com/zeit/serve/issues/303 is fixed. | ||
// | ||
// Details: | ||
// Register the mime type for `.wasm` files. This change is currently not | ||
// possible on upstream because of an unrelated issue. Once `mime` is | ||
// updated, this line can be removed. | ||
// https://github.com/pillarjs/send/pull/154 | ||
stream.mime.types.wasm = 'application/wasm' | ||
stream.mime.extensions['application/wasm'] = 'wasm' | ||
// TODO remove the change below when https://github.com/zeit/serve/issues/303 is fixed. | ||
// | ||
// Details: | ||
// Register the mime type for `.wasm` files. This change is currently not | ||
// possible on upstream because of an unrelated issue. Once `mime` is | ||
// updated, this line can be removed. | ||
// https://github.com/pillarjs/send/pull/154 | ||
stream.mime.types.wasm = 'application/wasm'; | ||
stream.mime.extensions['application/wasm'] = 'wasm'; | ||
// Serve files without a mime type as html for SPA, and text for non SPA | ||
// eslint-disable-next-line camelcase | ||
stream.mime.default_type = flags.single ? 'text/html' : 'text/plain' | ||
// Serve files without a mime type as html for SPA, and text for non SPA | ||
// eslint-disable-next-line camelcase | ||
stream.mime.default_type = flags.single ? 'text/html' : 'text/plain'; | ||
return stream( | ||
req, | ||
related, | ||
Object.assign( | ||
{ | ||
dotfiles: 'allow' | ||
}, | ||
streamOptions | ||
) | ||
).pipe(res) | ||
}) | ||
return stream( | ||
req, | ||
related, | ||
Object.assign( | ||
{ | ||
dotfiles: 'allow' | ||
}, | ||
streamOptions | ||
) | ||
).pipe(res); | ||
}); |
// Native | ||
const path = require('path') | ||
const path = require('path'); | ||
// Packages | ||
const fs = require('fs-extra') | ||
const { compile } = require('handlebars') | ||
const fs = require('fs-extra'); | ||
const {compile} = require('handlebars'); | ||
module.exports = () => { | ||
let viewContent = false | ||
const viewPath = path.normalize(path.join(__dirname, '/../views/index.hbs')) | ||
let viewContent = false; | ||
const viewPath = path.normalize(path.join(__dirname, '/../views/index.hbs')); | ||
try { | ||
viewContent = fs.readFileSync(viewPath, 'utf8') | ||
} catch (err) { | ||
throw err | ||
} | ||
try { | ||
viewContent = fs.readFileSync(viewPath, 'utf8'); | ||
} catch (err) { | ||
throw err; | ||
} | ||
return compile(viewContent) | ||
} | ||
return compile(viewContent); | ||
}; |
{ | ||
"name": "serve", | ||
"version": "6.5.5", | ||
"version": "6.5.6", | ||
"description": "Static file serving and directory listing", | ||
"scripts": { | ||
"precommit": "lint-staged", | ||
"lint": "xo", | ||
"test": "yarn lint && NODE_ENV=test nyc ava" | ||
"test": "yarn lint && NODE_ENV=test nyc ava", | ||
"lint": "zeit-eslint --ext .jsx,.js .", | ||
"lint-staged": "git diff --diff-filter=ACMRT --cached --name-only '*.js' '*.jsx' | xargs zeit-eslint" | ||
}, | ||
@@ -24,12 +24,2 @@ "files": [ | ||
}, | ||
"xo": { | ||
"extends": "prettier" | ||
}, | ||
"lint-staged": { | ||
"*.js": [ | ||
"yarn lint", | ||
"prettier --single-quote --no-semi --write", | ||
"git add" | ||
] | ||
}, | ||
"keywords": [ | ||
@@ -44,36 +34,42 @@ "now", | ||
"devDependencies": { | ||
"@zeit/eslint-config-node": "0.2.13", | ||
"@zeit/git-hooks": "0.1.4", | ||
"ava": "0.25.0", | ||
"eslint-config-prettier": "2.9.0", | ||
"husky": "0.14.3", | ||
"lint-staged": "7.0.0", | ||
"node-fetch": "2.1.1", | ||
"nyc": "11.4.1", | ||
"prettier": "1.11.1", | ||
"test-listen": "1.1.0", | ||
"xo": "0.20.3" | ||
"eslint": "4.19.1", | ||
"node-fetch": "2.1.2", | ||
"nyc": "11.7.1", | ||
"test-listen": "1.1.0" | ||
}, | ||
"dependencies": { | ||
"args": "3.0.8", | ||
"args": "4.0.0", | ||
"basic-auth": "2.0.0", | ||
"bluebird": "3.5.1", | ||
"boxen": "1.3.0", | ||
"chalk": "2.3.2", | ||
"chalk": "2.4.0", | ||
"clipboardy": "1.2.3", | ||
"dargs": "5.1.0", | ||
"detect-port": "1.2.2", | ||
"filesize": "3.6.0", | ||
"filesize": "3.6.1", | ||
"fs-extra": "5.0.0", | ||
"handlebars": "4.0.11", | ||
"ip": "1.1.5", | ||
"micro": "9.1.0", | ||
"micro": "9.1.4", | ||
"micro-compress": "1.0.0", | ||
"mime-types": "2.1.18", | ||
"node-version": "1.1.0", | ||
"node-version": "1.1.3", | ||
"openssl-self-signed-certificate": "1.1.6", | ||
"opn": "5.2.0", | ||
"opn": "5.3.0", | ||
"path-is-inside": "1.0.2", | ||
"path-type": "3.0.0", | ||
"send": "0.16.2", | ||
"update-check": "1.2.0" | ||
"update-check": "1.3.2" | ||
}, | ||
"eslintConfig": { | ||
"extends": [ | ||
"@zeit/eslint-config-node" | ||
] | ||
}, | ||
"git": { | ||
"pre-commit": "lint-staged" | ||
} | ||
} |
# serve | ||
[![Build Status](https://travis-ci.org/zeit/serve.svg?branch=master)](https://travis-ci.org/zeit/serve) | ||
[![Build Status](https://circleci.com/gh/zeit/serve.svg?&style=shield)](https://circleci.com/gh/zeit/serve) | ||
[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/micro/serve) | ||
Ever wanted to share a project on your network by running just a command? Then this module is exactly what you're looking for: It provides a neat interface for listing the directory's contents and switching into sub folders. | ||
Have you ever wanted to share a project on your network by running just a command? Then this module is exactly what you're looking for: It provides a neat interface for listing the directory's contents and switching into sub folders. | ||
@@ -14,3 +14,3 @@ In addition, it's also awesome when it comes to serving static sites! | ||
Install it (needs at least Node LTS): | ||
Firstly, install the package from [npm](https://npmjs.com/release) (you'll need at least Node.js 7.6.0): | ||
@@ -21,5 +21,11 @@ ```bash | ||
And run this command in your terminal: | ||
Alternatively, you can use [Yarn](https://yarnpkg.com/en/) to install it: | ||
```bash | ||
yarn global add serve | ||
``` | ||
Once that's done, you can run this command inside your project's directory: | ||
```bash | ||
serve [options] <path> | ||
@@ -26,0 +32,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
7
749
88
28284
+ Addedargs@4.0.0(transitive)
+ Addedcamelcase@5.0.0(transitive)
+ Addedchalk@2.4.0(transitive)
+ Addedfilesize@3.6.1(transitive)
+ Addedleven@2.1.0(transitive)
+ Addedmicro@9.1.4(transitive)
+ Addednode-version@1.1.3(transitive)
+ Addedopn@5.3.0(transitive)
+ Addedupdate-check@1.3.2(transitive)
- Removedargs@3.0.8(transitive)
- Removedchalk@2.1.0(transitive)
- Removedfilesize@3.6.0(transitive)
- Removedhas-flag@2.0.0(transitive)
- Removedlodash@4.17.21(transitive)
- Removedmicro@9.1.0(transitive)
- Removednode-version@1.1.0(transitive)
- Removedopn@5.2.0(transitive)
- Removedpkginfo@0.4.1(transitive)
- Removedstring-similarity@1.2.0(transitive)
- Removedsupports-color@4.5.0(transitive)
- Removedupdate-check@1.2.0(transitive)
Updatedargs@4.0.0
Updatedchalk@2.4.0
Updatedfilesize@3.6.1
Updatedmicro@9.1.4
Updatednode-version@1.1.3
Updatedopn@5.3.0
Updatedupdate-check@1.3.2