This commit is contained in:
Florian Metz
2024-02-10 05:12:56 +01:00
parent 4ccc02f16c
commit f55a83977b
19 changed files with 515 additions and 48 deletions

18
README.md Normal file
View File

@@ -0,0 +1,18 @@
# PreMiD Monorepo
This is the monorepo for the PreMiD project. PreMiD is a simple, configurable utility that allows you to show what you're watching/listening to on your Discord profile.
## Table of Contents
- [Packages](#packages)
- [License](#license)
## Packages
This monorepo is split into multiple packages / projects. Here's a list of them:
- [apps/pd](apps/pd/README.md) - A simple url shortener service to shorten urls longer than 256 characters.
## License
This project is licensed under the [MPL-2.0 License](LICENSE).

3
apps/pd/README.md Normal file
View File

@@ -0,0 +1,3 @@
# @premid/pd
A simple url shortener service to shorten urls longer than 256 characters.

View File

@@ -4,6 +4,7 @@ declare namespace NodeJS {
export interface ProcessEnv {
NODE_ENV?: "development" | "production";
REDIS_URL?: string;
MAX_FILE_SIZE?: number;
PORT?: string;
HOST?: string;
}

BIN
apps/pd/fixtures/1x1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 B

BIN
apps/pd/fixtures/test.mp4 Normal file

Binary file not shown.

View File

@@ -13,13 +13,20 @@
"license": "MPL-2.0",
"dependencies": {
"@fastify/cors": "^9.0.1",
"@fastify/multipart": "^8.1.0",
"@fastify/rate-limit": "^9.1.0",
"@keyv/redis": "^2.8.4",
"fastify": "^4.26.0",
"file-type": "^19.0.0",
"got": "^14.2.0",
"ioredis": "^5.3.2",
"ipaddr.js": "^2.1.0",
"keyv": "^4.5.4",
"mime-types": "^2.1.35",
"nanoid": "^5.0.5"
},
"devDependencies": {
"@types/mime-types": "^2.1.4",
"form-data": "^4.0.0"
}
}

View File

@@ -0,0 +1,10 @@
import { describe, expect, it } from "vitest";
import { createServer } from "../functions/createServer.js";
describe("createServer", () => {
it("should return a fastify instance", async () => {
const server = await createServer();
expect(server).toBeDefined();
});
});

View File

@@ -1,4 +1,5 @@
import cors from "@fastify/cors";
import fastifyMultipart from "@fastify/multipart";
import ratelimit from "@fastify/rate-limit";
import fastify from "fastify";
@@ -25,8 +26,15 @@ export async function createServer() {
timeWindow: "1 minute",
});
server.get("/create/image", createFromImage);
server.get("/create/base64", createFromBase64);
await server.register(fastifyMultipart, {
limits: {
fileSize: process.env.MAX_FILE_SIZE ?? 5 * 1024 * 1024,
files: 1,
},
});
server.post("/create/image", createFromImage);
server.post("/create/base64", createFromBase64);
server.get("/create/*", createShortenedLink);
server.get(

View File

@@ -1,7 +1,7 @@
import got from "got";
import { describe, expect, it, vi } from "vitest";
describe("getCloudFlareAddresses", async () => {
describe.concurrent("getCloudFlareAddresses", async () => {
it("should return an array of CIDR objects", async () => {
const { default: getCloudFlareAddr } = await import("../functions/getCloudFlareAddresses.js");

View File

@@ -10,3 +10,4 @@ const url = await server.listen({
});
console.log(`Server listening at ${url}`);
// TODO Make proper error codes & json responses

View File

@@ -0,0 +1,98 @@
import { describe, it } from "vitest";
import { createServer } from "../functions/createServer.js";
describe.concurrent("createFromBase64", async () => {
const server = await createServer();
it("should return a 400 when the body is not present", async ({ expect }) => {
const result = await server.inject({
method: "POST",
url: "/create/base64",
});
expect(result.statusCode).toBe(400);
expect(result.body).toMatchInlineSnapshot("\"Invalid body\"");
});
it("should return a 400 when the body is not a string", async ({ expect }) => {
const result = await server.inject({
method: "POST",
payload: new Blob([]),
url: "/create/base64",
});
expect(result.statusCode).toBe(400);
expect(result.body).toMatchInlineSnapshot("\"Invalid body\"");
});
it("should return a 400 when the body is not a valid base64 string", async ({ expect }) => {
const result = await server.inject({
headers: {
"Content-Type": "text/plain",
},
method: "POST",
payload: "data:image/png;base64t",
url: "/create/base64",
});
expect(result.statusCode).toBe(400);
expect(result.body).toMatchInlineSnapshot("\"Invalid base64 string\"");
});
it("should return a 400 when the base64 string is not a valid image", async ({ expect }) => {
const result = await server.inject({
headers: {
"Content-Type": "text/plain",
},
method: "POST",
payload: "data:image/sv;base64,a",
url: "/create/base64",
});
expect(result.statusCode).toBe(400);
expect(result.body).toMatchInlineSnapshot("\"Invalid base64 string\"");
});
it("should return a 400 when the base64 string is not a valid image", async ({ expect }) => {
const result = await server.inject({
headers: {
"Content-Type": "text/plain",
},
method: "POST",
payload: "data:image/svg+xml;base64,s",
url: "/create/base64",
});
expect(result.statusCode).toBe(400);
expect(result.body).toMatchInlineSnapshot("\"Supported types: png, jpeg, jpg, gif, webp\"");
});
it("should return a 200 when the base64 string is valid", async ({ expect }) => {
const result = await server.inject({
headers: {
"Content-Type": "text/plain",
},
method: "POST",
payload: "data:image/png;base64,s",
url: "/create/base64",
});
expect(result.statusCode).toBe(200);
expect(result.body).toStrictEqual(expect.any(String));
});
it("should return a 200 when the base64 string is valid", async ({ expect }) => {
const result = await server.inject({
headers: {
"Content-Type": "text/plain",
},
method: "POST",
payload: "data:image/png;base64,s",
url: "/create/base64",
});
expect(result.statusCode).toBe(200);
expect(result.body).toStrictEqual(expect.any(String));
});
});

View File

@@ -1,7 +1,47 @@
import { RouteHandlerMethod } from "fastify";
import mime from "mime-types";
import { nanoid } from "nanoid";
import { keyv } from "../constants.js";
const handler: RouteHandlerMethod = async (request, reply) => {
console.log("createFromBase64");
const { body } = request;
if (!body) return reply.status(400).send("Invalid body");
if (typeof body !== "string") return reply.status(400).send("Invalid body");
const matches = body.match(/^data:(.+);base64,(.+)/);
if (!matches || matches.length === 0) return reply.status(400).send("Invalid base64 string");
const type = mime.extension(matches.at(1) as string);
if (!type) return reply.status(400).send("Invalid base64 string");
if (!["png", "jpeg", "jpg", "gif", "webp"].includes(type)) return reply.status(400).send("Supported types: png, jpeg, jpg, gif, webp");
const url = await keyv.get(body);
if (url) {
await Promise.all([
keyv.set(url, body, 30 * 60 * 1000),
keyv.set(body, url, 30 * 60 * 1000),
]);
reply.header("Cache-control", `public, max-age=${30 * 60}`);
return reply.send(process.env.HOST + url);
}
const uniqueId = `${nanoid(10)}.${type}`;
await Promise.all([
keyv.set(body, uniqueId, 30 * 60 * 1000),
keyv.set(uniqueId, body, 30 * 60 * 1000),
]);
reply.header("Cache-control", `public, max-age=${30 * 60}`);
return reply.send(process.env.HOST + uniqueId);
};
export default handler;

View File

@@ -0,0 +1,107 @@
import { readFile } from "node:fs/promises";
import { RequestOptions } from "node:http";
import { AddressInfo } from "node:net";
import { afterAll, beforeAll, describe, it } from "vitest";
import { createServer } from "../functions/createServer.js";
describe.concurrent("createFromImage", async () => {
const server = await createServer(),
form = new FormData(),
defaultRequestOptions: RequestOptions = {
hostname: "localhost",
method: "POST",
path: "/create/image",
protocol: "http:",
};
let url: string;
beforeAll(async () => {
url = await server.listen();
defaultRequestOptions.port = (server.server.address() as AddressInfo).port;
});
afterAll(() => {
server.close();
});
it("should return a 400 when request is not multipart", async ({ expect }) => {
const result = await fetch(`${url}/create/image`, {
method: "POST",
});
expect(result.status).toBe(400);
expect(await result.text()).toMatchInlineSnapshot("\"Request is not multipart\"");
});
it("should return a 400 status code when no file is provided", async ({ expect }) => {
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(400);
expect(await result.text()).toMatchInlineSnapshot("\"Invalid file\"");
});
it("should return a 400 status code when the file is invalid", async ({ expect }) => {
form.set("file", Buffer.alloc(1024 * 1024 * 2));
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(400);
expect(await result.text()).toMatchInlineSnapshot("\"Invalid file\"");
});
it("should return a 400 status code when the file is invalid", async ({ expect }) => {
form.set("file", new Blob([new Uint8Array(1024 * 1024 * 2)]));
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(400);
expect(await result.text()).toMatchInlineSnapshot("\"Invalid file\"");
});
it("should return a 400 status code when the file is not an image", async ({ expect }) => {
form.set("file", new Blob([await readFile(new URL("../../fixtures/test.mp4", import.meta.url))]));
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(400);
expect(await result.text()).toMatchInlineSnapshot("\"Only png, jpeg, jpg, gif and webp are supported\"");
});
it("should return a 200 status code when the file is valid", async ({ expect }) => {
form.set("file", new Blob([await readFile(new URL("../../fixtures/1x1.png", import.meta.url))]));
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(200);
expect(await result.text()).toStrictEqual(expect.any(String));
});
it("should return a 200 status code when the file is valid and the same file is uploaded again", async ({ expect }) => {
form.set("file", new Blob([await readFile(new URL("../../fixtures/1x1.png", import.meta.url))]));
const result = await fetch(`${url}/create/image`, {
body: form,
method: "POST",
});
expect(result.status).toBe(200);
expect(await result.text()).toStrictEqual(expect.any(String));
});
});

View File

@@ -1,7 +1,52 @@
import { RouteHandlerMethod } from "fastify";
import { fileTypeFromBuffer } from "file-type";
import { nanoid } from "nanoid";
import { keyv } from "../constants.js";
const handler: RouteHandlerMethod = async (request, reply) => {
console.log("createFromImage");
if (!request.isMultipart()) return reply.status(400).send("Request is not multipart");
const file = await request.file();
if (!file) return reply.status(400).send("Invalid file");
const type = await fileTypeFromBuffer(await file.toBuffer());
if (!type) return reply.status(400).send("Invalid file");
if (![
"image/png",
"image/jpeg",
"image/jpg",
"image/gif",
"image/webp",
].includes(type.mime))
return reply.status(400).send("Only png, jpeg, jpg, gif and webp are supported");
const buffer = await file.toBuffer(),
body = `data:${type.mime};base64,${buffer.toString("base64")}`,
url = await keyv.get(body);
if (url) {
await Promise.all([
keyv.set(url, body, 30 * 60 * 1000),
keyv.set(body, url, 30 * 60 * 1000),
]);
reply.header("Cache-control", `public, max-age=${30 * 60}`);
return reply.send(process.env.HOST + url);
}
const uniqueId = `${nanoid(10)}.${type.ext}`;
await Promise.all([
keyv.set(body, uniqueId, 30 * 60 * 1000),
keyv.set(uniqueId, body, 30 * 60 * 1000),
]);
reply.header("Cache-control", `public, max-age=${30 * 60}`);
return reply.send(process.env.HOST + uniqueId);
};
export default handler;

View File

@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { createServer } from "../functions/createServer.js";
describe("/create", async () => {
describe.concurrent("/create", async () => {
const server = await createServer();
it("should return a 400 status code when no URL is provided", async () => {

File diff suppressed because one or more lines are too long

View File

@@ -1,7 +1,8 @@
{
"extends": "./tsconfig.app.json",
"include": ["environment.d.ts"],
"include": ["environment.d.ts", "src"],
"compilerOptions": {
"noEmit": true
"noEmit": true,
"types": ["@types/node"]
}
}

View File

@@ -21,9 +21,10 @@
"license": "MPL-2.0",
"devDependencies": {
"@commitlint/cli": "^18.6.0",
"@recodive/configs": "^1.7.1",
"@recodive/eslint-config": "^1.7.1",
"@recodive/configs": "^1.7.5",
"@recodive/eslint-config": "^1.7.5",
"@rushstack/eslint-patch": "^1.7.2",
"@types/node": "^20.11.17",
"@vitest/coverage-v8": "^1.2.2",
"@vitest/ui": "^1.2.2",
"eslint": "^8.56.0",

201
pnpm-lock.yaml generated
View File

@@ -10,16 +10,19 @@ importers:
devDependencies:
'@commitlint/cli':
specifier: ^18.6.0
version: 18.6.0(@types/node@20.11.16)(typescript@5.3.3)
version: 18.6.0(@types/node@20.11.17)(typescript@5.3.3)
'@recodive/configs':
specifier: ^1.7.1
version: 1.7.1(@commitlint/lint@17.8.1)(@types/node@20.11.16)(typescript@5.3.3)
specifier: ^1.7.5
version: 1.7.5(@commitlint/lint@17.8.1)(@types/node@20.11.17)(typescript@5.3.3)
'@recodive/eslint-config':
specifier: ^1.7.1
version: 1.7.1(@rushstack/eslint-patch@1.7.2)(eslint@8.56.0)
specifier: ^1.7.5
version: 1.7.5(@rushstack/eslint-patch@1.7.2)(eslint@8.56.0)
'@rushstack/eslint-patch':
specifier: ^1.7.2
version: 1.7.2
'@types/node':
specifier: ^20.11.17
version: 20.11.17
'@vitest/coverage-v8':
specifier: ^1.2.2
version: 1.2.2(vitest@1.2.2)
@@ -40,13 +43,16 @@ importers:
version: 5.3.3
vitest:
specifier: ^1.2.2
version: 1.2.2(@types/node@20.11.16)(@vitest/ui@1.2.2)
version: 1.2.2(@types/node@20.11.17)(@vitest/ui@1.2.2)
apps/pd:
dependencies:
'@fastify/cors':
specifier: ^9.0.1
version: 9.0.1
'@fastify/multipart':
specifier: ^8.1.0
version: 8.1.0
'@fastify/rate-limit':
specifier: ^9.1.0
version: 9.1.0
@@ -56,6 +62,9 @@ importers:
fastify:
specifier: ^4.26.0
version: 4.26.0
file-type:
specifier: ^19.0.0
version: 19.0.0
got:
specifier: ^14.2.0
version: 14.2.0
@@ -68,9 +77,19 @@ importers:
keyv:
specifier: ^4.5.4
version: 4.5.4
mime-types:
specifier: ^2.1.35
version: 2.1.35
nanoid:
specifier: ^5.0.5
version: 5.0.5
devDependencies:
'@types/mime-types':
specifier: ^2.1.4
version: 2.1.4
form-data:
specifier: ^4.0.0
version: 4.0.0
packages:
@@ -135,14 +154,14 @@ packages:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true
/@commitlint/cli@18.6.0(@types/node@20.11.16)(typescript@5.3.3):
/@commitlint/cli@18.6.0(@types/node@20.11.17)(typescript@5.3.3):
resolution: {integrity: sha512-FiH23cr9QG8VdfbmvJJZmdfHGVMCouOOAzoXZ3Cd7czGC52RbycwNt8YCI7SA69pAl+t30vh8LMaO/N+kcel6w==}
engines: {node: '>=v18'}
hasBin: true
dependencies:
'@commitlint/format': 18.6.0
'@commitlint/lint': 18.6.0
'@commitlint/load': 18.6.0(@types/node@20.11.16)(typescript@5.3.3)
'@commitlint/load': 18.6.0(@types/node@20.11.17)(typescript@5.3.3)
'@commitlint/read': 18.6.0
'@commitlint/types': 18.6.0
execa: 5.1.1
@@ -236,7 +255,7 @@ packages:
'@commitlint/types': 18.6.0
dev: true
/@commitlint/load@18.6.0(@types/node@20.11.16)(typescript@5.3.3):
/@commitlint/load@18.6.0(@types/node@20.11.17)(typescript@5.3.3):
resolution: {integrity: sha512-RRssj7TmzT0bowoEKlgwg8uQ7ORXWkw7lYLsZZBMi9aInsJuGNLNWcMxJxRZbwxG3jkCidGUg85WmqJvRjsaDA==}
engines: {node: '>=v18'}
dependencies:
@@ -246,7 +265,7 @@ packages:
'@commitlint/types': 18.6.0
chalk: 4.1.2
cosmiconfig: 8.3.6(typescript@5.3.3)
cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.16)(cosmiconfig@8.3.6)(typescript@5.3.3)
cosmiconfig-typescript-loader: 5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -611,6 +630,13 @@ packages:
fast-uri: 2.3.0
dev: false
/@fastify/busboy@1.2.1:
resolution: {integrity: sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==}
engines: {node: '>=14'}
dependencies:
text-decoding: 1.0.0
dev: false
/@fastify/cors@9.0.1:
resolution: {integrity: sha512-YY9Ho3ovI+QHIL2hW+9X4XqQjXLjJqsU+sMV/xFsxZkE8p3GNnYVFpoOxF7SsP5ZL76gwvbo3V9L+FIekBGU4Q==}
dependencies:
@@ -618,6 +644,10 @@ packages:
mnemonist: 0.39.6
dev: false
/@fastify/deepmerge@1.3.0:
resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
dev: false
/@fastify/error@3.4.1:
resolution: {integrity: sha512-wWSvph+29GR783IhmvdwWnN4bUxTD01Vm5Xad4i7i1VuAOItLvbPAb69sb0IQ2N57yprvhNIwAP5B6xfKTmjmQ==}
dev: false
@@ -634,6 +664,17 @@ packages:
fast-deep-equal: 3.1.3
dev: false
/@fastify/multipart@8.1.0:
resolution: {integrity: sha512-sRX9X4ZhAqRbe2kDvXY2NK7i6Wf1Rm2g/CjpGYYM7+Np8E6uWQXcj761j08qPfPO8PJXM+vJ7yrKbK1GPB+OeQ==}
dependencies:
'@fastify/busboy': 1.2.1
'@fastify/deepmerge': 1.3.0
'@fastify/error': 3.4.1
fastify-plugin: 4.5.1
secure-json-parse: 2.7.0
stream-wormhole: 1.1.0
dev: false
/@fastify/rate-limit@9.1.0:
resolution: {integrity: sha512-h5dZWCkuZXN0PxwqaFQLxeln8/LNwQwH9popywmDCFdKfgpi4b/HoMH1lluy6P+30CG9yzzpSpwTCIPNB9T1JA==}
dependencies:
@@ -747,10 +788,10 @@ packages:
resolution: {integrity: sha512-2LuNTFBIO0m7kKIQvvPHN6UE63VjpmL9rnEEaOOaiSPbZK+zUOYIzBAWcED+3XYzhYsd/0mD57VdxAEqqV52CQ==}
dev: true
/@recodive/configs@1.7.1(@commitlint/lint@17.8.1)(@types/node@20.11.16)(typescript@5.3.3):
resolution: {integrity: sha512-SGP+xrzzH5dONUfaXAGG/oGk1e3GNFrCRwf5yOQNSNVtc3qJP/q5FC4RbJTBIkrmzfRiGqhSm4hX/gIBI+R3Eg==}
/@recodive/configs@1.7.5(@commitlint/lint@17.8.1)(@types/node@20.11.17)(typescript@5.3.3):
resolution: {integrity: sha512-Dxq4RW9We3rPibXYsG5PAGTk607NIchvQ2RgtCAOce9XxyQA1b/ydc6U/3oIs+drCmXrcwXPJIE5M3xfJt7EyQ==}
dependencies:
commitlint: 18.6.0(@types/node@20.11.16)(typescript@5.3.3)
commitlint: 18.6.0(@types/node@20.11.17)(typescript@5.3.3)
commitlint-plugin-function-rules: 2.0.2(@commitlint/lint@17.8.1)
prompts: 2.4.2
transitivePeerDependencies:
@@ -759,8 +800,8 @@ packages:
- typescript
dev: true
/@recodive/eslint-config@1.7.1(@rushstack/eslint-patch@1.7.2)(eslint@8.56.0):
resolution: {integrity: sha512-4ka0nitFGPOaiPk7IHeX5+br5HOr5l9pGQiPXRxZ/z1ifAOg2AqqduLup3ooLKAYgOYEeUWkIT3Wa25ZybiDsA==}
/@recodive/eslint-config@1.7.5(@rushstack/eslint-patch@1.7.2)(eslint@8.56.0):
resolution: {integrity: sha512-N5Cw+B/pLxZqgKGmv2G/hMnRL+ANZX+E21CFijdnmkQgsierAE3fM1jv8cDwWtmzyNxWjKXJgz9y44hB+XVdnQ==}
peerDependencies:
'@rushstack/eslint-patch': ^1.2.0
eslint: '>= 8.38.0'
@@ -974,6 +1015,10 @@ packages:
defer-to-connect: 2.0.1
dev: false
/@tokenizer/token@0.3.0:
resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
dev: false
/@types/estree@1.0.5:
resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==}
dev: true
@@ -990,12 +1035,16 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true
/@types/mime-types@2.1.4:
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
dev: true
/@types/minimist@1.2.5:
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
dev: true
/@types/node@20.11.16:
resolution: {integrity: sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==}
/@types/node@20.11.17:
resolution: {integrity: sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw==}
dependencies:
undici-types: 5.26.5
dev: true
@@ -1162,7 +1211,7 @@ packages:
std-env: 3.7.0
test-exclude: 6.0.0
v8-to-istanbul: 9.2.0
vitest: 1.2.2(@types/node@20.11.16)(@vitest/ui@1.2.2)
vitest: 1.2.2(@types/node@20.11.17)(@vitest/ui@1.2.2)
transitivePeerDependencies:
- supports-color
dev: true
@@ -1209,7 +1258,7 @@ packages:
pathe: 1.1.2
picocolors: 1.0.0
sirv: 2.0.4
vitest: 1.2.2(@types/node@20.11.16)(@vitest/ui@1.2.2)
vitest: 1.2.2(@types/node@20.11.17)(@vitest/ui@1.2.2)
dev: true
/@vitest/utils@1.2.2:
@@ -1351,6 +1400,10 @@ packages:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
dev: true
/asynckit@0.4.0:
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
dev: true
/atomic-sleep@1.0.0:
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
engines: {node: '>=8.0.0'}
@@ -1405,7 +1458,7 @@ packages:
hasBin: true
dependencies:
caniuse-lite: 1.0.30001585
electron-to-chromium: 1.4.662
electron-to-chromium: 1.4.665
node-releases: 2.0.14
update-browserslist-db: 1.0.13(browserslist@4.22.3)
dev: true
@@ -1551,6 +1604,13 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
dependencies:
delayed-stream: 1.0.0
dev: true
/commitlint-plugin-function-rules@2.0.2(@commitlint/lint@17.8.1):
resolution: {integrity: sha512-SZIn5H3pTzZo1wlef17lYeduFDUHklvtYW9+jFs3uoXkezLbxTN13CrV9g2I9NRc9bhrW8RdkOesV2dZA224sg==}
engines: {node: '>=16'}
@@ -1560,12 +1620,12 @@ packages:
'@commitlint/lint': 17.8.1
dev: true
/commitlint@18.6.0(@types/node@20.11.16)(typescript@5.3.3):
/commitlint@18.6.0(@types/node@20.11.17)(typescript@5.3.3):
resolution: {integrity: sha512-CiY4gz0ftWwr96aYvXfbCX0pvJ1mupdUL2HACzXyyb7YMRU4vodjFROCZ8ny4i5Dhpu8uRZerPPaGkUOZY/kVQ==}
engines: {node: '>=v18'}
hasBin: true
dependencies:
'@commitlint/cli': 18.6.0(@types/node@20.11.16)(typescript@5.3.3)
'@commitlint/cli': 18.6.0(@types/node@20.11.17)(typescript@5.3.3)
'@commitlint/types': 18.6.0
transitivePeerDependencies:
- '@types/node'
@@ -1634,7 +1694,7 @@ packages:
browserslist: 4.22.3
dev: true
/cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.16)(cosmiconfig@8.3.6)(typescript@5.3.3):
/cosmiconfig-typescript-loader@5.0.0(@types/node@20.11.17)(cosmiconfig@8.3.6)(typescript@5.3.3):
resolution: {integrity: sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA==}
engines: {node: '>=v16'}
peerDependencies:
@@ -1642,7 +1702,7 @@ packages:
cosmiconfig: '>=8.2'
typescript: '>=4'
dependencies:
'@types/node': 20.11.16
'@types/node': 20.11.17
cosmiconfig: 8.3.6(typescript@5.3.3)
jiti: 1.21.0
typescript: 5.3.3
@@ -1731,6 +1791,11 @@ packages:
engines: {node: '>=10'}
dev: false
/delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
dev: true
/denque@2.1.0:
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
engines: {node: '>=0.10'}
@@ -1762,8 +1827,8 @@ packages:
is-obj: 2.0.0
dev: true
/electron-to-chromium@1.4.662:
resolution: {integrity: sha512-gfl1XVWTQmPHhqEG0kN77SpUxaqPpMb9r83PT4gvKhg7P3irSxru3lW85RxvK1uI1j2CAcTWPjG/HbE0IP/Rtg==}
/electron-to-chromium@1.4.665:
resolution: {integrity: sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==}
dev: true
/emoji-regex@8.0.0:
@@ -2177,6 +2242,15 @@ packages:
flat-cache: 3.2.0
dev: true
/file-type@19.0.0:
resolution: {integrity: sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==}
engines: {node: '>=18'}
dependencies:
readable-web-to-node-stream: 3.0.2
strtok3: 7.0.0
token-types: 5.0.1
dev: false
/fill-range@7.0.1:
resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
engines: {node: '>=8'}
@@ -2227,6 +2301,15 @@ packages:
engines: {node: '>= 18'}
dev: false
/form-data@4.0.0:
resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
engines: {node: '>= 6'}
dependencies:
asynckit: 0.4.0
combined-stream: 1.0.8
mime-types: 2.1.35
dev: true
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
@@ -2450,7 +2533,6 @@ packages:
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/ini@1.3.8:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
@@ -2855,6 +2937,16 @@ packages:
picomatch: 2.3.1
dev: true
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
/mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
@@ -3133,6 +3225,11 @@ packages:
resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
dev: true
/peek-readable@5.0.0:
resolution: {integrity: sha512-YtCKvLUOvwtMGmrniQPdO7MwPjgkFBtFIrmfSbYmYuq3tKDV/mcfAhBth1+C3ru7uXIZasc/pHnb+YDYNkkj4A==}
engines: {node: '>=14.16'}
dev: false
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@@ -3306,7 +3403,6 @@ packages:
inherits: 2.0.4
string_decoder: 1.3.0
util-deprecate: 1.0.2
dev: true
/readable-stream@4.5.2:
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
@@ -3319,6 +3415,13 @@ packages:
string_decoder: 1.3.0
dev: false
/readable-web-to-node-stream@3.0.2:
resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
engines: {node: '>=8'}
dependencies:
readable-stream: 3.6.2
dev: false
/real-require@0.2.0:
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
engines: {node: '>= 12.13.0'}
@@ -3641,6 +3744,11 @@ packages:
resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
dev: true
/stream-wormhole@1.1.0:
resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==}
engines: {node: '>=4.0.0'}
dev: false
/string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -3690,6 +3798,14 @@ packages:
acorn: 8.11.3
dev: true
/strtok3@7.0.0:
resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==}
engines: {node: '>=14.16'}
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 5.0.0
dev: false
/supports-color@5.5.0:
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
engines: {node: '>=4'}
@@ -3718,6 +3834,10 @@ packages:
minimatch: 3.1.2
dev: true
/text-decoding@1.0.0:
resolution: {integrity: sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==}
dev: false
/text-extensions@1.9.0:
resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==}
engines: {node: '>=0.10'}
@@ -3779,6 +3899,14 @@ packages:
engines: {node: '>=12'}
dev: false
/token-types@5.0.1:
resolution: {integrity: sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==}
engines: {node: '>=14.16'}
dependencies:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
dev: false
/totalist@3.0.1:
resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==}
engines: {node: '>=6'}
@@ -3862,7 +3990,6 @@ packages:
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/v8-to-istanbul@9.2.0:
resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
@@ -3880,7 +4007,7 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
/vite-node@1.2.2(@types/node@20.11.16):
/vite-node@1.2.2(@types/node@20.11.17):
resolution: {integrity: sha512-1as4rDTgVWJO3n1uHmUYqq7nsFgINQ9u+mRcXpjeOMJUmviqNKjcZB7UfRZrlM7MjYXMKpuWp5oGkjaFLnjawg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -3889,7 +4016,7 @@ packages:
debug: 4.3.4
pathe: 1.1.2
picocolors: 1.0.0
vite: 5.1.0(@types/node@20.11.16)
vite: 5.1.0(@types/node@20.11.17)
transitivePeerDependencies:
- '@types/node'
- less
@@ -3901,7 +4028,7 @@ packages:
- terser
dev: true
/vite@5.1.0(@types/node@20.11.16):
/vite@5.1.0(@types/node@20.11.17):
resolution: {integrity: sha512-STmSFzhY4ljuhz14bg9LkMTk3d98IO6DIArnTY6MeBwiD1Za2StcQtz7fzOUnRCqrHSD5+OS2reg4HOz1eoLnw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -3929,7 +4056,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 20.11.16
'@types/node': 20.11.17
esbuild: 0.19.12
postcss: 8.4.35
rollup: 4.9.6
@@ -3937,7 +4064,7 @@ packages:
fsevents: 2.3.3
dev: true
/vitest@1.2.2(@types/node@20.11.16)(@vitest/ui@1.2.2):
/vitest@1.2.2(@types/node@20.11.17)(@vitest/ui@1.2.2):
resolution: {integrity: sha512-d5Ouvrnms3GD9USIK36KG8OZ5bEvKEkITFtnGv56HFaSlbItJuYr7hv2Lkn903+AvRAgSixiamozUVfORUekjw==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -3962,7 +4089,7 @@ packages:
jsdom:
optional: true
dependencies:
'@types/node': 20.11.16
'@types/node': 20.11.17
'@vitest/expect': 1.2.2
'@vitest/runner': 1.2.2
'@vitest/snapshot': 1.2.2
@@ -3982,8 +4109,8 @@ packages:
strip-literal: 1.3.0
tinybench: 2.6.0
tinypool: 0.8.2
vite: 5.1.0(@types/node@20.11.16)
vite-node: 1.2.2(@types/node@20.11.16)
vite: 5.1.0(@types/node@20.11.17)
vite-node: 1.2.2(@types/node@20.11.17)
why-is-node-running: 2.2.2
transitivePeerDependencies:
- less