From 3d4b4361f397271cacaeae2de1c26cdf9d3f7931 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 8 Oct 2024 17:17:17 +0700 Subject: [PATCH 01/18] feat: localhost SSL support --- README.md | 11 +++-- certificates/localhost.crt | 23 ++++++++++ certificates/localhost.key | 28 ++++++++++++ certificates/openssl.cnf | 30 +++++++++++++ index.js | 30 ------------- lib/index.js | 33 ++++++++++++++ lib/server-http.js | 80 ++++++++++++++++++++++++++++++++++ lib/server-ssl.js | 88 ++++++++++++++++++++++++++++++++++++++ lib/server.js | 88 +++++--------------------------------- package.json | 5 ++- 10 files changed, 303 insertions(+), 113 deletions(-) create mode 100644 certificates/localhost.crt create mode 100644 certificates/localhost.key create mode 100644 certificates/openssl.cnf delete mode 100644 index.js create mode 100644 lib/index.js create mode 100644 lib/server-http.js create mode 100644 lib/server-ssl.js diff --git a/README.md b/README.md index 667b97d..8211065 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,13 @@ $ hexo server Option | Description | Default --- | --- | --- `-i`, `--ip` | Override the default server IP. | `::` when IPv6 is available, else `0.0.0.0` (note: in most systems, `::` also binds to `0.0.0.0`) -`-p`, `--port` | Override the default port. | 4000 -`-s`, `--static` | Only serve static files. | false -`-l`, `--log [format]` | Enable logger. Override log format. | false -`-o`, `--open` | Immediately open the server url in your default web browser. | false +`-p`, `--port` | Override the default port. | `4000` +`-s`, `--static` | Only serve static files. | `false` +`-l`, `--log [format]` | Enable logger. Override log format. | `false` +`-o`, `--open` | Immediately open the server url in your default web browser. | `false` +`-c`, `--cert` | Certificate path | `/certificates/localhost.crt` +`-ck`, `--key` | Certificate key path | `/certificates/localhost.key` +`h`, `--ssl` | Enable SSL localhost. If `--cert` and `--key` is present, ssl will enabled automatically. If `--cert` and `--key` is not present, but `--ssl` is preset, default certificate will be applied. | `false` ## Options diff --git a/certificates/localhost.crt b/certificates/localhost.crt new file mode 100644 index 0000000..d2b9db6 --- /dev/null +++ b/certificates/localhost.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwTCCAqkCFEXMrQ4TmdoCjzJOwz9otI85kQ6tMA0GCSqGSIb3DQEBCwUAMIGc +MQswCQYDVQQGEwJJRDESMBAGA1UECAwJRWFzdCBKYXZhMREwDwYDVQQHDAhTdXJh +YmF5YTEMMAoGA1UECgwDV01JMRIwEAYDVQQLDAlEZXZlbG9wZXIxHTAbBgNVBAMM +FGRldi53ZWJtYW5hamVtZW4uY29tMSUwIwYJKoZIhvcNAQkBFhZkaW1hc2xhbmph +a2FAZ21haWwuY29tMB4XDTI0MDcyNjA5MDExN1oXDTI1MDcyNjA5MDExN1owgZwx +CzAJBgNVBAYTAklEMRIwEAYDVQQIDAlFYXN0IEphdmExETAPBgNVBAcMCFN1cmFi +YXlhMQwwCgYDVQQKDANXTUkxEjAQBgNVBAsMCURldmVsb3BlcjEdMBsGA1UEAwwU +ZGV2LndlYm1hbmFqZW1lbi5jb20xJTAjBgkqhkiG9w0BCQEWFmRpbWFzbGFuamFr +YUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqL+4K +xiNBbMn1ssJSsHhB4TfhgcQ4tR9ov6SoVRvnv+50qHvY9mS/ksRCuWIKAC2e76nv +iV0zUY7c2ycBsTo7BTCm8KPL5TfLkvykg6m/BFPkR5wBDMA8jDpiZ1L+rE8ttXF7 +LxyYToSEjezOV92ivT+2MA3LwUoUkTWmLdinuQSfQN2gDzNCGFjqF1GDEwjiGiz9 +xVV7BmhnPMfhlt1jkE53nzSaYzjAUTYhSvw8hAWPNa+o90E1jJBC1T6c0AQKJ+I/ +rleZPAosMQdZvbokccwQXAF0uvxQ/gYu5TpB3gKp6ntjXmsqtqE837Egok4NF6Lx +b2sgHi9+7UtLbqS/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEpoBcHPiC/IZP/2 +jrf+qOWY51graP0lpi5NJBqcokbhmD1u3Ah7/oNgUqn2y4VCjQaM2lhNttjnKoWr +UYdVbzkZAFTtNDLbgNQgMRhN9J9J4+UqtrQ6sMqAENYO0ZWsx+Nth7hE1l4nsa0E +hCPtnnAgACCtS1KHiuDddkQ/gagt52Uf5QkdIGpKO6VEmv7SHkomiub9WUgT4kRK +HzBGup2zNegZ4HznCLhQtr2EqDaeBekYNRSfgF6G756QRFbnGnC1r362yi2pycFa +28I3tqDBN0Yr116urzYIFAXX1b/fkIEo8Om6jl7S+QRkDQBh/dvTZNADplJsvzvx +XRPbQp0= +-----END CERTIFICATE----- diff --git a/certificates/localhost.key b/certificates/localhost.key new file mode 100644 index 0000000..6dfde59 --- /dev/null +++ b/certificates/localhost.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqL+4KxiNBbMn1 +ssJSsHhB4TfhgcQ4tR9ov6SoVRvnv+50qHvY9mS/ksRCuWIKAC2e76nviV0zUY7c +2ycBsTo7BTCm8KPL5TfLkvykg6m/BFPkR5wBDMA8jDpiZ1L+rE8ttXF7LxyYToSE +jezOV92ivT+2MA3LwUoUkTWmLdinuQSfQN2gDzNCGFjqF1GDEwjiGiz9xVV7Bmhn +PMfhlt1jkE53nzSaYzjAUTYhSvw8hAWPNa+o90E1jJBC1T6c0AQKJ+I/rleZPAos +MQdZvbokccwQXAF0uvxQ/gYu5TpB3gKp6ntjXmsqtqE837Egok4NF6Lxb2sgHi9+ +7UtLbqS/AgMBAAECggEAGHaYTJMrqTFmnHtMJI+6UZn6qt844f/jGm8Fz66gOsFj +mQBJASh11fXWYVL9jTt2U1TXBBgmPgS4uPWFl2Au4yH9WtnXaa0yxoAD8e/9G/iW +yIcknSAEmA9+Kvv4OaRyIztkKxVcEmai9Nzjz8tgkA6NFHa23PWVXPx8jj0j2gPp +oPCEE2QL2fy0Mi6R6LIn5d85zsSn8TrZNdmvkMUxPcaSYyg2oxZ6J8/9Mn4oE9e5 +VmIt02TnN8q14KgixkcUQeI9JtgHwIZBP+jZIb1zx4C/8PXVs7CkZYifY518h/GM +4/w6AKJt3BZObLUwHkvucSTSVF2p2NdJZNN6SwX9QQKBgQDmgw8l1JYe2VtGfN+F +OdE8PuM8u3TO+r9POMK28gAC62VRoxPjfeNsVXTX2piliAnRSg+6nakn1vf8l9MP +DZaYj8jDMXzGU/vzvnzjfwyvjENmLCc4vkbu57gIOrD601DL/CqBYeBPwQGYxvSC +GE3UUXIOSOQ+VG58YDXrNfBgwQKBgQC9AU0DvL2tBZOHxTrWzyU4+BP8oych6q+u +n7QPNiQ+UvGMno8LngLXnoCnMz5T4PtAl0iZb51S+YTyXLQ8noqQEeJ4OivKS5hw +X2FBf+J/u4jrMQyIpcI34XCTtSl4EOQ7UxrxygtZIFugsX8PXFphKjo2wDaMBBV9 +cl0f6SLlfwKBgQCHHcVwSFciiAevnpyqjARwivBJ9ht3A5XGCyBfeiS1kWOXYb7T +t0PqiYDu0cxuIvqWOhJUMfwoRSKhZiEqDq36iTWF7OkVm77w1fSAqUU3VUFgj3sC +EM6lVSATesuoitsuZoZHxqZkOV8FPYGvDC36yS7Q3rsjKfyFXSPd1oUrQQKBgE/C +4cZm+0CuLsFIOXl3d4TgJEckbxpAGR2/ZdRZi9gFVsx6CXHkn9xwlmh5Fp99PWrX +rRqbYLAofrNs7d77JQyBj6ofGmXHmzApADkNB//Rm4ltbJWqJhlA+SpMdJCnyDlE +7AUHt9xH7IMXBMDtv3JryJ9cZGiYPJ1xCt2xnDlvAoGAQobmLEpUALINBBAtxRTz +OFl6J611TcWzrnhnr8tXmsBifirXg6usCn1HTLMP+TIfuQhpVLt7r7SbS5Ok/az4 +phsIfpfi1momli4uBwqr6ZD6z2bNeoxOvBa1XFCAzMwZ+y5Ylb0ReHkgNHibLF8A +69lB9UMeoSpe0O2SoqfQiPg= +-----END PRIVATE KEY----- diff --git a/certificates/openssl.cnf b/certificates/openssl.cnf new file mode 100644 index 0000000..90260ba --- /dev/null +++ b/certificates/openssl.cnf @@ -0,0 +1,30 @@ +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name +req_extensions = req_ext + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = ID +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = East Java +localityName = Locality Name (eg, city) +localityName_default = Surabaya +organizationName = Organization Name (eg, company) +organizationName_default = WMI +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Developer +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = dev.webmanajemen.com +commonName_max = 64 +emailAddress = Email Address +emailAddress_default = dimaslanjaka@gmail.com + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = dev.webmanajemen.com +DNS.2 = localhost +DNS.3 = 192.168.1.75 +DNS.3 = 127.0.0.1 diff --git a/index.js b/index.js deleted file mode 100644 index 9f366b4..0000000 --- a/index.js +++ /dev/null @@ -1,30 +0,0 @@ -/* global hexo */ - -'use strict'; - -hexo.config.server = Object.assign({ - port: 4000, - log: false, - // `undefined` uses Node's default (try `::` with fallback to `0.0.0.0`) - ip: undefined, - compress: false, - header: true -}, hexo.config.server); - -hexo.extend.console.register('server', 'Start the server.', { - desc: 'Start the server and watch for file changes.', - options: [ - {name: '-i, --ip', desc: 'Override the default server IP. Bind to all IP address by default.'}, - {name: '-p, --port', desc: 'Override the default port.'}, - {name: '-s, --static', desc: 'Only serve static files.'}, - {name: '-l, --log [format]', desc: 'Enable logger. Override log format.'}, - {name: '-o, --open', desc: 'Immediately open the server url in your default web browser.'} - ] -}, require('./lib/server')); - -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/header')); -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/gzip')); -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/logger')); -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/route')); -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/static')); -hexo.extend.filter.register('server_middleware', require('./lib/middlewares/redirect')); diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..c47d779 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,33 @@ +/* global hexo */ + +'use strict'; + +hexo.config.server = Object.assign({ + port: 4000, + log: false, + // `undefined` uses Node's default (try `::` with fallback to `0.0.0.0`) + ip: undefined, + compress: false, + header: true +}, hexo.config.server); + +hexo.extend.console.register('server', 'Start the server.', { + desc: 'Start the server and watch for file changes.', + options: [ + {name: '-i, --ip', desc: 'Override the default server IP. Bind to all IP address by default.'}, + {name: '-p, --port', desc: 'Override the default port.'}, + {name: '-s, --static', desc: 'Only serve static files.'}, + {name: '-l, --log [format]', desc: 'Enable logger. Override log format.'}, + {name: '-o, --open', desc: 'Immediately open the server url in your default web browser.'}, + {name: '-c, --cert [path]', desc: 'SSL certificate path.'}, + {name: '-ck, --key [path]', desc: 'SSL private certificate path.'}, + {name: '-h, --ssl', desc: 'Enable SSL localhost. If --cert and --key is present, ssl will enabled automatically. If --cert and --key is not present, but --ssl is preset, default certificate will be applied.'} + ] +}, require('./server')); + +hexo.extend.filter.register('server_middleware', require('./middlewares/header')); +hexo.extend.filter.register('server_middleware', require('./middlewares/gzip')); +hexo.extend.filter.register('server_middleware', require('./middlewares/logger')); +hexo.extend.filter.register('server_middleware', require('./middlewares/route')); +hexo.extend.filter.register('server_middleware', require('./middlewares/static')); +hexo.extend.filter.register('server_middleware', require('./middlewares/redirect')); diff --git a/lib/server-http.js b/lib/server-http.js new file mode 100644 index 0000000..588ed0e --- /dev/null +++ b/lib/server-http.js @@ -0,0 +1,80 @@ +'use strict'; + +const connect = require('connect'); +const http = require('http'); +const { underline } = require('picocolors'); +const Promise = require('bluebird'); +const open = require('open'); +const net = require('net'); + +module.exports = function(args) { + const app = connect(); + const { config } = this; + const ip = args.i || args.ip || config.server.ip || undefined; + const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; + const { root } = config; + + return checkPort(ip, port).then(() => this.extend.filter.exec('server_middleware', app, {context: this})).then(() => { + if (args.s || args.static) { + return this.load(); + } + + return this.watch(); + }).then(() => startServer(http.createServer(app), port, ip)).then(server => { + const addr = server.address(); + const addrString = formatAddress(ip || addr.address, addr.port, root); + + this.log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(addrString)); + this.emit('server'); + + if (args.o || args.open) { + open(addrString); + } + + return server; + }).catch(err => { + switch (err.code) { + case 'EADDRINUSE': + this.log.fatal(`Port ${port} has been used. Try other port instead.`); + break; + + case 'EACCES': + this.log.fatal(`Permission denied. You can't use port ${port}.`); + break; + } + + this.unwatch(); + throw err; + }); +}; + +function startServer(server, port, ip) { + return new Promise((resolve, reject) => { + server.listen(port, ip, resolve); + server.on('error', reject); + }).then(() => server); +} + +function checkPort(ip, port) { + if (port > 65535 || port < 1) { + return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); + } + + const server = net.createServer(); + + return new Promise((resolve, reject) => { + server.once('error', reject); + server.once('listening', resolve); + server.listen(port, ip); + }).then(() => { server.close(); }); +} + +function formatAddress(ip, port, root) { + let hostname = ip; + if (ip === '0.0.0.0' || ip === '::') { + hostname = 'localhost'; + } + + const path = root.startsWith('/') ? root : `/${root}`; + return new URL(`http://${hostname}:${port}${path}`).toString(); +} diff --git a/lib/server-ssl.js b/lib/server-ssl.js new file mode 100644 index 0000000..563888f --- /dev/null +++ b/lib/server-ssl.js @@ -0,0 +1,88 @@ +'use strict'; + +const connect = require('connect'); +const https = require('https'); +const fs = require('fs'); +const { underline } = require('picocolors'); +const Promise = require('bluebird'); +const open = require('open'); +const net = require('net'); + +module.exports = function(args) { + const app = connect(); + const { config } = this; + const ip = args.i || args.ip || config.server.ip || undefined; + const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; + const { root } = config; + + // SSL certificates path + const sslOptions = { + key: fs.readFileSync(args.key), + cert: fs.readFileSync(args.cert) + }; + + return checkPort(ip, port).then(() => this.extend.filter.exec('server_middleware', app, { context: this })).then(() => { + if (args.s || args.static) { + return this.load(); + } + + return this.watch(); + }).then(() => startServer(https.createServer(sslOptions, app), port, ip)).then(server => { // Create HTTPS server + const addr = server.address(); + const addrString = formatAddress(ip || addr.address, addr.port, root, true); // Use https + + this.log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(addrString)); + this.emit('server'); + + if (args.o || args.open) { + open(addrString); + } + + return server; + }).catch(err => { + switch (err.code) { + case 'EADDRINUSE': + this.log.fatal(`Port ${port} has been used. Try another port instead.`); + break; + + case 'EACCES': + this.log.fatal(`Permission denied. You can't use port ${port}.`); + break; + } + + this.unwatch(); + throw err; + }); +}; + +function startServer(server, port, ip) { + return new Promise((resolve, reject) => { + server.listen(port, ip, resolve); + server.on('error', reject); + }).then(() => server); +} + +function checkPort(ip, port) { + if (port > 65535 || port < 1) { + return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); + } + + const server = net.createServer(); + + return new Promise((resolve, reject) => { + server.once('error', reject); + server.once('listening', resolve); + server.listen(port, ip); + }).then(() => { server.close(); }); +} + +function formatAddress(ip, port, root, useHttps = false) { + let hostname = ip; + if (ip === '0.0.0.0' || ip === '::') { + hostname = 'localhost'; + } + + const protocol = useHttps ? 'https' : 'http'; // Change protocol based on HTTPS or HTTP + const path = root.startsWith('/') ? root : `/${root}`; + return new URL(`${protocol}://${hostname}:${port}${path}`).toString(); +} diff --git a/lib/server.js b/lib/server.js index 588ed0e..e973d1b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,80 +1,14 @@ -'use strict'; - -const connect = require('connect'); -const http = require('http'); -const { underline } = require('picocolors'); -const Promise = require('bluebird'); -const open = require('open'); -const net = require('net'); +const path = require('path'); +const serverSSL = require('./server-ssl'); +const serverHTTP = require('./server-http'); +/** + * @param {Record} args + */ module.exports = function(args) { - const app = connect(); - const { config } = this; - const ip = args.i || args.ip || config.server.ip || undefined; - const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; - const { root } = config; - - return checkPort(ip, port).then(() => this.extend.filter.exec('server_middleware', app, {context: this})).then(() => { - if (args.s || args.static) { - return this.load(); - } - - return this.watch(); - }).then(() => startServer(http.createServer(app), port, ip)).then(server => { - const addr = server.address(); - const addrString = formatAddress(ip || addr.address, addr.port, root); - - this.log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(addrString)); - this.emit('server'); - - if (args.o || args.open) { - open(addrString); - } - - return server; - }).catch(err => { - switch (err.code) { - case 'EADDRINUSE': - this.log.fatal(`Port ${port} has been used. Try other port instead.`); - break; - - case 'EACCES': - this.log.fatal(`Permission denied. You can't use port ${port}.`); - break; - } - - this.unwatch(); - throw err; - }); -}; - -function startServer(server, port, ip) { - return new Promise((resolve, reject) => { - server.listen(port, ip, resolve); - server.on('error', reject); - }).then(() => server); -} - -function checkPort(ip, port) { - if (port > 65535 || port < 1) { - return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); - } - - const server = net.createServer(); - - return new Promise((resolve, reject) => { - server.once('error', reject); - server.once('listening', resolve); - server.listen(port, ip); - }).then(() => { server.close(); }); -} - -function formatAddress(ip, port, root) { - let hostname = ip; - if (ip === '0.0.0.0' || ip === '::') { - hostname = 'localhost'; + const {cert = path.join(__dirname, '..', 'certificates/localhost.crt'), key = path.join(__dirname, '..', 'certificates/localhost.key'), ssl = false} = args; + if (ssl || ('cert' in args && 'key' in args)) { + return serverSSL.bind(this)(Object.assign(args, {key, cert})); } - - const path = root.startsWith('/') ? root : `/${root}`; - return new URL(`http://${hostname}:${port}${path}`).toString(); -} + return serverHTTP.bind(this)(args); +}; diff --git a/package.json b/package.json index 73ecbdd..2610d4f 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "hexo-server", "version": "3.0.0", "description": "Server module of Hexo.", - "main": "index", + "main": "lib/index.js", "scripts": { "eslint": "eslint .", "test": "mocha test/index.js", @@ -13,7 +13,8 @@ }, "files": [ "index.js", - "lib/" + "lib/", + "certificates" ], "repository": "hexojs/hexo-server", "homepage": "https://hexo.io/", From 8e54b1403aebe5d667f2325660df1d522eefa8a3 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 8 Oct 2024 17:28:17 +0700 Subject: [PATCH 02/18] feat(docs): tutorial generate certificate --- certificates/readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 certificates/readme.md diff --git a/certificates/readme.md b/certificates/readme.md new file mode 100644 index 0000000..2f7400f --- /dev/null +++ b/certificates/readme.md @@ -0,0 +1,11 @@ +## How to generate self-certificate + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt +``` + +## With custom config + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config openssl.cnf +``` \ No newline at end of file From 3264480a60a8f3cdec27f7898a7d32f102132ccc Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 8 Oct 2024 17:33:43 +0700 Subject: [PATCH 03/18] fix: short argument `-c` and `-ck` parser --- lib/server.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/server.js b/lib/server.js index e973d1b..21d7f94 100644 --- a/lib/server.js +++ b/lib/server.js @@ -6,9 +6,11 @@ const serverHTTP = require('./server-http'); * @param {Record} args */ module.exports = function(args) { - const {cert = path.join(__dirname, '..', 'certificates/localhost.crt'), key = path.join(__dirname, '..', 'certificates/localhost.key'), ssl = false} = args; - if (ssl || ('cert' in args && 'key' in args)) { - return serverSSL.bind(this)(Object.assign(args, {key, cert})); + const key = args.ck || args.key || path.join(__dirname, '..', 'certificates/localhost.key'); + const cert = args.c || args.cert || path.join(__dirname, '..', 'certificates/localhost.crt'); + const ssl = args.h || args.ssl || false; + if (ssl || ('c' in args && 'ck' in args) || ('cert' in args && 'key' in args)) { + return serverSSL.bind(this)(Object.assign(args, { key, cert })); } return serverHTTP.bind(this)(args); }; From b20f8bf4d4b909da9cc6c9c3f30812e6546770c4 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 8 Oct 2024 17:37:10 +0700 Subject: [PATCH 04/18] fix: `certificates` folder not bundled in tarball --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2610d4f..ebcafcb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "files": [ "index.js", "lib/", - "certificates" + "certificates/" ], "repository": "hexojs/hexo-server", "homepage": "https://hexo.io/", From 8815f0c5b7020cd2c13e877a1dc1695f3f2f4deb Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 29 Apr 2025 08:17:31 +0700 Subject: [PATCH 05/18] fix: fail to bind ipv6 --- lib/server-http.js | 5 ++++- lib/server-ssl.js | 8 ++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/server-http.js b/lib/server-http.js index 588ed0e..9488a5b 100644 --- a/lib/server-http.js +++ b/lib/server-http.js @@ -74,7 +74,10 @@ function formatAddress(ip, port, root) { if (ip === '0.0.0.0' || ip === '::') { hostname = 'localhost'; } - + // Fix IPV6 + if (hostname.includes(':')) { + hostname = `[${hostname}]`; + } const path = root.startsWith('/') ? root : `/${root}`; return new URL(`http://${hostname}:${port}${path}`).toString(); } diff --git a/lib/server-ssl.js b/lib/server-ssl.js index 563888f..47acfe1 100644 --- a/lib/server-ssl.js +++ b/lib/server-ssl.js @@ -81,8 +81,12 @@ function formatAddress(ip, port, root, useHttps = false) { if (ip === '0.0.0.0' || ip === '::') { hostname = 'localhost'; } - - const protocol = useHttps ? 'https' : 'http'; // Change protocol based on HTTPS or HTTP + // Fix IPV6 + if (hostname.includes(':')) { + hostname = `[${hostname}]`; + } + // Change protocol based on HTTPS or HTTP + const protocol = useHttps ? 'https' : 'http'; const path = root.startsWith('/') ? root : `/${root}`; return new URL(`${protocol}://${hostname}:${port}${path}`).toString(); } From 96b5f0dd7ee8e5383c5d7da4563ddff0f9fa789f Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 29 Apr 2025 08:24:41 +0700 Subject: [PATCH 06/18] chore: enable strict mode --- lib/server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/server.js b/lib/server.js index 21d7f94..7507b2b 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,3 +1,5 @@ +'use strict'; + const path = require('path'); const serverSSL = require('./server-ssl'); const serverHTTP = require('./server-http'); From c80f30dd29c05a102cd9b8c411ee080f6e0cad4c Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 29 Apr 2025 08:28:02 +0700 Subject: [PATCH 07/18] docs: add JSDoc comments for HTTP and HTTPS server functions --- lib/server-http.js | 15 +++++++++++++++ lib/server-ssl.js | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/lib/server-http.js b/lib/server-http.js index 9488a5b..9eba0b1 100644 --- a/lib/server-http.js +++ b/lib/server-http.js @@ -7,6 +7,21 @@ const Promise = require('bluebird'); const open = require('open'); const net = require('net'); +/** + * Starts a local HTTP server for Hexo with optional live reload or static serving. + * + * @this {import('hexo')} + * @param {Object} args - The CLI arguments. + * @param {string} [args.i] - IP address to bind. + * @param {string} [args.ip] - Alternative IP address to bind. + * @param {string|number} [args.p] - Port number to use. + * @param {string|number} [args.port] - Alternative port number. + * @param {boolean} [args.s] - Serve static files only. + * @param {boolean} [args.static] - Alternative flag to serve static files only. + * @param {boolean} [args.o] - Open the site automatically in a browser. + * @param {boolean} [args.open] - Alternative flag to open the site automatically. + * @returns {Promise} The created HTTP server. + */ module.exports = function(args) { const app = connect(); const { config } = this; diff --git a/lib/server-ssl.js b/lib/server-ssl.js index 47acfe1..0214ed0 100644 --- a/lib/server-ssl.js +++ b/lib/server-ssl.js @@ -8,6 +8,23 @@ const Promise = require('bluebird'); const open = require('open'); const net = require('net'); +/** + * Starts a local HTTPS server for Hexo with optional live reload or static serving. + * + * @this {import('hexo')} + * @param {Object} args - The CLI arguments. + * @param {string} [args.i] - IP address to bind. + * @param {string} [args.ip] - Alternative IP address to bind. + * @param {string|number} [args.p] - Port number to use. + * @param {string|number} [args.port] - Alternative port number. + * @param {string} args.key - Path to the SSL private key file. + * @param {string} args.cert - Path to the SSL certificate file. + * @param {boolean} [args.s] - Serve static files only. + * @param {boolean} [args.static] - Alternative flag to serve static files only. + * @param {boolean} [args.o] - Open the site automatically in a browser. + * @param {boolean} [args.open] - Alternative flag to open the site automatically. + * @returns {Promise} The created HTTPS server. + */ module.exports = function(args) { const app = connect(); const { config } = this; From f18cc7f089ec41baf52600d4c7466bb0cb4a55c6 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 29 Apr 2025 09:34:00 +0700 Subject: [PATCH 08/18] refactor: use `mkcert` to generate self-signed certificate --- README.md | 69 +++++++++++- certificates/localhost.crt | 23 ---- certificates/localhost.key | 28 ----- certificates/openssl.cnf | 30 ------ certificates/readme.md | 11 -- lib/mkcert.js | 210 +++++++++++++++++++++++++++++++++++++ lib/server.js | 8 +- 7 files changed, 281 insertions(+), 98 deletions(-) delete mode 100644 certificates/localhost.crt delete mode 100644 certificates/localhost.key delete mode 100644 certificates/openssl.cnf delete mode 100644 certificates/readme.md create mode 100644 lib/mkcert.js diff --git a/README.md b/README.md index 8211065..5fc07eb 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ Server module for [Hexo]. ## Installation -``` bash +```bash $ npm install hexo-server --save ``` ## Usage -``` bash +```bash $ hexo server ``` @@ -31,7 +31,7 @@ Option | Description | Default ## Options -``` yaml +```yaml server: port: 4000 log: false @@ -54,6 +54,69 @@ server: - **header**: Add `X-Powered-By: Hexo` header - **serveStatic**: Extra options passed to [serve-static](https://github.com/expressjs/serve-static#options) +## Generate self-certificate + +You can build your own OpenSSL from the official source: [https://openssl-library.org/source/](https://openssl-library.org/source/). + +### For Windows Users +You can download precompiled OpenSSL binaries for Windows from trusted sources like: +- [https://slproweb.com/products/Win32OpenSSL.html](https://slproweb.com/products/Win32OpenSSL.html) + +Make sure to install the version matching your system architecture (32-bit or 64-bit). + +Once installed, you can generate a self-signed certificate using the command line: + +### Default config + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt +``` + +### Custom config + +```bash +openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config openssl.cnf +``` + +### `openssl.cnf` contents + +```conf +[ req ] +default_bits = 2048 +distinguished_name = req_distinguished_name +req_extensions = req_ext + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = ID +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = East Java +localityName = Locality Name (eg, city) +localityName_default = Surabaya +organizationName = Organization Name (eg, company) +organizationName_default = WMI +organizationalUnitName = Organizational Unit Name (eg, section) +organizationalUnitName_default = Developer +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_default = dev.webmanajemen.com +commonName_max = 64 +emailAddress = Email Address +emailAddress_default = dimaslanjaka@gmail.com + +[ req_ext ] +subjectAltName = @alt_names + +[ alt_names ] +DNS.1 = dev.webmanajemen.com +DNS.2 = localhost +DNS.3 = 192.168.1.75 +DNS.4 = 127.0.0.1 +``` + +#### description + +- `alt_names` is your dev/localhost domain. (set on your `hosts` file) + ## License MIT diff --git a/certificates/localhost.crt b/certificates/localhost.crt deleted file mode 100644 index d2b9db6..0000000 --- a/certificates/localhost.crt +++ /dev/null @@ -1,23 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDwTCCAqkCFEXMrQ4TmdoCjzJOwz9otI85kQ6tMA0GCSqGSIb3DQEBCwUAMIGc -MQswCQYDVQQGEwJJRDESMBAGA1UECAwJRWFzdCBKYXZhMREwDwYDVQQHDAhTdXJh -YmF5YTEMMAoGA1UECgwDV01JMRIwEAYDVQQLDAlEZXZlbG9wZXIxHTAbBgNVBAMM -FGRldi53ZWJtYW5hamVtZW4uY29tMSUwIwYJKoZIhvcNAQkBFhZkaW1hc2xhbmph -a2FAZ21haWwuY29tMB4XDTI0MDcyNjA5MDExN1oXDTI1MDcyNjA5MDExN1owgZwx -CzAJBgNVBAYTAklEMRIwEAYDVQQIDAlFYXN0IEphdmExETAPBgNVBAcMCFN1cmFi -YXlhMQwwCgYDVQQKDANXTUkxEjAQBgNVBAsMCURldmVsb3BlcjEdMBsGA1UEAwwU -ZGV2LndlYm1hbmFqZW1lbi5jb20xJTAjBgkqhkiG9w0BCQEWFmRpbWFzbGFuamFr -YUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqL+4K -xiNBbMn1ssJSsHhB4TfhgcQ4tR9ov6SoVRvnv+50qHvY9mS/ksRCuWIKAC2e76nv -iV0zUY7c2ycBsTo7BTCm8KPL5TfLkvykg6m/BFPkR5wBDMA8jDpiZ1L+rE8ttXF7 -LxyYToSEjezOV92ivT+2MA3LwUoUkTWmLdinuQSfQN2gDzNCGFjqF1GDEwjiGiz9 -xVV7BmhnPMfhlt1jkE53nzSaYzjAUTYhSvw8hAWPNa+o90E1jJBC1T6c0AQKJ+I/ -rleZPAosMQdZvbokccwQXAF0uvxQ/gYu5TpB3gKp6ntjXmsqtqE837Egok4NF6Lx -b2sgHi9+7UtLbqS/AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEpoBcHPiC/IZP/2 -jrf+qOWY51graP0lpi5NJBqcokbhmD1u3Ah7/oNgUqn2y4VCjQaM2lhNttjnKoWr -UYdVbzkZAFTtNDLbgNQgMRhN9J9J4+UqtrQ6sMqAENYO0ZWsx+Nth7hE1l4nsa0E -hCPtnnAgACCtS1KHiuDddkQ/gagt52Uf5QkdIGpKO6VEmv7SHkomiub9WUgT4kRK -HzBGup2zNegZ4HznCLhQtr2EqDaeBekYNRSfgF6G756QRFbnGnC1r362yi2pycFa -28I3tqDBN0Yr116urzYIFAXX1b/fkIEo8Om6jl7S+QRkDQBh/dvTZNADplJsvzvx -XRPbQp0= ------END CERTIFICATE----- diff --git a/certificates/localhost.key b/certificates/localhost.key deleted file mode 100644 index 6dfde59..0000000 --- a/certificates/localhost.key +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCqL+4KxiNBbMn1 -ssJSsHhB4TfhgcQ4tR9ov6SoVRvnv+50qHvY9mS/ksRCuWIKAC2e76nviV0zUY7c -2ycBsTo7BTCm8KPL5TfLkvykg6m/BFPkR5wBDMA8jDpiZ1L+rE8ttXF7LxyYToSE -jezOV92ivT+2MA3LwUoUkTWmLdinuQSfQN2gDzNCGFjqF1GDEwjiGiz9xVV7Bmhn -PMfhlt1jkE53nzSaYzjAUTYhSvw8hAWPNa+o90E1jJBC1T6c0AQKJ+I/rleZPAos -MQdZvbokccwQXAF0uvxQ/gYu5TpB3gKp6ntjXmsqtqE837Egok4NF6Lxb2sgHi9+ -7UtLbqS/AgMBAAECggEAGHaYTJMrqTFmnHtMJI+6UZn6qt844f/jGm8Fz66gOsFj -mQBJASh11fXWYVL9jTt2U1TXBBgmPgS4uPWFl2Au4yH9WtnXaa0yxoAD8e/9G/iW -yIcknSAEmA9+Kvv4OaRyIztkKxVcEmai9Nzjz8tgkA6NFHa23PWVXPx8jj0j2gPp -oPCEE2QL2fy0Mi6R6LIn5d85zsSn8TrZNdmvkMUxPcaSYyg2oxZ6J8/9Mn4oE9e5 -VmIt02TnN8q14KgixkcUQeI9JtgHwIZBP+jZIb1zx4C/8PXVs7CkZYifY518h/GM -4/w6AKJt3BZObLUwHkvucSTSVF2p2NdJZNN6SwX9QQKBgQDmgw8l1JYe2VtGfN+F -OdE8PuM8u3TO+r9POMK28gAC62VRoxPjfeNsVXTX2piliAnRSg+6nakn1vf8l9MP -DZaYj8jDMXzGU/vzvnzjfwyvjENmLCc4vkbu57gIOrD601DL/CqBYeBPwQGYxvSC -GE3UUXIOSOQ+VG58YDXrNfBgwQKBgQC9AU0DvL2tBZOHxTrWzyU4+BP8oych6q+u -n7QPNiQ+UvGMno8LngLXnoCnMz5T4PtAl0iZb51S+YTyXLQ8noqQEeJ4OivKS5hw -X2FBf+J/u4jrMQyIpcI34XCTtSl4EOQ7UxrxygtZIFugsX8PXFphKjo2wDaMBBV9 -cl0f6SLlfwKBgQCHHcVwSFciiAevnpyqjARwivBJ9ht3A5XGCyBfeiS1kWOXYb7T -t0PqiYDu0cxuIvqWOhJUMfwoRSKhZiEqDq36iTWF7OkVm77w1fSAqUU3VUFgj3sC -EM6lVSATesuoitsuZoZHxqZkOV8FPYGvDC36yS7Q3rsjKfyFXSPd1oUrQQKBgE/C -4cZm+0CuLsFIOXl3d4TgJEckbxpAGR2/ZdRZi9gFVsx6CXHkn9xwlmh5Fp99PWrX -rRqbYLAofrNs7d77JQyBj6ofGmXHmzApADkNB//Rm4ltbJWqJhlA+SpMdJCnyDlE -7AUHt9xH7IMXBMDtv3JryJ9cZGiYPJ1xCt2xnDlvAoGAQobmLEpUALINBBAtxRTz -OFl6J611TcWzrnhnr8tXmsBifirXg6usCn1HTLMP+TIfuQhpVLt7r7SbS5Ok/az4 -phsIfpfi1momli4uBwqr6ZD6z2bNeoxOvBa1XFCAzMwZ+y5Ylb0ReHkgNHibLF8A -69lB9UMeoSpe0O2SoqfQiPg= ------END PRIVATE KEY----- diff --git a/certificates/openssl.cnf b/certificates/openssl.cnf deleted file mode 100644 index 90260ba..0000000 --- a/certificates/openssl.cnf +++ /dev/null @@ -1,30 +0,0 @@ -[ req ] -default_bits = 2048 -distinguished_name = req_distinguished_name -req_extensions = req_ext - -[ req_distinguished_name ] -countryName = Country Name (2 letter code) -countryName_default = ID -stateOrProvinceName = State or Province Name (full name) -stateOrProvinceName_default = East Java -localityName = Locality Name (eg, city) -localityName_default = Surabaya -organizationName = Organization Name (eg, company) -organizationName_default = WMI -organizationalUnitName = Organizational Unit Name (eg, section) -organizationalUnitName_default = Developer -commonName = Common Name (e.g. server FQDN or YOUR name) -commonName_default = dev.webmanajemen.com -commonName_max = 64 -emailAddress = Email Address -emailAddress_default = dimaslanjaka@gmail.com - -[ req_ext ] -subjectAltName = @alt_names - -[ alt_names ] -DNS.1 = dev.webmanajemen.com -DNS.2 = localhost -DNS.3 = 192.168.1.75 -DNS.3 = 127.0.0.1 diff --git a/certificates/readme.md b/certificates/readme.md deleted file mode 100644 index 2f7400f..0000000 --- a/certificates/readme.md +++ /dev/null @@ -1,11 +0,0 @@ -## How to generate self-certificate - -```bash -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -``` - -## With custom config - -```bash -openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config openssl.cnf -``` \ No newline at end of file diff --git a/lib/mkcert.js b/lib/mkcert.js new file mode 100644 index 0000000..7077519 --- /dev/null +++ b/lib/mkcert.js @@ -0,0 +1,210 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const { X509Certificate, createPrivateKey } = require('crypto'); +const { execSync } = require('child_process'); +const Log = console; +const MKCERT_VERSION = 'v1.4.4'; +const cacheDirectory = path.join(process.cwd(), 'node_modules/.cache/hexo-server-certificates'); +const keyPath = path.join(cacheDirectory, 'localhost-key.pem'); +const certPath = path.join(cacheDirectory, 'localhost.pem'); + +/** + * Returns the platform-specific mkcert binary name based on OS and architecture. + * + * @returns {string} The name of the mkcert binary file. + * @throws {Error} If the platform is not supported. + */ +function getBinaryName() { + const platform = process.platform; + const arch = process.arch === 'x64' ? 'amd64' : process.arch; + + if (platform === 'win32') { + return `mkcert-${MKCERT_VERSION}-windows-${arch}.exe`; + } + if (platform === 'darwin') { + return `mkcert-${MKCERT_VERSION}-darwin-${arch}`; + } + if (platform === 'linux') { + return `mkcert-${MKCERT_VERSION}-linux-${arch}`; + } + + throw new Error(`Unsupported platform: ${platform}`); +} + +/** + * Downloads the mkcert binary for the current platform and architecture. + * + * If the binary already exists in the cache directory, it returns the cached path. + * Otherwise, it downloads the binary, saves it to disk, sets executable permissions, + * and returns the binary path. + * + * @async + * @returns {Promise} The path to the downloaded or cached mkcert binary, or undefined if an error occurs. + */ +async function downloadBinary() { + try { + const binaryName = getBinaryName(); + const binaryPath = path.join(cacheDirectory, binaryName); + const downloadUrl = `https://github.com/FiloSottile/mkcert/releases/download/${MKCERT_VERSION}/${binaryName}`; + + // Fetch remote file size first + const headResponse = await fetch(downloadUrl, { method: 'HEAD' }); + + if (!headResponse.ok) { + throw new Error(`Failed to fetch file header. Status: ${headResponse.status}`); + } + + const remoteFileSize = parseInt(headResponse.headers.get('content-length'), 10); + + if (fs.existsSync(binaryPath)) { + const localStats = await fs.promises.stat(binaryPath); + // Fix file corruption + if (localStats.size === remoteFileSize) { + Log.info('Local mkcert binary is up-to-date, skipping download.'); + return binaryPath; + } + Log.info('Local mkcert binary size mismatch, re-downloading...'); + } else { + await fs.promises.mkdir(cacheDirectory, { recursive: true }); + } + + Log.info('Downloading mkcert package...'); + + const response = await fetch(downloadUrl); + + if (!response.ok || !response.body) { + throw new Error(`Download failed with status ${response.status}`); + } + + Log.info('Download response was successful, writing to disk'); + + const binaryWriteStream = fs.createWriteStream(binaryPath); + + await response.body.pipeTo( + new WritableStream({ + write(chunk) { + return new Promise((resolve, reject) => { + binaryWriteStream.write(chunk, error => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); + }, + close() { + return new Promise((resolve, reject) => { + binaryWriteStream.close(error => { + if (error) { + reject(error); + return; + } + resolve(); + }); + }); + } + }) + ); + + await fs.promises.chmod(binaryPath, 0o755); + + return binaryPath; + } catch (err) { + Log.error('Error downloading mkcert:', err); + throw err; // Important to rethrow if you want callers to know the download failed + } +} + +/** + * @typedef {Object} SelfSignedCertificate + * @property {string} key - Path to the generated private key file. + * @property {string} cert - Path to the generated certificate file. + * @property {string} rootCA - Path to the root Certificate Authority (CA) certificate. + */ + +/** + * Creates a self-signed SSL certificate using mkcert. + * + * @async + * @param {string|string[]} [host] - Optional additional host to include in the certificate. + * @returns {Promise} The paths to key, cert, and rootCA, or undefined on error. + */ +async function createSelfSignedCertificate(host) { + try { + const binaryPath = await downloadBinary(); + if (!binaryPath) throw new Error('missing mkcert binary'); + + await fs.promises.mkdir(cacheDirectory, { recursive: true }); + + // Ensure host is always an array + let hostList = []; + if (Array.isArray(host)) { + hostList = host; + } else if (typeof host === 'string' && host.length > 0) { + hostList = [host]; + } + + if (fs.existsSync(keyPath) && fs.existsSync(certPath)) { + const cert = new X509Certificate(fs.readFileSync(certPath)); + const key = fs.readFileSync(keyPath); + + // Check the certificate for each host + for (const h of hostList) { + if (!cert.checkHost(h)) { + Log.warn(`Certificate is not valid for host: ${h}`); + } else { + Log.info(`Certificate is valid for host: ${h}`); + } + } + + if (cert.checkPrivateKey(createPrivateKey(key))) { + Log.info('Using already generated self signed certificate'); + const caLocation = execSync(`"${binaryPath}" -CAROOT`).toString().trim(); + Log.info(`CA location at ${caLocation}`); + + return { + key: keyPath, + cert: certPath, + rootCA: `${caLocation}/rootCA.pem` + }; + } + } + + // Download mkcert binary + downloadBinary(); + + Log.info('Attempting to generate self signed certificate. This may prompt for your password'); + + const defaultHosts = ['localhost', '127.0.0.1', '::1']; + const allHosts = [...defaultHosts, ...hostList.filter(h => !defaultHosts.includes(h))]; + + // Install certificate for all hosts + execSync(`"${binaryPath}" -install -key-file "${keyPath}" -cert-file "${certPath}" ${allHosts.join(' ')}`, { + stdio: 'ignore' + }); + + const caLocation = execSync(`"${binaryPath}" -CAROOT`).toString().trim(); + + if (!fs.existsSync(keyPath) || !fs.existsSync(certPath)) { + throw new Error('Certificate files not found'); + } + + Log.info(`CA Root certificate created in ${caLocation}`); + Log.info(`Certificates created in ${cacheDirectory}`); + + return { + key: keyPath, + cert: certPath, + rootCA: `${caLocation}/rootCA.pem` + }; + } catch (err) { + Log.error('Failed to generate self-signed certificate. Falling back to http.', err); + } +} + +module.exports = { createSelfSignedCertificate, getBinaryName, downloadBinary, cacheDirectory, keyPath, certPath }; + +createSelfSignedCertificate(); diff --git a/lib/server.js b/lib/server.js index 7507b2b..fa0a076 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,17 +1,19 @@ 'use strict'; -const path = require('path'); const serverSSL = require('./server-ssl'); const serverHTTP = require('./server-http'); +const { createSelfSignedCertificate, keyPath, certPath } = require('./mkcert'); /** * @param {Record} args */ module.exports = function(args) { - const key = args.ck || args.key || path.join(__dirname, '..', 'certificates/localhost.key'); - const cert = args.c || args.cert || path.join(__dirname, '..', 'certificates/localhost.crt'); + const key = args.ck || args.key || keyPath; + const cert = args.c || args.cert || certPath; const ssl = args.h || args.ssl || false; if (ssl || ('c' in args && 'ck' in args) || ('cert' in args && 'key' in args)) { + // Attempt to create self-signed certificate + createSelfSignedCertificate(); return serverSSL.bind(this)(Object.assign(args, { key, cert })); } return serverHTTP.bind(this)(args); From 267d5d081124784f1ac76c392146b30eee1d101a Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 29 Apr 2025 09:34:21 +0700 Subject: [PATCH 09/18] refactor: remove immediate invocation of createSelfSignedCertificate --- lib/mkcert.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/mkcert.js b/lib/mkcert.js index 7077519..4fa0811 100644 --- a/lib/mkcert.js +++ b/lib/mkcert.js @@ -206,5 +206,3 @@ async function createSelfSignedCertificate(host) { } module.exports = { createSelfSignedCertificate, getBinaryName, downloadBinary, cacheDirectory, keyPath, certPath }; - -createSelfSignedCertificate(); From c7ae9ca108284d07e1e286724c4cf3bee73b14d9 Mon Sep 17 00:00:00 2001 From: Dimas Lanjaka Date: Sun, 8 Jun 2025 04:35:52 +0700 Subject: [PATCH 10/18] Update README.md Co-authored-by: Sukka Signed-off-by: Dimas Lanjaka --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5fc07eb..e06b1d4 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ Option | Description | Default `-s`, `--static` | Only serve static files. | `false` `-l`, `--log [format]` | Enable logger. Override log format. | `false` `-o`, `--open` | Immediately open the server url in your default web browser. | `false` -`-c`, `--cert` | Certificate path | `/certificates/localhost.crt` -`-ck`, `--key` | Certificate key path | `/certificates/localhost.key` +`-c`, `--cert` | Certificate path | `/path/to/cert.crt` +`-ck`, `--key` | Certificate key path | `/path/to/key.key` `h`, `--ssl` | Enable SSL localhost. If `--cert` and `--key` is present, ssl will enabled automatically. If `--cert` and `--key` is not present, but `--ssl` is preset, default certificate will be applied. | `false` ## Options From 49e40437534b759b5240e7af78129413c36d242d Mon Sep 17 00:00:00 2001 From: Dimas Lanjaka Date: Sun, 8 Jun 2025 04:36:23 +0700 Subject: [PATCH 11/18] Update README.md Co-authored-by: Sukka Signed-off-by: Dimas Lanjaka --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e06b1d4..97ab2c6 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Option | Description | Default `-o`, `--open` | Immediately open the server url in your default web browser. | `false` `-c`, `--cert` | Certificate path | `/path/to/cert.crt` `-ck`, `--key` | Certificate key path | `/path/to/key.key` -`h`, `--ssl` | Enable SSL localhost. If `--cert` and `--key` is present, ssl will enabled automatically. If `--cert` and `--key` is not present, but `--ssl` is preset, default certificate will be applied. | `false` +`--ssl` | Enable SSL localhost. If `--cert` and `--key` are present, SSL will be enabled automatically. If `--cert` and `--key` are not present, but `--ssl` is present, Hexo will automatically generate a self-signed certificate. | `false` ## Options From d567b31a2ad561bd40a67815ea83fe312e3defbd Mon Sep 17 00:00:00 2001 From: Dimas Lanjaka Date: Sun, 8 Jun 2025 04:38:28 +0700 Subject: [PATCH 12/18] Update lib/server.js Co-authored-by: Sukka Signed-off-by: Dimas Lanjaka --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index fa0a076..a572cb7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -14,7 +14,7 @@ module.exports = function(args) { if (ssl || ('c' in args && 'ck' in args) || ('cert' in args && 'key' in args)) { // Attempt to create self-signed certificate createSelfSignedCertificate(); - return serverSSL.bind(this)(Object.assign(args, { key, cert })); + return serverSSL.call(this, Object.assign(args, { key, cert })); } return serverHTTP.bind(this)(args); }; From c4e7a9d62b327f00f4c833c535aae51f224b99e7 Mon Sep 17 00:00:00 2001 From: Dimas Lanjaka Date: Sun, 8 Jun 2025 04:38:38 +0700 Subject: [PATCH 13/18] Update lib/server.js Co-authored-by: Sukka Signed-off-by: Dimas Lanjaka --- lib/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/server.js b/lib/server.js index a572cb7..0b295f3 100644 --- a/lib/server.js +++ b/lib/server.js @@ -16,5 +16,5 @@ module.exports = function(args) { createSelfSignedCertificate(); return serverSSL.call(this, Object.assign(args, { key, cert })); } - return serverHTTP.bind(this)(args); + return serverHTTP.call(this, args); }; From e56995e4a742ddd89938c81b714facd0ff99e2dc Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Wed, 18 Jun 2025 17:27:22 +0700 Subject: [PATCH 14/18] chore: separate shared server utils --- lib/server-http.js | 37 +----------------------- lib/server-ssl.js | 39 +------------------------ lib/server-utils.js | 70 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 74 deletions(-) create mode 100644 lib/server-utils.js diff --git a/lib/server-http.js b/lib/server-http.js index 9eba0b1..d84706f 100644 --- a/lib/server-http.js +++ b/lib/server-http.js @@ -3,9 +3,8 @@ const connect = require('connect'); const http = require('http'); const { underline } = require('picocolors'); -const Promise = require('bluebird'); const open = require('open'); -const net = require('net'); +const { checkPort, startServer, formatAddress } = require('./server-utils'); /** * Starts a local HTTP server for Hexo with optional live reload or static serving. @@ -62,37 +61,3 @@ module.exports = function(args) { throw err; }); }; - -function startServer(server, port, ip) { - return new Promise((resolve, reject) => { - server.listen(port, ip, resolve); - server.on('error', reject); - }).then(() => server); -} - -function checkPort(ip, port) { - if (port > 65535 || port < 1) { - return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); - } - - const server = net.createServer(); - - return new Promise((resolve, reject) => { - server.once('error', reject); - server.once('listening', resolve); - server.listen(port, ip); - }).then(() => { server.close(); }); -} - -function formatAddress(ip, port, root) { - let hostname = ip; - if (ip === '0.0.0.0' || ip === '::') { - hostname = 'localhost'; - } - // Fix IPV6 - if (hostname.includes(':')) { - hostname = `[${hostname}]`; - } - const path = root.startsWith('/') ? root : `/${root}`; - return new URL(`http://${hostname}:${port}${path}`).toString(); -} diff --git a/lib/server-ssl.js b/lib/server-ssl.js index 0214ed0..440752d 100644 --- a/lib/server-ssl.js +++ b/lib/server-ssl.js @@ -4,9 +4,8 @@ const connect = require('connect'); const https = require('https'); const fs = require('fs'); const { underline } = require('picocolors'); -const Promise = require('bluebird'); const open = require('open'); -const net = require('net'); +const { checkPort, startServer, formatAddress } = require('./server-utils'); /** * Starts a local HTTPS server for Hexo with optional live reload or static serving. @@ -71,39 +70,3 @@ module.exports = function(args) { throw err; }); }; - -function startServer(server, port, ip) { - return new Promise((resolve, reject) => { - server.listen(port, ip, resolve); - server.on('error', reject); - }).then(() => server); -} - -function checkPort(ip, port) { - if (port > 65535 || port < 1) { - return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); - } - - const server = net.createServer(); - - return new Promise((resolve, reject) => { - server.once('error', reject); - server.once('listening', resolve); - server.listen(port, ip); - }).then(() => { server.close(); }); -} - -function formatAddress(ip, port, root, useHttps = false) { - let hostname = ip; - if (ip === '0.0.0.0' || ip === '::') { - hostname = 'localhost'; - } - // Fix IPV6 - if (hostname.includes(':')) { - hostname = `[${hostname}]`; - } - // Change protocol based on HTTPS or HTTP - const protocol = useHttps ? 'https' : 'http'; - const path = root.startsWith('/') ? root : `/${root}`; - return new URL(`${protocol}://${hostname}:${port}${path}`).toString(); -} diff --git a/lib/server-utils.js b/lib/server-utils.js new file mode 100644 index 0000000..84cc15a --- /dev/null +++ b/lib/server-utils.js @@ -0,0 +1,70 @@ +'use strict'; + +const net = require('net'); +const Promise = require('bluebird'); + +/** + * Starts a given server and listens on the specified port and IP. + * + * @param {net.Server} server - The server instance to start. + * @param {number} port - The port number to listen on. + * @param {string} ip - The IP address to bind the server to. + * @returns {Promise} A Promise that resolves with the server once it's listening. + */ +function startServer(server, port, ip) { + return new Promise((resolve, reject) => { + server.listen(port, ip, resolve); + server.on('error', reject); + }).then(() => server); +} + +/** + * Checks whether a port can be used on the specified IP by attempting to bind a temporary server. + * + * @param {string} ip - The IP address to bind the test server to. + * @param {number} port - The port number to test. + * @returns {Promise} A Promise that resolves if the port is available, or rejects on error. + */ +function checkPort(ip, port) { + if (port > 65535 || port < 1) { + return Promise.reject(new RangeError(`Port number ${port} is invalid. Try a number between 1 and 65535.`)); + } + + const server = net.createServer(); + + return new Promise((resolve, reject) => { + server.once('error', reject); + server.once('listening', resolve); + server.listen(port, ip); + }).then(() => { server.close(); }); +} + +/** + * Formats a network address into a URL string. + * + * @param {string} ip - The IP address (e.g., '127.0.0.1', '::', '0.0.0.0'). + * @param {number} port - The port number. + * @param {string} root - The root path to append (e.g., '/', '/app'). + * @param {boolean} [useHttps=false] - Whether to use HTTPS instead of HTTP. + * @returns {string} The formatted URL. + */ +function formatAddress(ip, port, root, useHttps = false) { + let hostname = ip; + if (ip === '0.0.0.0' || ip === '::') { + hostname = 'localhost'; + } + // Fix IPV6 + if (hostname.includes(':')) { + hostname = `[${hostname}]`; + } + // Change protocol based on HTTPS or HTTP + const protocol = useHttps ? 'https' : 'http'; + const path = root.startsWith('/') ? root : `/${root}`; + return new URL(`${protocol}://${hostname}:${port}${path}`).toString(); +} + +module.exports = { + startServer, + checkPort, + formatAddress +}; From 46dc8764401e6fadefaed3e825b6a41cf41c8100 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Wed, 18 Jun 2025 17:30:12 +0700 Subject: [PATCH 15/18] chore: optimize codes --- lib/server-http.js | 58 +++++++++++++++++-------------------------- lib/server-ssl.js | 61 +++++++++++++++++----------------------------- lib/server.js | 18 +++++++++----- 3 files changed, 56 insertions(+), 81 deletions(-) diff --git a/lib/server-http.js b/lib/server-http.js index d84706f..c7a1556 100644 --- a/lib/server-http.js +++ b/lib/server-http.js @@ -10,54 +10,40 @@ const { checkPort, startServer, formatAddress } = require('./server-utils'); * Starts a local HTTP server for Hexo with optional live reload or static serving. * * @this {import('hexo')} - * @param {Object} args - The CLI arguments. - * @param {string} [args.i] - IP address to bind. - * @param {string} [args.ip] - Alternative IP address to bind. - * @param {string|number} [args.p] - Port number to use. - * @param {string|number} [args.port] - Alternative port number. - * @param {boolean} [args.s] - Serve static files only. - * @param {boolean} [args.static] - Alternative flag to serve static files only. - * @param {boolean} [args.o] - Open the site automatically in a browser. - * @param {boolean} [args.open] - Alternative flag to open the site automatically. - * @returns {Promise} The created HTTP server. + * @param {Object} args - CLI arguments. */ -module.exports = function(args) { +module.exports = async function(args) { const app = connect(); - const { config } = this; + const { config, extend, load, watch, log, emit, unwatch } = this; + const ip = args.i || args.ip || config.server.ip || undefined; const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; - const { root } = config; + const root = config.root; - return checkPort(ip, port).then(() => this.extend.filter.exec('server_middleware', app, {context: this})).then(() => { - if (args.s || args.static) { - return this.load(); - } + try { + await checkPort(ip, port); + await extend.filter.exec('server_middleware', app, { context: this }); - return this.watch(); - }).then(() => startServer(http.createServer(app), port, ip)).then(server => { + args.s || args.static ? await load() : await watch(); + + const server = await startServer(http.createServer(app), port, ip); const addr = server.address(); - const addrString = formatAddress(ip || addr.address, addr.port, root); + const url = formatAddress(ip || addr.address, addr.port, root); - this.log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(addrString)); - this.emit('server'); + log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(url)); + emit('server'); - if (args.o || args.open) { - open(addrString); - } + if (args.o || args.open) await open(url); return server; - }).catch(err => { - switch (err.code) { - case 'EADDRINUSE': - this.log.fatal(`Port ${port} has been used. Try other port instead.`); - break; - - case 'EACCES': - this.log.fatal(`Permission denied. You can't use port ${port}.`); - break; + } catch (err) { + if (err.code === 'EADDRINUSE') { + log.fatal(`Port ${port} has been used. Try another port instead.`); + } else if (err.code === 'EACCES') { + log.fatal(`Permission denied. You can't use port ${port}.`); } - this.unwatch(); + unwatch(); throw err; - }); + } }; diff --git a/lib/server-ssl.js b/lib/server-ssl.js index 440752d..8f395bc 100644 --- a/lib/server-ssl.js +++ b/lib/server-ssl.js @@ -11,62 +11,45 @@ const { checkPort, startServer, formatAddress } = require('./server-utils'); * Starts a local HTTPS server for Hexo with optional live reload or static serving. * * @this {import('hexo')} - * @param {Object} args - The CLI arguments. - * @param {string} [args.i] - IP address to bind. - * @param {string} [args.ip] - Alternative IP address to bind. - * @param {string|number} [args.p] - Port number to use. - * @param {string|number} [args.port] - Alternative port number. - * @param {string} args.key - Path to the SSL private key file. - * @param {string} args.cert - Path to the SSL certificate file. - * @param {boolean} [args.s] - Serve static files only. - * @param {boolean} [args.static] - Alternative flag to serve static files only. - * @param {boolean} [args.o] - Open the site automatically in a browser. - * @param {boolean} [args.open] - Alternative flag to open the site automatically. - * @returns {Promise} The created HTTPS server. + * @param {Object} args - CLI arguments. */ -module.exports = function(args) { +module.exports = async function(args) { const app = connect(); - const { config } = this; + const { config, extend, load, watch, log, emit, unwatch } = this; + const ip = args.i || args.ip || config.server.ip || undefined; const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; - const { root } = config; + const root = config.root; - // SSL certificates path const sslOptions = { key: fs.readFileSync(args.key), cert: fs.readFileSync(args.cert) }; - return checkPort(ip, port).then(() => this.extend.filter.exec('server_middleware', app, { context: this })).then(() => { - if (args.s || args.static) { - return this.load(); - } + try { + await checkPort(ip, port); + await extend.filter.exec('server_middleware', app, { context: this }); + + args.s || args.static ? await load() : await watch(); - return this.watch(); - }).then(() => startServer(https.createServer(sslOptions, app), port, ip)).then(server => { // Create HTTPS server + const server = await startServer(https.createServer(sslOptions, app), port, ip); const addr = server.address(); - const addrString = formatAddress(ip || addr.address, addr.port, root, true); // Use https + const url = formatAddress(ip || addr.address, addr.port, root, true); - this.log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(addrString)); - this.emit('server'); + log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(url)); + emit('server'); - if (args.o || args.open) { - open(addrString); - } + if (args.o || args.open) await open(url); return server; - }).catch(err => { - switch (err.code) { - case 'EADDRINUSE': - this.log.fatal(`Port ${port} has been used. Try another port instead.`); - break; - - case 'EACCES': - this.log.fatal(`Permission denied. You can't use port ${port}.`); - break; + } catch (err) { + if (err.code === 'EADDRINUSE') { + log.fatal(`Port ${port} has been used. Try another port instead.`); + } else if (err.code === 'EACCES') { + log.fatal(`Permission denied. You can't use port ${port}.`); } - this.unwatch(); + unwatch(); throw err; - }); + } }; diff --git a/lib/server.js b/lib/server.js index 0b295f3..44389cc 100644 --- a/lib/server.js +++ b/lib/server.js @@ -5,16 +5,22 @@ const serverHTTP = require('./server-http'); const { createSelfSignedCertificate, keyPath, certPath } = require('./mkcert'); /** + * Starts the appropriate server (HTTP or HTTPS) based on CLI args. + * * @param {Record} args */ module.exports = function(args) { - const key = args.ck || args.key || keyPath; - const cert = args.c || args.cert || certPath; - const ssl = args.h || args.ssl || false; - if (ssl || ('c' in args && 'ck' in args) || ('cert' in args && 'key' in args)) { - // Attempt to create self-signed certificate + const hasSSLArgs + = args.h || args.ssl + || (('ck' in args || 'key' in args) && ('c' in args || 'cert' in args)); + + if (hasSSLArgs) { + const key = args.ck || args.key || keyPath; + const cert = args.c || args.cert || certPath; + createSelfSignedCertificate(); - return serverSSL.call(this, Object.assign(args, { key, cert })); + return serverSSL.call(this, { ...args, key, cert }); } + return serverHTTP.call(this, args); }; From 01097ab66c90be77bac65c181d161cc00940406e Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Wed, 18 Jun 2025 17:34:40 +0700 Subject: [PATCH 16/18] refactor: consolidate server logic into server-core and remove server-http --- lib/{server-ssl.js => server-core.js} | 33 +++++++++++++----- lib/server-http.js | 49 --------------------------- lib/server.js | 7 ++-- 3 files changed, 28 insertions(+), 61 deletions(-) rename lib/{server-ssl.js => server-core.js} (58%) delete mode 100644 lib/server-http.js diff --git a/lib/server-ssl.js b/lib/server-core.js similarity index 58% rename from lib/server-ssl.js rename to lib/server-core.js index 8f395bc..ad0336c 100644 --- a/lib/server-ssl.js +++ b/lib/server-core.js @@ -1,6 +1,7 @@ 'use strict'; const connect = require('connect'); +const http = require('http'); const https = require('https'); const fs = require('fs'); const { underline } = require('picocolors'); @@ -8,23 +9,36 @@ const open = require('open'); const { checkPort, startServer, formatAddress } = require('./server-utils'); /** - * Starts a local HTTPS server for Hexo with optional live reload or static serving. + * Starts a local HTTP or HTTPS server for Hexo with optional live reload or static serving. * * @this {import('hexo')} * @param {Object} args - CLI arguments. + * @param {boolean} [args.https] - Use HTTPS if true. + * @param {string} [args.key] - Path to SSL key (required for HTTPS). + * @param {string} [args.cert] - Path to SSL cert (required for HTTPS). */ -module.exports = async function(args) { +const serverCore = async function(args) { const app = connect(); const { config, extend, load, watch, log, emit, unwatch } = this; const ip = args.i || args.ip || config.server.ip || undefined; const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; const root = config.root; + const useHttps = !!(args.https || args.key || args.cert); - const sslOptions = { - key: fs.readFileSync(args.key), - cert: fs.readFileSync(args.cert) - }; + let serverInstance; + if (useHttps) { + if (!args.key || !args.cert) { + throw new Error('SSL key and cert are required for HTTPS.'); + } + const sslOptions = { + key: fs.readFileSync(args.key), + cert: fs.readFileSync(args.cert) + }; + serverInstance = https.createServer(sslOptions, app); + } else { + serverInstance = http.createServer(app); + } try { await checkPort(ip, port); @@ -32,9 +46,9 @@ module.exports = async function(args) { args.s || args.static ? await load() : await watch(); - const server = await startServer(https.createServer(sslOptions, app), port, ip); + const server = await startServer(serverInstance, port, ip); const addr = server.address(); - const url = formatAddress(ip || addr.address, addr.port, root, true); + const url = formatAddress(ip || addr.address, addr.port, root, useHttps); log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(url)); emit('server'); @@ -53,3 +67,6 @@ module.exports = async function(args) { throw err; } }; + +module.exports = serverCore; +module.exports.serverCore = serverCore; // For backward compatibility diff --git a/lib/server-http.js b/lib/server-http.js deleted file mode 100644 index c7a1556..0000000 --- a/lib/server-http.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict'; - -const connect = require('connect'); -const http = require('http'); -const { underline } = require('picocolors'); -const open = require('open'); -const { checkPort, startServer, formatAddress } = require('./server-utils'); - -/** - * Starts a local HTTP server for Hexo with optional live reload or static serving. - * - * @this {import('hexo')} - * @param {Object} args - CLI arguments. - */ -module.exports = async function(args) { - const app = connect(); - const { config, extend, load, watch, log, emit, unwatch } = this; - - const ip = args.i || args.ip || config.server.ip || undefined; - const port = parseInt(args.p || args.port || config.server.port || process.env.port, 10) || 4000; - const root = config.root; - - try { - await checkPort(ip, port); - await extend.filter.exec('server_middleware', app, { context: this }); - - args.s || args.static ? await load() : await watch(); - - const server = await startServer(http.createServer(app), port, ip); - const addr = server.address(); - const url = formatAddress(ip || addr.address, addr.port, root); - - log.info('Hexo is running at %s . Press Ctrl+C to stop.', underline(url)); - emit('server'); - - if (args.o || args.open) await open(url); - - return server; - } catch (err) { - if (err.code === 'EADDRINUSE') { - log.fatal(`Port ${port} has been used. Try another port instead.`); - } else if (err.code === 'EACCES') { - log.fatal(`Permission denied. You can't use port ${port}.`); - } - - unwatch(); - throw err; - } -}; diff --git a/lib/server.js b/lib/server.js index 44389cc..86956ff 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,7 +1,6 @@ 'use strict'; -const serverSSL = require('./server-ssl'); -const serverHTTP = require('./server-http'); +const serverCore = require('./server-core'); const { createSelfSignedCertificate, keyPath, certPath } = require('./mkcert'); /** @@ -19,8 +18,8 @@ module.exports = function(args) { const cert = args.c || args.cert || certPath; createSelfSignedCertificate(); - return serverSSL.call(this, { ...args, key, cert }); + return serverCore.call(this, { ...args, key, cert, https: true }); } - return serverHTTP.call(this, args); + return serverCore.call(this, {...args, https: false }); }; From ac788b71442334dbbacd2daea7bf3490dea99f49 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Wed, 18 Jun 2025 17:37:15 +0700 Subject: [PATCH 17/18] refactor: change serverCore to a named async function for clarity --- lib/server-core.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/server-core.js b/lib/server-core.js index ad0336c..9967cf9 100644 --- a/lib/server-core.js +++ b/lib/server-core.js @@ -17,7 +17,7 @@ const { checkPort, startServer, formatAddress } = require('./server-utils'); * @param {string} [args.key] - Path to SSL key (required for HTTPS). * @param {string} [args.cert] - Path to SSL cert (required for HTTPS). */ -const serverCore = async function(args) { +async function serverCore(args) { const app = connect(); const { config, extend, load, watch, log, emit, unwatch } = this; @@ -66,7 +66,7 @@ const serverCore = async function(args) { unwatch(); throw err; } -}; +} module.exports = serverCore; module.exports.serverCore = serverCore; // For backward compatibility From de0b0351c3f75b332cffc7dad01fe78ad7dc8885 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Wed, 18 Jun 2025 17:40:01 +0700 Subject: [PATCH 18/18] docs: improve JSDoc comments for server function parameters and return type --- lib/server.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/server.js b/lib/server.js index 86956ff..39044ef 100644 --- a/lib/server.js +++ b/lib/server.js @@ -4,9 +4,17 @@ const serverCore = require('./server-core'); const { createSelfSignedCertificate, keyPath, certPath } = require('./mkcert'); /** - * Starts the appropriate server (HTTP or HTTPS) based on CLI args. + * Starts the appropriate server (HTTP or HTTPS) based on CLI arguments. * - * @param {Record} args + * @this {import('hexo')} + * @param {Record} args - CLI arguments. + * @param {boolean} [args.h] - Alias for `--https`, enables HTTPS mode. + * @param {boolean} [args.ssl] - Enables HTTPS mode. + * @param {string} [args.ck] - Alias for `--key`, path to SSL key. + * @param {string} [args.key] - Path to SSL key. + * @param {string} [args.c] - Alias for `--cert`, path to SSL certificate. + * @param {string} [args.cert] - Path to SSL certificate. + * @returns {Promise} The running HTTP/HTTPS server instance. */ module.exports = function(args) { const hasSSLArgs