From 3d4b4361f397271cacaeae2de1c26cdf9d3f7931 Mon Sep 17 00:00:00 2001 From: dimaslanjaka Date: Tue, 8 Oct 2024 17:17:17 +0700 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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/",