diff --git a/package.json b/package.json index 020e893..2b77596 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@root/walk": "^1.1.0", + "address": "^1.2.0", "commander": "^9.4.0", "dedent": "^0.7.0", "env-paths": "^3.0.0", @@ -41,6 +42,7 @@ "p-limit": "^4.0.0", "s-ago": "^2.2.0", "semver": "^7.3.7", + "serve-handler": "^6.1.3", "wrap-ansi": "^8.0.1", "yazl": "^2.5.1", "zod": "^3.18.0" @@ -51,6 +53,7 @@ "@types/lodash-es": "^4.17.6", "@types/node": "^18.7.3", "@types/semver": "^7.3.12", + "@types/serve-handler": "^6.1.1", "@types/wrap-ansi": "^8.0.1", "@types/yazl": "^2.4.2", "tsx": "^3.8.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5c923d..f65454d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,10 @@ specifiers: '@types/lodash-es': ^4.17.6 '@types/node': ^18.7.3 '@types/semver': ^7.3.12 + '@types/serve-handler': ^6.1.1 '@types/wrap-ansi': ^8.0.1 '@types/yazl': ^2.4.2 + address: ^1.2.0 commander: ^9.4.0 dedent: ^0.7.0 env-paths: ^3.0.0 @@ -27,6 +29,7 @@ specifiers: p-limit: ^4.0.0 s-ago: ^2.2.0 semver: ^7.3.7 + serve-handler: ^6.1.3 tsx: ^3.8.2 type-fest: ^2.18.0 typescript: ^4.7.4 @@ -36,6 +39,7 @@ specifiers: dependencies: '@root/walk': 1.1.0 + address: 1.2.0 commander: 9.4.0 dedent: 0.7.0 env-paths: 3.0.0 @@ -54,6 +58,7 @@ dependencies: p-limit: 4.0.0 s-ago: 2.2.0 semver: 7.3.7 + serve-handler: 6.1.3 wrap-ansi: 8.0.1 yazl: 2.5.1 zod: 3.18.0 @@ -64,6 +69,7 @@ devDependencies: '@types/lodash-es': 4.17.6 '@types/node': 18.7.3 '@types/semver': 7.3.12 + '@types/serve-handler': 6.1.1 '@types/wrap-ansi': 8.0.1 '@types/yazl': 2.4.2 tsx: 3.8.2 @@ -174,6 +180,12 @@ packages: resolution: {integrity: sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==} dev: true + /@types/serve-handler/6.1.1: + resolution: {integrity: sha512-bIwSmD+OV8w0t2e7EWsuQYlGoS1o5aEdVktgkXaa43Zm0qVWi21xaSRb3DQA1UXD+DJ5bRq1Rgu14ZczB+CjIQ==} + dependencies: + '@types/node': 18.7.3 + dev: true + /@types/wrap-ansi/8.0.1: resolution: {integrity: sha512-cjwgM6WWy9YakrQ36Pq0vg5XoNblVEaNq+/pHngKl4GyyDIxTeskPoG+tp4LsRk0lHrA4LaLJqlvYridi7mzlw==} dev: true @@ -184,6 +196,11 @@ packages: '@types/node': 18.7.3 dev: true + /address/1.2.0: + resolution: {integrity: sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==} + engines: {node: '>= 10.0.0'} + dev: false + /ansi-regex/6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} @@ -199,6 +216,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: false + /base64-js/1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} dev: false @@ -211,6 +232,13 @@ packages: readable-stream: 3.6.0 dev: false + /brace-expansion/1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: false + /buffer-crc32/0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} dev: false @@ -226,6 +254,11 @@ packages: ieee754: 1.2.1 dev: false + /bytes/3.0.0: + resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==} + engines: {node: '>= 0.8'} + dev: false + /cacheable-lookup/6.1.0: resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==} engines: {node: '>=10.6.0'} @@ -285,6 +318,15 @@ packages: json-buffer: 3.0.1 dev: false + /concat-map/0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false + + /content-disposition/0.5.2: + resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} + engines: {node: '>= 0.6'} + dev: false + /currently-unhandled/0.4.1: resolution: {integrity: sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==} engines: {node: '>=0.10.0'} @@ -559,6 +601,12 @@ packages: engines: {node: '>=12'} dev: false + /fast-url-parser/1.1.3: + resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + dependencies: + punycode: 1.4.1 + dev: false + /figures/5.0.0: resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} engines: {node: '>=14'} @@ -771,6 +819,18 @@ packages: yallist: 4.0.0 dev: false + /mime-db/1.33.0: + resolution: {integrity: sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types/2.1.18: + resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.33.0 + dev: false + /mimic-fn/2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -786,6 +846,12 @@ packages: engines: {node: '>=10'} dev: false + /minimatch/3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} + dependencies: + brace-expansion: 1.1.11 + dev: false + /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: false @@ -865,6 +931,14 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: false + /path-is-inside/1.0.2: + resolution: {integrity: sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==} + dev: false + + /path-to-regexp/2.2.1: + resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + dev: false + /pump/3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -872,11 +946,20 @@ packages: once: 1.4.0 dev: false + /punycode/1.4.1: + resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} + dev: false + /quick-lru/5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} dev: false + /range-parser/1.2.0: + resolution: {integrity: sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==} + engines: {node: '>= 0.6'} + dev: false + /readable-stream/3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} @@ -920,6 +1003,19 @@ packages: lru-cache: 6.0.0 dev: false + /serve-handler/6.1.3: + resolution: {integrity: sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==} + dependencies: + bytes: 3.0.0 + content-disposition: 0.5.2 + fast-url-parser: 1.1.3 + mime-types: 2.1.18 + minimatch: 3.0.4 + path-is-inside: 1.0.2 + path-to-regexp: 2.2.1 + range-parser: 1.2.0 + dev: false + /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: false diff --git a/src/commands/packwiz.ts b/src/commands/packwiz.ts index d2aa768..a6cdbfc 100644 --- a/src/commands/packwiz.ts +++ b/src/commands/packwiz.ts @@ -3,7 +3,7 @@ import { usePack } from "../pack.js" import fs from "fs-extra" import dedent from "dedent" import kleur from "kleur" -import { getSha512HexHash } from "../utils.js" +import { getLANAddress, getSha512HexHash, httpServeDirectory, optionParsePositiveInteger } from "../utils.js" import { output } from "../output.js" import { Visitor, walk } from "@root/walk" import { Path } from "../path.js" @@ -25,9 +25,28 @@ packwizCommand.command("import") packwizCommand.command("serve") .description("Start an HTTP server in the packwiz directory.") - .action(async () => { - output.failAndExit("Not implemented.") - // TODO: serve + .option("-p, --port ", "The port of the HTTP server.", optionParsePositiveInteger, 8000) + .option("-e, --expose", "Expose the HTTP server on all interfaces.") + .action(async options => { + const pack = await usePack() + const directoryPath = pack.paths.generated.resolve("packwiz") + if (!(await fs.pathExists(directoryPath.toString()))) + output.failAndExit(`The ${kleur.yellow("packwiz")} directory does not exist. Generate it by running ${kleur.yellow("horizr packwiz export")}.`) + + const lanAddress = await getLANAddress() + + httpServeDirectory(directoryPath, options.port, options.expose, () => { + const localAddress = `http://localhost:${options.port}/pack.toml` + + if (options.expose) { + output.println(dedent` + ${kleur.green("Serving at")} + Local: ${kleur.yellow(localAddress)} + Network: ${kleur.yellow(`http://${lanAddress}:${options.port}/pack.toml`)} + `) + } + else output.println(`${kleur.green("Serving at")} ${kleur.yellow(localAddress)}`) + }) }) packwizCommand.command("dev") @@ -62,7 +81,7 @@ packwizCommand.command("export") name = ${JSON.stringify(mod.modFile.name)} filename = ${JSON.stringify(mod.modFile.file.name)} side = "${mod.modFile.side.replace("client+server", "both")}" - + [download] hash-format = "sha512" hash = ${JSON.stringify(mod.modFile.file.hashes.sha512)} @@ -92,8 +111,6 @@ packwizCommand.command("export") if (dirent.isFile()) { const outputPath = outputDirectoryPath.resolve(relativePath) await fs.mkdirp(outputPath.getParent().toString()) - console.log(path) - console.log(outputPath.toString()) await fs.copy(path, outputPath.toString()) indexedFiles.push({ @@ -115,7 +132,7 @@ packwizCommand.command("export") const index = dedent` hash-format = "sha512" - + ${indexedFiles.map(file => dedent` [[files]] file = ${JSON.stringify(file.path)} diff --git a/src/utils.ts b/src/utils.ts index d5e639e..3ef99c3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -5,6 +5,37 @@ import { ZipFile } from "yazl" import { walk } from "@root/walk" import fs from "fs-extra" import { pEvent } from "p-event" +import serveHandler from "serve-handler" +import * as http from "http" +import addressWithCallback from "address" +import { promisify } from "util" + +const address = promisify(addressWithCallback) + +export const getLANAddress = () => address().then(r => r.ip) + +export function httpServeDirectory(path: Path, port: number, expose: boolean, onListen: () => void) { + const server = http.createServer((request, response) => { + return serveHandler(request, response, { + directoryListing: false, + public: path.toString(), + cleanUrls: false, + headers: [ + { + source: "**/*.toml", + headers: [{ + key: "Content-Type", + value: "application/toml" + }] + } + ] + }) + }) + + server.listen(port, expose ? "0.0.0.0" : "127.0.0.1", () => { + onListen() + }) +} export async function zipDirectory(directoryPath: Path, outputFilePath: Path) { const zipFile = new ZipFile()