├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── lib ├── index.js └── utils │ ├── index.js │ ├── parseJSON.js │ └── serveStatic.js ├── package-lock.json ├── package.json └── test ├── errors.test.js ├── files.test.js ├── files ├── audio.m4a ├── styles.css └── test.txt ├── middleware.test.js ├── parseJSON.test.js ├── router.test.js └── serveStatic.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | private.txt 3 | node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | test 3 | examples 4 | private.txt 5 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Cododev Technology Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cpeak 2 | 3 | [![npm version](https://badge.fury.io/js/cpeak.svg)](https://www.npmjs.com/package/cpeak) 4 | 5 | Cpeak is a minimal and fast Node.js framework inspired by Express.js. 6 | 7 | This project is designed to be improved until it's ready for use in complex production applications, aiming to be more performant and minimal than Express.js. This framework is intended for HTTP applications that primarily deal with JSON and file-based message bodies. 8 | 9 | This is an educational project that was started as part of the [Understanding Node.js: Core Concepts](https://www.udemy.com/course/understanding-nodejs-core-concepts/?referralCode=0BC21AC4DD6958AE6A95) course. If you want to learn how to build a framework like this, and get to a point where you can build things like this yourself, check out this course! 10 | 11 | This is the current demo, and the development of the project will begin starting from September 2025. 12 | 13 | ## Why Cpeak? 14 | 15 | - **Minimalism**: No unnecessary bloat, with zero dependencies. Just the core essentials you need to build fast and reliable applications. 16 | - **Performance**: Engineered to be fast, **Cpeak** won’t sacrifice speed for excessive customizability. 17 | - **Educational**: Every new change made in the project will be explained in great detail in this [YouTube playlist](https://www.youtube.com/playlist?list=PLCiGw8i6NhvqsA-ZZcChJ0kaHZ3hcIVdY). Follow this project and let's see what it takes to build an industry-leading product! 18 | - **Express.js Compatible**: You can easily refactor from Cpeak to Express.js and vice versa. Many npm packages that work with Express.js will also work with Cpeak. 19 | 20 | ## Table of Contents 21 | 22 | - [Getting Started](#getting-started) 23 | - [Hello World App](#hello-world-app) 24 | - [Documentation](#documentation) 25 | - [Including](#including) 26 | - [Initializing](#initializing) 27 | - [Middleware](#middleware) 28 | - [Route Handling](#route-handling) 29 | - [URL Variables & Parameters](#url-variables--parameters) 30 | - [Sending Files](#sending-files) 31 | - [Error Handling](#error-handling) 32 | - [Listening](#listening) 33 | - [Util Functions](#util-functions) 34 | - [serveStatic](#servestatic) 35 | - [parseJSON](#parsejson) 36 | - [Complete Example](#complete-example) 37 | - [Versioning Notice](#versioning-notice) 38 | 39 | ## Getting Started 40 | 41 | Ready to dive in? Install **Cpeak** via npm: 42 | 43 | ```bash 44 | npm install cpeak 45 | ``` 46 | 47 | Cpeak is a **pure ESM** package, and to use it, your project needs to be an ESM as well. You can learn more about that [here](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). 48 | 49 | ### Hello World App: 50 | 51 | ```javascript 52 | import cpeak from "cpeak"; 53 | 54 | const server = new cpeak(); 55 | 56 | server.route("get", "/", (req, res) => { 57 | res.json({ message: "Hi there!" }); 58 | }); 59 | 60 | server.listen(3000, () => { 61 | console.log("Server has started on port 3000"); 62 | }); 63 | ``` 64 | 65 | ## Documentation 66 | 67 | ### Including 68 | 69 | Include the framework like this: 70 | 71 | ```javascript 72 | import cpeak from "cpeak"; 73 | ``` 74 | 75 | Because of the minimalistic philosophy, you won’t add unnecessary objects to your memory as soon as you include the framework. If at any point you want to use a particular utility function (like `parseJSON` and `serveStatic`), include it like the line below, and only at that point will it be moved into memory: 76 | 77 | ```javascript 78 | import cpeak, { serveStatic, parseJSON } from "cpeak"; 79 | ``` 80 | 81 | ### Initializing 82 | 83 | Initialize the Cpeak server like this: 84 | 85 | ```javascript 86 | const server = new cpeak(); 87 | ``` 88 | 89 | Now you can use this server object to start listening, add route logic, add middleware functions, and handle errors. 90 | 91 | ### Middleware 92 | 93 | If you add a middleware function, that function will run before your route logic kicks in. Here you can customize the request object, return an error, or do anything else you want to do prior to your route logic, like authentication. 94 | 95 | After calling `next`, the next middleware function is going to run if there’s any; otherwise, the route logic is going to run. 96 | 97 | ```javascript 98 | server.beforeEach((req, res, next) => { 99 | if (req.headers.authentication) { 100 | // Your authentication logic... 101 | req.userId = ""; 102 | req.custom = "This is some string"; 103 | next(); 104 | } else { 105 | // Return an error and close the request... 106 | return res.status(401).json({ error: "Unauthorized" }); 107 | } 108 | }); 109 | 110 | server.beforeEach((req, res, next) => { 111 | console.log( 112 | "The custom value was added from the previous middleware: ", 113 | req.custom 114 | ); 115 | next(); 116 | }); 117 | ``` 118 | 119 | ### Route Handling 120 | 121 | You can add new routes like this: 122 | 123 | ```javascript 124 | server.route("patch", "/the-path-you-want", (req, res) => { 125 | // your route logic 126 | }); 127 | ``` 128 | 129 | First add the HTTP method name you want to handle, then the path, and finally, the callback. The `req` and `res` object types are the same as in the Node.js HTTP module (`http.IncomingMessage` and `http.ServerResponse`). You can read more about them in the [official Node.js documentation](https://nodejs.org/docs/latest/api/http.html). 130 | 131 | ### URL Variables & Parameters 132 | 133 | Since in HTTP these are called URL parameters: `/path?key1=value1&key2=value2&foo=900`, in Cpeak, we also call them `params` (short for HTTP URL parameters). 134 | We can also do custom path management, and we call them `vars` (short for URL variables). 135 | 136 | Here’s how we can read both: 137 | 138 | ```javascript 139 | // Imagine request URL is example.com/test/my-title/more-text?filter=newest 140 | server.route("patch", "/test/:title/more-text", (req, res) => { 141 | const title = req.vars.title; 142 | const filter = req.params.filter; 143 | 144 | console.log(title); // my-title 145 | console.log(filter); // newest 146 | }); 147 | ``` 148 | 149 | ### Sending Files 150 | 151 | You can send a file as a Node.js Stream anywhere in your route or middleware logic like this: 152 | 153 | ```javascript 154 | server.route("get", "/testing", (req, res) => { 155 | return res.status(200).sendFile("", ""); 156 | 157 | // Example: 158 | // return res.status(200).sendFile("./images/sun.jpeg", "image/jpeg"); 159 | }); 160 | ``` 161 | 162 | The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the `path` module for better compatibility) and also the correct HTTP MIME type for that file. 163 | 164 | ### Error Handling 165 | 166 | If anywhere in your route functions you want to return an error, it's cleaner to pass it to the `handleErr` function like this: 167 | 168 | ```javascript 169 | server.route("get", "/api/document/:title", (req, res, handleErr) => { 170 | const title = req.vars.title; 171 | 172 | if (title.length > 500) 173 | return handleErr({ status: 400, message: "Title too long." }); 174 | 175 | // The rest of your logic... 176 | }); 177 | ``` 178 | 179 | And then handle all the errors like this in the `handleErr` callback: 180 | 181 | ```javascript 182 | server.handleErr((error, req, res) => { 183 | if (error && error.status) { 184 | res.status(error.status).json({ error: error.message }); 185 | } else { 186 | // Log the unexpected errors somewhere so you can keep track of them... 187 | console.error(error); 188 | res.status(500).json({ 189 | error: "Sorry, something unexpected happened on our side.", 190 | }); 191 | } 192 | }); 193 | ``` 194 | 195 | The error object is the object that you passed to the `handleErr` function earlier in your routes. 196 | 197 | ### Listening 198 | 199 | Start listening on a specific port like this: 200 | 201 | ```javascript 202 | server.listen(3000, () => { 203 | console.log("Server has started on port 3000"); 204 | }); 205 | ``` 206 | 207 | ### Util Functions 208 | 209 | There are utility functions that you can include and use as middleware functions. These are meant to make it easier for you to build HTTP applications. In the future, many more will be added, and you only move them into memory once you include them. No need to have many npm dependencies for simple applications! 210 | 211 | The list of utility functions as of now: 212 | 213 | - serveStatic 214 | - parseJSON 215 | 216 | Including any one of them is done like this: 217 | 218 | ```javascript 219 | import cpeak, { utilName } from "cpeak"; 220 | ``` 221 | 222 | #### serveStatic 223 | 224 | With this middleware function, you can automatically set a folder in your project to be served by Cpeak. Here’s how to set it up: 225 | 226 | ```javascript 227 | server.beforeEach( 228 | serveStatic("./public", { 229 | mp3: "audio/mpeg", 230 | }) 231 | ); 232 | ``` 233 | 234 | If you have file types in your public folder that are not one of the following, make sure to add the MIME types manually as the second argument in the function as an object where each property key is the file extension, and each value is the correct MIME type for that. You can see all the available MIME types on the [IANA website](https://www.iana.org/assignments/media-types/media-types.xhtml). 235 | 236 | ``` 237 | html: "text/html", 238 | css: "text/css", 239 | js: "application/javascript", 240 | jpg: "image/jpeg", 241 | jpeg: "image/jpeg", 242 | png: "image/png", 243 | svg: "image/svg+xml", 244 | txt: "text/plain", 245 | eot: "application/vnd.ms-fontobject", 246 | otf: "font/otf", 247 | ttf: "font/ttf", 248 | woff: "font/woff", 249 | woff2: "font/woff2" 250 | ``` 251 | 252 | #### parseJSON 253 | 254 | With this middleware function, you can easily read and send JSON in HTTP message bodies in route and middleware functions. Fire it up like this: 255 | 256 | ```javascript 257 | server.beforeEach(parseJSON); 258 | ``` 259 | 260 | Read and send JSON from HTTP messages like this: 261 | 262 | ```javascript 263 | server.route("put", "/api/user", (req, res) => { 264 | // Reading JSON from the HTTP request: 265 | const email = req.body.email; 266 | 267 | // rest of your logic... 268 | 269 | // Sending JSON in the HTTP response: 270 | res.status(201).json({ message: "Something was created..." }); 271 | }); 272 | ``` 273 | 274 | ## Complete Example 275 | 276 | Here you can see all the features that Cpeak offers, in one small piece of code: 277 | 278 | ```javascript 279 | import cpeak, { serveStatic, parseJSON } from "cpeak"; 280 | 281 | const server = new cpeak(); 282 | 283 | server.beforeEach( 284 | serveStatic("./public", { 285 | mp3: "audio/mpeg", 286 | }) 287 | ); 288 | 289 | // For parsing JSON bodies 290 | server.beforeEach(parseJSON); 291 | 292 | // Adding custom middleware functions 293 | server.beforeEach((req, res, next) => { 294 | req.custom = "This is some string"; 295 | next(); 296 | }); 297 | 298 | // Adding route handlers 299 | server.route("get", "/api/document/:title", (req, res, handleErr) => { 300 | // Reading URL variables 301 | const title = req.vars.title; 302 | 303 | // Reading URL parameters (like /users?filter=active) 304 | const filter = req.params.filter; 305 | 306 | // Reading JSON request body 307 | const anything = req.body.anything; 308 | 309 | // Handling errors 310 | if (anything === "not-expected-thing") 311 | return handleErr({ status: 400, message: "Invalid property." }); 312 | 313 | // Sending a JSON response 314 | res.status(200).json({ message: "This is a test response" }); 315 | }); 316 | 317 | // Sending a file response 318 | server.route("get", "/file", (req, res) => { 319 | // Make sure to specify a correct path and MIME type... 320 | res.status(200).sendFile("", ""); 321 | }); 322 | 323 | // Handle all the errors that could happen in the routes 324 | server.handleErr((error, req, res) => { 325 | if (error && error.status) { 326 | res.status(error.status).json({ error: error.message }); 327 | } else { 328 | console.error(error); 329 | res.status(500).json({ 330 | error: "Sorry, something unexpected happened from our side.", 331 | }); 332 | } 333 | }); 334 | 335 | server.listen(3000, () => { 336 | console.log("Server has started on port 3000"); 337 | }); 338 | ``` 339 | 340 | ## Versioning Notice 341 | 342 | #### Version `1.x.x` 343 | 344 | Version `1.x.x` represents the initial release of our framework, developed during the _Understanding Node.js Core Concepts_ course. These versions laid the foundation for our project. 345 | 346 | #### Version `2.x.x` 347 | 348 | All version `2.x.x` releases are considered to be in active development, following the completion of the course. These versions include ongoing feature additions and API changes as we refine the framework. Frequent updates may require code changes, so version `2.x.x` is not recommended for production environments. 349 | For new features, bug fixes, and other changes that don't break existing code, the patch version will be increased. For changes that break existing code, the minor version will be increased. 350 | 351 | #### Version `3.x.x` 352 | 353 | Version `3.x.x` and beyond will be our first production-ready releases. These versions are intended for stable, long-term use, with a focus on backward compatibility and minimal breaking changes. 354 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import http from "node:http"; 2 | import fs from "node:fs/promises"; 3 | 4 | import { serveStatic, parseJSON } from "./utils/index.js"; 5 | 6 | class Cpeak { 7 | constructor() { 8 | this.server = http.createServer(); 9 | this.routes = {}; 10 | this.middleware = []; 11 | this.handleErr; 12 | 13 | this.server.on("request", (req, res) => { 14 | // Send a file back to the client 15 | res.sendFile = async (path, mime) => { 16 | const fileHandle = await fs.open(path, "r"); 17 | const fileStream = fileHandle.createReadStream(); 18 | 19 | res.setHeader("Content-Type", mime); 20 | 21 | fileStream.pipe(res); 22 | }; 23 | 24 | // Set the status code of the response 25 | res.status = (code) => { 26 | res.statusCode = code; 27 | return res; 28 | }; 29 | 30 | // Send a json data back to the client (for small json data, less than the highWaterMark) 31 | res.json = (data) => { 32 | // This is only good for bodies that their size is less than the highWaterMark value 33 | res.setHeader("Content-Type", "application/json"); 34 | res.end(JSON.stringify(data)); 35 | }; 36 | 37 | // Get the url without the URL parameters 38 | const urlWithoutParams = req.url.split("?")[0]; 39 | 40 | // Parse the URL parameters (like /users?key1=value1&key2=value2) 41 | // We put this here to also parse them for all the middleware functions 42 | const params = new URLSearchParams(req.url.split("?")[1]); 43 | req.params = Object.fromEntries(params.entries()); 44 | 45 | // Run all the middleware functions before we run the corresponding route 46 | const runMiddleware = (req, res, middleware, index) => { 47 | // Out exit point... 48 | if (index === middleware.length) { 49 | const routes = this.routes[req.method.toLowerCase()]; 50 | if (routes && typeof routes[Symbol.iterator] === "function") 51 | for (const route of routes) { 52 | const match = urlWithoutParams.match(route.regex); 53 | 54 | if (match) { 55 | // Parse the URL variables from the matched route (like /users/:id) 56 | const vars = this.#extractVars(route.path, match); 57 | req.vars = vars; 58 | 59 | // Call the route handler with the modified req and res objects 60 | return route.cb(req, res, (error) => { 61 | res.setHeader("Connection", "close"); 62 | this.handleErr(error, req, res); 63 | }); 64 | } 65 | } 66 | 67 | // If the requested route dose not exist, return 404 68 | return res 69 | .status(404) 70 | .json({ error: `Cannot ${req.method} ${urlWithoutParams}` }); 71 | } else { 72 | middleware[index](req, res, () => { 73 | runMiddleware(req, res, middleware, index + 1); 74 | }); 75 | } 76 | }; 77 | 78 | runMiddleware(req, res, this.middleware, 0); 79 | }); 80 | } 81 | 82 | route(method, path, cb) { 83 | if (!this.routes[method]) this.routes[method] = []; 84 | 85 | const regex = this.#pathToRegex(path); 86 | this.routes[method].push({ path, regex, cb }); 87 | } 88 | 89 | beforeEach(cb) { 90 | this.middleware.push(cb); 91 | } 92 | 93 | handleErr(cb) { 94 | this.handleErr = cb; 95 | } 96 | 97 | listen(port, cb) { 98 | this.server.listen(port, cb); 99 | } 100 | 101 | close(cb) { 102 | this.server.close(cb); 103 | } 104 | 105 | // ------------------------------ 106 | // PRIVATE METHODS: 107 | // ------------------------------ 108 | #pathToRegex(path) { 109 | const varNames = []; 110 | const regexString = 111 | "^" + 112 | path.replace(/:\w+/g, (match, offset) => { 113 | varNames.push(match.slice(1)); 114 | return "([^/]+)"; 115 | }) + 116 | "$"; 117 | 118 | const regex = new RegExp(regexString); 119 | return regex; 120 | } 121 | 122 | #extractVars(path, match) { 123 | // Extract url variable values from the matched route 124 | const varNames = (path.match(/:\w+/g) || []).map((varParam) => 125 | varParam.slice(1) 126 | ); 127 | const vars = {}; 128 | varNames.forEach((name, index) => { 129 | vars[name] = match[index + 1]; 130 | }); 131 | return vars; 132 | } 133 | } 134 | 135 | export { serveStatic, parseJSON }; 136 | 137 | export default Cpeak; 138 | -------------------------------------------------------------------------------- /lib/utils/index.js: -------------------------------------------------------------------------------- 1 | import { parseJSON } from "./parseJSON.js"; 2 | import { serveStatic } from "./serveStatic.js"; 3 | 4 | export { serveStatic, parseJSON }; 5 | -------------------------------------------------------------------------------- /lib/utils/parseJSON.js: -------------------------------------------------------------------------------- 1 | // Parsing JSON 2 | const parseJSON = (req, res, next) => { 3 | // This is only good for bodies that their size is less than the highWaterMark value 4 | if (req.headers["content-type"] === "application/json") { 5 | let body = ""; 6 | req.on("data", (chunk) => { 7 | body += chunk.toString("utf-8"); 8 | }); 9 | 10 | req.on("end", () => { 11 | body = JSON.parse(body); 12 | req.body = body; 13 | return next(); 14 | }); 15 | } else { 16 | next(); 17 | } 18 | }; 19 | 20 | export { parseJSON }; 21 | -------------------------------------------------------------------------------- /lib/utils/serveStatic.js: -------------------------------------------------------------------------------- 1 | import fs from "node:fs"; 2 | import path from "node:path"; 3 | 4 | const MIME_TYPES = { 5 | html: "text/html", 6 | css: "text/css", 7 | js: "application/javascript", 8 | jpg: "image/jpeg", 9 | jpeg: "image/jpeg", 10 | png: "image/png", 11 | svg: "image/svg+xml", 12 | txt: "text/plain", 13 | eot: "application/vnd.ms-fontobject", 14 | otf: "font/otf", 15 | ttf: "font/ttf", 16 | woff: "font/woff", 17 | woff2: "font/woff2", 18 | }; 19 | 20 | const serveStatic = (folderPath, newMimeTypes) => { 21 | // For new user defined mime types 22 | if (newMimeTypes) { 23 | Object.assign(MIME_TYPES, newMimeTypes); 24 | } 25 | 26 | function processFolder(folderPath, parentFolder) { 27 | const staticFiles = []; 28 | 29 | // Read the contents of the folder 30 | const files = fs.readdirSync(folderPath); 31 | 32 | // Loop through the files and subfolders 33 | for (const file of files) { 34 | const fullPath = path.join(folderPath, file); 35 | 36 | // Check if it's a directory 37 | if (fs.statSync(fullPath).isDirectory()) { 38 | // If it's a directory, recursively process it 39 | const subfolderFiles = processFolder(fullPath, parentFolder); 40 | staticFiles.push(...subfolderFiles); 41 | } else { 42 | // If it's a file, add it to the array 43 | const relativePath = path.relative(parentFolder, fullPath); 44 | const fileExtension = path.extname(file).slice(1); 45 | if (MIME_TYPES[fileExtension]) staticFiles.push("/" + relativePath); 46 | } 47 | } 48 | 49 | return staticFiles; 50 | } 51 | 52 | const filesArrayToFilesMap = (filesArray) => { 53 | const filesMap = {}; 54 | for (const file of filesArray) { 55 | const fileExtension = path.extname(file).slice(1); 56 | filesMap[file] = { 57 | path: folderPath + file, 58 | mime: MIME_TYPES[fileExtension], 59 | }; 60 | } 61 | return filesMap; 62 | }; 63 | 64 | // Start processing the folder 65 | const filesMap = filesArrayToFilesMap(processFolder(folderPath, folderPath)); 66 | 67 | return function (req, res, next) { 68 | if (filesMap.hasOwnProperty(req.url)) { 69 | const fileRoute = filesMap[req.url]; 70 | return res.sendFile(fileRoute.path, fileRoute.mime); 71 | } else { 72 | next(); 73 | } 74 | }; 75 | }; 76 | 77 | export { serveStatic }; 78 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpeak", 3 | "version": "2.2.4", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cpeak", 9 | "version": "2.2.4", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "mocha": "^10.7.3", 13 | "supertest": "^7.0.0" 14 | } 15 | }, 16 | "node_modules/ansi-colors": { 17 | "version": "4.1.3", 18 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", 19 | "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", 20 | "dev": true, 21 | "engines": { 22 | "node": ">=6" 23 | } 24 | }, 25 | "node_modules/ansi-regex": { 26 | "version": "5.0.1", 27 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", 28 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", 29 | "dev": true, 30 | "engines": { 31 | "node": ">=8" 32 | } 33 | }, 34 | "node_modules/ansi-styles": { 35 | "version": "4.3.0", 36 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 37 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 38 | "dev": true, 39 | "dependencies": { 40 | "color-convert": "^2.0.1" 41 | }, 42 | "engines": { 43 | "node": ">=8" 44 | }, 45 | "funding": { 46 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 47 | } 48 | }, 49 | "node_modules/anymatch": { 50 | "version": "3.1.3", 51 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 52 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 53 | "dev": true, 54 | "dependencies": { 55 | "normalize-path": "^3.0.0", 56 | "picomatch": "^2.0.4" 57 | }, 58 | "engines": { 59 | "node": ">= 8" 60 | } 61 | }, 62 | "node_modules/argparse": { 63 | "version": "2.0.1", 64 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 65 | "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 66 | "dev": true 67 | }, 68 | "node_modules/asap": { 69 | "version": "2.0.6", 70 | "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", 71 | "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", 72 | "dev": true 73 | }, 74 | "node_modules/asynckit": { 75 | "version": "0.4.0", 76 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 77 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", 78 | "dev": true 79 | }, 80 | "node_modules/balanced-match": { 81 | "version": "1.0.2", 82 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 83 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 84 | "dev": true 85 | }, 86 | "node_modules/binary-extensions": { 87 | "version": "2.3.0", 88 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", 89 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", 90 | "dev": true, 91 | "engines": { 92 | "node": ">=8" 93 | }, 94 | "funding": { 95 | "url": "https://github.com/sponsors/sindresorhus" 96 | } 97 | }, 98 | "node_modules/brace-expansion": { 99 | "version": "2.0.1", 100 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 101 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 102 | "dev": true, 103 | "dependencies": { 104 | "balanced-match": "^1.0.0" 105 | } 106 | }, 107 | "node_modules/braces": { 108 | "version": "3.0.3", 109 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", 110 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", 111 | "dev": true, 112 | "dependencies": { 113 | "fill-range": "^7.1.1" 114 | }, 115 | "engines": { 116 | "node": ">=8" 117 | } 118 | }, 119 | "node_modules/browser-stdout": { 120 | "version": "1.3.1", 121 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 122 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 123 | "dev": true 124 | }, 125 | "node_modules/call-bind": { 126 | "version": "1.0.7", 127 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", 128 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", 129 | "dev": true, 130 | "dependencies": { 131 | "es-define-property": "^1.0.0", 132 | "es-errors": "^1.3.0", 133 | "function-bind": "^1.1.2", 134 | "get-intrinsic": "^1.2.4", 135 | "set-function-length": "^1.2.1" 136 | }, 137 | "engines": { 138 | "node": ">= 0.4" 139 | }, 140 | "funding": { 141 | "url": "https://github.com/sponsors/ljharb" 142 | } 143 | }, 144 | "node_modules/camelcase": { 145 | "version": "6.3.0", 146 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 147 | "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 148 | "dev": true, 149 | "engines": { 150 | "node": ">=10" 151 | }, 152 | "funding": { 153 | "url": "https://github.com/sponsors/sindresorhus" 154 | } 155 | }, 156 | "node_modules/chalk": { 157 | "version": "4.1.2", 158 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 159 | "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 160 | "dev": true, 161 | "dependencies": { 162 | "ansi-styles": "^4.1.0", 163 | "supports-color": "^7.1.0" 164 | }, 165 | "engines": { 166 | "node": ">=10" 167 | }, 168 | "funding": { 169 | "url": "https://github.com/chalk/chalk?sponsor=1" 170 | } 171 | }, 172 | "node_modules/chalk/node_modules/supports-color": { 173 | "version": "7.2.0", 174 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 175 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 176 | "dev": true, 177 | "dependencies": { 178 | "has-flag": "^4.0.0" 179 | }, 180 | "engines": { 181 | "node": ">=8" 182 | } 183 | }, 184 | "node_modules/chokidar": { 185 | "version": "3.6.0", 186 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", 187 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", 188 | "dev": true, 189 | "dependencies": { 190 | "anymatch": "~3.1.2", 191 | "braces": "~3.0.2", 192 | "glob-parent": "~5.1.2", 193 | "is-binary-path": "~2.1.0", 194 | "is-glob": "~4.0.1", 195 | "normalize-path": "~3.0.0", 196 | "readdirp": "~3.6.0" 197 | }, 198 | "engines": { 199 | "node": ">= 8.10.0" 200 | }, 201 | "funding": { 202 | "url": "https://paulmillr.com/funding/" 203 | }, 204 | "optionalDependencies": { 205 | "fsevents": "~2.3.2" 206 | } 207 | }, 208 | "node_modules/cliui": { 209 | "version": "7.0.4", 210 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", 211 | "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", 212 | "dev": true, 213 | "dependencies": { 214 | "string-width": "^4.2.0", 215 | "strip-ansi": "^6.0.0", 216 | "wrap-ansi": "^7.0.0" 217 | } 218 | }, 219 | "node_modules/color-convert": { 220 | "version": "2.0.1", 221 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 222 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 223 | "dev": true, 224 | "dependencies": { 225 | "color-name": "~1.1.4" 226 | }, 227 | "engines": { 228 | "node": ">=7.0.0" 229 | } 230 | }, 231 | "node_modules/color-name": { 232 | "version": "1.1.4", 233 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 234 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 235 | "dev": true 236 | }, 237 | "node_modules/combined-stream": { 238 | "version": "1.0.8", 239 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 240 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 241 | "dev": true, 242 | "dependencies": { 243 | "delayed-stream": "~1.0.0" 244 | }, 245 | "engines": { 246 | "node": ">= 0.8" 247 | } 248 | }, 249 | "node_modules/component-emitter": { 250 | "version": "1.3.1", 251 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", 252 | "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", 253 | "dev": true, 254 | "funding": { 255 | "url": "https://github.com/sponsors/sindresorhus" 256 | } 257 | }, 258 | "node_modules/cookiejar": { 259 | "version": "2.1.4", 260 | "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", 261 | "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", 262 | "dev": true 263 | }, 264 | "node_modules/debug": { 265 | "version": "4.3.6", 266 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", 267 | "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", 268 | "dev": true, 269 | "dependencies": { 270 | "ms": "2.1.2" 271 | }, 272 | "engines": { 273 | "node": ">=6.0" 274 | }, 275 | "peerDependenciesMeta": { 276 | "supports-color": { 277 | "optional": true 278 | } 279 | } 280 | }, 281 | "node_modules/debug/node_modules/ms": { 282 | "version": "2.1.2", 283 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 284 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 285 | "dev": true 286 | }, 287 | "node_modules/decamelize": { 288 | "version": "4.0.0", 289 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", 290 | "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", 291 | "dev": true, 292 | "engines": { 293 | "node": ">=10" 294 | }, 295 | "funding": { 296 | "url": "https://github.com/sponsors/sindresorhus" 297 | } 298 | }, 299 | "node_modules/define-data-property": { 300 | "version": "1.1.4", 301 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 302 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 303 | "dev": true, 304 | "dependencies": { 305 | "es-define-property": "^1.0.0", 306 | "es-errors": "^1.3.0", 307 | "gopd": "^1.0.1" 308 | }, 309 | "engines": { 310 | "node": ">= 0.4" 311 | }, 312 | "funding": { 313 | "url": "https://github.com/sponsors/ljharb" 314 | } 315 | }, 316 | "node_modules/delayed-stream": { 317 | "version": "1.0.0", 318 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 319 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 320 | "dev": true, 321 | "engines": { 322 | "node": ">=0.4.0" 323 | } 324 | }, 325 | "node_modules/dezalgo": { 326 | "version": "1.0.4", 327 | "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", 328 | "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", 329 | "dev": true, 330 | "dependencies": { 331 | "asap": "^2.0.0", 332 | "wrappy": "1" 333 | } 334 | }, 335 | "node_modules/diff": { 336 | "version": "5.2.0", 337 | "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", 338 | "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", 339 | "dev": true, 340 | "engines": { 341 | "node": ">=0.3.1" 342 | } 343 | }, 344 | "node_modules/emoji-regex": { 345 | "version": "8.0.0", 346 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 347 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", 348 | "dev": true 349 | }, 350 | "node_modules/es-define-property": { 351 | "version": "1.0.0", 352 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", 353 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", 354 | "dev": true, 355 | "dependencies": { 356 | "get-intrinsic": "^1.2.4" 357 | }, 358 | "engines": { 359 | "node": ">= 0.4" 360 | } 361 | }, 362 | "node_modules/es-errors": { 363 | "version": "1.3.0", 364 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 365 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 366 | "dev": true, 367 | "engines": { 368 | "node": ">= 0.4" 369 | } 370 | }, 371 | "node_modules/escalade": { 372 | "version": "3.1.2", 373 | "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", 374 | "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", 375 | "dev": true, 376 | "engines": { 377 | "node": ">=6" 378 | } 379 | }, 380 | "node_modules/escape-string-regexp": { 381 | "version": "4.0.0", 382 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 383 | "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 384 | "dev": true, 385 | "engines": { 386 | "node": ">=10" 387 | }, 388 | "funding": { 389 | "url": "https://github.com/sponsors/sindresorhus" 390 | } 391 | }, 392 | "node_modules/fast-safe-stringify": { 393 | "version": "2.1.1", 394 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", 395 | "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", 396 | "dev": true 397 | }, 398 | "node_modules/fill-range": { 399 | "version": "7.1.1", 400 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", 401 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", 402 | "dev": true, 403 | "dependencies": { 404 | "to-regex-range": "^5.0.1" 405 | }, 406 | "engines": { 407 | "node": ">=8" 408 | } 409 | }, 410 | "node_modules/find-up": { 411 | "version": "5.0.0", 412 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 413 | "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 414 | "dev": true, 415 | "dependencies": { 416 | "locate-path": "^6.0.0", 417 | "path-exists": "^4.0.0" 418 | }, 419 | "engines": { 420 | "node": ">=10" 421 | }, 422 | "funding": { 423 | "url": "https://github.com/sponsors/sindresorhus" 424 | } 425 | }, 426 | "node_modules/flat": { 427 | "version": "5.0.2", 428 | "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", 429 | "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", 430 | "dev": true, 431 | "bin": { 432 | "flat": "cli.js" 433 | } 434 | }, 435 | "node_modules/form-data": { 436 | "version": "4.0.0", 437 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 438 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 439 | "dev": true, 440 | "dependencies": { 441 | "asynckit": "^0.4.0", 442 | "combined-stream": "^1.0.8", 443 | "mime-types": "^2.1.12" 444 | }, 445 | "engines": { 446 | "node": ">= 6" 447 | } 448 | }, 449 | "node_modules/formidable": { 450 | "version": "3.5.1", 451 | "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", 452 | "integrity": "sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==", 453 | "dev": true, 454 | "dependencies": { 455 | "dezalgo": "^1.0.4", 456 | "hexoid": "^1.0.0", 457 | "once": "^1.4.0" 458 | }, 459 | "funding": { 460 | "url": "https://ko-fi.com/tunnckoCore/commissions" 461 | } 462 | }, 463 | "node_modules/fs.realpath": { 464 | "version": "1.0.0", 465 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 466 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 467 | "dev": true 468 | }, 469 | "node_modules/fsevents": { 470 | "version": "2.3.3", 471 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 472 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 473 | "dev": true, 474 | "hasInstallScript": true, 475 | "optional": true, 476 | "os": [ 477 | "darwin" 478 | ], 479 | "engines": { 480 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 481 | } 482 | }, 483 | "node_modules/function-bind": { 484 | "version": "1.1.2", 485 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 486 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 487 | "dev": true, 488 | "funding": { 489 | "url": "https://github.com/sponsors/ljharb" 490 | } 491 | }, 492 | "node_modules/get-caller-file": { 493 | "version": "2.0.5", 494 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 495 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 496 | "dev": true, 497 | "engines": { 498 | "node": "6.* || 8.* || >= 10.*" 499 | } 500 | }, 501 | "node_modules/get-intrinsic": { 502 | "version": "1.2.4", 503 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", 504 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", 505 | "dev": true, 506 | "dependencies": { 507 | "es-errors": "^1.3.0", 508 | "function-bind": "^1.1.2", 509 | "has-proto": "^1.0.1", 510 | "has-symbols": "^1.0.3", 511 | "hasown": "^2.0.0" 512 | }, 513 | "engines": { 514 | "node": ">= 0.4" 515 | }, 516 | "funding": { 517 | "url": "https://github.com/sponsors/ljharb" 518 | } 519 | }, 520 | "node_modules/glob": { 521 | "version": "8.1.0", 522 | "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", 523 | "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", 524 | "deprecated": "Glob versions prior to v9 are no longer supported", 525 | "dev": true, 526 | "dependencies": { 527 | "fs.realpath": "^1.0.0", 528 | "inflight": "^1.0.4", 529 | "inherits": "2", 530 | "minimatch": "^5.0.1", 531 | "once": "^1.3.0" 532 | }, 533 | "engines": { 534 | "node": ">=12" 535 | }, 536 | "funding": { 537 | "url": "https://github.com/sponsors/isaacs" 538 | } 539 | }, 540 | "node_modules/glob-parent": { 541 | "version": "5.1.2", 542 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 543 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 544 | "dev": true, 545 | "dependencies": { 546 | "is-glob": "^4.0.1" 547 | }, 548 | "engines": { 549 | "node": ">= 6" 550 | } 551 | }, 552 | "node_modules/gopd": { 553 | "version": "1.0.1", 554 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 555 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 556 | "dev": true, 557 | "dependencies": { 558 | "get-intrinsic": "^1.1.3" 559 | }, 560 | "funding": { 561 | "url": "https://github.com/sponsors/ljharb" 562 | } 563 | }, 564 | "node_modules/has-flag": { 565 | "version": "4.0.0", 566 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 567 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 568 | "dev": true, 569 | "engines": { 570 | "node": ">=8" 571 | } 572 | }, 573 | "node_modules/has-property-descriptors": { 574 | "version": "1.0.2", 575 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 576 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 577 | "dev": true, 578 | "dependencies": { 579 | "es-define-property": "^1.0.0" 580 | }, 581 | "funding": { 582 | "url": "https://github.com/sponsors/ljharb" 583 | } 584 | }, 585 | "node_modules/has-proto": { 586 | "version": "1.0.3", 587 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", 588 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", 589 | "dev": true, 590 | "engines": { 591 | "node": ">= 0.4" 592 | }, 593 | "funding": { 594 | "url": "https://github.com/sponsors/ljharb" 595 | } 596 | }, 597 | "node_modules/has-symbols": { 598 | "version": "1.0.3", 599 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 600 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 601 | "dev": true, 602 | "engines": { 603 | "node": ">= 0.4" 604 | }, 605 | "funding": { 606 | "url": "https://github.com/sponsors/ljharb" 607 | } 608 | }, 609 | "node_modules/hasown": { 610 | "version": "2.0.2", 611 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 612 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 613 | "dev": true, 614 | "dependencies": { 615 | "function-bind": "^1.1.2" 616 | }, 617 | "engines": { 618 | "node": ">= 0.4" 619 | } 620 | }, 621 | "node_modules/he": { 622 | "version": "1.2.0", 623 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 624 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 625 | "dev": true, 626 | "bin": { 627 | "he": "bin/he" 628 | } 629 | }, 630 | "node_modules/hexoid": { 631 | "version": "1.0.0", 632 | "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", 633 | "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", 634 | "dev": true, 635 | "engines": { 636 | "node": ">=8" 637 | } 638 | }, 639 | "node_modules/inflight": { 640 | "version": "1.0.6", 641 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 642 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 643 | "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", 644 | "dev": true, 645 | "dependencies": { 646 | "once": "^1.3.0", 647 | "wrappy": "1" 648 | } 649 | }, 650 | "node_modules/inherits": { 651 | "version": "2.0.4", 652 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 653 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 654 | "dev": true 655 | }, 656 | "node_modules/is-binary-path": { 657 | "version": "2.1.0", 658 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 659 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 660 | "dev": true, 661 | "dependencies": { 662 | "binary-extensions": "^2.0.0" 663 | }, 664 | "engines": { 665 | "node": ">=8" 666 | } 667 | }, 668 | "node_modules/is-extglob": { 669 | "version": "2.1.1", 670 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 671 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 672 | "dev": true, 673 | "engines": { 674 | "node": ">=0.10.0" 675 | } 676 | }, 677 | "node_modules/is-fullwidth-code-point": { 678 | "version": "3.0.0", 679 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 680 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", 681 | "dev": true, 682 | "engines": { 683 | "node": ">=8" 684 | } 685 | }, 686 | "node_modules/is-glob": { 687 | "version": "4.0.3", 688 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 689 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 690 | "dev": true, 691 | "dependencies": { 692 | "is-extglob": "^2.1.1" 693 | }, 694 | "engines": { 695 | "node": ">=0.10.0" 696 | } 697 | }, 698 | "node_modules/is-number": { 699 | "version": "7.0.0", 700 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 701 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 702 | "dev": true, 703 | "engines": { 704 | "node": ">=0.12.0" 705 | } 706 | }, 707 | "node_modules/is-plain-obj": { 708 | "version": "2.1.0", 709 | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", 710 | "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", 711 | "dev": true, 712 | "engines": { 713 | "node": ">=8" 714 | } 715 | }, 716 | "node_modules/is-unicode-supported": { 717 | "version": "0.1.0", 718 | "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", 719 | "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", 720 | "dev": true, 721 | "engines": { 722 | "node": ">=10" 723 | }, 724 | "funding": { 725 | "url": "https://github.com/sponsors/sindresorhus" 726 | } 727 | }, 728 | "node_modules/js-yaml": { 729 | "version": "4.1.0", 730 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 731 | "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 732 | "dev": true, 733 | "dependencies": { 734 | "argparse": "^2.0.1" 735 | }, 736 | "bin": { 737 | "js-yaml": "bin/js-yaml.js" 738 | } 739 | }, 740 | "node_modules/locate-path": { 741 | "version": "6.0.0", 742 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 743 | "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 744 | "dev": true, 745 | "dependencies": { 746 | "p-locate": "^5.0.0" 747 | }, 748 | "engines": { 749 | "node": ">=10" 750 | }, 751 | "funding": { 752 | "url": "https://github.com/sponsors/sindresorhus" 753 | } 754 | }, 755 | "node_modules/log-symbols": { 756 | "version": "4.1.0", 757 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", 758 | "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", 759 | "dev": true, 760 | "dependencies": { 761 | "chalk": "^4.1.0", 762 | "is-unicode-supported": "^0.1.0" 763 | }, 764 | "engines": { 765 | "node": ">=10" 766 | }, 767 | "funding": { 768 | "url": "https://github.com/sponsors/sindresorhus" 769 | } 770 | }, 771 | "node_modules/methods": { 772 | "version": "1.1.2", 773 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 774 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 775 | "dev": true, 776 | "engines": { 777 | "node": ">= 0.6" 778 | } 779 | }, 780 | "node_modules/mime": { 781 | "version": "2.6.0", 782 | "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", 783 | "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", 784 | "dev": true, 785 | "bin": { 786 | "mime": "cli.js" 787 | }, 788 | "engines": { 789 | "node": ">=4.0.0" 790 | } 791 | }, 792 | "node_modules/mime-db": { 793 | "version": "1.52.0", 794 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 795 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 796 | "dev": true, 797 | "engines": { 798 | "node": ">= 0.6" 799 | } 800 | }, 801 | "node_modules/mime-types": { 802 | "version": "2.1.35", 803 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 804 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 805 | "dev": true, 806 | "dependencies": { 807 | "mime-db": "1.52.0" 808 | }, 809 | "engines": { 810 | "node": ">= 0.6" 811 | } 812 | }, 813 | "node_modules/minimatch": { 814 | "version": "5.1.6", 815 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 816 | "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 817 | "dev": true, 818 | "dependencies": { 819 | "brace-expansion": "^2.0.1" 820 | }, 821 | "engines": { 822 | "node": ">=10" 823 | } 824 | }, 825 | "node_modules/mocha": { 826 | "version": "10.7.3", 827 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", 828 | "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", 829 | "dev": true, 830 | "dependencies": { 831 | "ansi-colors": "^4.1.3", 832 | "browser-stdout": "^1.3.1", 833 | "chokidar": "^3.5.3", 834 | "debug": "^4.3.5", 835 | "diff": "^5.2.0", 836 | "escape-string-regexp": "^4.0.0", 837 | "find-up": "^5.0.0", 838 | "glob": "^8.1.0", 839 | "he": "^1.2.0", 840 | "js-yaml": "^4.1.0", 841 | "log-symbols": "^4.1.0", 842 | "minimatch": "^5.1.6", 843 | "ms": "^2.1.3", 844 | "serialize-javascript": "^6.0.2", 845 | "strip-json-comments": "^3.1.1", 846 | "supports-color": "^8.1.1", 847 | "workerpool": "^6.5.1", 848 | "yargs": "^16.2.0", 849 | "yargs-parser": "^20.2.9", 850 | "yargs-unparser": "^2.0.0" 851 | }, 852 | "bin": { 853 | "_mocha": "bin/_mocha", 854 | "mocha": "bin/mocha.js" 855 | }, 856 | "engines": { 857 | "node": ">= 14.0.0" 858 | } 859 | }, 860 | "node_modules/ms": { 861 | "version": "2.1.3", 862 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 863 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 864 | "dev": true 865 | }, 866 | "node_modules/normalize-path": { 867 | "version": "3.0.0", 868 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 869 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 870 | "dev": true, 871 | "engines": { 872 | "node": ">=0.10.0" 873 | } 874 | }, 875 | "node_modules/object-inspect": { 876 | "version": "1.13.2", 877 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", 878 | "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", 879 | "dev": true, 880 | "engines": { 881 | "node": ">= 0.4" 882 | }, 883 | "funding": { 884 | "url": "https://github.com/sponsors/ljharb" 885 | } 886 | }, 887 | "node_modules/once": { 888 | "version": "1.4.0", 889 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 890 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 891 | "dev": true, 892 | "dependencies": { 893 | "wrappy": "1" 894 | } 895 | }, 896 | "node_modules/p-limit": { 897 | "version": "3.1.0", 898 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 899 | "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 900 | "dev": true, 901 | "dependencies": { 902 | "yocto-queue": "^0.1.0" 903 | }, 904 | "engines": { 905 | "node": ">=10" 906 | }, 907 | "funding": { 908 | "url": "https://github.com/sponsors/sindresorhus" 909 | } 910 | }, 911 | "node_modules/p-locate": { 912 | "version": "5.0.0", 913 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 914 | "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 915 | "dev": true, 916 | "dependencies": { 917 | "p-limit": "^3.0.2" 918 | }, 919 | "engines": { 920 | "node": ">=10" 921 | }, 922 | "funding": { 923 | "url": "https://github.com/sponsors/sindresorhus" 924 | } 925 | }, 926 | "node_modules/path-exists": { 927 | "version": "4.0.0", 928 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 929 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 930 | "dev": true, 931 | "engines": { 932 | "node": ">=8" 933 | } 934 | }, 935 | "node_modules/picomatch": { 936 | "version": "2.3.1", 937 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 938 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 939 | "dev": true, 940 | "engines": { 941 | "node": ">=8.6" 942 | }, 943 | "funding": { 944 | "url": "https://github.com/sponsors/jonschlinkert" 945 | } 946 | }, 947 | "node_modules/qs": { 948 | "version": "6.13.0", 949 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 950 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 951 | "dev": true, 952 | "dependencies": { 953 | "side-channel": "^1.0.6" 954 | }, 955 | "engines": { 956 | "node": ">=0.6" 957 | }, 958 | "funding": { 959 | "url": "https://github.com/sponsors/ljharb" 960 | } 961 | }, 962 | "node_modules/randombytes": { 963 | "version": "2.1.0", 964 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 965 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 966 | "dev": true, 967 | "dependencies": { 968 | "safe-buffer": "^5.1.0" 969 | } 970 | }, 971 | "node_modules/readdirp": { 972 | "version": "3.6.0", 973 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 974 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 975 | "dev": true, 976 | "dependencies": { 977 | "picomatch": "^2.2.1" 978 | }, 979 | "engines": { 980 | "node": ">=8.10.0" 981 | } 982 | }, 983 | "node_modules/require-directory": { 984 | "version": "2.1.1", 985 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 986 | "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", 987 | "dev": true, 988 | "engines": { 989 | "node": ">=0.10.0" 990 | } 991 | }, 992 | "node_modules/safe-buffer": { 993 | "version": "5.2.1", 994 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 995 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 996 | "dev": true, 997 | "funding": [ 998 | { 999 | "type": "github", 1000 | "url": "https://github.com/sponsors/feross" 1001 | }, 1002 | { 1003 | "type": "patreon", 1004 | "url": "https://www.patreon.com/feross" 1005 | }, 1006 | { 1007 | "type": "consulting", 1008 | "url": "https://feross.org/support" 1009 | } 1010 | ] 1011 | }, 1012 | "node_modules/serialize-javascript": { 1013 | "version": "6.0.2", 1014 | "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", 1015 | "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", 1016 | "dev": true, 1017 | "dependencies": { 1018 | "randombytes": "^2.1.0" 1019 | } 1020 | }, 1021 | "node_modules/set-function-length": { 1022 | "version": "1.2.2", 1023 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 1024 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 1025 | "dev": true, 1026 | "dependencies": { 1027 | "define-data-property": "^1.1.4", 1028 | "es-errors": "^1.3.0", 1029 | "function-bind": "^1.1.2", 1030 | "get-intrinsic": "^1.2.4", 1031 | "gopd": "^1.0.1", 1032 | "has-property-descriptors": "^1.0.2" 1033 | }, 1034 | "engines": { 1035 | "node": ">= 0.4" 1036 | } 1037 | }, 1038 | "node_modules/side-channel": { 1039 | "version": "1.0.6", 1040 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", 1041 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", 1042 | "dev": true, 1043 | "dependencies": { 1044 | "call-bind": "^1.0.7", 1045 | "es-errors": "^1.3.0", 1046 | "get-intrinsic": "^1.2.4", 1047 | "object-inspect": "^1.13.1" 1048 | }, 1049 | "engines": { 1050 | "node": ">= 0.4" 1051 | }, 1052 | "funding": { 1053 | "url": "https://github.com/sponsors/ljharb" 1054 | } 1055 | }, 1056 | "node_modules/string-width": { 1057 | "version": "4.2.3", 1058 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", 1059 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", 1060 | "dev": true, 1061 | "dependencies": { 1062 | "emoji-regex": "^8.0.0", 1063 | "is-fullwidth-code-point": "^3.0.0", 1064 | "strip-ansi": "^6.0.1" 1065 | }, 1066 | "engines": { 1067 | "node": ">=8" 1068 | } 1069 | }, 1070 | "node_modules/strip-ansi": { 1071 | "version": "6.0.1", 1072 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", 1073 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", 1074 | "dev": true, 1075 | "dependencies": { 1076 | "ansi-regex": "^5.0.1" 1077 | }, 1078 | "engines": { 1079 | "node": ">=8" 1080 | } 1081 | }, 1082 | "node_modules/strip-json-comments": { 1083 | "version": "3.1.1", 1084 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", 1085 | "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", 1086 | "dev": true, 1087 | "engines": { 1088 | "node": ">=8" 1089 | }, 1090 | "funding": { 1091 | "url": "https://github.com/sponsors/sindresorhus" 1092 | } 1093 | }, 1094 | "node_modules/superagent": { 1095 | "version": "9.0.2", 1096 | "resolved": "https://registry.npmjs.org/superagent/-/superagent-9.0.2.tgz", 1097 | "integrity": "sha512-xuW7dzkUpcJq7QnhOsnNUgtYp3xRwpt2F7abdRYIpCsAt0hhUqia0EdxyXZQQpNmGtsCzYHryaKSV3q3GJnq7w==", 1098 | "dev": true, 1099 | "dependencies": { 1100 | "component-emitter": "^1.3.0", 1101 | "cookiejar": "^2.1.4", 1102 | "debug": "^4.3.4", 1103 | "fast-safe-stringify": "^2.1.1", 1104 | "form-data": "^4.0.0", 1105 | "formidable": "^3.5.1", 1106 | "methods": "^1.1.2", 1107 | "mime": "2.6.0", 1108 | "qs": "^6.11.0" 1109 | }, 1110 | "engines": { 1111 | "node": ">=14.18.0" 1112 | } 1113 | }, 1114 | "node_modules/supertest": { 1115 | "version": "7.0.0", 1116 | "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.0.0.tgz", 1117 | "integrity": "sha512-qlsr7fIC0lSddmA3tzojvzubYxvlGtzumcdHgPwbFWMISQwL22MhM2Y3LNt+6w9Yyx7559VW5ab70dgphm8qQA==", 1118 | "dev": true, 1119 | "dependencies": { 1120 | "methods": "^1.1.2", 1121 | "superagent": "^9.0.1" 1122 | }, 1123 | "engines": { 1124 | "node": ">=14.18.0" 1125 | } 1126 | }, 1127 | "node_modules/supports-color": { 1128 | "version": "8.1.1", 1129 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", 1130 | "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", 1131 | "dev": true, 1132 | "dependencies": { 1133 | "has-flag": "^4.0.0" 1134 | }, 1135 | "engines": { 1136 | "node": ">=10" 1137 | }, 1138 | "funding": { 1139 | "url": "https://github.com/chalk/supports-color?sponsor=1" 1140 | } 1141 | }, 1142 | "node_modules/to-regex-range": { 1143 | "version": "5.0.1", 1144 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1145 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1146 | "dev": true, 1147 | "dependencies": { 1148 | "is-number": "^7.0.0" 1149 | }, 1150 | "engines": { 1151 | "node": ">=8.0" 1152 | } 1153 | }, 1154 | "node_modules/workerpool": { 1155 | "version": "6.5.1", 1156 | "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", 1157 | "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", 1158 | "dev": true 1159 | }, 1160 | "node_modules/wrap-ansi": { 1161 | "version": "7.0.0", 1162 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", 1163 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", 1164 | "dev": true, 1165 | "dependencies": { 1166 | "ansi-styles": "^4.0.0", 1167 | "string-width": "^4.1.0", 1168 | "strip-ansi": "^6.0.0" 1169 | }, 1170 | "engines": { 1171 | "node": ">=10" 1172 | }, 1173 | "funding": { 1174 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1" 1175 | } 1176 | }, 1177 | "node_modules/wrappy": { 1178 | "version": "1.0.2", 1179 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1180 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1181 | "dev": true 1182 | }, 1183 | "node_modules/y18n": { 1184 | "version": "5.0.8", 1185 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", 1186 | "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", 1187 | "dev": true, 1188 | "engines": { 1189 | "node": ">=10" 1190 | } 1191 | }, 1192 | "node_modules/yargs": { 1193 | "version": "16.2.0", 1194 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", 1195 | "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", 1196 | "dev": true, 1197 | "dependencies": { 1198 | "cliui": "^7.0.2", 1199 | "escalade": "^3.1.1", 1200 | "get-caller-file": "^2.0.5", 1201 | "require-directory": "^2.1.1", 1202 | "string-width": "^4.2.0", 1203 | "y18n": "^5.0.5", 1204 | "yargs-parser": "^20.2.2" 1205 | }, 1206 | "engines": { 1207 | "node": ">=10" 1208 | } 1209 | }, 1210 | "node_modules/yargs-parser": { 1211 | "version": "20.2.9", 1212 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", 1213 | "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", 1214 | "dev": true, 1215 | "engines": { 1216 | "node": ">=10" 1217 | } 1218 | }, 1219 | "node_modules/yargs-unparser": { 1220 | "version": "2.0.0", 1221 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", 1222 | "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", 1223 | "dev": true, 1224 | "dependencies": { 1225 | "camelcase": "^6.0.0", 1226 | "decamelize": "^4.0.0", 1227 | "flat": "^5.0.2", 1228 | "is-plain-obj": "^2.1.0" 1229 | }, 1230 | "engines": { 1231 | "node": ">=10" 1232 | } 1233 | }, 1234 | "node_modules/yocto-queue": { 1235 | "version": "0.1.0", 1236 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 1237 | "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 1238 | "dev": true, 1239 | "engines": { 1240 | "node": ">=10" 1241 | }, 1242 | "funding": { 1243 | "url": "https://github.com/sponsors/sindresorhus" 1244 | } 1245 | } 1246 | } 1247 | } 1248 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cpeak", 3 | "version": "2.2.5", 4 | "description": "A minimal and fast Node.js HTTP framework.", 5 | "type": "module", 6 | "main": "./lib/index.js", 7 | "scripts": { 8 | "test": "mocha test/**/*.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/agile8118/cpeak.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/agile8118/cpeak/issues" 16 | }, 17 | "homepage": "https://github.com/agile8118/cpeak#readme", 18 | "author": "Cododev Technology", 19 | "license": "MIT", 20 | "keywords": [ 21 | "cpeak", 22 | "backend", 23 | "router", 24 | "nodejs", 25 | "http", 26 | "framework" 27 | ], 28 | "devDependencies": { 29 | "mocha": "^10.7.3", 30 | "supertest": "^7.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/errors.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import cpeak from "../lib/index.js"; 4 | 5 | const PORT = 7543; 6 | const request = supertest(`http://localhost:${PORT}`); 7 | 8 | describe("Error handling with handleErr", function () { 9 | let server; 10 | 11 | before(function (done) { 12 | server = new cpeak(); 13 | 14 | server.route("patch", "/foo/:bar", (req, res, handleErr) => { 15 | const bar = req.vars.bar; 16 | 17 | if (bar === "random") { 18 | return handleErr({ status: 403, message: "an error msg" }); 19 | } 20 | 21 | return res.status(200).json({ bar }); 22 | }); 23 | 24 | server.handleErr((error, req, res) => { 25 | return res.status(error.status).json({ error: error.message }); 26 | }); 27 | 28 | server.listen(PORT, done); 29 | }); 30 | 31 | after(function (done) { 32 | server.close(done); 33 | }); 34 | 35 | it("should get an error using the handleErr function from a router", async function () { 36 | const res = await request.patch("/foo/random"); 37 | assert.strictEqual(res.status, 403); 38 | assert.deepStrictEqual(res.body, { error: "an error msg" }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/files.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import fs from "node:fs/promises"; 4 | import cpeak from "../lib/index.js"; 5 | 6 | const PORT = 7543; 7 | const request = supertest(`http://localhost:${PORT}`); 8 | 9 | describe("Returning files with sendFile", function () { 10 | let server; 11 | 12 | before(function (done) { 13 | server = new cpeak(); 14 | 15 | server.route("get", "/file", (req, res) => { 16 | res.status(200).sendFile("./test/files/test.txt", "text/plain"); 17 | }); 18 | 19 | server.listen(PORT, done); 20 | }); 21 | 22 | after(function (done) { 23 | server.close(done); 24 | }); 25 | 26 | it("should get a file as the response with the correct MIME type", async function () { 27 | const res = await request.get("/file"); 28 | 29 | const fileContent = await fs.readFile("./test/files/test.txt", "utf-8"); 30 | 31 | assert.strictEqual(res.status, 200); 32 | assert.strictEqual(res.headers["content-type"], "text/plain"); 33 | assert.strictEqual(res.text, fileContent); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/files/audio.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agile8118/cpeak/6573f0b0cc554a69248c24a0a847e6d7179de5c0/test/files/audio.m4a -------------------------------------------------------------------------------- /test/files/styles.css: -------------------------------------------------------------------------------- 1 | /* some styles for testing... */ 2 | .my-class { 3 | color: red; 4 | } 5 | 6 | .my-class-2 { 7 | color: blue; 8 | } 9 | 10 | -------------------------------------------------------------------------------- /test/files/test.txt: -------------------------------------------------------------------------------- 1 | This is a test file. -------------------------------------------------------------------------------- /test/middleware.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import cpeak from "../lib/index.js"; 4 | 5 | const PORT = 7543; 6 | const request = supertest(`http://localhost:${PORT}`); 7 | 8 | describe("Middleware functions", function () { 9 | let server; 10 | 11 | before(function (done) { 12 | server = new cpeak(); 13 | 14 | server.beforeEach((req, res, next) => { 15 | const value = req.params.value; 16 | 17 | if (value === "random") 18 | return res.status(400).json({ error: "an error msg" }); 19 | 20 | next(); 21 | }); 22 | 23 | server.beforeEach((req, res, next) => { 24 | req.foo = "text"; 25 | next(); 26 | }); 27 | 28 | server.beforeEach((req, res, next) => { 29 | res.unauthorized = () => { 30 | res.statusCode = 401; 31 | return res; 32 | }; 33 | next(); 34 | }); 35 | 36 | server.route("get", "/bar", (req, res) => { 37 | res.status(200).json({ message: req.foo }); 38 | }); 39 | 40 | server.route("get", "/bar-more", (req, res) => { 41 | res.unauthorized().json({}); 42 | }); 43 | 44 | server.listen(PORT, done); 45 | }); 46 | 47 | after(function (done) { 48 | server.close(done); 49 | }); 50 | 51 | it("should modify the req object with a new property", async function () { 52 | const res = await request.get("/bar"); 53 | assert.strictEqual(res.status, 200); 54 | assert.strictEqual(res.body.message, "text"); 55 | }); 56 | 57 | it("should modify the res object with a new method", async function () { 58 | const res = await request.get("/bar-more"); 59 | assert.strictEqual(res.status, 401); 60 | }); 61 | 62 | it("should exit the middleware and route chain if a middleware wants to", async function () { 63 | const res = await request.get("/bar?value=random"); 64 | assert.strictEqual(res.status, 400); 65 | assert.strictEqual(res.body.message, undefined); 66 | assert.deepStrictEqual(res.body, { error: "an error msg" }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /test/parseJSON.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import cpeak, { parseJSON } from "../lib/index.js"; 4 | 5 | const PORT = 7543; 6 | const request = supertest(`http://localhost:${PORT}`); 7 | 8 | describe("Parsing request bodies with parseJSON", function () { 9 | let server; 10 | 11 | before(function (done) { 12 | server = new cpeak(); 13 | 14 | server.beforeEach(parseJSON); 15 | 16 | server.route("post", "/do-something", (req, res) => { 17 | res.status(205).json({ receivedData: req.body }); 18 | }); 19 | 20 | server.listen(PORT, done); 21 | }); 22 | 23 | after(function (done) { 24 | server.close(done); 25 | }); 26 | 27 | it("should return the same data that was sent in request body as JSON", async function () { 28 | const obj = { 29 | key1: "value1", 30 | key2: 42, 31 | key3: { 32 | nestedKey1: "nestedValue1", 33 | nestedKey2: ["arrayValue1", "arrayValue2", 1000], 34 | }, 35 | key4: true, 36 | }; 37 | 38 | const res = await request.post("/do-something").send(obj); 39 | 40 | assert.strictEqual(res.status, 205); 41 | assert.deepStrictEqual(res.body.receivedData, obj); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/router.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import cpeak from "../lib/index.js"; 4 | 5 | const PORT = 7543; 6 | const request = supertest(`http://localhost:${PORT}`); 7 | 8 | describe("General route logic & URL variables and parameters", function () { 9 | let server; 10 | 11 | before(function (done) { 12 | server = new cpeak(); 13 | 14 | server.route("get", "/hello", (req, res) => { 15 | res.status(200).json({ message: "Hello, World!" }); 16 | }); 17 | 18 | server.route("get", "/document/:title/more/:another/final", (req, res) => { 19 | const title = req.vars.title; 20 | const another = req.vars.another; 21 | const params = req.params; 22 | 23 | res.status(200).json({ title, another, params }); 24 | }); 25 | 26 | server.listen(PORT, done); 27 | }); 28 | 29 | after(function (done) { 30 | server.close(done); 31 | }); 32 | 33 | it("should return a simple response with no variables and parameters", async function () { 34 | const res = await request.get("/hello"); 35 | assert.strictEqual(res.status, 200); 36 | assert.deepStrictEqual(res.body, { message: "Hello, World!" }); 37 | }); 38 | 39 | it("should return a 404 for unknown routes", async function () { 40 | const res = await request.get("/unknown"); 41 | assert.strictEqual(res.status, 404); 42 | assert.deepStrictEqual(res.body, { 43 | error: "Cannot GET /unknown", 44 | }); 45 | }); 46 | 47 | it("should return a 404 for not handled methods", async function () { 48 | const res = await request.patch("/random"); 49 | assert.strictEqual(res.status, 404); 50 | assert.deepStrictEqual(res.body, { 51 | error: "Cannot PATCH /random", 52 | }); 53 | }); 54 | 55 | it("should return the correct URL variables and parameters", async function () { 56 | const expectedResponseBody = { 57 | title: "some-title", 58 | another: "thisISsome__more-text", 59 | params: { 60 | filter: "comments-date", 61 | page: "2", 62 | sortBy: "date-desc", 63 | tags: JSON.stringify(["nodejs", "express", "url-params"]), 64 | author: JSON.stringify({ name: "John Doe", id: 123 }), 65 | isPublished: "true", 66 | metadata: JSON.stringify({ version: "1.0.0", language: "en" }), 67 | }, 68 | }; 69 | 70 | const res = await request 71 | .get("/document/some-title/more/thisISsome__more-text/final") 72 | .query({ 73 | filter: "comments-date", 74 | page: 2, 75 | sortBy: "date-desc", 76 | tags: JSON.stringify(["nodejs", "express", "url-params"]), 77 | author: JSON.stringify({ name: "John Doe", id: 123 }), 78 | isPublished: true, 79 | metadata: JSON.stringify({ version: "1.0.0", language: "en" }), 80 | }); 81 | 82 | assert.strictEqual(res.status, 200); 83 | assert.deepStrictEqual(res.body, expectedResponseBody); 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /test/serveStatic.test.js: -------------------------------------------------------------------------------- 1 | import assert from "node:assert"; 2 | import supertest from "supertest"; 3 | import fs from "node:fs/promises"; 4 | import cpeak, { serveStatic } from "../lib/index.js"; 5 | 6 | const PORT = 7543; 7 | const request = supertest(`http://localhost:${PORT}`); 8 | 9 | describe("Serving static files with serveStatic", function () { 10 | let server; 11 | 12 | before(function (done) { 13 | server = new cpeak(); 14 | 15 | server.beforeEach(serveStatic("./test/files", { m4a: "audio/mp4" })); 16 | 17 | server.listen(PORT, done); 18 | }); 19 | 20 | after(function (done) { 21 | server.close(done); 22 | }); 23 | 24 | it("should return the correct file with the correct MIME type", async function () { 25 | const textRes = await request.get("/test.txt"); 26 | const cssRes = await request.get("/styles.css"); 27 | 28 | const fileTextContent = await fs.readFile("./test/files/test.txt", "utf-8"); 29 | const fileCssContent = await fs.readFile( 30 | "./test/files/styles.css", 31 | "utf-8" 32 | ); 33 | 34 | assert.strictEqual(textRes.status, 200); 35 | assert.strictEqual(textRes.headers["content-type"], "text/plain"); 36 | assert.strictEqual(textRes.text, fileTextContent); 37 | 38 | assert.strictEqual(cssRes.status, 200); 39 | assert.strictEqual(cssRes.headers["content-type"], "text/css"); 40 | assert.strictEqual(cssRes.text, fileCssContent); 41 | }); 42 | 43 | it("should return the correct file with the specified MIME type by the developer", async function () { 44 | const res = await request.get("/audio.m4a"); 45 | 46 | // read the file as binary 47 | const fileBuffer = await fs.readFile("./test/files/audio.m4a"); 48 | 49 | assert.strictEqual(res.status, 200); 50 | assert.strictEqual(res.headers["content-type"], "audio/mp4"); 51 | assert.deepStrictEqual(res.body, fileBuffer); 52 | }); 53 | }); 54 | --------------------------------------------------------------------------------