diff --git a/package-lock.json b/package-lock.json index a493777..3fac8da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "applura", - "version": "1.1.1", + "version": "1.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "applura", - "version": "1.1.1", + "version": "1.1.3", "license": "MIT", "dependencies": { "bestzip": "^2.2.1", + "mkcert": "^3.2.0", "read": "^3.0.1" }, "bin": { @@ -1580,6 +1581,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/compress-commons": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", @@ -3074,6 +3083,21 @@ "node": "*" } }, + "node_modules/mkcert": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mkcert/-/mkcert-3.2.0.tgz", + "integrity": "sha512-026Eivq9RoOjOuLJGzbhGwXUAjBxRX11Z7Jbm4/7lqT/Av+XNy9SPrJte6+UpEt7i+W3e/HZYxQqlQcqXZWSzg==", + "dependencies": { + "commander": "^11.0.0", + "node-forge": "^1.3.1" + }, + "bin": { + "mkcert": "dist/cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3094,6 +3118,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index 61a2c48..d1de757 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ }, "dependencies": { "bestzip": "^2.2.1", + "mkcert": "^3.2.0", "read": "^3.0.1" } } diff --git a/src/commands/serve.js b/src/commands/serve.js new file mode 100644 index 0000000..0d100c9 --- /dev/null +++ b/src/commands/serve.js @@ -0,0 +1,76 @@ +import path from "node:path"; +import https from "node:https"; +import fs from "fs"; +import { createCA, createCert } from "mkcert"; +/** + * + * @param args + */ +export default async function serve(args) { + const PORT = 443; + + const MIME_TYPES = { + default: "application/octet-stream", + html: "text/html; charset=UTF-8", + js: "application/javascript", + css: "text/css", + png: "image/png", + jpg: "image/jpg", + gif: "image/gif", + ico: "image/x-icon", + svg: "image/svg+xml", + }; + + const STATIC_PATH = path.join(process.cwd(), args[0]); + + const toBool = [() => true, () => false]; + + const prepareFile = async (url) => { + const paths = [STATIC_PATH, url]; + if (url.endsWith("/")) paths.push("index.html"); + const filePath = path.join(...paths); + const pathTraversal = !filePath.startsWith(STATIC_PATH); + const exists = await fs.promises.access(filePath).then(...toBool); + const found = !pathTraversal && exists; + const streamPath = found ? filePath : STATIC_PATH + "/index.html"; + const ext = path.extname(streamPath).substring(1).toLowerCase(); + const stream = fs.createReadStream(streamPath); + return { found, ext, stream }; + }; + + const ca = await createCA({ + organization: "Hello CA", + countryCode: "UA", + state: "Bagmati", + locality: "Kathmandu", + validity: 365 + }); + + const cert = await createCert({ + ca: { key: ca.key, cert: ca.cert }, + domains: ["127.0.0.1", "local.applura.app"], + validity: 365 + }); + + const options = { + cert: cert.cert, + key: cert.key + } + const server = https + .createServer(options, async (req, res) => { + const file = await prepareFile(req.url); + const statusCode = file.found ? 200 : 404; + const mimeType = MIME_TYPES[file.ext] || MIME_TYPES.default; + res.writeHead(statusCode, { "Content-Type": mimeType }); + file.stream.pipe(res); + console.log(`${req.method} ${req.url} ${statusCode}`); + }); + + server.listen(PORT, () => { + console.log(`Server available at: \n https://127.0.0.1/ \n https://local.applura.app/`); + }); + + return new Promise((res) => { + server.on('close', res); + }) +} diff --git a/src/main.js b/src/main.js index 6599886..f061a61 100644 --- a/src/main.js +++ b/src/main.js @@ -14,6 +14,7 @@ import { } from "./routines/setup.js"; import deployKey from "./commands/deploy-key.js"; import deploy from "./commands/deploy.js"; +import serve from "./commands/serve.js"; import { getContext } from "./lib/context.js"; const commands = { @@ -21,6 +22,7 @@ const commands = { init: () => {}, "deploy-key": deployKey, deploy: deploy, + serve: serve, }; export default async function main(cwd, argv, stdStreams, env) {