From ac5a785651331ee7f48a71e55065cfc47123f6d6 Mon Sep 17 00:00:00 2001 From: Daan <20974756+daanschenkel@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:22:24 +0200 Subject: [PATCH 1/2] Rework some of the endpoints --- README.md | 56 ++++++++++++++++++++++++++++++++++++++------ src/api/fileStats.ts | 14 ----------- src/api/files.ts | 14 ++++++++--- src/api/index.ts | 34 +++++++++++++++++---------- src/http.ts | 51 +++++++++++++++++++++++++++++++--------- 5 files changed, 122 insertions(+), 47 deletions(-) delete mode 100644 src/api/fileStats.ts diff --git a/README.md b/README.md index fe7ca3b..f138f28 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,20 @@ The `S3_KEY_SECRET` environment variable is the accessKeyId. The secret key is n LittleTinyStorage is a REST API. You can use it with any HTTP client, like Postman or cURL. The API does not use JSON, but returns data in plaintext. +## HEAD + +The HEAD method is used to get the file size without downloading the file. This can be useful for checking if a file exists or is accessible without downloading it. + +``` +HEAD /:bucket/:file +``` + +Example: + +``` +HEAD /mybucket/myfile.txt +``` + ## Get a file ``` @@ -184,7 +198,7 @@ This endpoint will return the decoded token if the token is valid. The values ma > Note: These details are also returned in the response header when running operations on a file using a token. ``` -GET /api/:bucket/checkToken?token={TOKEN} +GET /api/:bucket/token?token={TOKEN} ``` Example: @@ -211,16 +225,16 @@ All fields are optional, and will count as wildcards if not specified. | delete | DELETE | ``` -GET /api/:bucket/generateToken?file={FILE}&type={TYPE}&expiresIn={EXPIRESIN} +POST /api/:bucket/token?file={FILE}&type={TYPE}&expiresIn={EXPIRESIN} ``` Example: ``` -GET /api/bucket1/generateToken?file=taxfraud.txt&type=download&expiresIn=60s -GET /api/bucket1/generateToken?file=taxfraud.txt -GET /api/bucket1/generateToken?type=rename -GET /api/bucket1/generateToken?expiresIn=60y +POST /api/bucket1/token?file=taxfraud.txt&type=download&expiresIn=60s +POST /api/bucket1/token?file=taxfraud.txt +POST /api/bucket1/token?type=rename +POST /api/bucket1/token?expiresIn=60y ``` ## Get a list of files @@ -267,7 +281,35 @@ Example: This endpoint will return the current configuration of the server. ``` -GET /api/getEnv +GET /api/env +``` + +Example: + +``` +{ + "envType": "file | env", + "env": [ + { + "key": "HEYA", + "value": "true", + "comment": "Used to check if the .env file is loaded, THIS IS REQUIRED!" + }, + { + "key": "PORT", + "value": "7999", + "comment": "The port LTS runs on." + }, + ] +} +``` + +### Set the current configuration + +This endpoint will set the current configuration of the server. + +``` +POST /api/env ``` Example: diff --git a/src/api/fileStats.ts b/src/api/fileStats.ts deleted file mode 100644 index 1fe5d35..0000000 --- a/src/api/fileStats.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IncomingMessage } from "http"; -import { ExtraInfo } from "."; -import { getFilesAndStats } from "../reader.js"; -export default async function handle( - req: IncomingMessage, - res: any, - params: URLSearchParams, - resp: Function, - extra: ExtraInfo -) { - const files = getFilesAndStats(extra.bucket); - - return resp(res, 200, files); -} diff --git a/src/api/files.ts b/src/api/files.ts index ab83bdf..e390f7c 100644 --- a/src/api/files.ts +++ b/src/api/files.ts @@ -1,6 +1,6 @@ import { IncomingMessage } from "http"; import { ExtraInfo } from "."; -import { getFiles } from "../reader.js"; +import { getFiles, getFilesAndStats } from "../reader.js"; export default async function handle( req: IncomingMessage, res: any, @@ -8,7 +8,15 @@ export default async function handle( resp: Function, extra: ExtraInfo ) { - const files = getFiles(extra.bucket); + const stats = params.get("metadata") === "true"; - return resp(res, 200, files); + if (!stats) { + const files = getFiles(extra.bucket); + + return resp(res, 200, files); + } else { + const files = getFilesAndStats(extra.bucket); + + return resp(res, 200, files); + } } diff --git a/src/api/index.ts b/src/api/index.ts index de36666..bc539f6 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -10,7 +10,6 @@ import ping from "./ping.js"; import authed from "./authed.js"; import buckets from "./buckets.js"; import files from "./files.js"; -import fileStats from "./fileStats.js"; import getEnv from "./getEnv.js"; import setEnv from "./setEnv.js"; @@ -18,24 +17,24 @@ import { getURLParam } from "../utils.js"; const routes = [ { - path: "checkToken", + path: "token", handler: checkToken, + method: "GET", }, { - path: "generateToken", + path: "token", handler: generateToken, + method: "POST", }, { path: "hello", handler: hello, + method: "GET", }, { path: "files", handler: files, - }, - { - path: "fileStats", - handler: fileStats, + method: "GET", }, ]; @@ -43,6 +42,7 @@ const unauthedRoutes = [ { path: "ping", handler: ping, + method: "GET", }, ]; @@ -51,18 +51,22 @@ const authedRoutes = [ { path: "authed", handler: authed, + method: "GET", }, { path: "buckets", handler: buckets, + method: "GET", }, { - path: "getEnv", //EXTRA REMINDER TO NEVER ALLOW PUBLIC ACCESS TO THIS!!!! + path: "env", //EXTRA REMINDER TO NEVER ALLOW PUBLIC ACCESS TO THIS!!!! handler: getEnv, + method: "GET", }, { - path: "setEnv", //EXTRA REMINDER TO NEVER ALLOW PUBLIC ACCESS TO THIS!!!! + path: "env", //EXTRA REMINDER TO NEVER ALLOW PUBLIC ACCESS TO THIS!!!! handler: setEnv, + method: "POST", }, ]; @@ -77,7 +81,9 @@ export async function handleAPIRequest( const apiPath = getURLParam(req?.url ?? "", 3); if (bucket && !apiPath && !req.headers.authorization) { - const route = unauthedRoutes.find((route) => route.path === bucket); + const route = unauthedRoutes.find( + (route) => route.path === bucket && route.method === req.method + ); if (route) return await route.handler(req, res, params, resp, { buckets, @@ -94,7 +100,9 @@ export async function handleAPIRequest( return resp(res, 401, "Invalid API key"); if (bucket && !apiPath) { - const route = authedRoutes.find((route) => route.path === bucket); + const route = authedRoutes.find( + (route) => route.path === bucket && route.method === req.method + ); if (route) return await route.handler(req, res, params, resp, { buckets, @@ -111,7 +119,9 @@ export async function handleAPIRequest( if (!buckets.includes(bucket)) return resp(res, 400, "Invalid bucket"); - const route = routes.find((route) => route.path === apiPath); + const route = routes.find( + (route) => route.path === apiPath && route.method === req.method + ); if (!route) return resp(res, 400, `i dont know how to ${apiPath}`); return await route.handler(req, res, params, resp, { buckets, diff --git a/src/http.ts b/src/http.ts index f039bba..005a901 100644 --- a/src/http.ts +++ b/src/http.ts @@ -9,7 +9,13 @@ import * as path from "path"; import { IncomingMessage, ServerResponse } from "http"; import { verifyToken } from "./jwt.js"; import * as http from "http"; -import { deleteFile, streamFile, moveFile, pipeFile } from "./reader.js"; +import { + deleteFile, + streamFile, + moveFile, + pipeFile, + getFileStats, +} from "./reader.js"; //TODO: find a better way to do this + make it editable by user const directoryTemplate = ` @@ -110,9 +116,16 @@ export const requestListener = async function ( ); if (canAccess !== true) return resp(res, 401, canAccess); - const foundFile = streamFile(bucket, file); - if (!foundFile) return resp(res, 404); - return resp(res, 200, foundFile, "file"); + if (req.method === "HEAD") { + const stats = getFileStats(bucket, file); + if (!stats) return resp(res, 404); + res.setHeader("Content-Length", stats.size); + return resp(res, 204); + } else if (req.method === "GET") { + const foundFile = streamFile(bucket, file); + if (!foundFile) return resp(res, 404); + return resp(res, 200, foundFile, "file"); + } else return resp(res, 400, "Invalid method"); } } } @@ -172,16 +185,25 @@ export const requestListener = async function ( else if (req.method === "POST") type = "upload"; else if (req.method === "DELETE") type = "delete"; else if (req.method === "PUT") type = "rename"; + else if (req.method === "HEAD") type = "head"; else return resp(res, 400, "Invalid method"); if ( - (await canAccessFile(bucket, null, file, "download", res)) && - type === "download" + ((await canAccessFile(bucket, null, file, "download", res)) && + type === "download") || + type === "head" ) { - //if accessible without authentication - const foundFile = streamFile(bucket, file); - if (!foundFile) return resp(res, 404); - return resp(res, 200, foundFile, "file"); + if (req.method === "HEAD") { + const stats = getFileStats(bucket, file); + if (!stats) return resp(res, 404); + res.setHeader("Content-Length", stats.size); + return resp(res, 204); + } else if (req.method === "GET") { + //if accessible without authentication + const foundFile = streamFile(bucket, file); + if (!foundFile) return resp(res, 404); + return resp(res, 200, foundFile, "file"); + } } const key = params.get("key"); @@ -191,13 +213,20 @@ export const requestListener = async function ( const canAccess = await canAccessFile(bucket, key, file, type, res); if (canAccess !== true) return resp(res, 401, canAccess); + if (type === "head") { + const stats = getFileStats(bucket, file); + if (!stats) return resp(res, 404); + res.setHeader("Content-Length", stats.size); + return resp(res, 204); + } + if (type === "download") { const foundFile = streamFile(bucket, file); if (!foundFile) return resp(res, 404); return resp(res, 200, foundFile, "file"); } if (type === "upload") { - await pipeFile(bucket, file, req); + await pipeFile(bucket, file, req); return resp(res, 200); } if (type === "delete") { From 8cce7fb853c59dc32abd3be0c2cd71888111077e Mon Sep 17 00:00:00 2001 From: Daan <20974756+daanschenkel@users.noreply.github.com> Date: Sun, 11 Aug 2024 11:22:32 +0200 Subject: [PATCH 2/2] Add nodemon for dev --- package-lock.json | 314 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 2 files changed, 318 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index cce410f..021d068 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "jstoxml": "^5.0.2", "kleur": "^4.1.5", "mime": "^4.0.4", + "nodemon": "^3.1.4", "typescript": "^5.5.4" }, "devDependencies": { @@ -47,6 +48,18 @@ "@types/node": "*" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/anzip": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/anzip/-/anzip-0.2.0.tgz", @@ -58,6 +71,42 @@ "node": ">=10" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -66,6 +115,50 @@ "node": "*" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -85,6 +178,92 @@ "pend": "~1.2.0" } }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/jose": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/jose/-/jose-5.6.3.tgz", @@ -120,11 +299,141 @@ "node": ">=16" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/nodemon": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz", + "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/typescript": { "version": "5.5.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", @@ -137,6 +446,11 @@ "node": ">=14.17" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" + }, "node_modules/undici-types": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", diff --git a/package.json b/package.json index 35c8fd8..0cb5bff 100644 --- a/package.json +++ b/package.json @@ -21,11 +21,13 @@ "build": "npm run tsc && npm run build:windows && npm run build:linux && npm run build:macos-x64 && npm run build:macos-arm64", "tsc": "tsc", "start": "npm run tsc && node dist/index.js", - "test": "npm run tsc && node test.js" + "test": "npm run tsc && node test.js", + "dev": "nodemon --watch src --ext ts --exec npm run start" }, "devDependencies": { "@types/jsonwebtoken": "^9.0.6", "@types/node": "^22.1.0", - "@types/yauzl": "^2.10.3" + "@types/yauzl": "^2.10.3", + "nodemon": "^3.1.4" } }