diff --git a/.vscode/extensions.json b/.vscode/extensions.json
index de1f33f44b47..c354a1411cd2 100644
--- a/.vscode/extensions.json
+++ b/.vscode/extensions.json
@@ -2,6 +2,9 @@
"recommendations": [
"esbenp.prettier-vscode",
"vitest.explorer",
- "huntertran.auto-markdown-toc"
+ "huntertran.auto-markdown-toc",
+ "ms-vscode.vscode-typescript-next",
+ "dbaeumer.vscode-eslint",
+ "esbenp.prettier-vscode"
]
}
diff --git a/README.md b/README.md
index 077d57cf161d..fa5aad3e984c 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,23 @@
[![](https://github.com/monkeytypegame/monkeytype/blob/master/frontend/static/images/githubbanner2.png?raw=true)](https://monkeytype.com/)
-![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
-![SASS](https://img.shields.io/badge/SASS-hotpink.svg?style=for-the-badge&logo=SASS&logoColor=white)
+![ChartJs](https://img.shields.io/badge/Chart.js-FF6384?style=for-the-badge&logo=chartdotjs&logoColor=white)
+![Eslint](https://img.shields.io/badge/eslint-4B32C3?style=for-the-badge&logo=eslint&logoColor=white)
+![Express](https://img.shields.io/badge/-Express-373737?style=for-the-badge&logo=Express&logoColor=white)
+![Firebase](https://img.shields.io/badge/firebase-ffca28?style=for-the-badge&logo=firebase&logoColor=black)
+![Fontawesome](https://img.shields.io/badge/fontawesome-538DD7?style=for-the-badge&logo=fontawesome&logoColor=white)
![HTML5](https://img.shields.io/badge/html5-%23E34F26.svg?style=for-the-badge&logo=html5&logoColor=white)
+![JQuery](https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white)
+![MongoDB](https://img.shields.io/badge/-MongoDB-13aa52?style=for-the-badge&logo=mongodb&logoColor=white)
+![PNPM](https://img.shields.io/badge/pnpm-F69220?style=for-the-badge&logo=pnpm&logoColor=white)
+![Redis](https://img.shields.io/badge/Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white)
+![SASS](https://img.shields.io/badge/SASS-hotpink.svg?style=for-the-badge&logo=SASS&logoColor=white)
+![TsRest](https://img.shields.io/badge/-TSREST-9333ea?style=for-the-badge&logoColor=white&logo=data:image/svg%2bxml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjwhLS0gQ3JlYXRlZCB3aXRoIElua3NjYXBlIChodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy8pIC0tPgoKPHN2ZwogICB3aWR0aD0iMjAuMzA2Nzc4bW0iCiAgIGhlaWdodD0iMTIuMDgzMjMzbW0iCiAgIHZpZXdCb3g9IjAgMCAyMC4zMDY3NzggMTIuMDgzMjMzIgogICB2ZXJzaW9uPSIxLjEiCiAgIGlkPSJzdmcxIgogICB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIKICAgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIgogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPgogIDxzb2RpcG9kaTpuYW1lZHZpZXcKICAgICBpZD0ibmFtZWR2aWV3MSIKICAgICBwYWdlY29sb3I9IiM1MDUwNTAiCiAgICAgYm9yZGVyY29sb3I9IiNmZmZmZmYiCiAgICAgYm9yZGVyb3BhY2l0eT0iMSIKICAgICBpbmtzY2FwZTpzaG93cGFnZXNoYWRvdz0iMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMCIKICAgICBpbmtzY2FwZTpwYWdlY2hlY2tlcmJvYXJkPSIxIgogICAgIGlua3NjYXBlOmRlc2tjb2xvcj0iI2QxZDFkMSIKICAgICBpbmtzY2FwZTpkb2N1bWVudC11bml0cz0ibW0iIC8+CiAgPGRlZnMKICAgICBpZD0iZGVmczEiIC8+CiAgPGcKICAgICBpbmtzY2FwZTpsYWJlbD0iTGF5ZXIgMSIKICAgICBpbmtzY2FwZTpncm91cG1vZGU9ImxheWVyIgogICAgIGlkPSJsYXllcjEiCiAgICAgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTMuODE5ODA1NCwtMi4yMTQ3MTkzKSI+CiAgICA8cGF0aAogICAgICAgZD0ibSAxNS40NTgwMzUsOC45NzMzOTUzIDguNjMzMjUsMC4wNDQ4NyAwLjAwOSwtMS42NjgxOTggLTguNjMzMjIsLTAuMDQ0ODUgeiBtIDAuMDI2MywtNS4wNTYxMDggOC42MzMyNSwwLjA0NDg1IDAuMDA5LC0xLjcwMjU2OCAtOC42MzMyNSwtMC4wNDQ4NSB6IG0gLTAuMDQ0OCw4LjYzMzI0NzcgOC42MzMyMywwLjA0NDg1IC0wLjAwOSwxLjcwMjU2NyAtOC42MzMyNSwtMC4wNDQ4NSB6IgogICAgICAgZmlsbD0iI2ZmZmZmZiIKICAgICAgIGlkPSJwYXRoMSIKICAgICAgIHN0eWxlPSJzdHJva2Utd2lkdGg6MC4yNjQ1ODMiIC8+CiAgICA8cGF0aAogICAgICAgZD0ibSAxMS4xMTE3MjUsMTAuMjg2NjI4IGMgMS42NTEsLTAuNjE5MTI0NyAyLjU5Njg4LC0xLjk2MDU2MjcgMi41OTY4OCwtMy44MDA3Mzk3IDAsLTIuNjQ4NDc5IC0xLjkyNjE2LC00LjI0Nzg4NSAtNS4wNzMzNzk2LC00LjI0Nzg4NSBoIC00LjgxNTQyIHYgMS43MDI1OTQgaCA0Ljc0NjYzIGMgMi4wODA5Mzk2LDAgMy4xNjQ0MDk2LDAuOTI4Njg3IDMuMTY0NDA5NiwyLjU0NTI5MSAwLDEuNTk5NDA2IC0xLjA4MzQ3LDIuNTQ1MjkyIC0zLjE2NDQwOTYsMi41NDUyOTIgaCAtNC43NDY2MyB2IDUuMjQ1MzYzNyBoIDEuOTYwNTYgdiAtMy41NzcxNjYgaCAyLjg1NDg2IGMgMC4yMDYzNywwIDAuNDI5OTUsMCAwLjYxOTEyLC0wLjAxNzIgbCAyLjUyODA5OTYsMy41OTQzNjQgaCAyLjEzMjU0IHoiCiAgICAgICBmaWxsPSIjZmZmZmZmIgogICAgICAgaWQ9InBhdGgyIgogICAgICAgc3R5bGU9InN0cm9rZS13aWR0aDowLjI2NDU4MyIgLz4KICA8L2c+Cjwvc3ZnPgo=)
+![Turborepo](https://img.shields.io/badge/-Turborepo-EF4444?style=for-the-badge&logo=turborepo&logoColor=white)
+![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white)
+![Vite](https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=Vite&logoColor=white)
+![Vitest](https://img.shields.io/badge/vitest-6E9F18?style=for-the-badge&logo=vitest&logoColor=white)
+![Zod](https://img.shields.io/badge/-Zod-3E67B1?style=for-the-badge&logo=zod&logoColor=white)
# About
diff --git a/backend/__tests__/api/controllers/ape-key.spec.ts b/backend/__tests__/api/controllers/ape-key.spec.ts
index 4855ee5f4109..5f33d32fea25 100644
--- a/backend/__tests__/api/controllers/ape-key.spec.ts
+++ b/backend/__tests__/api/controllers/ape-key.spec.ts
@@ -15,7 +15,7 @@ describe("ApeKeyController", () => {
beforeEach(async () => {
await enableApeKeysEndpoints(true);
- getUserMock.mockResolvedValue(user(uid, { canManageApeKeys: true }));
+ getUserMock.mockResolvedValue(user(uid, {}));
vi.useFakeTimers();
vi.setSystemTime(1000);
});
diff --git a/backend/__tests__/api/controllers/psa.spec.ts b/backend/__tests__/api/controllers/psa.spec.ts
new file mode 100644
index 000000000000..9dcff487fda4
--- /dev/null
+++ b/backend/__tests__/api/controllers/psa.spec.ts
@@ -0,0 +1,80 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as PsaDal from "../../../src/dal/psa";
+import * as Prometheus from "../../../src/utils/prometheus";
+import { ObjectId } from "mongodb";
+const mockApp = request(app);
+
+describe("Psa Controller", () => {
+ describe("get psa", () => {
+ const getPsaMock = vi.spyOn(PsaDal, "get");
+ const recordClientVersionMock = vi.spyOn(Prometheus, "recordClientVersion");
+
+ afterEach(() => {
+ getPsaMock.mockReset();
+ recordClientVersionMock.mockReset();
+ });
+
+ it("get psas without authorization", async () => {
+ //GIVEN
+ const psaOne: PsaDal.DBPSA = {
+ _id: new ObjectId(),
+ message: "test2",
+ date: 1000,
+ level: 1,
+ sticky: true,
+ };
+ const psaTwo: PsaDal.DBPSA = {
+ _id: new ObjectId(),
+ message: "test2",
+ date: 2000,
+ level: 2,
+ sticky: false,
+ };
+ getPsaMock.mockResolvedValue([psaOne, psaTwo]);
+
+ //WHEN
+ const { body } = await mockApp.get("/psas").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "PSAs retrieved",
+ data: [
+ {
+ _id: psaOne._id.toHexString(),
+ date: 1000,
+ level: 1,
+ message: "test2",
+ sticky: true,
+ },
+ {
+ _id: psaTwo._id.toHexString(),
+ date: 2000,
+ level: 2,
+ message: "test2",
+ sticky: false,
+ },
+ ],
+ });
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("unknown");
+ });
+ it("get psas with authorization", async () => {
+ await mockApp
+ .get("/psas")
+ .set("authorization", `Uid 123456789`)
+ .expect(200);
+ });
+
+ it("get psas records x-client-version", async () => {
+ await mockApp.get("/psas").set("x-client-version", "1.0").expect(200);
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("1.0");
+ });
+ it("get psas records client-version", async () => {
+ await mockApp.get("/psas").set("client-version", "2.0").expect(200);
+
+ expect(recordClientVersionMock).toHaveBeenCalledWith("2.0");
+ });
+ });
+});
diff --git a/backend/__tests__/api/controllers/public.spec.ts b/backend/__tests__/api/controllers/public.spec.ts
new file mode 100644
index 000000000000..accc90a9acb1
--- /dev/null
+++ b/backend/__tests__/api/controllers/public.spec.ts
@@ -0,0 +1,144 @@
+import request from "supertest";
+import app from "../../../src/app";
+import * as PublicDal from "../../../src/dal/public";
+const mockApp = request(app);
+
+describe("PublicController", () => {
+ describe("get speed histogram", () => {
+ const getSpeedHistogramMock = vi.spyOn(PublicDal, "getSpeedHistogram");
+
+ afterEach(() => {
+ getSpeedHistogramMock.mockReset();
+ });
+
+ it("gets for english time 60", async () => {
+ //GIVEN
+ getSpeedHistogramMock.mockResolvedValue({ "0": 1, "10": 2 });
+
+ //WHEN
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode: "time", mode2: "60" });
+ //.expect(200);
+ console.log(body);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Public speed histogram retrieved",
+ data: { "0": 1, "10": 2 },
+ });
+
+ expect(getSpeedHistogramMock).toHaveBeenCalledWith(
+ "english",
+ "time",
+ "60"
+ );
+ });
+
+ it("gets for mode", async () => {
+ for (const mode of ["time", "words", "quote", "zen", "custom"]) {
+ const response = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode, mode2: "custom" });
+ expect(response.status, "for mode " + mode).toEqual(200);
+ }
+ });
+
+ it("gets for mode2", async () => {
+ for (const mode2 of [
+ "10",
+ "25",
+ "50",
+ "100",
+ "15",
+ "30",
+ "60",
+ "120",
+ "zen",
+ "custom",
+ ]) {
+ const response = await mockApp
+ .get("/public/speedHistogram")
+ .query({ language: "english", mode: "words", mode2 });
+
+ expect(response.status, "for mode2 " + mode2).toEqual(200);
+ }
+ });
+ it("fails for missing query", async () => {
+ const { body } = await mockApp.get("/public/speedHistogram").expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Required',
+ '"mode" Required',
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for invalid query", async () => {
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({
+ language: "en?gli.sh",
+ mode: "unknownMode",
+ mode2: "unknownMode2",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: [
+ '"language" Invalid',
+ `"mode" Invalid enum value. Expected 'time' | 'words' | 'quote' | 'custom' | 'zen', received 'unknownMode'`,
+ '"mode2" Needs to be either a number, "zen" or "custom."',
+ ],
+ });
+ });
+ it("fails for unknown query", async () => {
+ const { body } = await mockApp
+ .get("/public/speedHistogram")
+ .query({
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ extra: "value",
+ })
+ .expect(422);
+
+ expect(body).toEqual({
+ message: "Invalid query schema",
+ validationErrors: ["Unrecognized key(s) in object: 'extra'"],
+ });
+ });
+ });
+ describe("get typing stats", () => {
+ const getTypingStatsMock = vi.spyOn(PublicDal, "getTypingStats");
+
+ afterEach(() => {
+ getTypingStatsMock.mockReset();
+ });
+
+ it("gets without authentication", async () => {
+ //GIVEN
+ getTypingStatsMock.mockResolvedValue({
+ testsCompleted: 23,
+ testsStarted: 42,
+ timeTyping: 1000,
+ } as any);
+
+ //WHEN
+ const { body } = await mockApp.get("/public/typingStats").expect(200);
+
+ //THEN
+ expect(body).toEqual({
+ message: "Public typing stats retrieved",
+ data: {
+ testsCompleted: 23,
+ testsStarted: 42,
+ timeTyping: 1000,
+ },
+ });
+ });
+ });
+});
diff --git a/backend/__tests__/dal/admin-uids.spec.ts b/backend/__tests__/dal/admin-uids.spec.ts
index aecf35355882..daadbbcab537 100644
--- a/backend/__tests__/dal/admin-uids.spec.ts
+++ b/backend/__tests__/dal/admin-uids.spec.ts
@@ -6,7 +6,7 @@ describe("AdminUidsDal", () => {
it("should return true for existing admin user", async () => {
//GIVEN
const uid = new ObjectId().toHexString();
- AdminUidsDal.getCollection().insertOne({
+ await AdminUidsDal.getCollection().insertOne({
_id: new ObjectId(),
uid: uid,
});
@@ -17,7 +17,7 @@ describe("AdminUidsDal", () => {
it("should return false for non-existing admin user", async () => {
//GIVEN
- AdminUidsDal.getCollection().insertOne({
+ await AdminUidsDal.getCollection().insertOne({
_id: new ObjectId(),
uid: "admin",
});
diff --git a/backend/__tests__/utils/misc.spec.ts b/backend/__tests__/utils/misc.spec.ts
index 71c32cad5a5c..a379e0fb07f8 100644
--- a/backend/__tests__/utils/misc.spec.ts
+++ b/backend/__tests__/utils/misc.spec.ts
@@ -649,5 +649,8 @@ describe("Misc Utils", () => {
},
]);
});
+ it("handles undefined", () => {
+ expect(misc.replaceObjectIds(undefined as any)).toBeUndefined();
+ });
});
});
diff --git a/backend/package.json b/backend/package.json
index 7a36cc3069f1..cf0d4edea2d8 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -12,11 +12,11 @@
"start": "node ./dist/server.js",
"test": "vitest run",
"test-coverage": "vitest run --coverage",
- "dev": "concurrently \"tsx watch --clear-screen=false --inspect ./src/server.ts\" \"tsc --preserveWatchOutput --noEmit --watch\" \"npx eslint-watch \"./src/**/*.ts\"\"",
+ "dev": "concurrently -p none \"tsx watch --clear-screen=false --inspect ./src/server.ts\" \"tsc --preserveWatchOutput --noEmit --watch\" \"esw src/ -w --ext .ts --cache --color\"",
"knip": "knip",
"docker-db-only": "docker compose -f docker/compose.db-only.yml up",
"docker": "docker compose -f docker/compose.yml up",
- "gen-docs": "tsx scripts/openapi.ts dist/static/api/openapi.json && redocly build-docs -o dist/static/api/internal.html internal@v2 && redocly bundle -o dist/static/api/public.json public-filter && redocly build-docs -o dist/static/api/public.html public@v2"
+ "gen-docs": "tsx scripts/openapi.ts dist/static/api/openapi.json && openapi-recursive-tagging dist/static/api/openapi.json dist/static/api/openapi-tagged.json && redocly build-docs -o dist/static/api/internal.html internal@v2 && redocly bundle -o dist/static/api/public.json public-filter && redocly build-docs -o dist/static/api/public.html public@v2"
},
"engines": {
"node": "20.16.0"
@@ -90,6 +90,7 @@
"eslint": "8.57.0",
"eslint-watch": "8.0.0",
"ioredis-mock": "7.4.0",
+ "openapi-recursive-tagging": "0.0.6",
"readline-sync": "1.4.10",
"supertest": "6.2.3",
"tsx": "4.16.2",
diff --git a/backend/redocly.yaml b/backend/redocly.yaml
index 7bacf646af47..1898e389184a 100644
--- a/backend/redocly.yaml
+++ b/backend/redocly.yaml
@@ -5,7 +5,7 @@ apis:
internal@v2:
root: dist/static/api/openapi.json
public-filter:
- root: dist/static/api/openapi.json
+ root: dist/static/api/openapi-tagged.json
decorators:
filter-in:
property: x-public
diff --git a/backend/scripts/openapi.ts b/backend/scripts/openapi.ts
index 75317f4c8ce2..b5140de6c76c 100644
--- a/backend/scripts/openapi.ts
+++ b/backend/scripts/openapi.ts
@@ -66,6 +66,16 @@ export function getOpenApi(): OpenAPIObject {
description: "Ape keys provide access to certain API endpoints.",
"x-displayName": "Ape Keys",
},
+ {
+ name: "public",
+ description: "Public endpoints such as typing stats.",
+ "x-displayName": "public",
+ },
+ {
+ name: "psas",
+ description: "Public service announcements.",
+ "x-displayName": "PSAs",
+ },
{
name: "admin",
description:
diff --git a/backend/src/api/controllers/psa.ts b/backend/src/api/controllers/psa.ts
index 18a3e31b93d9..721527eef8ea 100644
--- a/backend/src/api/controllers/psa.ts
+++ b/backend/src/api/controllers/psa.ts
@@ -1,7 +1,11 @@
+import { GetPsaResponse } from "@monkeytype/contracts/psas";
import * as PsaDAL from "../../dal/psa";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
+import { replaceObjectIds } from "../../utils/misc";
-export async function getPsas(): Promise {
+export async function getPsas(
+ _req: MonkeyTypes.Request2
+): Promise {
const data = await PsaDAL.get();
- return new MonkeyResponse("PSAs retrieved", data);
+ return new MonkeyResponse2("PSAs retrieved", replaceObjectIds(data));
}
diff --git a/backend/src/api/controllers/public.ts b/backend/src/api/controllers/public.ts
index f49dca0310ff..505f5caa9bba 100644
--- a/backend/src/api/controllers/public.ts
+++ b/backend/src/api/controllers/public.ts
@@ -1,21 +1,22 @@
+import {
+ GetSpeedHistogramQuery,
+ GetSpeedHistogramResponse,
+ GetTypingStatsResponse,
+} from "@monkeytype/contracts/public";
import * as PublicDAL from "../../dal/public";
-import { MonkeyResponse } from "../../utils/monkey-response";
+import { MonkeyResponse2 } from "../../utils/monkey-response";
-export async function getPublicSpeedHistogram(
- req: MonkeyTypes.Request
-): Promise {
+export async function getSpeedHistogram(
+ req: MonkeyTypes.Request2
+): Promise {
const { language, mode, mode2 } = req.query;
- const data = await PublicDAL.getSpeedHistogram(
- language as string,
- mode as string,
- mode2 as string
- );
- return new MonkeyResponse("Public speed histogram retrieved", data);
+ const data = await PublicDAL.getSpeedHistogram(language, mode, mode2);
+ return new MonkeyResponse2("Public speed histogram retrieved", data);
}
-export async function getPublicTypingStats(
- _req: MonkeyTypes.Request
-): Promise {
+export async function getTypingStats(
+ _req: MonkeyTypes.Request2
+): Promise {
const data = await PublicDAL.getTypingStats();
- return new MonkeyResponse("Public typing stats retrieved", data);
+ return new MonkeyResponse2("Public typing stats retrieved", data);
}
diff --git a/backend/src/api/controllers/user.ts b/backend/src/api/controllers/user.ts
index 11bc33b8ebe4..0bca837afe17 100644
--- a/backend/src/api/controllers/user.ts
+++ b/backend/src/api/controllers/user.ts
@@ -40,8 +40,18 @@ import { addImportantLog, addLog, deleteUserLogs } from "../../dal/logs";
import { sendForgotPasswordEmail as authSendForgotPasswordEmail } from "../../utils/auth";
async function verifyCaptcha(captcha: string): Promise {
- if (!(await verify(captcha))) {
- throw new MonkeyError(422, "Captcha check failed");
+ let verified = false;
+ try {
+ verified = await verify(captcha);
+ } catch (e) {
+ //fetch to recaptcha api can sometimes fail
+ throw new MonkeyError(
+ 422,
+ "Request to the Captcha API failed, please try again later"
+ );
+ }
+ if (!verified) {
+ throw new MonkeyError(422, "Captcha challenge failed");
}
}
diff --git a/backend/src/api/routes/ape-keys.ts b/backend/src/api/routes/ape-keys.ts
index e6f85650d61b..2038c887a0a1 100644
--- a/backend/src/api/routes/ape-keys.ts
+++ b/backend/src/api/routes/ape-keys.ts
@@ -15,7 +15,7 @@ const commonMiddleware = [
}),
checkUserPermissions({
criteria: (user) => {
- return user.canManageApeKeys ?? false;
+ return user.canManageApeKeys ?? true;
},
invalidMessage: "You have lost access to ape keys, please contact support",
}),
diff --git a/backend/src/api/routes/docs.ts b/backend/src/api/routes/docs.ts
index 6c02825a0cc3..5892e1eda64f 100644
--- a/backend/src/api/routes/docs.ts
+++ b/backend/src/api/routes/docs.ts
@@ -1,4 +1,4 @@
-import { Router } from "express";
+import { Response, Router } from "express";
import * as swaggerUi from "swagger-ui-express";
import publicSwaggerSpec from "../../documentation/public-swagger.json";
@@ -12,6 +12,7 @@ const router = Router();
const root = __dirname + "../../../static";
router.use("/v2/internal", (req, res) => {
+ setCsp(res);
res.sendFile("api/internal.html", { root });
});
@@ -21,6 +22,7 @@ router.use("/v2/internal.json", (req, res) => {
});
router.use(["/v2/public", "/v2/"], (req, res) => {
+ setCsp(res);
res.sendFile("api/public.html", { root });
});
@@ -38,3 +40,10 @@ router.use(
);
export default router;
+
+function setCsp(res: Response): void {
+ res.setHeader(
+ "Content-Security-Policy",
+ "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' monkeytype.com cdn.redoc.ly data:;object-src 'none';script-src 'self' cdn.redoc.ly 'unsafe-inline'; worker-src blob: data;script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
+ );
+}
diff --git a/backend/src/api/routes/index.ts b/backend/src/api/routes/index.ts
index 6f3b22b6c375..659a541804cc 100644
--- a/backend/src/api/routes/index.ts
+++ b/backend/src/api/routes/index.ts
@@ -19,7 +19,6 @@ import leaderboards from "./leaderboards";
import addSwaggerMiddlewares from "./swagger";
import { asyncHandler } from "../../middlewares/utility";
import { MonkeyResponse } from "../../utils/monkey-response";
-import { recordClientVersion } from "../../utils/prometheus";
import {
Application,
IRouter,
@@ -43,8 +42,6 @@ const APP_START_TIME = Date.now();
const API_ROUTE_MAP = {
"/users": users,
"/results": results,
- "/psas": psas,
- "/public": publicStats,
"/leaderboards": leaderboards,
"/quotes": quotes,
"/webhooks": webhooks,
@@ -57,6 +54,8 @@ const router = s.router(contract, {
apeKeys,
configs,
presets,
+ psas,
+ public: publicStats,
});
export function addApiRoutes(app: Application): void {
@@ -78,16 +77,29 @@ export function addApiRoutes(app: Application): void {
function applyTsRestApiRoutes(app: IRouter): void {
createExpressEndpoints(contract, router, app, {
jsonQuery: true,
- requestValidationErrorHandler(err, req, res, next) {
- if (err.body?.issues === undefined) {
+ requestValidationErrorHandler(err, _req, res, next) {
+ let message: string | undefined = undefined;
+ let validationErrors: string[] | undefined = undefined;
+
+ if (err.pathParams?.issues !== undefined) {
+ message = "Invalid path parameter schema";
+ validationErrors = err.pathParams.issues.map(prettyErrorMessage);
+ } else if (err.query?.issues !== undefined) {
+ message = "Invalid query schema";
+ validationErrors = err.query.issues.map(prettyErrorMessage);
+ } else if (err.body?.issues !== undefined) {
+ message = "Invalid request data schema";
+ validationErrors = err.body.issues.map(prettyErrorMessage);
+ }
+
+ if (message !== undefined) {
+ res
+ .status(422)
+ .json({ message, validationErrors } as MonkeyValidationError);
+ } else {
next();
return;
}
- const issues = err.body?.issues.map(prettyErrorMessage);
- res.status(422).json({
- message: "Invalid request data schema",
- validationErrors: issues,
- } as MonkeyValidationError);
},
globalMiddleware: [authenticateTsRestRequest()],
});
@@ -139,20 +151,6 @@ function applyApiRoutes(app: Application): void {
return;
}
- if (req.path === "/psas") {
- const clientVersion =
- (req.headers["x-client-version"] as string) ||
- req.headers["client-version"];
- recordClientVersion(clientVersion?.toString() ?? "unknown");
- }
-
- if (req.path.startsWith("/docs")) {
- res.setHeader(
- "Content-Security-Policy",
- "default-src 'self';base-uri 'self';block-all-mixed-content;font-src 'self' https: data:;frame-ancestors 'self';img-src 'self' monkeytype.com cdn.redoc.ly data:;object-src 'none';script-src 'self' cdn.redoc.ly 'unsafe-inline'; worker-src blob: data;script-src-attr 'none';style-src 'self' https: 'unsafe-inline';upgrade-insecure-requests"
- );
- }
-
next();
}
);
diff --git a/backend/src/api/routes/psas.ts b/backend/src/api/routes/psas.ts
index 6fbbd67598bd..093b4c870e06 100644
--- a/backend/src/api/routes/psas.ts
+++ b/backend/src/api/routes/psas.ts
@@ -1,10 +1,14 @@
-import { Router } from "express";
-import * as PsaController from "../controllers/psa";
+import { psasContract } from "@monkeytype/contracts/psas";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import { asyncHandler } from "../../middlewares/utility";
-
-const router = Router();
-
-router.get("/", RateLimit.psaGet, asyncHandler(PsaController.getPsas));
+import * as PsaController from "../controllers/psa";
+import { callController } from "../ts-rest-adapter";
+import { recordClientVersion } from "../../middlewares/utility";
-export default router;
+const s = initServer();
+export default s.router(psasContract, {
+ get: {
+ middleware: [recordClientVersion(), RateLimit.psaGet],
+ handler: async (r) => callController(PsaController.getPsas)(r),
+ },
+});
diff --git a/backend/src/api/routes/public.ts b/backend/src/api/routes/public.ts
index 86491c6b346a..b5e310fb72e4 100644
--- a/backend/src/api/routes/public.ts
+++ b/backend/src/api/routes/public.ts
@@ -1,41 +1,17 @@
-import { Router } from "express";
-import * as PublicController from "../controllers/public";
+import { publicContract } from "@monkeytype/contracts/public";
+import { initServer } from "@ts-rest/express";
import * as RateLimit from "../../middlewares/rate-limit";
-import { asyncHandler } from "../../middlewares/utility";
-import joi from "joi";
-import { validateRequest } from "../../middlewares/validation";
-
-const GET_MODE_STATS_VALIDATION_SCHEMA = {
- language: joi
- .string()
- .max(50)
- .pattern(/^[a-zA-Z0-9_+]+$/)
- .required(),
- mode: joi
- .string()
- .valid("time", "words", "quote", "zen", "custom")
- .required(),
- mode2: joi
- .string()
- .regex(/^(\d)+|custom|zen/)
- .required(),
-};
-
-const router = Router();
-
-router.get(
- "/speedHistogram",
- RateLimit.publicStatsGet,
- validateRequest({
- query: GET_MODE_STATS_VALIDATION_SCHEMA,
- }),
- asyncHandler(PublicController.getPublicSpeedHistogram)
-);
-
-router.get(
- "/typingStats",
- RateLimit.publicStatsGet,
- asyncHandler(PublicController.getPublicTypingStats)
-);
+import * as PublicController from "../controllers/public";
+import { callController } from "../ts-rest-adapter";
-export default router;
+const s = initServer();
+export default s.router(publicContract, {
+ getSpeedHistogram: {
+ middleware: [RateLimit.publicStatsGet],
+ handler: async (r) => callController(PublicController.getSpeedHistogram)(r),
+ },
+ getTypingStats: {
+ middleware: [RateLimit.publicStatsGet],
+ handler: async (r) => callController(PublicController.getTypingStats)(r),
+ },
+});
diff --git a/backend/src/constants/profanities.ts b/backend/src/constants/profanities.ts
index e653327d1ba8..ef7330efb926 100644
--- a/backend/src/constants/profanities.ts
+++ b/backend/src/constants/profanities.ts
@@ -312,7 +312,6 @@ export const profanities = [
"feces",
"felcher",
"ficken",
- "fitt",
"flikker",
"foreskin",
"fotze",
diff --git a/backend/src/dal/psa.ts b/backend/src/dal/psa.ts
index a343c49dd59c..904c30b8a09a 100644
--- a/backend/src/dal/psa.ts
+++ b/backend/src/dal/psa.ts
@@ -1,7 +1,7 @@
-import { PSA } from "@monkeytype/shared-types";
+import { PSA } from "@monkeytype/contracts/schemas/psas";
import * as db from "../init/db";
-type DBPSA = MonkeyTypes.WithObjectId;
+export type DBPSA = MonkeyTypes.WithObjectId;
export async function get(): Promise {
return await db.collection("psa").find().toArray();
diff --git a/backend/src/dal/public.ts b/backend/src/dal/public.ts
index 08b72edd0e4a..50a230a7867c 100644
--- a/backend/src/dal/public.ts
+++ b/backend/src/dal/public.ts
@@ -1,10 +1,13 @@
import * as db from "../init/db";
import { roundTo2 } from "../utils/misc";
import MonkeyError from "../utils/error";
-import { PublicTypingStats, SpeedHistogram } from "@monkeytype/shared-types";
+import {
+ TypingStats,
+ SpeedHistogram,
+} from "@monkeytype/contracts/schemas/public";
-type PublicTypingStatsDB = PublicTypingStats & { _id: "stats" };
-type PublicSpeedStatsDB = {
+export type PublicTypingStatsDB = TypingStats & { _id: "stats" };
+export type PublicSpeedStatsDB = {
_id: "speedStatsHistogram";
english_time_15: SpeedHistogram;
english_time_60: SpeedHistogram;
diff --git a/backend/src/documentation/internal-swagger.json b/backend/src/documentation/internal-swagger.json
index d19a4fc74479..a999ae0da4cd 100644
--- a/backend/src/documentation/internal-swagger.json
+++ b/backend/src/documentation/internal-swagger.json
@@ -23,10 +23,6 @@
"name": "users",
"description": "User data and related operations"
},
- {
- "name": "psas",
- "description": "Public service announcements"
- },
{
"name": "leaderboards",
"description": "Leaderboard data"
@@ -416,20 +412,6 @@
}
}
},
- "/psas": {
- "get": {
- "tags": ["psas"],
- "summary": "Gets the latest public service announcements",
- "responses": {
- "default": {
- "description": "",
- "schema": {
- "$ref": "#/definitions/Response"
- }
- }
- }
- }
- },
"/leaderboards": {
"get": {
"tags": ["leaderboards"],
diff --git a/backend/src/middlewares/utility.ts b/backend/src/middlewares/utility.ts
index bbfa5e87ccff..ff443dc3ef66 100644
--- a/backend/src/middlewares/utility.ts
+++ b/backend/src/middlewares/utility.ts
@@ -1,7 +1,8 @@
import _ from "lodash";
-import type { Response, NextFunction, RequestHandler } from "express";
+import type { Request, Response, NextFunction, RequestHandler } from "express";
import { handleMonkeyResponse, MonkeyResponse } from "../utils/monkey-response";
import { isDevEnvironment } from "../utils/misc";
+import { recordClientVersion as prometheusRecordClientVersion } from "../utils/prometheus";
export const emptyMiddleware = (
_req: MonkeyTypes.Request,
@@ -45,3 +46,18 @@ export function useInProduction(
isDevEnvironment() ? emptyMiddleware : middleware
);
}
+
+/**
+ * record the client version from the `x-client-version` or ` client-version` header to prometheus
+ */
+export function recordClientVersion(): RequestHandler {
+ return (req: Request, _res: Response, next: NextFunction) => {
+ const clientVersion =
+ (req.headers["x-client-version"] as string) ||
+ req.headers["client-version"];
+
+ prometheusRecordClientVersion(clientVersion?.toString() ?? "unknown");
+
+ next();
+ };
+}
diff --git a/backend/src/utils/misc.ts b/backend/src/utils/misc.ts
index 5b91628d2738..26e83cf772be 100644
--- a/backend/src/utils/misc.ts
+++ b/backend/src/utils/misc.ts
@@ -331,5 +331,6 @@ export function replaceObjectId(
export function replaceObjectIds(
data: T[]
): (T & { _id: string })[] {
+ if (data === undefined) return data;
return data.map((it) => replaceObjectId(it));
}
diff --git a/backend/src/utils/pb.ts b/backend/src/utils/pb.ts
index 76041605d017..b00d747a5765 100644
--- a/backend/src/utils/pb.ts
+++ b/backend/src/utils/pb.ts
@@ -3,7 +3,6 @@ import FunboxList from "../constants/funbox-list";
import { DBResult } from "@monkeytype/shared-types";
import {
Mode,
- Mode2,
PersonalBest,
PersonalBests,
} from "@monkeytype/contracts/schemas/shared";
@@ -39,7 +38,7 @@ export function checkAndUpdatePb(
result: Result
): CheckAndUpdatePbResult {
const mode = result.mode;
- const mode2 = result.mode2 as Mode2<"time">;
+ const mode2 = result.mode2;
const userPb = userPersonalBests ?? {};
userPb[mode] ??= {};
@@ -175,7 +174,7 @@ function updateLeaderboardPersonalBests(
}
const mode = result.mode;
- const mode2 = result.mode2 as Mode2<"time">;
+ const mode2 = result.mode2;
lbPersonalBests[mode] = lbPersonalBests[mode] ?? {};
const lbMode2 = lbPersonalBests[mode][mode2] as MonkeyTypes.LbPersonalBests;
diff --git a/backend/src/utils/prometheus.ts b/backend/src/utils/prometheus.ts
index f98fdc90c904..040e1492ed42 100644
--- a/backend/src/utils/prometheus.ts
+++ b/backend/src/utils/prometheus.ts
@@ -105,7 +105,7 @@ export function incrementResult(res: Result): void {
punctuation,
} = res;
- let m2 = mode2 as string;
+ let m2 = mode2;
if (mode === "time" && !["15", "30", "60", "120"].includes(mode2)) {
m2 = "custom";
}
diff --git a/frontend/src/html/pages/settings.html b/frontend/src/html/pages/settings.html
index db8520dcaa62..a4d38b440c85 100644
--- a/frontend/src/html/pages/settings.html
+++ b/frontend/src/html/pages/settings.html
@@ -1566,7 +1566,7 @@
(
diff --git a/frontend/src/styles/test.scss b/frontend/src/styles/test.scss
index 3a677f3e88ae..dfdaf1149877 100644
--- a/frontend/src/styles/test.scss
+++ b/frontend/src/styles/test.scss
@@ -262,7 +262,7 @@
}
&.tape .word {
- margin: 0.25em 0.5em 0.75em 0;
+ margin: 0.25em 0.6em 0.75em 0;
}
/* a little hack for right-to-left languages */
@@ -451,7 +451,7 @@
position: relative;
font-size: 1em;
line-height: 1em;
- margin: 0.25em;
+ margin: 0.25em 0.3em;
font-variant: no-common-ligatures;
border-bottom: 2px solid transparent;
letter {
diff --git a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts
index ded38548e18b..47904cf51085 100644
--- a/frontend/src/ts/ape/adapters/ts-rest-adapter.ts
+++ b/frontend/src/ts/ape/adapters/ts-rest-adapter.ts
@@ -3,7 +3,6 @@ import { Method } from "axios";
import { getIdToken } from "firebase/auth";
import { envConfig } from "../../constants/env-config";
import { getAuthenticatedUser, isAuthenticated } from "../../firebase";
-import { EndpointMetadata } from "@monkeytype/contracts/schemas/api";
function timeoutSignal(ms: number): AbortSignal {
const ctrl = new AbortController();
@@ -17,20 +16,14 @@ function buildApi(timeout: number): (args: ApiFetcherArgs) => Promise<{
headers: Headers;
}> {
return async (request: ApiFetcherArgs) => {
- const isPublicEndpoint =
- (request.route.metadata as EndpointMetadata | undefined)
- ?.authenticationOptions?.isPublic ?? false;
-
try {
const headers: HeadersInit = {
...request.headers,
"X-Client-Version": envConfig.clientVersion,
};
- if (!isPublicEndpoint) {
- const token = isAuthenticated()
- ? await getIdToken(getAuthenticatedUser())
- : "";
+ if (isAuthenticated()) {
+ const token = await getIdToken(getAuthenticatedUser());
headers["Authorization"] = `Bearer ${token}`;
}
diff --git a/frontend/src/ts/ape/endpoints/index.ts b/frontend/src/ts/ape/endpoints/index.ts
index 5b7b849fef2b..609c791b99ad 100644
--- a/frontend/src/ts/ape/endpoints/index.ts
+++ b/frontend/src/ts/ape/endpoints/index.ts
@@ -1,16 +1,12 @@
import Leaderboards from "./leaderboards";
-import Psas from "./psas";
import Quotes from "./quotes";
import Results from "./results";
import Users from "./users";
-import Public from "./public";
import Configuration from "./configuration";
import Dev from "./dev";
export default {
Leaderboards,
- Psas,
- Public,
Quotes,
Results,
Users,
diff --git a/frontend/src/ts/ape/endpoints/psas.ts b/frontend/src/ts/ape/endpoints/psas.ts
deleted file mode 100644
index 6477a7878f20..000000000000
--- a/frontend/src/ts/ape/endpoints/psas.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { PSA } from "@monkeytype/shared-types";
-
-const BASE_PATH = "/psas";
-
-export default class Psas {
- constructor(private httpClient: Ape.HttpClient) {
- this.httpClient = httpClient;
- }
-
- async get(): Ape.EndpointResponse {
- return await this.httpClient.get(BASE_PATH);
- }
-}
diff --git a/frontend/src/ts/ape/endpoints/public.ts b/frontend/src/ts/ape/endpoints/public.ts
deleted file mode 100644
index 5f4b417d3630..000000000000
--- a/frontend/src/ts/ape/endpoints/public.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { PublicTypingStats, SpeedHistogram } from "@monkeytype/shared-types";
-
-const BASE_PATH = "/public";
-
-type SpeedStatsQuery = {
- language: string;
- mode: string;
- mode2: string;
-};
-
-export default class Public {
- constructor(private httpClient: Ape.HttpClient) {
- this.httpClient = httpClient;
- }
-
- async getSpeedHistogram(
- searchQuery: SpeedStatsQuery
- ): Ape.EndpointResponse {
- return await this.httpClient.get(`${BASE_PATH}/speedHistogram`, {
- searchQuery,
- });
- }
-
- async getTypingStats(): Ape.EndpointResponse {
- return await this.httpClient.get(`${BASE_PATH}/typingStats`);
- }
-}
diff --git a/frontend/src/ts/ape/index.ts b/frontend/src/ts/ape/index.ts
index 137a0887165a..46ddac7b3aef 100644
--- a/frontend/src/ts/ape/index.ts
+++ b/frontend/src/ts/ape/index.ts
@@ -2,27 +2,22 @@ import endpoints from "./endpoints";
import { buildHttpClient } from "./adapters/axios-adapter";
import { envConfig } from "../constants/env-config";
import { buildClient } from "./adapters/ts-rest-adapter";
-import { configsContract } from "@monkeytype/contracts/configs";
-import { presetsContract } from "@monkeytype/contracts/presets";
-import { apeKeysContract } from "@monkeytype/contracts/ape-keys";
+import { contract } from "@monkeytype/contracts";
const API_PATH = "";
const BASE_URL = envConfig.backendUrl;
const API_URL = `${BASE_URL}${API_PATH}`;
const httpClient = buildHttpClient(API_URL, 10_000);
+const tsRestClient = buildClient(contract, BASE_URL, 10_000);
// API Endpoints
const Ape = {
+ ...tsRestClient,
users: new endpoints.Users(httpClient),
- configs: buildClient(configsContract, BASE_URL, 10_000),
results: new endpoints.Results(httpClient),
- psas: new endpoints.Psas(httpClient),
quotes: new endpoints.Quotes(httpClient),
leaderboards: new endpoints.Leaderboards(httpClient),
- presets: buildClient(presetsContract, BASE_URL, 10_000),
- publicStats: new endpoints.Public(httpClient),
- apeKeys: buildClient(apeKeysContract, BASE_URL, 10_000),
configuration: new endpoints.Configuration(httpClient),
dev: new endpoints.Dev(buildHttpClient(API_URL, 240_000)),
};
diff --git a/frontend/src/ts/commandline/lists/navigation.ts b/frontend/src/ts/commandline/lists/navigation.ts
index 71b2c11239fe..d9d7e1460780 100644
--- a/frontend/src/ts/commandline/lists/navigation.ts
+++ b/frontend/src/ts/commandline/lists/navigation.ts
@@ -1,4 +1,5 @@
import { navigate } from "../../controllers/route-controller";
+import { isAuthenticated } from "../../firebase";
import { toggleFullscreen } from "../../utils/misc";
const commands: MonkeyTypes.Command[] = [
@@ -45,10 +46,7 @@ const commands: MonkeyTypes.Command[] = [
alias: "navigate go to stats",
icon: "fa-user",
exec: (): void => {
- //todo probably base this on some state instead of the dom
- $("header nav .textButton.view-account").hasClass("hidden")
- ? navigate("/login")
- : navigate("/account");
+ isAuthenticated() ? navigate("/account") : navigate("/login");
},
},
{
diff --git a/frontend/src/ts/controllers/account-controller.ts b/frontend/src/ts/controllers/account-controller.ts
index d9a74680bb37..555b99bcd8ee 100644
--- a/frontend/src/ts/controllers/account-controller.ts
+++ b/frontend/src/ts/controllers/account-controller.ts
@@ -46,6 +46,7 @@ import * as ConnectionState from "../states/connection";
import { navigate } from "./route-controller";
import { getHtmlByUserFlags } from "./user-flag-controller";
import { FirebaseError } from "firebase/app";
+import * as PSA from "../elements/psa";
export const gmailProvider = new GoogleAuthProvider();
export const githubProvider = new GithubAuthProvider();
@@ -226,6 +227,7 @@ async function readyFunction(
const hash = window.location.hash;
console.debug(`account controller ready`);
if (authInitialisedAndConnected) {
+ void PSA.show();
console.debug(`auth state changed, user ${user ? true : false}`);
console.debug(user);
if (user) {
diff --git a/frontend/src/ts/db.ts b/frontend/src/ts/db.ts
index 39356c8f052f..10c8fcb5955a 100644
--- a/frontend/src/ts/db.ts
+++ b/frontend/src/ts/db.ts
@@ -623,29 +623,26 @@ export async function getLocalPB(
difficulty: Difficulty,
lazyMode: boolean,
funbox: string
-): Promise {
+): Promise {
const funboxes = (await getFunboxList()).filter((fb) => {
return funbox?.split("#").includes(fb.name);
});
if (!funboxes.every((f) => f.canGetPb)) {
- return 0;
+ return undefined;
}
- if (dbSnapshot === null || dbSnapshot?.personalBests === null) return 0;
-
- const bestsByMode = dbSnapshot?.personalBests[mode][mode2] as PersonalBest[];
-
- if (bestsByMode === undefined) return 0;
- return (
- bestsByMode.find(
- (pb) =>
- (pb.punctuation ?? false) === punctuation &&
- (pb.numbers ?? false) === numbers &&
- pb.difficulty === difficulty &&
- pb.language === language &&
- (pb.lazyMode ?? false) === lazyMode
- )?.wpm ?? 0
+ const pbs = dbSnapshot?.personalBests?.[mode]?.[mode2] as
+ | PersonalBest[]
+ | undefined;
+
+ return pbs?.find(
+ (pb) =>
+ (pb.punctuation ?? false) === punctuation &&
+ (pb.numbers ?? false) === numbers &&
+ pb.difficulty === difficulty &&
+ pb.language === language &&
+ (pb.lazyMode ?? false) === lazyMode
);
}
diff --git a/frontend/src/ts/elements/psa.ts b/frontend/src/ts/elements/psa.ts
index c6c8acae90d6..324e2a863fc9 100644
--- a/frontend/src/ts/elements/psa.ts
+++ b/frontend/src/ts/elements/psa.ts
@@ -4,7 +4,7 @@ import { secondsToString } from "../utils/date-and-time";
import * as Notifications from "./notifications";
import { format } from "date-fns/format";
import * as Alerts from "./alerts";
-import { PSA } from "@monkeytype/shared-types";
+import { PSA } from "@monkeytype/contracts/schemas/psas";
function clearMemory(): void {
window.localStorage.setItem("confirmedPSAs", JSON.stringify([]));
@@ -61,7 +61,7 @@ async function getLatest(): Promise {
} else if (response.status !== 200) {
return null;
}
- return response.data;
+ return response.body.data;
}
export async function show(): Promise {
diff --git a/frontend/src/ts/modals/pb-tables.ts b/frontend/src/ts/modals/pb-tables.ts
index 736d8766618c..a78a8c9c8573 100644
--- a/frontend/src/ts/modals/pb-tables.ts
+++ b/frontend/src/ts/modals/pb-tables.ts
@@ -34,7 +34,7 @@ function update(mode: Mode): void {
if (allmode2 === undefined) return;
const list: PBWithMode2[] = [];
- (Object.keys(allmode2) as Mode2[]).forEach(function (key) {
+ Object.keys(allmode2).forEach(function (key) {
let pbs = allmode2[key] ?? [];
pbs = pbs.sort(function (a, b) {
return b.wpm - a.wpm;
diff --git a/frontend/src/ts/modals/share-test-settings.ts b/frontend/src/ts/modals/share-test-settings.ts
index 14bed5c51725..ca6285fe4d77 100644
--- a/frontend/src/ts/modals/share-test-settings.ts
+++ b/frontend/src/ts/modals/share-test-settings.ts
@@ -43,7 +43,7 @@ function updateURL(): void {
}
if (getCheckboxValue("mode2")) {
- settings[1] = getMode2(Config, currentQuote) as Mode2;
+ settings[1] = getMode2(Config, currentQuote);
}
if (getCheckboxValue("customText")) {
diff --git a/frontend/src/ts/pages/about.ts b/frontend/src/ts/pages/about.ts
index 6e93ebf05f3e..df85802de421 100644
--- a/frontend/src/ts/pages/about.ts
+++ b/frontend/src/ts/pages/about.ts
@@ -8,7 +8,10 @@ import * as ChartController from "../controllers/chart-controller";
import * as ConnectionState from "../states/connection";
import { intervalToDuration } from "date-fns/intervalToDuration";
import * as Skeleton from "../utils/skeleton";
-import { PublicTypingStats, SpeedHistogram } from "@monkeytype/shared-types";
+import {
+ TypingStats,
+ SpeedHistogram,
+} from "@monkeytype/contracts/schemas/public";
function reset(): void {
$(".pageAbout .contributors").empty();
@@ -19,7 +22,7 @@ function reset(): void {
}
let speedHistogramResponseData: SpeedHistogram | null;
-let typingStatsResponseData: PublicTypingStats | null;
+let typingStatsResponseData: TypingStats | null;
function updateStatsAndHistogram(): void {
if (speedHistogramResponseData) {
@@ -97,25 +100,27 @@ async function getStatsAndHistogramData(): Promise {
return;
}
- const speedStats = await Ape.publicStats.getSpeedHistogram({
- language: "english",
- mode: "time",
- mode2: "60",
+ const speedStats = await Ape.public.getSpeedHistogram({
+ query: {
+ language: "english",
+ mode: "time",
+ mode2: "60",
+ },
});
- if (speedStats.status >= 200 && speedStats.status < 300) {
- speedHistogramResponseData = speedStats.data;
+ if (speedStats.status === 200) {
+ speedHistogramResponseData = speedStats.body.data;
} else {
Notifications.add(
- `Failed to get global speed stats for histogram: ${speedStats.message}`,
+ `Failed to get global speed stats for histogram: ${speedStats.body.message}`,
-1
);
}
- const typingStats = await Ape.publicStats.getTypingStats();
- if (typingStats.status >= 200 && typingStats.status < 300) {
- typingStatsResponseData = typingStats.data;
+ const typingStats = await Ape.public.getTypingStats();
+ if (typingStats.status === 200) {
+ typingStatsResponseData = typingStats.body.data;
} else {
Notifications.add(
- `Failed to get global typing stats: ${speedStats.message}`,
+ `Failed to get global typing stats: ${speedStats.body.message}`,
-1
);
}
diff --git a/frontend/src/ts/ready.ts b/frontend/src/ts/ready.ts
index e373d44dcd74..8af4be4e034e 100644
--- a/frontend/src/ts/ready.ts
+++ b/frontend/src/ts/ready.ts
@@ -3,7 +3,6 @@ import * as Misc from "./utils/misc";
import * as MonkeyPower from "./elements/monkey-power";
import * as Notifications from "./elements/notifications";
import * as CookiesModal from "./modals/cookies";
-import * as PSA from "./elements/psa";
import * as ConnectionState from "./states/connection";
import * as FunboxList from "./test/funbox/funbox-list";
//@ts-expect-error
@@ -46,7 +45,6 @@ $((): void => {
.stop(true, true)
.animate({ opacity: 1 }, 250);
if (ConnectionState.get()) {
- void PSA.show();
void ServerConfiguration.sync().then(() => {
if (!ServerConfiguration.get()?.users.signUp) {
$(".signInOut").addClass("hidden");
diff --git a/frontend/src/ts/test/caret.ts b/frontend/src/ts/test/caret.ts
index 1feb1766df12..3be4dfb4f0fe 100644
--- a/frontend/src/ts/test/caret.ts
+++ b/frontend/src/ts/test/caret.ts
@@ -63,8 +63,12 @@ function getTargetPositionLeft(
const currentLetter = currentWordNodeList[inputLen] as
| HTMLElement
| undefined;
- const lastWordLetter = currentWordNodeList[wordLen - 1] as HTMLElement;
- const lastInputLetter = currentWordNodeList[inputLen - 1] as HTMLElement;
+ const lastWordLetter = currentWordNodeList[wordLen - 1] as
+ | HTMLElement
+ | undefined;
+ const lastInputLetter = currentWordNodeList[inputLen - 1] as
+ | HTMLElement
+ | undefined;
if (isLanguageRightToLeft) {
if (inputLen < wordLen && currentLetter) {
@@ -73,11 +77,11 @@ function getTargetPositionLeft(
(fullWidthCaret ? 0 : fullWidthCaretWidth);
} else if (!invisibleExtraLetters) {
positionOffsetToWord =
- lastInputLetter.offsetLeft -
+ (lastInputLetter?.offsetLeft ?? 0) -
(fullWidthCaret ? fullWidthCaretWidth : 0);
} else {
positionOffsetToWord =
- lastWordLetter.offsetLeft -
+ (lastWordLetter?.offsetLeft ?? 0) -
(fullWidthCaret ? fullWidthCaretWidth : 0);
}
} else {
@@ -85,10 +89,12 @@ function getTargetPositionLeft(
positionOffsetToWord = currentLetter?.offsetLeft;
} else if (!invisibleExtraLetters) {
positionOffsetToWord =
- lastInputLetter.offsetLeft + lastInputLetter.offsetWidth;
+ (lastInputLetter?.offsetLeft ?? 0) +
+ (lastInputLetter?.offsetWidth ?? 0);
} else {
positionOffsetToWord =
- lastWordLetter.offsetLeft + lastWordLetter.offsetWidth;
+ (lastWordLetter?.offsetLeft ?? 0) +
+ (lastWordLetter?.offsetWidth ?? 0);
}
}
result = activeWordElement.offsetLeft + positionOffsetToWord;
@@ -143,7 +149,9 @@ export async function updatePosition(noAnim = false): Promise {
const currentLetter = currentWordNodeList[inputLen] as
| HTMLElement
| undefined;
- const lastWordLetter = currentWordNodeList[wordLen - 1] as HTMLElement;
+ const lastWordLetter = currentWordNodeList[wordLen - 1] as
+ | HTMLElement
+ | undefined;
const spaceWidth = getSpaceWidth(activeWordEl);
@@ -156,7 +164,7 @@ export async function updatePosition(noAnim = false): Promise {
lastWordLetter?.offsetHeight ||
Config.fontSize * Numbers.convertRemToPixels(1);
- const letterPosTop = lastWordLetter.offsetTop;
+ const letterPosTop = lastWordLetter?.offsetTop ?? 0;
const diff = letterHeight - caret.offsetHeight;
let newTop = activeWordEl.offsetTop + letterPosTop + diff / 2;
if (Config.caretStyle === "underline") {
diff --git a/frontend/src/ts/test/pace-caret.ts b/frontend/src/ts/test/pace-caret.ts
index 29a6fab9791b..2ff540e87a6a 100644
--- a/frontend/src/ts/test/pace-caret.ts
+++ b/frontend/src/ts/test/pace-caret.ts
@@ -8,7 +8,6 @@ import * as Numbers from "../utils/numbers";
import * as JSONData from "../utils/json-data";
import * as TestState from "./test-state";
import * as ConfigEvent from "../observables/config-event";
-import { Mode2 } from "@monkeytype/contracts/schemas/shared";
type Settings = {
wpm: number;
@@ -67,21 +66,22 @@ async function resetCaretPosition(): Promise {
export async function init(): Promise {
$("#paceCaret").addClass("hidden");
- const mode2 = Misc.getMode2(Config, TestWords.currentQuote) as Mode2<
- typeof Config.mode
- >;
- let wpm;
+ const mode2 = Misc.getMode2(Config, TestWords.currentQuote);
+ let wpm = 0;
if (Config.paceCaret === "pb") {
- wpm = await DB.getLocalPB(
- Config.mode,
- mode2,
- Config.punctuation,
- Config.numbers,
- Config.language,
- Config.difficulty,
- Config.lazyMode,
- Config.funbox
- );
+ wpm =
+ (
+ await DB.getLocalPB(
+ Config.mode,
+ mode2,
+ Config.punctuation,
+ Config.numbers,
+ Config.language,
+ Config.difficulty,
+ Config.lazyMode,
+ Config.funbox
+ )
+ )?.wpm ?? 0;
} else if (Config.paceCaret === "tagPb") {
wpm = await DB.getActiveTagsPB(
Config.mode,
diff --git a/frontend/src/ts/test/result.ts b/frontend/src/ts/test/result.ts
index 2193e6850331..c6f6ea649049 100644
--- a/frontend/src/ts/test/result.ts
+++ b/frontend/src/ts/test/result.ts
@@ -170,7 +170,7 @@ async function updateGraph(): Promise {
export async function updateGraphPBLine(): Promise {
const themecolors = await ThemeColors.getAll();
- const lpb = await DB.getLocalPB(
+ const localPb = await DB.getLocalPB(
result.mode,
result.mode2,
result.punctuation ?? false,
@@ -180,9 +180,12 @@ export async function updateGraphPBLine(): Promise {
result.lazyMode ?? false,
result.funbox ?? "none"
);
- if (lpb === 0) return;
+ const localPbWpm = localPb?.wpm ?? 0;
+ if (localPbWpm === 0) return;
const typingSpeedUnit = getTypingSpeedUnit(Config.typingSpeedUnit);
- const chartlpb = Numbers.roundTo2(typingSpeedUnit.fromWpm(lpb)).toFixed(2);
+ const chartlpb = Numbers.roundTo2(
+ typingSpeedUnit.fromWpm(localPbWpm)
+ ).toFixed(2);
resultAnnotation.push({
display: true,
type: "line",
@@ -388,7 +391,7 @@ export async function updateCrown(dontSave: boolean): Promise {
const canGetPb = await resultCanGetPb();
if (canGetPb.value) {
- const lpb = await DB.getLocalPB(
+ const localPb = await DB.getLocalPB(
Config.mode,
result.mode2,
Config.punctuation,
@@ -398,7 +401,8 @@ export async function updateCrown(dontSave: boolean): Promise {
Config.lazyMode,
Config.funbox
);
- pbDiff = result.wpm - lpb;
+ const localPbWpm = localPb?.wpm ?? 0;
+ pbDiff = result.wpm - localPbWpm;
if (pbDiff <= 0) {
hideCrown();
} else {
@@ -409,7 +413,7 @@ export async function updateCrown(dontSave: boolean): Promise {
);
}
} else {
- const lpb = await DB.getLocalPB(
+ const localPb = await DB.getLocalPB(
Config.mode,
result.mode2,
Config.punctuation,
@@ -419,7 +423,8 @@ export async function updateCrown(dontSave: boolean): Promise {
Config.lazyMode,
"none"
);
- pbDiff = result.wpm - lpb;
+ const localPbWpm = localPb?.wpm ?? 0;
+ pbDiff = result.wpm - localPbWpm;
if (pbDiff <= 0) {
// hideCrown();
showCrown("warning");
diff --git a/frontend/src/ts/test/test-logic.ts b/frontend/src/ts/test/test-logic.ts
index 398d02be5b9d..a53a14b335d3 100644
--- a/frontend/src/ts/test/test-logic.ts
+++ b/frontend/src/ts/test/test-logic.ts
@@ -64,6 +64,7 @@ import {
} from "@monkeytype/shared-types";
import { QuoteLength } from "@monkeytype/contracts/schemas/configs";
import { Mode } from "@monkeytype/contracts/schemas/shared";
+
let failReason = "";
const koInputVisual = document.getElementById("koInputVisual") as HTMLElement;
@@ -1196,22 +1197,30 @@ async function saveResult(
if (response?.data?.isPb !== undefined && response.data.isPb) {
//new pb
- if (
- //@ts-expect-error TODO fix this
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
- DB.getSnapshot()?.personalBests?.[Config.mode]?.[completedEvent.mode2]
- ) {
+ const localPb = await DB.getLocalPB(
+ completedEvent.mode,
+ completedEvent.mode2,
+ completedEvent.punctuation,
+ completedEvent.numbers,
+ completedEvent.language,
+ completedEvent.difficulty,
+ completedEvent.lazyMode,
+ completedEvent.funbox
+ );
+
+ if (localPb !== undefined) {
Result.showConfetti();
}
Result.showCrown("normal");
+
await DB.saveLocalPB(
- Config.mode,
+ completedEvent.mode,
completedEvent.mode2,
- Config.punctuation,
- Config.numbers,
- Config.language,
- Config.difficulty,
- Config.lazyMode,
+ completedEvent.punctuation,
+ completedEvent.numbers,
+ completedEvent.language,
+ completedEvent.difficulty,
+ completedEvent.lazyMode,
completedEvent.wpm,
completedEvent.acc,
completedEvent.rawWpm,
diff --git a/frontend/src/ts/test/test-ui.ts b/frontend/src/ts/test/test-ui.ts
index 1c77cd17a544..73a03b3570eb 100644
--- a/frontend/src/ts/test/test-ui.ts
+++ b/frontend/src/ts/test/test-ui.ts
@@ -899,14 +899,6 @@ export async function updateWordElement(inputOverride?: string): Promise {
ret += `` + currentWord[i] + "";
}
}
-
- if (Config.highlightMode === "letter") {
- if (input.length > currentWord.length && !Config.blindMode) {
- wordAtIndex.classList.add("error");
- } else if (input.length === currentWord.length) {
- wordAtIndex.classList.remove("error");
- }
- }
}
wordAtIndex.innerHTML = ret;
diff --git a/frontend/static/quotes/code_csharp.json b/frontend/static/quotes/code_csharp.json
index 61f2a5a87a11..2e0f0f8a3df4 100644
--- a/frontend/static/quotes/code_csharp.json
+++ b/frontend/static/quotes/code_csharp.json
@@ -14,7 +14,7 @@
"length": 63
},
{
- "text": "static int factorial(int n)\n{\n\tint result = 1;\n\tfor (int i = 1; i <= n; i++)\n\t{\n\t\tresult = result * i;\n\t}\n\treturn result;\n}",
+ "text": "static int Factorial(int n)\n{\n\tint result = 1;\n\tfor (int i = 1; i <= n; i++)\n\t{\n\t\tresult = result * i;\n\t}\n\treturn result;\n}",
"id": 2,
"source": "Geeks for Geeks - C# Methods",
"length": 123
diff --git a/frontend/static/quotes/english.json b/frontend/static/quotes/english.json
index b9c3175c6bc2..9360c4287ceb 100644
--- a/frontend/static/quotes/english.json
+++ b/frontend/static/quotes/english.json
@@ -8060,10 +8060,10 @@
"length": 489
},
{
- "text": "Let's go in the garden. You'll see something waiting, right there where you left it, lying upside down. When you finally find it you'll see how it's faded. The underside is lighter when you turn it around.",
+ "text": "Let's go in the garden. You'll find something waiting, right there where you left it, lying upside down. When you finally find it you'll see how it's faded. The underside is lighter when you turn it around.",
"source": "Everything Stays",
"id": 1414,
- "length": 205
+ "length": 206
},
{
"text": "When you lose control, you'll reap the harvest you have sown. And as the fear grows, the bad blood slows and turns to stone. And it's too late to lose the weight you used to need to throw around. So have a good drown, as you go down, all alone... Dragged down by the stone.",
@@ -29734,12 +29734,6 @@
"length": 318,
"id": 5358
},
- {
- "text": "The whole world's gone mad. They claim to be my dad's best friend, and want to help me with my revenge. Bizarre, isn't it? I'm handed everything, even the names of the men who stole the people I loved...",
- "source": "91 days",
- "length": 203,
- "id": 5359
- },
{
"text": "It is nice to be important, but it's more important to be nice.",
"source": "Roger Federer",
@@ -31469,12 +31463,6 @@
"length": 256,
"id": 5720
},
- {
- "text": "When I was younger, I left a trail of broken hearts like a rockstar. I'm not proud of it.",
- "source": "Modern Family",
- "length": 89,
- "id": 5722
- },
{
"text": "Mistakes are always forgivable, if one has the courage to admit them.",
"source": "Bruce Lee",
@@ -31674,7 +31662,7 @@
"id": 5760
},
{
- "text": "My worst breakup was with Stacy. It was a Sunday morning, we were reading the paper, and I said, \"Oh my God, I think the Eagles could clinch the NFC East!\" and she said, \"We're done\".",
+ "text": "My worst breakup was with Stacy. It was a Sunday morning, we were reading the paper, and I said, \"Oh my God, I think the Eagles could clinch the NFC East!\" and she said, \"We're done.\"",
"source": "The Office",
"length": 183,
"id": 5761
diff --git a/frontend/static/quotes/indonesian.json b/frontend/static/quotes/indonesian.json
index 248a0d63fef4..e3c23bcaec9b 100644
--- a/frontend/static/quotes/indonesian.json
+++ b/frontend/static/quotes/indonesian.json
@@ -307,12 +307,6 @@
"length": 118,
"id": 53
},
- {
- "text": "Jatuh hati tidak pernah bisa memilih. Tuhan memilihkan. Kita hanyalah korban. Kecewa adalah konsekuensi, bahagia adalah bonus.",
- "source": "Fiersa Besari",
- "length": 126,
- "id": 54
- },
{
"text": "Jadilah kamu manusia yang pada kelahiranmu semua orang tertawa bahagia, tetapi hanya kamu sendiri yang menangis dan pada kematianmu semua orang menangis sedih, tetapi hanya kamu sendiri yang tersenyum.",
"source": "Mahatma Gandhi",
diff --git a/frontend/static/quotes/russian.json b/frontend/static/quotes/russian.json
index 618498d56eaf..71da114a37b7 100644
--- a/frontend/static/quotes/russian.json
+++ b/frontend/static/quotes/russian.json
@@ -2308,8 +2308,8 @@
{
"id": 403,
"source": "Лосев Лев - Иосиф Бродский",
- "text": "Создание стихотворения - всегда катартический опыт, его хочется продлить. Неопубликованные, стихи словно бы не окончены, а публикация - расставание навсегда.",
- "length": 157
+ "text": "Создание стихотворения - всегда катартический опыт, его хочется продлить. Неопубликованные стихи словно бы не окончены, а публикация - расставание навсегда.",
+ "length": 156
},
{
"id": 404,
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index c0890404911f..1360fc8f7002 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -33,6 +33,11 @@ const BASE_CONFIG = {
open: process.env.SERVER_OPEN !== "false",
port: 3000,
host: process.env.BACKEND_URL !== undefined,
+ watch: {
+ //we rebuild the whole contracts package when a file changes
+ //so we only want to watch one file
+ ignored: [/.*\/packages\/contracts\/dist\/(?!configs).*/],
+ },
},
clearScreen: false,
root: "src",
diff --git a/package.json b/package.json
index 30a4b388dd7b..90caf020601d 100644
--- a/package.json
+++ b/package.json
@@ -8,6 +8,7 @@
"full-check": "turbo lint ts-check build test validate-json",
"prepare": "husky install",
"pre-commit": "lint-staged",
+ "ts-check": "turbo run ts-check",
"lint": "turbo run lint",
"lint-be": "turbo run lint --filter @monkeytype/backend",
"lint-fe": "turbo run lint --filter @monkeytype/frontend",
@@ -29,12 +30,13 @@
"start-fe": "turbo run start --filter @monkeytype/frontend",
"docker": "cd backend && docker compose up",
"audit-fe": "cd frontend && npm run audit",
- "release": "node ./bin/release.mjs",
- "release-fe": "node ./bin/release.mjs --fe",
- "release-be": "node ./bin/release.mjs --be",
- "release-no-deploy": "node ./bin/release.mjs --no-deploy",
- "release-dry": "node ./bin/release.mjs --dry",
- "hotfix": "npm run build-fe && cd frontend && npm run deploy-live && cd .. && sh ./bin/purgeCfCache.sh",
+ "release": "monkeytype-release",
+ "release-fe": "monkeytype-release --fe",
+ "release-be": "monkeytype-release --be",
+ "release-no-deploy": "monkeytype-release --no-deploy",
+ "release-dry": "monkeytype-release --dry",
+ "hotfix-fe": "npm run build-fe && cd frontend && npm run deploy-live && monkeytype-purge",
+ "hotfix-be": "monkeytype-deploy-be",
"pretty-check": "prettier --check .",
"pretty-check-be": "prettier --check ./backend",
"pretty-check-fe": "prettier --check ./frontend/src",
@@ -56,22 +58,16 @@
"devDependencies": {
"@commitlint/cli": "17.7.1",
"@commitlint/config-conventional": "17.7.0",
+ "@vitest/coverage-v8": "1.6.0",
+ "@monkeytype/release": "workspace:*",
"conventional-changelog": "4.0.0",
- "eslint": "8.57.0",
- "eslint-config-prettier": "9.0.0",
- "eslint-import-resolver-typescript": "3.6.1",
- "eslint-plugin-import": "2.29.0",
- "eslint-plugin-json": "2.1.2",
- "eslint-plugin-require-path-exists": "1.1.9",
"husky": "8.0.1",
"knip": "2.19.2",
"lint-staged": "13.2.3",
"only-allow": "1.2.1",
"prettier": "2.5.1",
- "readline-sync": "1.4.10",
"turbo": "2.0.9",
- "typescript": "5.5.4",
- "wait-for-localhost-cli": "3.2.0"
+ "vitest": "1.6.0"
},
"lint-staged": {
"*.{json,scss,css,html}": [
diff --git a/packages/contracts/esbuild.config.js b/packages/contracts/esbuild.config.js
index 67701bcd40fb..6da919b4a63c 100644
--- a/packages/contracts/esbuild.config.js
+++ b/packages/contracts/esbuild.config.js
@@ -81,9 +81,10 @@ if (isWatch) {
console.log("Starting watch mode...");
chokidar.watch("./src/**/*.ts").on(
"change",
- (path) => {
+ (_path) => {
console.log("File change detected...");
- build(path, false, false);
+ // build(path, false, false);
+ buildAll(false, false);
},
{
ignoreInitial: true,
diff --git a/packages/contracts/src/admin.ts b/packages/contracts/src/admin.ts
index 0d68e7e41d9c..76595e930b66 100644
--- a/packages/contracts/src/admin.ts
+++ b/packages/contracts/src/admin.ts
@@ -58,7 +58,7 @@ export const adminContract = c.router(
summary: "test permission",
description: "Check for admin permission for the current user",
method: "GET",
- path: "/",
+ path: "",
responses: {
200: MonkeyResponseSchema,
},
diff --git a/packages/contracts/src/ape-keys.ts b/packages/contracts/src/ape-keys.ts
index c5657d32cfba..4b6801646501 100644
--- a/packages/contracts/src/ape-keys.ts
+++ b/packages/contracts/src/ape-keys.ts
@@ -46,7 +46,7 @@ export const apeKeysContract = c.router(
summary: "get ape keys",
description: "Get ape keys of the current user.",
method: "GET",
- path: "/",
+ path: "",
responses: {
200: GetApeKeyResponseSchema,
},
@@ -55,7 +55,7 @@ export const apeKeysContract = c.router(
summary: "add ape key",
description: "Add an ape key for the current user.",
method: "POST",
- path: "/",
+ path: "",
body: AddApeKeyRequestSchema.strict(),
responses: {
200: AddApeKeyResponseSchema,
diff --git a/packages/contracts/src/configs.ts b/packages/contracts/src/configs.ts
index 8790de4df88e..115a27d5d457 100644
--- a/packages/contracts/src/configs.ts
+++ b/packages/contracts/src/configs.ts
@@ -22,14 +22,14 @@ export const configsContract = c.router(
summary: "get config",
description: "Get config of the current user.",
method: "GET",
- path: "/",
+ path: "",
responses: {
200: GetConfigResponseSchema,
},
},
save: {
method: "PATCH",
- path: "/",
+ path: "",
body: PartialConfigSchema.strict(),
responses: {
200: MonkeyResponseSchema,
@@ -40,7 +40,7 @@ export const configsContract = c.router(
},
delete: {
method: "DELETE",
- path: "/",
+ path: "",
body: c.noBody(),
responses: {
200: MonkeyResponseSchema,
diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts
index 533e4a3d2fe9..1224a0e19041 100644
--- a/packages/contracts/src/index.ts
+++ b/packages/contracts/src/index.ts
@@ -3,6 +3,8 @@ import { adminContract } from "./admin";
import { apeKeysContract } from "./ape-keys";
import { configsContract } from "./configs";
import { presetsContract } from "./presets";
+import { psasContract } from "./psas";
+import { publicContract } from "./public";
const c = initContract();
@@ -11,4 +13,6 @@ export const contract = c.router({
apeKeys: apeKeysContract,
configs: configsContract,
presets: presetsContract,
+ psas: psasContract,
+ public: publicContract,
});
diff --git a/packages/contracts/src/presets.ts b/packages/contracts/src/presets.ts
index c100cf310f36..9c82da01edb8 100644
--- a/packages/contracts/src/presets.ts
+++ b/packages/contracts/src/presets.ts
@@ -34,7 +34,7 @@ export const presetsContract = c.router(
summary: "get presets",
description: "Get presets of the current user.",
method: "GET",
- path: "/",
+ path: "",
responses: {
200: GetPresetResponseSchema,
},
@@ -43,7 +43,7 @@ export const presetsContract = c.router(
summary: "add preset",
description: "Add a new preset for the current user.",
method: "POST",
- path: "/",
+ path: "",
body: AddPresetRequestSchema.strict(),
responses: {
200: AddPresetResponseSchemna,
@@ -53,7 +53,7 @@ export const presetsContract = c.router(
summary: "update preset",
description: "Update an existing preset for the current user.",
method: "PATCH",
- path: "/",
+ path: "",
body: PresetSchema.strict(),
responses: {
200: MonkeyResponseSchema,
diff --git a/packages/contracts/src/psas.ts b/packages/contracts/src/psas.ts
new file mode 100644
index 000000000000..a3783573210f
--- /dev/null
+++ b/packages/contracts/src/psas.ts
@@ -0,0 +1,37 @@
+import { initContract } from "@ts-rest/core";
+import { z } from "zod";
+import { PSASchema } from "./schemas/psas";
+
+import {
+ CommonResponses,
+ EndpointMetadata,
+ responseWithData,
+} from "./schemas/api";
+export const GetPsaResponseSchema = responseWithData(z.array(PSASchema));
+export type GetPsaResponse = z.infer;
+
+const c = initContract();
+export const psasContract = c.router(
+ {
+ get: {
+ summary: "get psas",
+ description: "Get list of public service announcements",
+ method: "GET",
+ path: "",
+ responses: {
+ 200: GetPsaResponseSchema,
+ },
+ },
+ },
+ {
+ pathPrefix: "/psas",
+ strictStatusCodes: true,
+ metadata: {
+ openApiTags: "psas",
+ authenticationOptions: {
+ isPublic: true,
+ },
+ } as EndpointMetadata,
+ commonResponses: CommonResponses,
+ }
+);
diff --git a/packages/contracts/src/public.ts b/packages/contracts/src/public.ts
new file mode 100644
index 000000000000..93b2b81f97de
--- /dev/null
+++ b/packages/contracts/src/public.ts
@@ -0,0 +1,70 @@
+import { initContract } from "@ts-rest/core";
+import { z } from "zod";
+import {
+ CommonResponses,
+ EndpointMetadata,
+ responseWithData,
+} from "./schemas/api";
+import { SpeedHistogramSchema, TypingStatsSchema } from "./schemas/public";
+import { Mode2Schema, ModeSchema } from "./schemas/shared";
+import { LanguageSchema } from "./schemas/util";
+
+export const GetSpeedHistogramQuerySchema = z
+ .object({
+ language: LanguageSchema,
+ mode: ModeSchema,
+ mode2: Mode2Schema,
+ })
+ .strict();
+export type GetSpeedHistogramQuery = z.infer<
+ typeof GetSpeedHistogramQuerySchema
+>;
+
+export const GetSpeedHistogramResponseSchema =
+ responseWithData(SpeedHistogramSchema);
+export type GetSpeedHistogramResponse = z.infer<
+ typeof GetSpeedHistogramResponseSchema
+>;
+
+export const GetTypingStatsResponseSchema = responseWithData(TypingStatsSchema);
+export type GetTypingStatsResponse = z.infer<
+ typeof GetTypingStatsResponseSchema
+>;
+
+const c = initContract();
+export const publicContract = c.router(
+ {
+ getSpeedHistogram: {
+ summary: "get speed histogram",
+ description:
+ "get number of users personal bests grouped by wpm level (multiples of ten)",
+ method: "GET",
+ path: "/speedHistogram",
+ query: GetSpeedHistogramQuerySchema,
+ responses: {
+ 200: GetSpeedHistogramResponseSchema,
+ },
+ },
+
+ getTypingStats: {
+ summary: "get typing stats",
+ description: "get number of tests and time users spend typing.",
+ method: "GET",
+ path: "/typingStats",
+ responses: {
+ 200: GetTypingStatsResponseSchema,
+ },
+ },
+ },
+ {
+ pathPrefix: "/public",
+ strictStatusCodes: true,
+ metadata: {
+ openApiTags: "public",
+ authenticationOptions: {
+ isPublic: true,
+ },
+ } as EndpointMetadata,
+ commonResponses: CommonResponses,
+ }
+);
diff --git a/packages/contracts/src/schemas/api.ts b/packages/contracts/src/schemas/api.ts
index 37d3b6d2e6e0..305d6100aa8c 100644
--- a/packages/contracts/src/schemas/api.ts
+++ b/packages/contracts/src/schemas/api.ts
@@ -1,6 +1,12 @@
import { z, ZodSchema } from "zod";
-export type OpenApiTag = "configs" | "presets" | "ape-keys" | "admin";
+export type OpenApiTag =
+ | "configs"
+ | "presets"
+ | "ape-keys"
+ | "admin"
+ | "psas"
+ | "public";
export type EndpointMetadata = {
/** Authentication options, by default a bearer token is required. */
@@ -74,4 +80,7 @@ export const CommonResponses = {
422: MonkeyValidationErrorSchema.describe("Request validation failed"),
429: MonkeyClientError.describe("Rate limit exceeded"),
500: MonkeyServerError.describe("Generic server error"),
+ 503: MonkeyServerError.describe(
+ "Endpoint disabled or server is under maintenance"
+ ),
};
diff --git a/packages/contracts/src/schemas/psas.ts b/packages/contracts/src/schemas/psas.ts
new file mode 100644
index 000000000000..2ec6d074ade9
--- /dev/null
+++ b/packages/contracts/src/schemas/psas.ts
@@ -0,0 +1,11 @@
+import { z } from "zod";
+import { IdSchema } from "./util";
+
+export const PSASchema = z.object({
+ _id: IdSchema,
+ message: z.string(),
+ date: z.number().int().min(0).optional(),
+ level: z.number().int().optional(),
+ sticky: z.boolean().optional(),
+});
+export type PSA = z.infer;
diff --git a/packages/contracts/src/schemas/public.ts b/packages/contracts/src/schemas/public.ts
new file mode 100644
index 000000000000..5a89122eb1f7
--- /dev/null
+++ b/packages/contracts/src/schemas/public.ts
@@ -0,0 +1,15 @@
+import { z } from "zod";
+import { StringNumberSchema } from "./util";
+
+export const SpeedHistogramSchema = z.record(
+ StringNumberSchema,
+ z.number().int()
+);
+export type SpeedHistogram = z.infer;
+
+export const TypingStatsSchema = z.object({
+ timeTyping: z.number().nonnegative(),
+ testsCompleted: z.number().int().nonnegative(),
+ testsStarted: z.number().int().nonnegative(),
+});
+export type TypingStats = z.infer;
diff --git a/packages/contracts/src/schemas/shared.ts b/packages/contracts/src/schemas/shared.ts
index 0826839a8256..5eb97b5d4fee 100644
--- a/packages/contracts/src/schemas/shared.ts
+++ b/packages/contracts/src/schemas/shared.ts
@@ -1,4 +1,4 @@
-import { z } from "zod";
+import { literal, z } from "zod";
import { StringNumberSchema } from "./util";
//used by config and shared
@@ -33,9 +33,19 @@ export const PersonalBestsSchema = z.object({
});
export type PersonalBests = z.infer;
-//used by user and config
+//used by user, config, public
export const ModeSchema = PersonalBestsSchema.keyof();
export type Mode = z.infer;
+
+export const Mode2Schema = z.union(
+ [StringNumberSchema, literal("zen"), literal("custom")],
+ {
+ errorMap: () => ({
+ message: 'Needs to be either a number, "zen" or "custom."',
+ }),
+ }
+);
+
export type Mode2 = M extends M
? keyof PersonalBests[M]
: never;
diff --git a/packages/contracts/src/schemas/util.ts b/packages/contracts/src/schemas/util.ts
index 74e5ed963aef..25d9a4b77b2e 100644
--- a/packages/contracts/src/schemas/util.ts
+++ b/packages/contracts/src/schemas/util.ts
@@ -1,8 +1,12 @@
import { z, ZodString } from "zod";
-export const StringNumberSchema = z.custom<`${number}`>((val) => {
- return typeof val === "string" ? /^\d+$/.test(val) : false;
-});
+export const StringNumberSchema = z
+
+ .custom<`${number}`>((val) => {
+ if (typeof val === "number") val = val.toString();
+ return typeof val === "string" ? /^\d+$/.test(val) : false;
+ }, 'Needs to be a number or a number represented as a string e.g. "10".')
+ .transform(String);
export type StringNumber = z.infer;
@@ -13,3 +17,9 @@ export type Id = z.infer;
export const TagSchema = token().max(50);
export type Tag = z.infer;
+
+export const LanguageSchema = z
+ .string()
+ .max(50)
+ .regex(/^[a-zA-Z0-9_+]+$/);
+export type Language = z.infer;
diff --git a/packages/eslint-config/index.js b/packages/eslint-config/index.js
index f757881ea464..37ffb2691125 100644
--- a/packages/eslint-config/index.js
+++ b/packages/eslint-config/index.js
@@ -31,6 +31,14 @@ module.exports = {
"no-duplicate-imports": ["error"],
"no-constant-condition": ["error"],
"no-constant-binary-expression": "error",
+ "no-unused-vars": [
+ "error",
+ {
+ argsIgnorePattern: "^(_|e|event)",
+ caughtErrorsIgnorePattern: "^(_|e|error)",
+ varsIgnorePattern: "^_",
+ },
+ ],
"import/no-duplicates": "off",
"import/no-unresolved": [
"error",
@@ -85,9 +93,9 @@ module.exports = {
],
"@typescript-eslint/explicit-function-return-type": ["error"],
"@typescript-eslint/ban-ts-comment": "off",
- "@typescript-eslint/no-empty-function": "warn",
+ "@typescript-eslint/no-empty-function": "error",
"@typescript-eslint/no-unused-vars": [
- "warn",
+ "error",
{
argsIgnorePattern: "^(_|e|event)",
caughtErrorsIgnorePattern: "^(_|e|error)",
@@ -108,7 +116,7 @@ module.exports = {
checksVoidReturn: false,
},
],
- "@typescript-eslint/promise-function-async": "warn",
+ "@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/strict-boolean-expressions": [
"error",
@@ -116,7 +124,7 @@ module.exports = {
],
"@typescript-eslint/non-nullable-type-assertion-style": "off",
"@typescript-eslint/no-unnecessary-condition": "off",
- "@typescript-eslint/consistent-type-definitions": ["warn", "type"],
+ "@typescript-eslint/consistent-type-definitions": ["error", "type"],
"@typescript-eslint/no-invalid-void-type": "off",
"import/namespace": "off",
},
diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json
index 52745214fb38..43411d45cf27 100644
--- a/packages/eslint-config/package.json
+++ b/packages/eslint-config/package.json
@@ -4,6 +4,10 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "8.0.1",
"@typescript-eslint/parser": "8.0.1",
- "eslint-config-prettier": "9.1.0"
+ "eslint-config-prettier": "9.1.0",
+ "eslint-import-resolver-typescript": "3.6.1",
+ "eslint-plugin-import": "2.29.0",
+ "eslint-plugin-json": "3.1.0",
+ "eslint-plugin-require-path-exists": "1.1.9"
}
}
diff --git a/packages/release/.eslintrc.cjs b/packages/release/.eslintrc.cjs
new file mode 100644
index 000000000000..922de4abe568
--- /dev/null
+++ b/packages/release/.eslintrc.cjs
@@ -0,0 +1,5 @@
+/** @type {import("eslint").Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ["@monkeytype/eslint-config"],
+};
diff --git a/bin/deployBackend.sh b/packages/release/bin/deployBackend.sh
similarity index 54%
rename from bin/deployBackend.sh
rename to packages/release/bin/deployBackend.sh
index f5d89b62b5a8..13d8a8ca7d1b 100755
--- a/bin/deployBackend.sh
+++ b/packages/release/bin/deployBackend.sh
@@ -1,6 +1,11 @@
#!/bin/bash
-source .env
+# Determine the directory of the script
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Source the .env file from the parent directory of the script's directory
+source "$SCRIPT_DIR/../.env"
+
echo "Deploying backend to $BE_HOST with script $BE_SCRIPT_PATH"
# Connect to SSH and execute remote script
diff --git a/bin/purgeCfCache.sh b/packages/release/bin/purgeCfCache.sh
similarity index 70%
rename from bin/purgeCfCache.sh
rename to packages/release/bin/purgeCfCache.sh
index 11cfaacf23c8..eadb48c5d0ae 100755
--- a/bin/purgeCfCache.sh
+++ b/packages/release/bin/purgeCfCache.sh
@@ -1,5 +1,11 @@
#!/bin/bash
-source .env
+
+# Determine the directory of the script
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+# Source the .env file from the parent directory of the script's directory
+source "$SCRIPT_DIR/../.env"
+
echo "Purging Cloudflare cache for zone $CF_ZONE_ID"
response=$(curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$CF_ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_KEY" \
diff --git a/example.env b/packages/release/example.env
similarity index 100%
rename from example.env
rename to packages/release/example.env
diff --git a/packages/release/package.json b/packages/release/package.json
new file mode 100644
index 000000000000..04cf07237665
--- /dev/null
+++ b/packages/release/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@monkeytype/release",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "nodemon --watch src --exec 'node ./src/index.js --dry'",
+ "dev-changelog": "nodemon ./src/buildChangelog.js",
+ "lint": "eslint \"./**/*.js\"",
+ "purge-cf-cache": "./bin/purgeCfCache.sh"
+ },
+ "devDependencies": {
+ "@monkeytype/eslint-config": "workspace:*",
+ "eslint": "8.57.0",
+ "nodemon": "3.1.4"
+ },
+ "bin": {
+ "monkeytype-release": "./src/index.js",
+ "monkeytype-purge": "./bin/purgeCfCache.sh",
+ "monkeytype-deploy-be": "./bin/deployBackend.sh"
+ },
+ "dependencies": {
+ "@octokit/rest": "20.1.1",
+ "dotenv": "16.4.5",
+ "readline-sync": "1.4.10"
+ }
+}
diff --git a/bin/buildChangelog.mjs b/packages/release/src/buildChangelog.js
similarity index 98%
rename from bin/buildChangelog.mjs
rename to packages/release/src/buildChangelog.js
index 6bc330c927f5..2f1f832f16d7 100644
--- a/bin/buildChangelog.mjs
+++ b/packages/release/src/buildChangelog.js
@@ -1,4 +1,3 @@
-import conventionalChangelog from "conventional-changelog";
import { exec } from "child_process";
// const stream = conventionalChangelog(
@@ -41,7 +40,7 @@ async function getLog() {
return new Promise((resolve, reject) => {
exec(
`git log --oneline $(git describe --tags --abbrev=0 @^)..@ --pretty="format:${lineDelimiter}%H${logDelimiter}%h${logDelimiter}%s${logDelimiter}%b"`,
- (err, stdout, stderr) => {
+ (err, stdout, _stderr) => {
if (err) {
reject(err);
}
@@ -250,7 +249,7 @@ function convertStringToLog(logString) {
//split message using regex based on fix(language): spelling mistakes in Nepali wordlist and quotes (sapradhan) (#4528)
//scope is optional, username is optional, pr number is optional
- const [__, type, scope, message, message2, message3] = title.split(
+ const [_, type, scope, message, message2, message3] = title.split(
/^(\w+)(?:\(([^)]+)\))?:\s+(.+?)\s*(?:\(([^)]+)\))?(?:\s+\(([^)]+)\))?(?:\s+\(([^)]+)\))?$/
);
diff --git a/bin/release.mjs b/packages/release/src/index.js
old mode 100644
new mode 100755
similarity index 79%
rename from bin/release.mjs
rename to packages/release/src/index.js
index f285f97e04d9..3d7d7a72aa71
--- a/bin/release.mjs
+++ b/packages/release/src/index.js
@@ -1,12 +1,10 @@
import { execSync } from "child_process";
import { Octokit } from "@octokit/rest";
import dotenv from "dotenv";
-import { readFileSync } from "fs";
+import fs, { readFileSync } from "fs";
import readlineSync from "readline-sync";
-import path from "path";
-import fs from "fs";
+import path, { dirname } from "path";
import { fileURLToPath } from "url";
-import { dirname } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -18,6 +16,9 @@ const isFrontend = args.includes("--fe");
const noDeploy = args.includes("--no-deploy");
const isBackend = args.includes("--be");
const isDryRun = args.includes("--dry");
+const noSyncCheck = args.includes("--no-sync-check");
+
+const PROJECT_ROOT = path.resolve(__dirname, "../../../");
const runCommand = (command, force) => {
if (isDryRun && !force) {
@@ -35,19 +36,41 @@ const runCommand = (command, force) => {
}
};
+const runProjectRootCommand = (command, force) => {
+ if (isDryRun && !force) {
+ console.log(`[Dry Run] Command: ${command}`);
+ return "[Dry Run] Command executed.";
+ } else {
+ try {
+ const output = execSync(`cd ${PROJECT_ROOT} && ${command}`, {
+ stdio: "pipe",
+ }).toString();
+ return output;
+ } catch (error) {
+ console.error(`Error executing command ${command}`);
+ console.error(error);
+ process.exit(1);
+ }
+ }
+};
+
const checkBranchSync = () => {
console.log("Checking if local master branch is in sync with origin...");
- if (isDryRun) {
+ if (noSyncCheck) {
+ console.log("Skipping sync check.");
+ } else if (isDryRun) {
console.log("[Dry Run] Checking sync...");
} else {
try {
// Fetch the latest changes from the remote repository
- runCommand("git fetch origin");
+ runProjectRootCommand("git fetch origin");
// Get the commit hashes of the local and remote master branches
- const localMaster = runCommand("git rev-parse master").trim();
- const remoteMaster = runCommand("git rev-parse origin/master").trim();
+ const localMaster = runProjectRootCommand("git rev-parse master").trim();
+ const remoteMaster = runProjectRootCommand(
+ "git rev-parse origin/master"
+ ).trim();
if (localMaster !== remoteMaster) {
console.error(
@@ -65,8 +88,12 @@ const checkBranchSync = () => {
const getCurrentVersion = () => {
console.log("Getting current version...");
- const packageJson = JSON.parse(readFileSync("./package.json", "utf-8"));
- return packageJson.version;
+
+ const rootPackageJson = JSON.parse(
+ readFileSync(`${PROJECT_ROOT}/package.json`, "utf-8")
+ );
+
+ return rootPackageJson.version;
};
const incrementVersion = (currentVersion) => {
@@ -134,28 +161,32 @@ const buildProject = () => {
filter = "--filter @monkeytype/backend";
}
- runCommand("npx turbo lint test validate-json build " + filter);
+ runProjectRootCommand("npx turbo lint test validate-json build " + filter);
};
const deployBackend = () => {
console.log("Deploying backend...");
- runCommand("sh ./bin/deployBackend.sh");
+ runCommand("sh ../bin/deployBackend.sh");
};
const deployFrontend = () => {
console.log("Deploying frontend...");
- runCommand("cd frontend && npx firebase deploy -P live --only hosting");
+ runProjectRootCommand(
+ "cd frontend && npx firebase deploy -P live --only hosting"
+ );
};
const purgeCache = () => {
console.log("Purging Cloudflare cache...");
- runCommand("sh ./bin/purgeCfCache.sh");
+ runCommand("sh ../bin/purgeCfCache.sh");
};
const generateChangelog = async () => {
console.log("Generating changelog...");
- const changelog = runCommand("node bin/buildChangelog.mjs", true);
+ const p = path.resolve(__dirname, "./buildChangelog.js");
+
+ const changelog = runCommand(`node ${p}`, true);
return changelog;
};
diff --git a/packages/shared-types/src/index.ts b/packages/shared-types/src/index.ts
index 695c637b470e..d069ecd3ca02 100644
--- a/packages/shared-types/src/index.ts
+++ b/packages/shared-types/src/index.ts
@@ -301,25 +301,6 @@ export type ResultFilters = {
} & Record;
};
-export type PSA = {
- _id: string;
- message: string;
- sticky?: boolean;
- level?: number;
- date?: number;
-};
-
-export type SpeedHistogram = {
- [key: string]: number;
-};
-
-export type PublicTypingStats = {
- type: string;
- timeTyping: number;
- testsCompleted: number;
- testsStarted: number;
-};
-
export type LeaderboardEntry = {
_id: string;
wpm: number;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2eb2832a2abb..2d9ccbb0e4a4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -14,27 +14,15 @@ importers:
'@commitlint/config-conventional':
specifier: 17.7.0
version: 17.7.0
+ '@monkeytype/release':
+ specifier: workspace:*
+ version: link:packages/release
+ '@vitest/coverage-v8':
+ specifier: 1.6.0
+ version: 1.6.0(vitest@1.6.0(@types/node@20.5.1)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3))
conventional-changelog:
specifier: 4.0.0
version: 4.0.0
- eslint:
- specifier: 8.57.0
- version: 8.57.0
- eslint-config-prettier:
- specifier: 9.0.0
- version: 9.0.0(eslint@8.57.0)
- eslint-import-resolver-typescript:
- specifier: 3.6.1
- version: 3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0)
- eslint-plugin-import:
- specifier: 2.29.0
- version: 2.29.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
- eslint-plugin-json:
- specifier: 2.1.2
- version: 2.1.2
- eslint-plugin-require-path-exists:
- specifier: 1.1.9
- version: 1.1.9
husky:
specifier: 8.0.1
version: 8.0.1
@@ -50,18 +38,12 @@ importers:
prettier:
specifier: 2.5.1
version: 2.5.1
- readline-sync:
- specifier: 1.4.10
- version: 1.4.10
turbo:
specifier: 2.0.9
version: 2.0.9
- typescript:
- specifier: 5.5.4
- version: 5.5.4
- wait-for-localhost-cli:
- specifier: 3.2.0
- version: 3.2.0
+ vitest:
+ specifier: 1.6.0
+ version: 1.6.0(@types/node@20.5.1)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3)
backend:
dependencies:
@@ -264,6 +246,9 @@ importers:
ioredis-mock:
specifier: 7.4.0
version: 7.4.0(ioredis@4.28.5)
+ openapi-recursive-tagging:
+ specifier: 0.0.6
+ version: 0.0.6
readline-sync:
specifier: 1.4.10
version: 1.4.10
@@ -524,6 +509,40 @@ importers:
eslint-config-prettier:
specifier: 9.1.0
version: 9.1.0(eslint@8.57.0)
+ eslint-import-resolver-typescript:
+ specifier: 3.6.1
+ version: 3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0)
+ eslint-plugin-import:
+ specifier: 2.29.0
+ version: 2.29.0(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-plugin-json:
+ specifier: 3.1.0
+ version: 3.1.0
+ eslint-plugin-require-path-exists:
+ specifier: 1.1.9
+ version: 1.1.9
+
+ packages/release:
+ dependencies:
+ '@octokit/rest':
+ specifier: 20.1.1
+ version: 20.1.1
+ dotenv:
+ specifier: 16.4.5
+ version: 16.4.5
+ readline-sync:
+ specifier: 1.4.10
+ version: 1.4.10
+ devDependencies:
+ '@monkeytype/eslint-config':
+ specifier: workspace:*
+ version: link:../eslint-config
+ eslint:
+ specifier: 8.57.0
+ version: 8.57.0
+ nodemon:
+ specifier: 3.1.4
+ version: 3.1.4
packages/shared-types:
dependencies:
@@ -2193,6 +2212,58 @@ packages:
resolution: {integrity: sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ '@octokit/auth-token@4.0.0':
+ resolution: {integrity: sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==}
+ engines: {node: '>= 18'}
+
+ '@octokit/core@5.2.0':
+ resolution: {integrity: sha512-1LFfa/qnMQvEOAdzlQymH0ulepxbxnCYAKJZfMci/5XJyIHWgEYnDmgnKakbTh7CH2tFQ5O60oYDvns4i9RAIg==}
+ engines: {node: '>= 18'}
+
+ '@octokit/endpoint@9.0.5':
+ resolution: {integrity: sha512-ekqR4/+PCLkEBF6qgj8WqJfvDq65RH85OAgrtnVp1mSxaXF03u2xW/hUdweGS5654IlC0wkNYC18Z50tSYTAFw==}
+ engines: {node: '>= 18'}
+
+ '@octokit/graphql@7.1.0':
+ resolution: {integrity: sha512-r+oZUH7aMFui1ypZnAvZmn0KSqAUgE1/tUXIWaqUCa1758ts/Jio84GZuzsvUkme98kv0WFY8//n0J1Z+vsIsQ==}
+ engines: {node: '>= 18'}
+
+ '@octokit/openapi-types@22.2.0':
+ resolution: {integrity: sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==}
+
+ '@octokit/plugin-paginate-rest@11.3.1':
+ resolution: {integrity: sha512-ryqobs26cLtM1kQxqeZui4v8FeznirUsksiA+RYemMPJ7Micju0WSkv50dBksTuZks9O5cg4wp+t8fZ/cLY56g==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ '@octokit/core': '5'
+
+ '@octokit/plugin-request-log@4.0.1':
+ resolution: {integrity: sha512-GihNqNpGHorUrO7Qa9JbAl0dbLnqJVrV8OXe2Zm5/Y4wFkZQDfTreBzVmiRfJVfE4mClXdihHnbpyyO9FSX4HA==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ '@octokit/core': '5'
+
+ '@octokit/plugin-rest-endpoint-methods@13.2.2':
+ resolution: {integrity: sha512-EI7kXWidkt3Xlok5uN43suK99VWqc8OaIMktY9d9+RNKl69juoTyxmLoWPIZgJYzi41qj/9zU7G/ljnNOJ5AFA==}
+ engines: {node: '>= 18'}
+ peerDependencies:
+ '@octokit/core': ^5
+
+ '@octokit/request-error@5.1.0':
+ resolution: {integrity: sha512-GETXfE05J0+7H2STzekpKObFe765O5dlAKUTLNGeH+x47z7JjXHfsHKo5z21D/o/IOZTUEI6nyWyR+bZVP/n5Q==}
+ engines: {node: '>= 18'}
+
+ '@octokit/request@8.4.0':
+ resolution: {integrity: sha512-9Bb014e+m2TgBeEJGEbdplMVWwPmL1FPtggHQRkV+WVsMggPtEkLKPlcVYm/o8xKLkpJ7B+6N8WfQMtDLX2Dpw==}
+ engines: {node: '>= 18'}
+
+ '@octokit/rest@20.1.1':
+ resolution: {integrity: sha512-MB4AYDsM5jhIHro/dq4ix1iWTLGToIGk6cWF5L6vanFaMble5jTX/UBQyiv05HsWnwUtY8JrfHy2LWfKwihqMw==}
+ engines: {node: '>= 18'}
+
+ '@octokit/types@13.5.0':
+ resolution: {integrity: sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==}
+
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
@@ -2844,6 +2915,10 @@ packages:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
engines: {node: '>=0.10.0'}
+ ansi-regex@4.1.1:
+ resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==}
+ engines: {node: '>=6'}
+
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@@ -3165,6 +3240,9 @@ packages:
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
engines: {node: '>= 10.0.0'}
+ before-after-hook@2.2.3:
+ resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
+
bignumber.js@9.1.2:
resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==}
@@ -3302,10 +3380,6 @@ packages:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
engines: {node: '>=8'}
- camelcase-keys@7.0.2:
- resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==}
- engines: {node: '>=12'}
-
camelcase@3.0.0:
resolution: {integrity: sha512-4nhGqUkc4BqbBBB4Q6zLuD7lzzrHYrjKGeYaEji/3tFR5VdJu9v+LilhGIVe8wxEJPPOeWo7eg8dwY13TZ1BNg==}
engines: {node: '>=0.10.0'}
@@ -3449,6 +3523,9 @@ packages:
cliui@3.2.0:
resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==}
+ cliui@5.0.0:
+ resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==}
+
cliui@7.0.4:
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
@@ -3892,10 +3969,6 @@ packages:
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
engines: {node: '>=0.10.0'}
- decamelize@5.0.1:
- resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==}
- engines: {node: '>=10'}
-
decko@1.2.0:
resolution: {integrity: sha512-m8FnyHXV1QX+S1cl+KPFDIl6NMkxtKsy6+U/aYyjrOqWMuwAwYWu7ePqrsUHtDR5Y8Yk2pi/KIDSgF+vT4cPOQ==}
@@ -3999,6 +4072,9 @@ packages:
engines: {node: ^10.13 || ^12 || >=14}
hasBin: true
+ deprecation@2.3.1:
+ resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
+
destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
@@ -4189,6 +4265,9 @@ packages:
electron-to-chromium@1.5.5:
resolution: {integrity: sha512-QR7/A7ZkMS8tZuoftC/jfqNkZLQO779SSW3YuZHP4eXpj3EffGLFcB/Xu9AAZQzLccTiCV+EmUo3ha4mQ9wnlA==}
+ emoji-regex@7.0.3:
+ resolution: {integrity: sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -4328,12 +4407,6 @@ packages:
engines: {node: '>=6.0'}
hasBin: true
- eslint-config-prettier@9.0.0:
- resolution: {integrity: sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==}
- hasBin: true
- peerDependencies:
- eslint: '>=7.0.0'
-
eslint-config-prettier@9.1.0:
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
hasBin: true
@@ -4381,9 +4454,9 @@ packages:
'@typescript-eslint/parser':
optional: true
- eslint-plugin-json@2.1.2:
- resolution: {integrity: sha512-isM/fsUxS4wN1+nLsWoV5T4gLgBQnsql3nMTr8u+cEls1bL8rRQO5CP5GtxJxaOfbcKqnz401styw+H/P+e78Q==}
- engines: {node: '>=8.10.0'}
+ eslint-plugin-json@3.1.0:
+ resolution: {integrity: sha512-MrlG2ynFEHe7wDGwbUuFPsaT2b1uhuEFhJ+W1f1u+1C2EkXmTYJp4B1aAdQQ8M+CC3t//N/oRKiIVw14L2HR1g==}
+ engines: {node: '>=12.0'}
eslint-plugin-require-path-exists@1.1.9:
resolution: {integrity: sha512-moZRfrPr4GFyT/W8PHzjzC7D4Hnj7Us+GYj0fbVKQoPvP4xIF8VG702L1jzyhqE8eIYkcs8p1CoqSfjk9WkxBg==}
@@ -4665,6 +4738,10 @@ packages:
resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==}
engines: {node: '>=4'}
+ find-up@3.0.0:
+ resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
+ engines: {node: '>=6'}
+
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -5330,10 +5407,6 @@ packages:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
- indent-string@5.0.0:
- resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
- engines: {node: '>=12'}
-
indexes-of@1.0.1:
resolution: {integrity: sha512-bup+4tap3Hympa+JBJUG7XuOsdNQ6fxt0MHyXMKuLBKn0OqsTfvUxkUrroEX1+B2VsSHvCjiIcZVxRtYa4nllA==}
@@ -5504,6 +5577,10 @@ packages:
resolution: {integrity: sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==}
engines: {node: '>=0.10.0'}
+ is-fullwidth-code-point@2.0.0:
+ resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==}
+ engines: {node: '>=4'}
+
is-fullwidth-code-point@3.0.0:
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
engines: {node: '>=8'}
@@ -6028,6 +6105,10 @@ packages:
resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==}
engines: {node: '>=4'}
+ locate-path@3.0.0:
+ resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
+ engines: {node: '>=6'}
+
locate-path@5.0.0:
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
engines: {node: '>=8'}
@@ -6284,10 +6365,6 @@ packages:
mensch@0.3.4:
resolution: {integrity: sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g==}
- meow@10.1.5:
- resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
meow@8.1.2:
resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==}
engines: {node: '>=10'}
@@ -6989,6 +7066,10 @@ packages:
resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==}
engines: {node: '>=12'}
+ openapi-recursive-tagging@0.0.6:
+ resolution: {integrity: sha512-ZtHR0jHeIMTuyeenbee7j0fK58Uf9ZMFGTXv3abQvsdjImfdEXfmmNlMNgpW27DmoomOgwwpGNGldUUQd0oJ4g==}
+ hasBin: true
+
openapi-sampler@1.5.1:
resolution: {integrity: sha512-tIWIrZUKNAsbqf3bd9U1oH6JEXo8LNYuDlXw26By67EygpjT+ArFnsxxyTMjFWRfbqo5ozkvgSQDK69Gd8CddA==}
@@ -7041,6 +7122,10 @@ packages:
resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==}
engines: {node: '>=4'}
+ p-locate@3.0.0:
+ resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==}
+ engines: {node: '>=6'}
+
p-locate@4.1.0:
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
engines: {node: '>=8'}
@@ -7517,10 +7602,6 @@ packages:
resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==}
engines: {node: '>=8'}
- quick-lru@5.1.1:
- resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
- engines: {node: '>=10'}
-
quote-unquote@1.0.0:
resolution: {integrity: sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==}
@@ -7596,10 +7677,6 @@ packages:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
- read-pkg-up@8.0.0:
- resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==}
- engines: {node: '>=12'}
-
read-pkg@1.1.0:
resolution: {integrity: sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==}
engines: {node: '>=0.10.0'}
@@ -7612,10 +7689,6 @@ packages:
resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==}
engines: {node: '>=8'}
- read-pkg@6.0.0:
- resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==}
- engines: {node: '>=12'}
-
readable-stream@2.3.8:
resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
@@ -7650,10 +7723,6 @@ packages:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
- redent@4.0.0:
- resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==}
- engines: {node: '>=12'}
-
redeyed@2.1.1:
resolution: {integrity: sha512-FNpGGo1DycYAdnrKFxCMmKYgo/mILAqtRYbkdQD8Ep/Hk2PQ5+aEAEx+IU713RTDmuBaH0c8P5ZozurNu5ObRQ==}
@@ -7764,6 +7833,9 @@ packages:
require-main-filename@1.0.1:
resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==}
+ require-main-filename@2.0.0:
+ resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
+
requirejs-config-file@4.0.0:
resolution: {integrity: sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw==}
engines: {node: '>=10.13.0'}
@@ -8300,6 +8372,10 @@ packages:
resolution: {integrity: sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==}
engines: {node: '>=0.10.0'}
+ string-width@3.1.0:
+ resolution: {integrity: sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==}
+ engines: {node: '>=6'}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -8340,6 +8416,10 @@ packages:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
engines: {node: '>=0.10.0'}
+ strip-ansi@5.2.0:
+ resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==}
+ engines: {node: '>=6'}
+
strip-ansi@6.0.1:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
@@ -8372,10 +8452,6 @@ packages:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
- strip-indent@4.0.0:
- resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==}
- engines: {node: '>=12'}
-
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
@@ -8663,10 +8739,6 @@ packages:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'}
- trim-newlines@4.1.1:
- resolution: {integrity: sha512-jRKj0n0jXWo6kh62nA5TEh3+4igKDXLvzBJcPpiizP7oOolUrYIxmVBG9TOtHYFHoddUk6YvAkGeGoSVTXfQXQ==}
- engines: {node: '>=12'}
-
triple-beam@1.4.1:
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
engines: {node: '>= 14.0.0'}
@@ -8792,10 +8864,6 @@ packages:
resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==}
engines: {node: '>=8'}
- type-fest@1.4.0:
- resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
- engines: {node: '>=10'}
-
type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
@@ -8927,6 +8995,9 @@ packages:
resolution: {integrity: sha512-HXSMyIcf2XTvwZ6ZZQLfxfViRm/yTGoRgDeTbojtq6rezeyKB0sTBcKH2fhddnteAHRcHiKgr/ACpbgjGOC6RQ==}
engines: {node: '>=12.18.2'}
+ universal-user-agent@6.0.1:
+ resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
+
universalify@2.0.1:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
@@ -9187,8 +9258,8 @@ packages:
vlq@0.2.3:
resolution: {integrity: sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==}
- vscode-json-languageservice@3.11.0:
- resolution: {integrity: sha512-QxI+qV97uD7HHOCjh3MrM1TfbdwmTXrMckri5Tus1/FQiG3baDZb2C9Y0y8QThs7PwHYBIQXcAc59ZveCRZKPA==}
+ vscode-json-languageservice@4.2.1:
+ resolution: {integrity: sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==}
vscode-jsonrpc@6.0.0:
resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==}
@@ -9207,9 +9278,6 @@ packages:
vscode-languageserver-types@3.16.0:
resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==}
- vscode-languageserver-types@3.16.0-next.2:
- resolution: {integrity: sha512-QjXB7CKIfFzKbiCJC4OWC8xUncLsxo19FzGVp/ADFvvi87PlmBSCAtZI5xwGjF5qE0xkLf0jjKUn3DzmpDP52Q==}
-
vscode-languageserver@7.0.0:
resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==}
hasBin: true
@@ -9217,21 +9285,9 @@ packages:
vscode-nls@5.2.0:
resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==}
- vscode-uri@2.1.2:
- resolution: {integrity: sha512-8TEXQxlldWAuIODdukIb+TR5s+9Ds40eSJrw+1iDDA9IFORPjMELarNQE3myz5XIkWWpdprmJjm1/SxMlWOC8A==}
-
vscode-uri@3.0.8:
resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==}
- wait-for-localhost-cli@3.2.0:
- resolution: {integrity: sha512-Qeb/137B3PSiQSddXMGS40i7w2G2pWJ/01bJLQ4pk+WZ7V3+i7fIbKajf6/MPcBqYPJvNdYWAuTJ53ahhqZBog==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
- hasBin: true
-
- wait-for-localhost@4.1.0:
- resolution: {integrity: sha512-i3yX7qgAnxFXWOS6Om7SNi19HRygFvCGh0iy0nOsrYOSiIlhUqoUuzLoW4jHVYS4dzNGzRU100uwTyluOMWcjw==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
walkdir@0.4.1:
resolution: {integrity: sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ==}
engines: {node: '>=6.0.0'}
@@ -9292,6 +9348,9 @@ packages:
which-module@1.0.0:
resolution: {integrity: sha512-F6+WgncZi/mJDrammbTuHe1q0R5hOXv/mBaiNA2TCNT/LTHusX0V+CJnj9XT8ki5ln2UZyyddDgHfCzyrOH7MQ==}
+ which-module@2.0.1:
+ resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
+
which-pm-runs@1.1.0:
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
engines: {node: '>=4'}
@@ -9397,6 +9456,10 @@ packages:
resolution: {integrity: sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@5.1.0:
+ resolution: {integrity: sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==}
+ engines: {node: '>=6'}
+
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -9438,6 +9501,9 @@ packages:
y18n@3.2.2:
resolution: {integrity: sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==}
+ y18n@4.0.3:
+ resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
+
y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
@@ -9460,6 +9526,9 @@ packages:
engines: {node: '>= 14'}
hasBin: true
+ yargs-parser@15.0.3:
+ resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==}
+
yargs-parser@20.2.9:
resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
engines: {node: '>=10'}
@@ -9471,6 +9540,9 @@ packages:
yargs-parser@5.0.1:
resolution: {integrity: sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==}
+ yargs@14.2.3:
+ resolution: {integrity: sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==}
+
yargs@16.2.0:
resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
engines: {node: '>=10'}
@@ -11465,6 +11537,69 @@ snapshots:
'@npmcli/name-from-folder@2.0.0': {}
+ '@octokit/auth-token@4.0.0': {}
+
+ '@octokit/core@5.2.0':
+ dependencies:
+ '@octokit/auth-token': 4.0.0
+ '@octokit/graphql': 7.1.0
+ '@octokit/request': 8.4.0
+ '@octokit/request-error': 5.1.0
+ '@octokit/types': 13.5.0
+ before-after-hook: 2.2.3
+ universal-user-agent: 6.0.1
+
+ '@octokit/endpoint@9.0.5':
+ dependencies:
+ '@octokit/types': 13.5.0
+ universal-user-agent: 6.0.1
+
+ '@octokit/graphql@7.1.0':
+ dependencies:
+ '@octokit/request': 8.4.0
+ '@octokit/types': 13.5.0
+ universal-user-agent: 6.0.1
+
+ '@octokit/openapi-types@22.2.0': {}
+
+ '@octokit/plugin-paginate-rest@11.3.1(@octokit/core@5.2.0)':
+ dependencies:
+ '@octokit/core': 5.2.0
+ '@octokit/types': 13.5.0
+
+ '@octokit/plugin-request-log@4.0.1(@octokit/core@5.2.0)':
+ dependencies:
+ '@octokit/core': 5.2.0
+
+ '@octokit/plugin-rest-endpoint-methods@13.2.2(@octokit/core@5.2.0)':
+ dependencies:
+ '@octokit/core': 5.2.0
+ '@octokit/types': 13.5.0
+
+ '@octokit/request-error@5.1.0':
+ dependencies:
+ '@octokit/types': 13.5.0
+ deprecation: 2.3.1
+ once: 1.4.0
+
+ '@octokit/request@8.4.0':
+ dependencies:
+ '@octokit/endpoint': 9.0.5
+ '@octokit/request-error': 5.1.0
+ '@octokit/types': 13.5.0
+ universal-user-agent: 6.0.1
+
+ '@octokit/rest@20.1.1':
+ dependencies:
+ '@octokit/core': 5.2.0
+ '@octokit/plugin-paginate-rest': 11.3.1(@octokit/core@5.2.0)
+ '@octokit/plugin-request-log': 4.0.1(@octokit/core@5.2.0)
+ '@octokit/plugin-rest-endpoint-methods': 13.2.2(@octokit/core@5.2.0)
+
+ '@octokit/types@13.5.0':
+ dependencies:
+ '@octokit/openapi-types': 22.2.0
+
'@one-ini/wasm@0.1.1': {}
'@opentelemetry/api@1.8.0': {}
@@ -12056,6 +12191,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.5.1)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3))':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@bcoe/v8-coverage': 0.2.3
+ debug: 4.3.6(supports-color@5.5.0)
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.6
+ istanbul-reports: 3.1.7
+ magic-string: 0.30.11
+ magicast: 0.3.4
+ picocolors: 1.0.1
+ std-env: 3.7.0
+ strip-literal: 2.1.0
+ test-exclude: 6.0.0
+ vitest: 1.6.0(@types/node@20.5.1)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitest/expect@1.6.0':
dependencies:
'@vitest/spy': 1.6.0
@@ -12174,6 +12328,8 @@ snapshots:
ansi-regex@2.1.1: {}
+ ansi-regex@4.1.1: {}
+
ansi-regex@5.0.1: {}
ansi-regex@6.0.1: {}
@@ -12519,6 +12675,8 @@ snapshots:
- encoding
- supports-color
+ before-after-hook@2.2.3: {}
+
bignumber.js@9.1.2: {}
binary-extensions@1.13.1: {}
@@ -12712,13 +12870,6 @@ snapshots:
map-obj: 4.3.0
quick-lru: 4.0.1
- camelcase-keys@7.0.2:
- dependencies:
- camelcase: 6.3.0
- map-obj: 4.3.0
- quick-lru: 5.1.1
- type-fest: 1.4.0
-
camelcase@3.0.0: {}
camelcase@5.3.1: {}
@@ -12887,6 +13038,12 @@ snapshots:
strip-ansi: 3.0.1
wrap-ansi: 2.1.0
+ cliui@5.0.0:
+ dependencies:
+ string-width: 3.1.0
+ strip-ansi: 5.2.0
+ wrap-ansi: 5.1.0
+
cliui@7.0.4:
dependencies:
string-width: 4.2.3
@@ -13332,8 +13489,6 @@ snapshots:
decamelize@1.2.0: {}
- decamelize@5.0.1: {}
-
decko@1.2.0: {}
decode-uri-component@0.2.2: {}
@@ -13427,6 +13582,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ deprecation@2.3.1: {}
+
destroy@1.2.0: {}
detect-file@1.0.0: {}
@@ -13651,6 +13808,8 @@ snapshots:
electron-to-chromium@1.5.5: {}
+ emoji-regex@7.0.3: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -13922,10 +14081,6 @@ snapshots:
optionalDependencies:
source-map: 0.6.1
- eslint-config-prettier@9.0.0(eslint@8.57.0):
- dependencies:
- eslint: 8.57.0
-
eslint-config-prettier@9.1.0(eslint@8.57.0):
dependencies:
eslint: 8.57.0
@@ -13938,13 +14093,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-import-resolver-typescript@3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0):
+ eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0):
dependencies:
debug: 4.3.6(supports-color@5.5.0)
enhanced-resolve: 5.17.1
eslint: 8.57.0
- eslint-module-utils: 2.8.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0)
- eslint-plugin-import: 2.29.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0)
+ eslint-plugin-import: 2.29.0(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
fast-glob: 3.3.2
get-tsconfig: 4.7.6
is-core-module: 2.15.0
@@ -13955,17 +14110,18 @@ snapshots:
- eslint-import-resolver-webpack
- supports-color
- eslint-module-utils@2.8.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0):
+ eslint-module-utils@2.8.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7
optionalDependencies:
+ '@typescript-eslint/parser': 8.0.1(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-import-resolver-typescript: 3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0)
+ eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0)
transitivePeerDependencies:
- supports-color
- eslint-plugin-import@2.29.0(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
+ eslint-plugin-import@2.29.0(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@@ -13975,7 +14131,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
- eslint-module-utils: 2.8.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0)
+ eslint-module-utils: 2.8.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@8.0.1(eslint@8.57.0)(typescript@5.5.4))(eslint-plugin-import@2.29.0)(eslint@8.57.0))(eslint@8.57.0)
hasown: 2.0.2
is-core-module: 2.15.0
is-glob: 4.0.3
@@ -13985,15 +14141,17 @@ snapshots:
object.values: 1.2.0
semver: 6.3.1
tsconfig-paths: 3.15.0
+ optionalDependencies:
+ '@typescript-eslint/parser': 8.0.1(eslint@8.57.0)(typescript@5.5.4)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
- eslint-plugin-json@2.1.2:
+ eslint-plugin-json@3.1.0:
dependencies:
lodash: 4.17.21
- vscode-json-languageservice: 3.11.0
+ vscode-json-languageservice: 4.2.1
eslint-plugin-require-path-exists@1.1.9:
dependencies:
@@ -14464,6 +14622,10 @@ snapshots:
dependencies:
locate-path: 2.0.0
+ find-up@3.0.0:
+ dependencies:
+ locate-path: 3.0.0
+
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@@ -15401,8 +15563,6 @@ snapshots:
indent-string@4.0.0: {}
- indent-string@5.0.0: {}
-
indexes-of@1.0.1: {}
inflight@1.0.6:
@@ -15594,6 +15754,8 @@ snapshots:
dependencies:
number-is-nan: 1.0.1
+ is-fullwidth-code-point@2.0.0: {}
+
is-fullwidth-code-point@3.0.0: {}
is-fullwidth-code-point@4.0.0: {}
@@ -16132,6 +16294,11 @@ snapshots:
p-locate: 2.0.0
path-exists: 3.0.0
+ locate-path@3.0.0:
+ dependencies:
+ p-locate: 3.0.0
+ path-exists: 3.0.0
+
locate-path@5.0.0:
dependencies:
p-locate: 4.1.0
@@ -16388,21 +16555,6 @@ snapshots:
mensch@0.3.4: {}
- meow@10.1.5:
- dependencies:
- '@types/minimist': 1.2.5
- camelcase-keys: 7.0.2
- decamelize: 5.0.1
- decamelize-keys: 1.1.1
- hard-rejection: 2.1.0
- minimist-options: 4.1.0
- normalize-package-data: 3.0.3
- read-pkg-up: 8.0.0
- redent: 4.0.0
- trim-newlines: 4.1.1
- type-fest: 1.4.0
- yargs-parser: 20.2.9
-
meow@8.1.2:
dependencies:
'@types/minimist': 1.2.5
@@ -17206,7 +17358,7 @@ snapshots:
oas-kit-common: 1.0.8
reftools: 1.1.9
yaml: 1.10.2
- yargs: 17.0.1
+ yargs: 17.7.2
oas-schema-walker@1.1.5: {}
@@ -17348,6 +17500,12 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
+ openapi-recursive-tagging@0.0.6:
+ dependencies:
+ reftools: 1.1.9
+ yaml: 1.10.2
+ yargs: 14.2.3
+
openapi-sampler@1.5.1:
dependencies:
'@types/json-schema': 7.0.15
@@ -17414,6 +17572,10 @@ snapshots:
dependencies:
p-limit: 1.3.0
+ p-locate@3.0.0:
+ dependencies:
+ p-limit: 2.3.0
+
p-locate@4.1.0:
dependencies:
p-limit: 2.3.0
@@ -17893,8 +18055,6 @@ snapshots:
quick-lru@4.0.1: {}
- quick-lru@5.1.1: {}
-
quote-unquote@1.0.0: {}
raf@3.4.1:
@@ -17986,12 +18146,6 @@ snapshots:
read-pkg: 5.2.0
type-fest: 0.8.1
- read-pkg-up@8.0.0:
- dependencies:
- find-up: 5.0.0
- read-pkg: 6.0.0
- type-fest: 1.4.0
-
read-pkg@1.1.0:
dependencies:
load-json-file: 1.1.0
@@ -18011,13 +18165,6 @@ snapshots:
parse-json: 5.2.0
type-fest: 0.6.0
- read-pkg@6.0.0:
- dependencies:
- '@types/normalize-package-data': 2.4.4
- normalize-package-data: 3.0.3
- parse-json: 5.2.0
- type-fest: 1.4.0
-
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
@@ -18069,11 +18216,6 @@ snapshots:
indent-string: 4.0.0
strip-indent: 3.0.0
- redent@4.0.0:
- dependencies:
- indent-string: 5.0.0
- strip-indent: 4.0.0
-
redeyed@2.1.1:
dependencies:
esprima: 4.0.1
@@ -18203,6 +18345,8 @@ snapshots:
require-main-filename@1.0.1: {}
+ require-main-filename@2.0.0: {}
+
requirejs-config-file@4.0.0:
dependencies:
esprima: 4.0.1
@@ -18789,6 +18933,12 @@ snapshots:
is-fullwidth-code-point: 1.0.0
strip-ansi: 3.0.1
+ string-width@3.1.0:
+ dependencies:
+ emoji-regex: 7.0.3
+ is-fullwidth-code-point: 2.0.0
+ strip-ansi: 5.2.0
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -18855,6 +19005,10 @@ snapshots:
dependencies:
ansi-regex: 2.1.1
+ strip-ansi@5.2.0:
+ dependencies:
+ ansi-regex: 4.1.1
+
strip-ansi@6.0.1:
dependencies:
ansi-regex: 5.0.1
@@ -18879,10 +19033,6 @@ snapshots:
dependencies:
min-indent: 1.0.1
- strip-indent@4.0.0:
- dependencies:
- min-indent: 1.0.1
-
strip-json-comments@2.0.1: {}
strip-json-comments@3.1.1: {}
@@ -19037,7 +19187,7 @@ snapshots:
oas-validator: 5.0.8
reftools: 1.1.9
yaml: 1.10.2
- yargs: 17.0.1
+ yargs: 17.7.2
transitivePeerDependencies:
- encoding
@@ -19243,8 +19393,6 @@ snapshots:
trim-newlines@3.0.1: {}
- trim-newlines@4.1.1: {}
-
triple-beam@1.4.1: {}
ts-api-utils@1.3.0(typescript@5.5.4):
@@ -19350,8 +19498,6 @@ snapshots:
type-fest@0.8.1: {}
- type-fest@1.4.0: {}
-
type-is@1.6.18:
dependencies:
media-typer: 0.3.0
@@ -19497,6 +19643,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ universal-user-agent@6.0.1: {}
+
universalify@2.0.1: {}
unpipe@1.0.0: {}
@@ -19697,6 +19845,23 @@ snapshots:
- supports-color
- terser
+ vite-node@1.6.0(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3):
+ dependencies:
+ cac: 6.7.14
+ debug: 4.3.6(supports-color@5.5.0)
+ pathe: 1.1.2
+ picocolors: 1.0.1
+ vite: 5.1.7(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3)
+ transitivePeerDependencies:
+ - '@types/node'
+ - less
+ - lightningcss
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
vite-plugin-checker@0.6.4(eslint@8.57.0)(optionator@0.9.4)(typescript@5.5.4)(vite@5.1.7(@types/node@20.14.11)(sass@1.70.0)(terser@5.31.3)):
dependencies:
'@babel/code-frame': 7.24.7
@@ -19762,6 +19927,17 @@ snapshots:
sass: 1.70.0
terser: 5.31.3
+ vite@5.1.7(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3):
+ dependencies:
+ esbuild: 0.19.12
+ postcss: 8.4.40
+ rollup: 4.19.1
+ optionalDependencies:
+ '@types/node': 20.5.1
+ fsevents: 2.3.3
+ sass: 1.70.0
+ terser: 5.31.3
+
vitest-mongodb@1.0.0:
dependencies:
debug: 4.3.6(supports-color@5.5.0)
@@ -19808,15 +19984,49 @@ snapshots:
- supports-color
- terser
+ vitest@1.6.0(@types/node@20.5.1)(happy-dom@13.4.1)(sass@1.70.0)(terser@5.31.3):
+ dependencies:
+ '@vitest/expect': 1.6.0
+ '@vitest/runner': 1.6.0
+ '@vitest/snapshot': 1.6.0
+ '@vitest/spy': 1.6.0
+ '@vitest/utils': 1.6.0
+ acorn-walk: 8.3.3
+ chai: 4.5.0
+ debug: 4.3.6(supports-color@5.5.0)
+ execa: 8.0.1
+ local-pkg: 0.5.0
+ magic-string: 0.30.11
+ pathe: 1.1.2
+ picocolors: 1.0.1
+ std-env: 3.7.0
+ strip-literal: 2.1.0
+ tinybench: 2.8.0
+ tinypool: 0.8.4
+ vite: 5.1.7(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3)
+ vite-node: 1.6.0(@types/node@20.5.1)(sass@1.70.0)(terser@5.31.3)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 20.5.1
+ happy-dom: 13.4.1
+ transitivePeerDependencies:
+ - less
+ - lightningcss
+ - sass
+ - stylus
+ - sugarss
+ - supports-color
+ - terser
+
vlq@0.2.3: {}
- vscode-json-languageservice@3.11.0:
+ vscode-json-languageservice@4.2.1:
dependencies:
jsonc-parser: 3.3.1
vscode-languageserver-textdocument: 1.0.11
- vscode-languageserver-types: 3.16.0-next.2
+ vscode-languageserver-types: 3.16.0
vscode-nls: 5.2.0
- vscode-uri: 2.1.2
+ vscode-uri: 3.0.8
vscode-jsonrpc@6.0.0: {}
@@ -19835,25 +20045,14 @@ snapshots:
vscode-languageserver-types@3.16.0: {}
- vscode-languageserver-types@3.16.0-next.2: {}
-
vscode-languageserver@7.0.0:
dependencies:
vscode-languageserver-protocol: 3.16.0
vscode-nls@5.2.0: {}
- vscode-uri@2.1.2: {}
-
vscode-uri@3.0.8: {}
- wait-for-localhost-cli@3.2.0:
- dependencies:
- meow: 10.1.5
- wait-for-localhost: 4.1.0
-
- wait-for-localhost@4.1.0: {}
-
walkdir@0.4.1: {}
wawoff2@2.0.1:
@@ -19924,6 +20123,8 @@ snapshots:
which-module@1.0.0: {}
+ which-module@2.0.1: {}
+
which-pm-runs@1.1.0: {}
which-typed-array@1.1.15:
@@ -20105,6 +20306,12 @@ snapshots:
string-width: 1.0.2
strip-ansi: 3.0.1
+ wrap-ansi@5.1.0:
+ dependencies:
+ ansi-styles: 3.2.1
+ string-width: 3.1.0
+ strip-ansi: 5.2.0
+
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
@@ -20140,6 +20347,8 @@ snapshots:
y18n@3.2.2: {}
+ y18n@4.0.3: {}
+
y18n@5.0.8: {}
yallist@3.1.1: {}
@@ -20152,6 +20361,11 @@ snapshots:
yaml@2.5.0: {}
+ yargs-parser@15.0.3:
+ dependencies:
+ camelcase: 5.3.1
+ decamelize: 1.2.0
+
yargs-parser@20.2.9: {}
yargs-parser@21.1.1: {}
@@ -20161,6 +20375,20 @@ snapshots:
camelcase: 3.0.0
object.assign: 4.1.5
+ yargs@14.2.3:
+ dependencies:
+ cliui: 5.0.0
+ decamelize: 1.2.0
+ find-up: 3.0.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ require-main-filename: 2.0.0
+ set-blocking: 2.0.0
+ string-width: 3.1.0
+ which-module: 2.0.1
+ y18n: 4.0.3
+ yargs-parser: 15.0.3
+
yargs@16.2.0:
dependencies:
cliui: 7.0.4
diff --git a/vitest.config.js b/vitest.config.js
new file mode 100644
index 000000000000..82eb8ea19c95
--- /dev/null
+++ b/vitest.config.js
@@ -0,0 +1,11 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ coverage: {
+ enabled: true,
+ include: ["**/*.ts"],
+ reporter: ["json"],
+ },
+ },
+});
diff --git a/vitest.workspace.json b/vitest.workspace.json
new file mode 100644
index 000000000000..61cc010455a8
--- /dev/null
+++ b/vitest.workspace.json
@@ -0,0 +1 @@
+["packages/*", "frontend", "backend"]