diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..c1040fe --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,40 @@ +name: Build & Linter + +on: + push: + branches: + - "**" + pull_request: + branches: + - "**" + +permissions: + contents: read + +jobs: + build: + name: Build & Typecheck + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + run_install: false + - name: Install dependencies with pnpm + working-directory: ./src + run: pnpm install + - name: Check linting + working-directory: ./src + run: pnpm exec eslint . --max-warnings=0 + - name: Build + working-directory: ./src + run: pnpm run build + diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..9e21c98 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +pnpm --prefix=./src/ lint-staged diff --git a/flake.nix b/flake.nix index 0aa5758..50be788 100644 --- a/flake.nix +++ b/flake.nix @@ -19,6 +19,24 @@ flake-utils.lib.eachDefaultSystem ( system: let pkgs = nixpkgs.legacyPackages.${system}; + tmux-setup = pkgs.writeShellScriptBin "tmux-setup" '' + #!/usr/bin/env sh + SESSION="transcendance" + DIR=$(git rev-parse --show-toplevel 2>/dev/null || pwd) + if ! tmux has-session -t $SESSION 2>/dev/null; then + tmux new-session -d -s $SESSION -c "$DIR" -n dev + tmux send-keys -t $SESSION:0 'vim' C-m + tmux split-window -h -p 30 -t $SESSION:0 -c "$DIR" + tmux send-keys -t $SESSION:0.1 'exec zsh' C-m + tmux split-window -v -p 30 -t $SESSION:0.1 -c "$DIR" + tmux send-keys -t $SESSION:0.2 'watch -n0.5 npx --prefix=./src/ eslint .' C-m + tmux new-window -t $SESSION:1 -n git -c "$DIR" + tmux send-keys -t $SESSION:1 'lazygit' C-m + fi + tmux select-window -t $SESSION:0 + tmux select-pane -t $SESSION:0.0 + tmux attach -t $SESSION + ''; in { devShell = pkgs.mkShellNoCC { packages = with pkgs; [ @@ -31,6 +49,7 @@ dbmlSQLite.packages.${system}.default sqlite-interactive clang + tmux-setup ]; shellHook = '' export PODMAN_COMPOSE_WARNING_LOGS="false"; diff --git a/src/.husky/pre-commit b/src/.husky/pre-commit new file mode 100644 index 0000000..e506909 --- /dev/null +++ b/src/.husky/pre-commit @@ -0,0 +1,2 @@ +#!/usr/bin/env sh +pnpm lint-staged diff --git a/src/@shared/src/auth/index.ts b/src/@shared/src/auth/index.ts index c41aa26..1a25d67 100644 --- a/src/@shared/src/auth/index.ts +++ b/src/@shared/src/auth/index.ts @@ -1,23 +1,23 @@ -import OTP from "otp"; -import cookie from "@fastify/cookie"; -import fastifyJwt from "@fastify/jwt"; -import fp from "fastify-plugin"; -import { FastifyPluginAsync, preValidationAsyncHookHandler } from "fastify"; -import { Static, Type } from "@sinclair/typebox"; -import { UserId } from "@shared/database/mixin/user"; -import { useDatabase } from "@shared/database"; -import { isNullish, makeResponse } from "@shared/utils"; +import OTP from 'otp'; +import cookie from '@fastify/cookie'; +import fastifyJwt from '@fastify/jwt'; +import fp from 'fastify-plugin'; +import { FastifyPluginAsync, preValidationAsyncHookHandler } from 'fastify'; +import { Static, Type } from '@sinclair/typebox'; +import { UserId } from '@shared/database/mixin/user'; +import { useDatabase } from '@shared/database'; +import { isNullish, makeResponse } from '@shared/utils'; -const kRouteAuthDone = Symbol("shared-route-auth-done"); +const kRouteAuthDone = Symbol('shared-route-auth-done'); type AuthedUser = { id: UserId; name: string; }; -declare module "fastify" { +declare module 'fastify' { export interface FastifyInstance { - signJwt: (kind: "auth" | "otp", who: string) => string; + signJwt: (kind: 'auth' | 'otp', who: string) => string; [s: symbol]: boolean; } export interface FastifyRequest { @@ -26,17 +26,22 @@ declare module "fastify" { export interface FastifyContextConfig { requireAuth?: boolean; } + export interface RouteOptions { + [kRouteAuthDone]: boolean; + } } export const Otp = OTP; let jwtAdded = false; export const jwtPlugin = fp(async (fastify, _opts) => { + void _opts; + if (jwtAdded) return; jwtAdded = true; - let env = process.env.JWT_SECRET; - if (isNullish(env)) throw "JWT_SECRET is not defined"; - if (!fastify.hasDecorator("signJwt")) { - void fastify.decorate("signJwt", (kind, who) => + const env = process.env.JWT_SECRET; + if (isNullish(env)) throw 'JWT_SECRET is not defined'; + if (!fastify.hasDecorator('signJwt')) { + void fastify.decorate('signJwt', (kind, who) => fastify.jwt.sign({ kind, who, createdAt: Date.now() }), ); void fastify.register(fastifyJwt, { @@ -48,16 +53,16 @@ export const jwtPlugin = fp(async (fastify, _opts) => { export const JwtType = Type.Object({ kind: Type.Union([ - Type.Const("otp", { - description: "the token is only valid for otp call", + Type.Const('otp', { + description: 'the token is only valid for otp call', }), - Type.Const("auth", { - description: "the token is valid for authentication", + Type.Const('auth', { + description: 'the token is valid for authentication', }), ]), - who: Type.String({ description: "the login of the user" }), + who: Type.String({ description: 'the login of the user' }), createdAt: Type.Integer({ - description: "Unix timestamp of when the token as been created at", + description: 'Unix timestamp of when the token as been created at', }), }); @@ -65,56 +70,63 @@ export type JwtType = Static; let authAdded = false; export const authPlugin = fp(async (fastify, _opts) => { - if (authAdded) return void console.log("skipping"); + void _opts; + + if (authAdded) return void console.log('skipping'); authAdded = true; - await fastify.register(useDatabase as any, {}); - await fastify.register(jwtPlugin as any, {}); + await fastify.register(useDatabase as FastifyPluginAsync, {}); + await fastify.register(jwtPlugin as FastifyPluginAsync, {}); await fastify.register(cookie); - if (!fastify.hasRequestDecorator("authUser")) - fastify.decorateRequest("authUser", undefined); - fastify.addHook("onRoute", (routeOpts) => { + if (!fastify.hasRequestDecorator('authUser')) { fastify.decorateRequest('authUser', undefined); } + fastify.addHook('onRoute', (routeOpts) => { if ( routeOpts.config?.requireAuth && - !(routeOpts as any)[kRouteAuthDone] + !routeOpts[kRouteAuthDone] ) { - let f: preValidationAsyncHookHandler = async function(req, res) { + const f: preValidationAsyncHookHandler = async function(req, res) { try { - if (isNullish(req.cookies.token)) + if (isNullish(req.cookies.token)) { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.noCookie")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.noCookie')), ); - let tok = this.jwt.verify(req.cookies.token); - if (tok.kind != "auth") + } + const tok = this.jwt.verify(req.cookies.token); + if (tok.kind != 'auth') { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.invalidKind")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.invalidKind')), ); - let user = this.db.getUserFromName(tok.who); - if (isNullish(user)) + } + const user = this.db.getUserFromName(tok.who); + if (isNullish(user)) { return res - .clearCookie("token") + .clearCookie('token') .send( - JSON.stringify(makeResponse("notLoggedIn", "auth.noUser")), + JSON.stringify(makeResponse('notLoggedIn', 'auth.noUser')), ); + } req.authUser = { id: user.id, name: tok.who }; - } catch { + } + catch { return res - .clearCookie("token") - .send(JSON.stringify(makeResponse("notLoggedIn", "auth.invalid"))); + .clearCookie('token') + .send(JSON.stringify(makeResponse('notLoggedIn', 'auth.invalid'))); } }; if (!routeOpts.preValidation) { routeOpts.preValidation = [f]; - } else if (Array.isArray(routeOpts.preValidation)) { + } + else if (Array.isArray(routeOpts.preValidation)) { routeOpts.preValidation.push(f); - } else { + } + else { routeOpts.preValidation = [routeOpts.preValidation, f]; } - (routeOpts as any)[kRouteAuthDone] = true; + routeOpts[kRouteAuthDone] = true; } }); }); diff --git a/src/@shared/src/database/index.ts b/src/@shared/src/database/index.ts index d77b176..1f6d237 100644 --- a/src/@shared/src/database/index.ts +++ b/src/@shared/src/database/index.ts @@ -1,8 +1,8 @@ -import fp from 'fastify-plugin' -import { FastifyInstance, FastifyPluginAsync } from 'fastify' +import fp from 'fastify-plugin'; +import { FastifyInstance, FastifyPluginAsync } from 'fastify'; -import { Database as DbImpl } from "./mixin/_base"; -import { UserImpl, IUserDb } from "./mixin/user"; +import { Database as DbImpl } from './mixin/_base'; +import { UserImpl, IUserDb } from './mixin/user'; import { isNullish } from '@shared/utils'; @@ -21,17 +21,15 @@ let dbAdded = false; export const useDatabase = fp(async function( f: FastifyInstance, - _options: {}) { - if (dbAdded) - return; + _options: object) { + void _options; + if (dbAdded) { return; } dbAdded = true; - let path = process.env.DATABASE_DIR; - if (isNullish(path)) - throw "env `DATABASE_DIR` not defined"; - f.log.info(`Opening database with path: ${path}/database.db`) - let db: Database = new DbImpl(`${path}/database.db`) as Database; - if (!f.hasDecorator("db")) - f.decorate('db', db); + const path = process.env.DATABASE_DIR; + if (isNullish(path)) { throw 'env `DATABASE_DIR` not defined'; } + f.log.info(`Opening database with path: ${path}/database.db`); + const db: Database = new DbImpl(`${path}/database.db`) as Database; + if (!f.hasDecorator('db')) { f.decorate('db', db); } }); export default useDatabase; diff --git a/src/@shared/src/database/mixin/_base.ts b/src/@shared/src/database/mixin/_base.ts index 1d601f1..5811c9e 100644 --- a/src/@shared/src/database/mixin/_base.ts +++ b/src/@shared/src/database/mixin/_base.ts @@ -1,57 +1,57 @@ -import sqlite from "better-sqlite3"; +import sqlite from 'better-sqlite3'; -// @ts-ignore: this file is included using vite, typescript doesn't know how to include this... -import initSql from "../init.sql?raw" +// @ts-expect-error: this file is included using vite, typescript doesn't know how to include this... +import initSql from '../init.sql?raw'; export type SqliteReturn = object | undefined; // Only way to use the database. Everything must be done through this. // Prefer to use prepared statement `using this.db.prepare` // -// this is the base, meaning that no actual query are made from this file. +// this is the base, meaning that no actual query are made from this file. // To create a new query function, go open another file, create a class that inherit from this class // in the `index.ts` file, import the new class, and make the `Database` class inherit it export class Database { - private db: sqlite.Database; - private st: Map = new Map(); + private db: sqlite.Database; + private st: Map = new Map(); - /** + /** * Create a new instance of the database, and init it to a known state * the file ./init.sql will be ran onto the database, creating any table that might be missing */ - constructor(db_path: string) { - this.db = sqlite(db_path, {}); - this.db.pragma('journal_mode = WAL'); - this.db.transaction(() => this.db.exec(initSql))(); - } + constructor(db_path: string) { + this.db = sqlite(db_path, {}); + this.db.pragma('journal_mode = WAL'); + this.db.transaction(() => this.db.exec(initSql))(); + } - /** + /** * close the database */ - public destroy(): void { - // remove any statement from the cache - this.st.clear(); - // close the database - this.db?.close(); - } + public destroy(): void { + // remove any statement from the cache + this.st.clear(); + // close the database + this.db?.close(); + } - /** + /** * use this to create queries. This will create statements (kinda expensive) and cache them * since they will be cached, this means that they are only created once, * otherwise they'll be just spat out from the cache - * the statements are cached by the {query} argument, + * the statements are cached by the {query} argument, * meaning that if you try to make two identiqual statement, but with different {query} they won't be cached * * @example this.prepare('SELECT * FROM users WHERE id = ?') * @example this.prepare('SELECT * FROM users LIMIT 100 OFFSET ?') */ - protected prepare(query: string): sqlite.Statement { - let st = this.st.get(query); - if (st !== undefined) return st; + protected prepare(query: string): sqlite.Statement { + let st = this.st.get(query); + if (st !== undefined) return st; - st = this.db.prepare(query); - this.st.set(query, st); - return st; - } + st = this.db.prepare(query); + this.st.set(query, st); + return st; + } } diff --git a/src/@shared/src/database/mixin/_template.ts b/src/@shared/src/database/mixin/_template.ts index 05ced1e..c7fbee5 100644 --- a/src/@shared/src/database/mixin/_template.ts +++ b/src/@shared/src/database/mixin/_template.ts @@ -1,4 +1,4 @@ -import type { Database } from "./_base"; +import type { Database } from './_base'; // never use this directly @@ -9,28 +9,28 @@ export interface ITemplateDb extends Database { }; export const UserImpl: Omit = { - /** + /** * whole function description * * @param id the argument description * * @returns what does the function return ? */ - normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | undefined { - void id; - return undefined; - }, - /** + normalFunction(this: ITemplateDb, id: TemplateId): TemplateData | undefined { + void id; + return undefined; + }, + /** * whole function description * * @param id the argument description * * @returns what does the function return ? */ - async asyncFunction(this: ITemplateDb, id: TemplateId): Promise { - void id; - return undefined; - }, + async asyncFunction(this: ITemplateDb, id: TemplateId): Promise { + void id; + return undefined; + }, }; export type TemplateId = number & { readonly __brand: unique symbol }; @@ -43,13 +43,13 @@ export type TemplateData = { // this function will be able to be called from everywhere export async function freeFloatingExportedFunction(): Promise { - return false; + return false; } // this function will never be able to be called outside of this module async function privateFunction(): Promise { - return undefined + return undefined; } -//silence warnings +// silence warnings void privateFunction; diff --git a/src/@shared/src/database/mixin/user.ts b/src/@shared/src/database/mixin/user.ts index 1fc54fc..bb65edf 100644 --- a/src/@shared/src/database/mixin/user.ts +++ b/src/@shared/src/database/mixin/user.ts @@ -1,7 +1,7 @@ -import type { Database, SqliteReturn } from "./_base"; -import { Otp } from "@shared/auth"; -import { isNullish } from "@shared/utils"; -import * as bcrypt from "bcrypt"; +import type { Database, SqliteReturn } from './_base'; +import { Otp } from '@shared/auth'; +import { isNullish } from '@shared/utils'; +import * as bcrypt from 'bcrypt'; // never use this directly @@ -38,8 +38,8 @@ export const UserImpl: Omit = { getUserFromName(this: IUserDb, name: string): User | undefined { return userFromRow( this.prepare( - "SELECT * FROM user WHERE name = @name LIMIT 1", - ).get({ name }), + 'SELECT * FROM user WHERE name = @name LIMIT 1', + ).get({ name }) as (Partial | undefined), ); }, @@ -52,9 +52,9 @@ export const UserImpl: Omit = { */ getUserFromRawId(this: IUserDb, id: number): User | undefined { return userFromRow( - this.prepare("SELECT * FROM user WHERE id = @id LIMIT 1").get({ + this.prepare('SELECT * FROM user WHERE id = @id LIMIT 1').get({ id, - }) as SqliteReturn, + }) as (Partial | undefined), ); }, @@ -70,8 +70,8 @@ export const UserImpl: Omit = { password = await hashPassword(password); return userFromRow( this.prepare( - "INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *", - ).get({ name, password }), + 'INSERT OR FAIL INTO user (name, password) VALUES (@name, @password) RETURNING *', + ).get({ name, password }) as (Partial | undefined), ); }, @@ -88,30 +88,29 @@ export const UserImpl: Omit = { password = await hashPassword(password); return userFromRow( this.prepare( - "UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *", + 'UPDATE OR FAIL user SET password = @password WHERE id = @id RETURNING *', ).get({ password, id }) as SqliteReturn, ); }, getUserOtpSecret(this: IUserDb, id: UserId): string | undefined { - let otp: any = this.prepare("SELECT otp FROM user WHERE id = @id LIMIT 1").get({ id }) as SqliteReturn; + const otp = this.prepare('SELECT otp FROM user WHERE id = @id LIMIT 1').get({ id }) as ({ otp: string } | null | undefined); if (isNullish(otp?.otp)) return undefined; return otp.otp; }, ensureUserOtpSecret(this: IUserDb, id: UserId): string | undefined { - let otp = this.getUserOtpSecret(id); - if (!isNullish(otp)) - return otp; - let otpGen = new Otp(); - const res: any = this.prepare("UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp") - .get({ id, otp: otpGen.secret }); + const otp = this.getUserOtpSecret(id); + if (!isNullish(otp)) { return otp; } + const otpGen = new Otp(); + const res = this.prepare('UPDATE OR IGNORE user SET otp = @otp WHERE id = @id RETURNING otp') + .get({ id, otp: otpGen.secret }) as ({ otp: string } | null | undefined); return res?.otp; }, deleteUserOtpSecret(this: IUserDb, id: UserId): void { - this.prepare("UPDATE OR IGNORE user SET otp = NULL WHERE id = @id").run({ id }); - } + this.prepare('UPDATE OR IGNORE user SET otp = NULL WHERE id = @id').run({ id }); + }, }; export type UserId = number & { readonly __brand: unique symbol }; @@ -155,12 +154,14 @@ async function hashPassword( * * @returns The user if it exists, undefined otherwise */ -function userFromRow(row: any): User | undefined { +function userFromRow(row?: Partial): User | undefined { if (isNullish(row)) return undefined; + if (isNullish(row.id)) return undefined; + if (isNullish(row.name)) return undefined; return { - id: row.id as UserId, - name: row.name || undefined, - password: row.password || undefined, - otp: row.otp || undefined, + id: row.id, + name: row.name, + password: row.password ?? undefined, + otp: row.otp ?? undefined, }; -} +} diff --git a/src/@shared/src/utils/index.ts b/src/@shared/src/utils/index.ts index 350b93e..56af348 100644 --- a/src/@shared/src/utils/index.ts +++ b/src/@shared/src/utils/index.ts @@ -1,4 +1,4 @@ -import { Type } from "@sinclair/typebox"; +import { TObject, TProperties, Type } from '@sinclair/typebox'; /** * @description Represent a message key @@ -12,7 +12,7 @@ import { Type } from "@sinclair/typebox"; * @example `pong.you.lost` */ export type MessageKey = string; -export type ResponseBase = { +export type ResponseBase = { kind: string, msg: MessageKey, payload?: T, @@ -20,30 +20,31 @@ export type ResponseBase = { /** * @description Builds a response from a `kind`, `key` and an arbitrary payload - * + * * * USE THIS FUNCTION TO ALLOW GREPING :) * * * @example makeResponse("failure", "login.failure.invalid") * @example makeResponse("success", "login.success", { token: "supersecrettoken" }) */ -export function makeResponse(kind: string, key: MessageKey, payload?: T): ResponseBase { - console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`) - return { kind, msg: key, payload } +export function makeResponse(kind: string, key: MessageKey, payload?: T): ResponseBase { + console.log(`making response {kind: ${JSON.stringify(kind)}; key: ${JSON.stringify(key)}}`); + return { kind, msg: key, payload }; } -/** +/** * @description Create a typebox Type for a response. - * + * * @example typeResponse("failure", ["login.failure.invalid", "login.failure.generic", "login.failure.missingPassword"]) * @example typeResponse("otpRequired", "login.otpRequired", { token: Type.String() }) * @example typeResponse("success", "login.success", { token: Type.String() }) */ -export function typeResponse(kind: string, key: MessageKey | MessageKey[], payload?: any): any { +export function typeResponse(kind: string, key: MessageKey | MessageKey[], payload?: TProperties): TObject { let tKey; if (key instanceof Array) { tKey = Type.Union(key.map(l => Type.Const(l))); - } else { + } + else { tKey = Type.Const(key); } @@ -51,10 +52,9 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo kind: Type.Const(kind), msg: tKey, }; - if (payload !== undefined) - Object.assign(Ty, { payload: Type.Object(payload) }) + if (payload !== undefined) {Object.assign(Ty, { payload: Type.Object(payload) });} - return Type.Object(Ty) + return Type.Object(Ty); } /** @@ -69,5 +69,5 @@ export function typeResponse(kind: string, key: MessageKey | MessageKey[], paylo * @example assert_equal(isNullish(false), false); */ export function isNullish(v: T | undefined | null): v is (null | undefined) { - return v === null || v === undefined + return v === null || v === undefined; } diff --git a/src/auth/extra/login_demo.js b/src/auth/extra/login_demo.js index 188e749..c20f847 100644 --- a/src/auth/extra/login_demo.js +++ b/src/auth/extra/login_demo.js @@ -1,107 +1,104 @@ const headers = { 'Accept': 'application/json', - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }; -const tUsername = document.querySelector("#t-username") +const tUsername = document.querySelector('#t-username'); -const iUsername = document.querySelector("#i-username"); -const iPassword = document.querySelector("#i-password"); -const iOtp = document.querySelector("#i-otp"); +const iUsername = document.querySelector('#i-username'); +const iPassword = document.querySelector('#i-password'); +const iOtp = document.querySelector('#i-otp'); -const bOtpSend = document.querySelector("#b-otpSend"); -const bLogin = document.querySelector("#b-login"); -const bLogout = document.querySelector("#b-logout"); -const bSignin = document.querySelector("#b-signin"); -const bWhoami = document.querySelector("#b-whoami"); +const bOtpSend = document.querySelector('#b-otpSend'); +const bLogin = document.querySelector('#b-login'); +const bLogout = document.querySelector('#b-logout'); +const bSignin = document.querySelector('#b-signin'); +const bWhoami = document.querySelector('#b-whoami'); -const bOtpStatus = document.querySelector("#b-otpStatus"); -const bOtpEnable = document.querySelector("#b-otpEnable"); -const bOtpDisable = document.querySelector("#b-otpDisable"); +const bOtpStatus = document.querySelector('#b-otpStatus'); +const bOtpEnable = document.querySelector('#b-otpEnable'); +const bOtpDisable = document.querySelector('#b-otpDisable'); -const dResponse = document.querySelector("#d-response"); +const dResponse = document.querySelector('#d-response'); function setResponse(obj) { - let obj_str = JSON.stringify(obj, null, 4); + const obj_str = JSON.stringify(obj, null, 4); dResponse.innerText = obj_str; } let otpToken = null; -bOtpSend.addEventListener("click", async () => { - let res = await fetch("/api/auth/otp", { method: "POST", body: JSON.stringify({ code: iOtp.value, token: otpToken }), headers }); +bOtpSend.addEventListener('click', async () => { + const res = await fetch('/api/auth/otp', { method: 'POST', body: JSON.stringify({ code: iOtp.value, token: otpToken }), headers }); const json = await res.json(); setResponse(json); - if (json.kind === "success") { - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token}`; + if (json.kind === 'success') { + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token}`;} } }); -bOtpStatus.addEventListener("click", async () => { - let res = await fetch("/api/auth/statusOtp"); +bOtpStatus.addEventListener('click', async () => { + const res = await fetch('/api/auth/statusOtp'); const json = await res.json(); setResponse(json); }); -bOtpEnable.addEventListener("click", async () => { - let res = await fetch("/api/auth/enableOtp", { method: "PUT" }); +bOtpEnable.addEventListener('click', async () => { + const res = await fetch('/api/auth/enableOtp', { method: 'PUT' }); const json = await res.json(); setResponse(json); }); -bOtpDisable.addEventListener("click", async () => { - let res = await fetch("/api/auth/disableOtp", { method: "PUT" }); +bOtpDisable.addEventListener('click', async () => { + const res = await fetch('/api/auth/disableOtp', { method: 'PUT' }); const json = await res.json(); setResponse(json); }); -bWhoami.addEventListener("click", async () => { - let username = ""; +bWhoami.addEventListener('click', async () => { + let username = ''; try { - let res = await fetch("/api/auth/whoami"); + const res = await fetch('/api/auth/whoami'); const json = await res.json(); setResponse(json); - if (json?.kind === "success") - username = json?.payload?.name; - else - username = `` - } catch { - username = `` + if (json?.kind === 'success') {username = json?.payload?.name;} + else {username = ``;} + } + catch { + username = ''; } tUsername.innerText = username; }); -bLogin.addEventListener("click", async () => { +bLogin.addEventListener('click', async () => { const name = iUsername.value; const password = iPassword.value; - let res = await fetch("/api/auth/login", { method: "POST", body: JSON.stringify({ name, password }), headers }); - let json = await res.json(); - if (json?.kind === "otpRequired") { + const res = await fetch('/api/auth/login', { method: 'POST', body: JSON.stringify({ name, password }), headers }); + const json = await res.json(); + if (json?.kind === 'otpRequired') { otpToken = json?.payload?.token; - } else if (json?.kind === "success") { - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token}`; + } + else if (json?.kind === 'success') { + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token}`;} } setResponse(json); -}) +}); -bLogout.addEventListener("click", async () => { - let res = await fetch("/api/auth/logout", { method: "POST" }); +bLogout.addEventListener('click', async () => { + const res = await fetch('/api/auth/logout', { method: 'POST' }); setResponse(await res.json()); -}) +}); -bSignin.addEventListener("click", async () => { +bSignin.addEventListener('click', async () => { const name = iUsername.value; const password = iPassword.value; - let res = await fetch("/api/auth/signin", { method: "POST", body: JSON.stringify({ name, password }), headers }); - let json = await res.json(); - if (json?.payload?.token) - document.cookie = `token=${json?.payload?.token};`; + const res = await fetch('/api/auth/signin', { method: 'POST', body: JSON.stringify({ name, password }), headers }); + const json = await res.json(); + if (json?.payload?.token) {document.cookie = `token=${json?.payload?.token};`;} setResponse(json); -}) +}); diff --git a/src/auth/src/app.ts b/src/auth/src/app.ts index 0aba7d4..e9156e7 100644 --- a/src/auth/src/app.ts +++ b/src/auth/src/app.ts @@ -1,17 +1,14 @@ -import { FastifyPluginAsync } from 'fastify' -import fastifyFormBody from '@fastify/formbody' -import fastifyMultipart from '@fastify/multipart' -import { mkdir } from 'node:fs/promises' -import fp from 'fastify-plugin' -import * as db from '@shared/database' -import * as auth from '@shared/auth' +import { FastifyPluginAsync } from 'fastify'; +import fastifyFormBody from '@fastify/formbody'; +import fastifyMultipart from '@fastify/multipart'; +import * as db from '@shared/database'; +import * as auth from '@shared/auth'; -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); - // When using .decorate you have to specify added properties for Typescript declare module 'fastify' { export interface FastifyInstance { @@ -19,25 +16,23 @@ declare module 'fastify' { } } -const app: FastifyPluginAsync = async ( - fastify, - opts -): Promise => { - await fastify.register(db.useDatabase as any, {}) - await fastify.register(auth.jwtPlugin as any, {}) - await fastify.register(auth.authPlugin as any, {}) - +const app: FastifyPluginAsync = async (fastify, opts): Promise => { + void opts; + await fastify.register(db.useDatabase as FastifyPluginAsync, {}); + await fastify.register(auth.jwtPlugin as FastifyPluginAsync, {}); + await fastify.register(auth.authPlugin as FastifyPluginAsync, {}); + // Place here your custom code! for (const plugin of Object.values(plugins)) { - void fastify.register(plugin as any, {}); + void fastify.register(plugin as FastifyPluginAsync, {}); } for (const route of Object.values(routes)) { - void fastify.register(route as any, {}); + void fastify.register(route as FastifyPluginAsync, {}); } - void fastify.register(fastifyFormBody, {}) - void fastify.register(fastifyMultipart, {}) -} + void fastify.register(fastifyFormBody, {}); + void fastify.register(fastifyMultipart, {}); +}; -export default app -export { app } +export default app; +export { app }; diff --git a/src/auth/src/plugins/sensible.ts b/src/auth/src/plugins/sensible.ts index 5be2c8a..8c2093c 100644 --- a/src/auth/src/plugins/sensible.ts +++ b/src/auth/src/plugins/sensible.ts @@ -1,5 +1,5 @@ -import fp from 'fastify-plugin' -import sensible, { FastifySensibleOptions } from '@fastify/sensible' +import fp from 'fastify-plugin'; +import sensible, { FastifySensibleOptions } from '@fastify/sensible'; /** * This plugins adds some utilities to handle http errors @@ -7,5 +7,5 @@ import sensible, { FastifySensibleOptions } from '@fastify/sensible' * @see https://github.com/fastify/fastify-sensible */ export default fp(async (fastify) => { - fastify.register(sensible) -}) + fastify.register(sensible); +}); diff --git a/src/auth/src/routes/disableOtp.ts b/src/auth/src/routes/disableOtp.ts index 7c09325..7b9b9ef 100644 --- a/src/auth/src/routes/disableOtp.ts +++ b/src/auth/src/routes/disableOtp.ts @@ -1,25 +1,26 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { makeResponse, typeResponse, isNullish } from "@shared/utils" +import { Static, Type } from '@sinclair/typebox'; +import { makeResponse, typeResponse, isNullish } from '@shared/utils'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "disableOtp.success"), - typeResponse("failure", "disableOtp.failure.generic") + typeResponse('success', 'disableOtp.success'), + typeResponse('failure', 'disableOtp.failure.generic'), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.put( - "/api/auth/disableOtp", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/disableOtp', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "disableOtp.failure.generic"); + void _res; + if (isNullish(req.authUser)) {return makeResponse('failure', 'disableOtp.failure.generic');} this.db.deleteUserOtpSecret(req.authUser.id); - return makeResponse("success", "disableOtp.success"); + return makeResponse('success', 'disableOtp.success'); }, ); }; diff --git a/src/auth/src/routes/enableOtp.ts b/src/auth/src/routes/enableOtp.ts index 3d053f0..dc83da2 100644 --- a/src/auth/src/routes/enableOtp.ts +++ b/src/auth/src/routes/enableOtp.ts @@ -1,29 +1,29 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" -import { Otp } from "@shared/auth"; +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; +import { Otp } from '@shared/auth'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "enableOtp.success", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }), - typeResponse("failure", ["enableOtp.failure.noUser", "enableOtp.failure.noSecret"]) + typeResponse('success', 'enableOtp.success', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), + typeResponse('failure', ['enableOtp.failure.noUser', 'enableOtp.failure.noSecret']), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.put( - "/api/auth/enableOtp", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/enableOtp', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "enableOtp.failure.noUser"); - let otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); - if (isNullish(otpSecret)) - return makeResponse("failure", "enableOtp.failure.noSecret"); - let otp = new Otp({ secret: otpSecret }); - return makeResponse("success", "enableOtp.success", { url: otp.totpURL }); + void _res; + if (isNullish(req.authUser)) {return makeResponse('failure', 'enableOtp.failure.noUser');} + const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); + if (isNullish(otpSecret)) {return makeResponse('failure', 'enableOtp.failure.noSecret');} + const otp = new Otp({ secret: otpSecret }); + return makeResponse('success', 'enableOtp.success', { url: otp.totpURL }); }, ); }; diff --git a/src/auth/src/routes/login.ts b/src/auth/src/routes/login.ts index c869814..10121a9 100644 --- a/src/auth/src/routes/login.ts +++ b/src/auth/src/routes/login.ts @@ -1,8 +1,8 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils" -import { verifyUserPassword } from "@shared/database/mixin/user"; +import { Static, Type } from '@sinclair/typebox'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; +import { verifyUserPassword } from '@shared/database/mixin/user'; export const LoginReq = Type.Object({ name: Type.String(), @@ -12,44 +12,44 @@ export const LoginReq = Type.Object({ export type LoginReq = Static; export const LoginRes = Type.Union([ - typeResponse("failed", ["login.failed.generic", "login.failed.invalid"]), - typeResponse("otpRequired", "login.otpRequired", { token: Type.String({ description: "JWT to send with the OTP to finish login" }) }), - typeResponse("success", "login.success", { token: Type.String({ description: "JWT that represent a logged in user" }) }), + typeResponse('failed', ['login.failed.generic', 'login.failed.invalid']), + typeResponse('otpRequired', 'login.otpRequired', { token: Type.String({ description: 'JWT to send with the OTP to finish login' }) }), + typeResponse('success', 'login.success', { token: Type.String({ description: 'JWT that represent a logged in user' }) }), ]); export type LoginRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: LoginReq; Response: LoginRes }>( - "/api/auth/login", - { schema: { body: LoginReq, response: { "2xx": LoginRes } }, }, + '/api/auth/login', + { schema: { body: LoginReq, response: { '2xx': LoginRes } } }, async function(req, _res) { + void _res; try { - let { name, password } = req.body; - let user = this.db.getUserFromName(name); + const { name, password } = req.body; + const user = this.db.getUserFromName(name); // does the user exist // does it have a password setup ? - if (isNullish(user?.password)) - return makeResponse("failed", "login.failed.invalid"); + if (isNullish(user?.password)) {return makeResponse('failed', 'login.failed.invalid');} // does the password he provided match the one we have - if (!(await verifyUserPassword(user, password))) - return makeResponse("failed", "login.failed.invalid"); + if (!(await verifyUserPassword(user, password))) {return makeResponse('failed', 'login.failed.invalid');} // does the user has 2FA up ? if (!isNullish(user.otp)) { // yes -> we ask them to fill it, // send them somehting to verify that they indeed passed throught the user+password phase - return makeResponse("otpRequired", "login.otpRequired", { token: this.signJwt("otp", user.name) }); + return makeResponse('otpRequired', 'login.otpRequired', { token: this.signJwt('otp', user.name) }); } // every check has been passed, they are now logged in, using this token to say who they are... - return makeResponse("success", "login.success", { token: this.signJwt("auth", user.name) }); + return makeResponse('success', 'login.success', { token: this.signJwt('auth', user.name) }); } catch { - return makeResponse("failed", "login.failed.generic"); + return makeResponse('failed', 'login.failed.generic'); } }, ); diff --git a/src/auth/src/routes/logout.ts b/src/auth/src/routes/logout.ts index 64a5734..f2fce67 100644 --- a/src/auth/src/routes/logout.ts +++ b/src/auth/src/routes/logout.ts @@ -1,10 +1,12 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post( - "/api/auth/logout", + '/api/auth/logout', async function(_req, res) { - return res.clearCookie("token").send("{}") + void _req; + return res.clearCookie('token').send('{}'); }, ); }; diff --git a/src/auth/src/routes/otp.ts b/src/auth/src/routes/otp.ts index 8266d0e..d555eeb 100644 --- a/src/auth/src/routes/otp.ts +++ b/src/auth/src/routes/otp.ts @@ -1,19 +1,19 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { JwtType, Otp } from "@shared/auth"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils"; +import { Static, Type } from '@sinclair/typebox'; +import { JwtType, Otp } from '@shared/auth'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; const OtpReq = Type.Object({ - token: Type.String({ description: "The token given at the login phase" }), - code: Type.String({ description: "The OTP given by the user" }), + token: Type.String({ description: 'The token given at the login phase' }), + code: Type.String({ description: 'The OTP given by the user' }), }); type OtpReq = Static; const OtpRes = Type.Union([ - typeResponse("failed", ["otp.failed.generic", "otp.failed.invalid", "otp.failed.timeout", "otp.failed.noSecret"]), - typeResponse("success", "otp.success", { token: Type.String({ description: "the JWT Token" }) }), + typeResponse('failed', ['otp.failed.generic', 'otp.failed.invalid', 'otp.failed.timeout', 'otp.failed.noSecret']), + typeResponse('success', 'otp.success', { token: Type.String({ description: 'the JWT Token' }) }), ]); type OtpRes = Static; @@ -21,35 +21,40 @@ type OtpRes = Static; const OTP_TOKEN_TIMEOUT_SEC = 120; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: OtpReq }>( - "/api/auth/otp", - { schema: { body: OtpReq, response: { "2xx": OtpRes } } }, + '/api/auth/otp', + { schema: { body: OtpReq, response: { '2xx': OtpRes } } }, async function(req, _res) { + void _res; try { const { token, code } = req.body; // lets try to decode+verify the jwt - let dJwt = this.jwt.verify(token); + const dJwt = this.jwt.verify(token); // is the jwt a valid `otp` jwt ? - if (dJwt.kind != "otp") + if (dJwt.kind != 'otp') { // no ? fuck off then - return makeResponse("failed", "otp.failed.invalid"); + return makeResponse('failed', 'otp.failed.invalid'); + } // is it too old ? - if (dJwt.createdAt + OTP_TOKEN_TIMEOUT_SEC * 1000 < Date.now()) + if (dJwt.createdAt + OTP_TOKEN_TIMEOUT_SEC * 1000 < Date.now()) { // yes ? fuck off then, redo the password - return makeResponse("failed", "otp.failed.timeout"); + return makeResponse('failed', 'otp.failed.timeout'); + } // get the Otp sercret from the db - let user = this.db.getUserFromName(dJwt.who); - if (isNullish(user?.otp)) + const user = this.db.getUserFromName(dJwt.who); + if (isNullish(user?.otp)) { // oops, either no user, or user without otpSecret // fuck off - return makeResponse("failed", "otp.failed.noSecret"); + return makeResponse('failed', 'otp.failed.noSecret'); + } // good lets now verify the token you gave us is the correct one... - let otpHandle = new Otp({ secret: user.otp }); + const otpHandle = new Otp({ secret: user.otp }); - let now = Date.now(); + const now = Date.now(); const tokens = [ // we also get the last code, to mitiage the delay between client<->server roundtrip... otpHandle.totp(now - 30 * 1000), @@ -58,14 +63,16 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => { ]; // checking if any of the array match - if (tokens.some((c) => c === code)) + if (tokens.some((c) => c === code)) { // they do ! // gg you are now logged in ! - return makeResponse("success", "otp.success", { token: this.signJwt("auth", dJwt.who) }); - } catch { - return makeResponse("failed", "otp.failed.generic"); + return makeResponse('success', 'otp.success', { token: this.signJwt('auth', dJwt.who) }); + } } - return makeResponse("failed", "otp.failed.generic"); + catch { + return makeResponse('failed', 'otp.failed.generic'); + } + return makeResponse('failed', 'otp.failed.generic'); }, ); }; diff --git a/src/auth/src/routes/signin.ts b/src/auth/src/routes/signin.ts index e6e84bb..b22ade6 100644 --- a/src/auth/src/routes/signin.ts +++ b/src/auth/src/routes/signin.ts @@ -1,9 +1,9 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { typeResponse, makeResponse, isNullish } from "@shared/utils"; +import { Static, Type } from '@sinclair/typebox'; +import { typeResponse, makeResponse, isNullish } from '@shared/utils'; -const USERNAME_CHECK: RegExp = /^[a-zA-Z\_0-9]+$/; +const USERNAME_CHECK: RegExp = /^[a-zA-Z_0-9]+$/; const SignInReq = Type.Object({ name: Type.String(), @@ -13,51 +13,46 @@ const SignInReq = Type.Object({ type SignInReq = Static; const SignInRes = Type.Union([ - typeResponse("failed", [ - "signin.failed.generic", - "signin.failed.username.existing", - "signin.failed.username.toolong", - "signin.failed.username.tooshort", - "signin.failed.username.invalid", - "signin.failed.password.toolong", - "signin.failed.password.tooshort", - "signin.failed.password.invalid", + typeResponse('failed', [ + 'signin.failed.generic', + 'signin.failed.username.existing', + 'signin.failed.username.toolong', + 'signin.failed.username.tooshort', + 'signin.failed.username.invalid', + 'signin.failed.password.toolong', + 'signin.failed.password.tooshort', + 'signin.failed.password.invalid', ]), - typeResponse("success", "signin.success", { token: Type.String({ description: "the JWT token" }) }), -]) + typeResponse('success', 'signin.success', { token: Type.String({ description: 'the JWT token' }) }), +]); type SignInRes = Static; -const route: FastifyPluginAsync = async (fastify, opts): Promise => { +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.post<{ Body: SignInReq }>( - "/api/auth/signin", - { schema: { body: SignInReq, response: { "200": SignInRes, "5xx": Type.Object({}) } }, }, - async function(req, res) { + '/api/auth/signin', + { schema: { body: SignInReq, response: { '200': SignInRes, '5xx': Type.Object({}) } } }, + async function(req, _res) { + void _res; const { name, password } = req.body; - if (name.length < 4) - return makeResponse("failed", "signin.failed.username.tooshort"); - if (name.length > 32) - return makeResponse("failed", "signin.failed.username.toolong"); - if (!USERNAME_CHECK.test(name)) - return makeResponse("failed", "signin.failed.username.invalid"); + if (name.length < 4) {return makeResponse('failed', 'signin.failed.username.tooshort');} + if (name.length > 32) {return makeResponse('failed', 'signin.failed.username.toolong');} + if (!USERNAME_CHECK.test(name)) {return makeResponse('failed', 'signin.failed.username.invalid');} // username if good now :) - if (password.length < 8) - return makeResponse("failed", "signin.failed.password.tooshort"); - if (password.length > 64) - return makeResponse("failed", "signin.failed.password.toolong"); + if (password.length < 8) {return makeResponse('failed', 'signin.failed.password.tooshort');} + if (password.length > 64) {return makeResponse('failed', 'signin.failed.password.toolong');} // password is good too ! - if (this.db.getUserFromName(name) !== undefined) - return makeResponse("failed", "signin.failed.username.existing"); - let u = await this.db.createUser(name, password); - if (isNullish(u)) - return makeResponse("failed", "signin.failed.generic"); + if (this.db.getUserFromName(name) !== undefined) {return makeResponse('failed', 'signin.failed.username.existing');} + const u = await this.db.createUser(name, password); + if (isNullish(u)) {return makeResponse('failed', 'signin.failed.generic');} // every check has been passed, they are now logged in, using this token to say who they are... - let userToken = this.signJwt('auth', u.name); - return makeResponse("success", "signin.success", { token: userToken }); + const userToken = this.signJwt('auth', u.name); + return makeResponse('success', 'signin.success', { token: userToken }); }, ); }; diff --git a/src/auth/src/routes/statusOtp.ts b/src/auth/src/routes/statusOtp.ts index d79fd1d..755f2c6 100644 --- a/src/auth/src/routes/statusOtp.ts +++ b/src/auth/src/routes/statusOtp.ts @@ -1,30 +1,30 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" -import { Otp } from "@shared/auth"; +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; +import { Otp } from '@shared/auth'; export const StatusOtpRes = Type.Union([ - typeResponse("success", "statusOtp.success.enabled", { url: Type.String({ description: "The otp url to feed into a 2fa app" }) }), - typeResponse("success", "statusOtp.success.disabled"), - typeResponse("failure", "statusOtp.failure.generic") + typeResponse('success', 'statusOtp.success.enabled', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), + typeResponse('success', 'statusOtp.success.disabled'), + typeResponse('failure', 'statusOtp.failure.generic'), ]); export type StatusOtpRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.get( - "/api/auth/statusOtp", - { schema: { response: { "2xx": StatusOtpRes } }, config: { requireAuth: true } }, + '/api/auth/statusOtp', + { schema: { response: { '2xx': StatusOtpRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "statusOtp.failure.generic"); - let otpSecret = this.db.getUserOtpSecret(req.authUser.id); - if (isNullish(otpSecret)) - return makeResponse("success", "statusOtp.success.disabled"); - let otp = new Otp({ secret: otpSecret }) - return makeResponse("success", "statusOtp.success.enabled", { url: otp.totpURL }); + void _res; + if (isNullish(req.authUser)) {return makeResponse('failure', 'statusOtp.failure.generic');} + const otpSecret = this.db.getUserOtpSecret(req.authUser.id); + if (isNullish(otpSecret)) {return makeResponse('success', 'statusOtp.success.disabled');} + const otp = new Otp({ secret: otpSecret }); + return makeResponse('success', 'statusOtp.success.enabled', { url: otp.totpURL }); }, ); }; diff --git a/src/auth/src/routes/whoami.ts b/src/auth/src/routes/whoami.ts index 5f88cc1..13232b6 100644 --- a/src/auth/src/routes/whoami.ts +++ b/src/auth/src/routes/whoami.ts @@ -1,24 +1,25 @@ -import { FastifyPluginAsync } from "fastify"; +import { FastifyPluginAsync } from 'fastify'; -import { Static, Type } from "@sinclair/typebox"; -import { isNullish, makeResponse, typeResponse } from "@shared/utils" +import { Static, Type } from '@sinclair/typebox'; +import { isNullish, makeResponse, typeResponse } from '@shared/utils'; export const WhoAmIRes = Type.Union([ - typeResponse("success", "whoami.success", { name: Type.String() }), - typeResponse("failure", "whoami.failure.generic") + typeResponse('success', 'whoami.success', { name: Type.String() }), + typeResponse('failure', 'whoami.failure.generic'), ]); export type WhoAmIRes = Static; const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; fastify.get( - "/api/auth/whoami", - { schema: { response: { "2xx": WhoAmIRes } }, config: { requireAuth: true } }, + '/api/auth/whoami', + { schema: { response: { '2xx': WhoAmIRes } }, config: { requireAuth: true } }, async function(req, _res) { - if (isNullish(req.authUser)) - return makeResponse("failure", "whoami.failure.generic") - return makeResponse("success", "whoami.success", { name: req.authUser.name }) + void _res; + if (isNullish(req.authUser)) {return makeResponse('failure', 'whoami.failure.generic');} + return makeResponse('success', 'whoami.success', { name: req.authUser.name }); }, ); }; diff --git a/src/auth/src/run.ts b/src/auth/src/run.ts index efaa386..9f9e3dd 100644 --- a/src/auth/src/run.ts +++ b/src/auth/src/run.ts @@ -1,7 +1,7 @@ // this sould only be used by the docker file ! -import fastify, { FastifyInstance } from "fastify"; -import app from "./app" +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; const start = async () => { const envToLogger = { @@ -16,15 +16,16 @@ const start = async () => { }, production: true, test: false, - } + }; const f: FastifyInstance = fastify({ logger: envToLogger.development }); try { await f.register(app, {}); - await f.listen({ port: 80, host: '0.0.0.0' }) - } catch (err) { - f.log.error(err) - process.exit(1) + await f.listen({ port: 80, host: '0.0.0.0' }); } -} -start() + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/auth/vite.config.js b/src/auth/vite.config.js index 89a197c..aeb8fd8 100644 --- a/src/auth/vite.config.js +++ b/src/auth/vite.config.js @@ -1,8 +1,8 @@ -import { defineConfig } from 'vite' -import tsconfigPaths from 'vite-tsconfig-paths' -import nodeExternals from 'rollup-plugin-node-externals' -import path from 'node:path' -import fs from 'node:fs' +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import nodeExternals from 'rollup-plugin-node-externals'; +import path from 'node:path'; +import fs from 'node:fs'; function collectDeps(...pkgJsonPaths) { const allDeps = new Set(); @@ -20,27 +20,32 @@ function collectDeps(...pkgJsonPaths) { const externals = collectDeps( './package.json', - '../@shared/package.json' + '../@shared/package.json', ); export default defineConfig({ - root: __dirname, // service root + root: __dirname, + // service root plugins: [tsconfigPaths(), nodeExternals()], build: { ssr: true, outDir: 'dist', emptyOutDir: true, lib: { - entry: path.resolve(__dirname, 'src/run.ts'), // adjust main entry - formats: ['cjs'], // CommonJS for Node.js + entry: path.resolve(__dirname, 'src/run.ts'), + // adjust main entry + formats: ['cjs'], + // CommonJS for Node.js fileName: () => 'index.js', }, rollupOptions: { external: externals, }, - target: 'node22', // or whatever Node version you use + target: 'node22', + // or whatever Node version you use sourcemap: true, - minify: false, // for easier debugging - } -}) + minify: false, + // for easier debugging + }, +}); diff --git a/src/eslint.config.js b/src/eslint.config.js new file mode 100644 index 0000000..5b8cab6 --- /dev/null +++ b/src/eslint.config.js @@ -0,0 +1,55 @@ +import js from '@eslint/js'; +import ts from 'typescript-eslint'; + +export default [ + js.configs.recommended, + ...ts.configs.recommended, + { + languageOptions: { + ecmaVersion: 'latest', + }, + rules: { + 'arrow-spacing': ['warn', { before: true, after: true }], + 'brace-style': ['error', 'stroustrup', { allowSingleLine: true }], + 'comma-dangle': ['error', 'always-multiline'], + 'comma-spacing': 'error', + 'comma-style': 'error', + curly: ['error', 'multi-line', 'consistent'], + 'dot-location': ['error', 'property'], + 'handle-callback-err': 'off', + indent: ['error', 'tab'], + 'keyword-spacing': 'error', + 'max-nested-callbacks': ['error', { max: 4 }], + 'max-statements-per-line': ['error', { max: 2 }], + 'no-console': 'off', + 'no-empty-function': 'error', + 'no-floating-decimal': 'error', + 'no-inline-comments': 'error', + 'no-lonely-if': 'error', + 'no-multi-spaces': 'error', + 'no-multiple-empty-lines': ['error', { max: 2, maxEOF: 1, maxBOF: 0 }], + 'no-shadow': ['error', { allow: ['err', 'resolve', 'reject'] }], + 'no-trailing-spaces': ['error'], + 'no-var': 'error', + 'no-undef': 'off', + 'object-curly-spacing': ['error', 'always'], + 'prefer-const': 'error', + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'space-before-blocks': 'error', + 'space-before-function-paren': [ + 'error', + { + anonymous: 'never', + named: 'never', + asyncArrow: 'always', + }, + ], + 'space-in-parens': 'error', + 'space-infix-ops': 'error', + 'space-unary-ops': 'error', + 'spaced-comment': 'error', + yoda: 'error', + }, + }, +]; diff --git a/src/icons/src/app.ts b/src/icons/src/app.ts index d2b29df..1ad0932 100644 --- a/src/icons/src/app.ts +++ b/src/icons/src/app.ts @@ -1,13 +1,14 @@ -import { FastifyPluginAsync } from 'fastify' -import fastifyFormBody from '@fastify/formbody' -import fastifyMultipart from '@fastify/multipart' -import { mkdir } from 'node:fs/promises' -import fp from 'fastify-plugin' -import * as db from '@shared/database' +import { FastifyPluginAsync } from 'fastify'; +import fastifyFormBody from '@fastify/formbody'; +import fastifyMultipart from '@fastify/multipart'; +import { mkdir } from 'node:fs/promises'; +import fp from 'fastify-plugin'; +import * as db from '@shared/database'; +import { authPlugin, jwtPlugin } from '@shared/auth'; -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const plugins = import.meta.glob('./plugins/**/*.ts', { eager: true }); -// @ts-ignore: import.meta.glob is a vite thing. Typescript doesn't know this... +// @ts-expect-error: import.meta.glob is a vite thing. Typescript doesn't know this... const routes = import.meta.glob('./routes/**/*.ts', { eager: true }); @@ -20,30 +21,33 @@ declare module 'fastify' { const app: FastifyPluginAsync = async ( fastify, - opts + _opts, ): Promise => { + void _opts; // Place here your custom code! for (const plugin of Object.values(plugins)) { - void fastify.register(plugin as any, {}); + void fastify.register(plugin as FastifyPluginAsync, {}); } for (const route of Object.values(routes)) { - void fastify.register(route as any, {}); + void fastify.register(route as FastifyPluginAsync, {}); } - await fastify.register(db.useDatabase as any, {}) - void fastify.register(fastifyFormBody, {}) - void fastify.register(fastifyMultipart, {}) - console.log(fastify.db.getUser(1)); + await fastify.register(db.useDatabase as FastifyPluginAsync, {}); + await fastify.register(authPlugin as FastifyPluginAsync, {}); + await fastify.register(jwtPlugin as FastifyPluginAsync, {}); + + void fastify.register(fastifyFormBody, {}); + void fastify.register(fastifyMultipart, {}); // The use of fastify-plugin is required to be able // to export the decorators to the outer scope - void fastify.register(fp(async (fastify) => { - const image_store = process.env.USER_ICONS_STORE ?? "/tmp/icons"; - fastify.decorate('image_store', image_store) - await mkdir(fastify.image_store, { recursive: true }) - })) + void fastify.register(fp(async (fastify2) => { + const image_store = process.env.USER_ICONS_STORE ?? '/tmp/icons'; + fastify2.decorate('image_store', image_store); + await mkdir(fastify2.image_store, { recursive: true }); + })); -} +}; -export default app -export { app } +export default app; +export { app }; diff --git a/src/icons/src/plugins/sensible.ts b/src/icons/src/plugins/sensible.ts index 5be2c8a..8c2093c 100644 --- a/src/icons/src/plugins/sensible.ts +++ b/src/icons/src/plugins/sensible.ts @@ -1,5 +1,5 @@ -import fp from 'fastify-plugin' -import sensible, { FastifySensibleOptions } from '@fastify/sensible' +import fp from 'fastify-plugin'; +import sensible, { FastifySensibleOptions } from '@fastify/sensible'; /** * This plugins adds some utilities to handle http errors @@ -7,5 +7,5 @@ import sensible, { FastifySensibleOptions } from '@fastify/sensible' * @see https://github.com/fastify/fastify-sensible */ export default fp(async (fastify) => { - fastify.register(sensible) -}) + fastify.register(sensible); +}); diff --git a/src/icons/src/routes/set.ts b/src/icons/src/routes/set.ts index 7e854c3..8063719 100644 --- a/src/icons/src/routes/set.ts +++ b/src/icons/src/routes/set.ts @@ -1,49 +1,51 @@ -import { FastifyPluginAsync } from 'fastify' -import { join } from 'node:path' -import { open } from 'node:fs/promises' -import sharp from 'sharp' -import rawBody from 'raw-body' -import { isNullish } from '@shared/utils' +import { FastifyPluginAsync } from 'fastify'; +import { join } from 'node:path'; +import { open } from 'node:fs/promises'; +import sharp from 'sharp'; +import rawBody from 'raw-body'; +import { isNullish } from '@shared/utils'; -const route: FastifyPluginAsync = async (fastify, opts): Promise => { +const route: FastifyPluginAsync = async (fastify, _opts): Promise => { + void _opts; // await fastify.register(authMethod, {}); // here we register plugins that will be active for the current fastify instance (aka everything in this function) // we register a route handler for: `/` // it sets some configuration options, and set the actual function that will handle the request - fastify.addContentTypeParser('*', function(request, payload, done: any) { - done() + fastify.addContentTypeParser('*', function(request, payload, done) { + done(null); }); - fastify.post('/:userid', async function(request, reply) { - let buffer = await rawBody(request.raw); + fastify.post<{ Params: { userid: string } }>('/:userid', async function(request, reply) { + const buffer = await rawBody(request.raw); // this is how we get the `:userid` part of things - const userid: string | undefined = (request.params as any)['userid']; + const userid: string | undefined = (request.params)['userid']; if (isNullish(userid)) { return await reply.code(403); } - const image_store: string = fastify.getDecorator('image_store') - const image_path = join(image_store, userid) + const image_store: string = fastify.getDecorator('image_store'); + const image_path = join(image_store, userid); try { - let img = sharp(buffer); + const img = sharp(buffer); img.resize({ height: 128, width: 128, fit: 'fill', - }) - const data = await img.png({ compressionLevel: 6 }).toBuffer() - let image_file = await open(image_path, "w", 0o666) + }); + const data = await img.png({ compressionLevel: 6 }).toBuffer(); + const image_file = await open(image_path, 'w', 0o666); await image_file.write(data); - await image_file.close() - } catch (e: any) { + await image_file.close(); + } + catch (e) { fastify.log.error(`Error: ${e}`); reply.code(400); - return { status: "error", message: e.toString() }; + return { status: 'error' }; } - }) -} + }); +}; -export default route +export default route; diff --git a/src/icons/src/run.ts b/src/icons/src/run.ts index efaa386..9f9e3dd 100644 --- a/src/icons/src/run.ts +++ b/src/icons/src/run.ts @@ -1,7 +1,7 @@ // this sould only be used by the docker file ! -import fastify, { FastifyInstance } from "fastify"; -import app from "./app" +import fastify, { FastifyInstance } from 'fastify'; +import app from './app'; const start = async () => { const envToLogger = { @@ -16,15 +16,16 @@ const start = async () => { }, production: true, test: false, - } + }; const f: FastifyInstance = fastify({ logger: envToLogger.development }); try { await f.register(app, {}); - await f.listen({ port: 80, host: '0.0.0.0' }) - } catch (err) { - f.log.error(err) - process.exit(1) + await f.listen({ port: 80, host: '0.0.0.0' }); } -} -start() + catch (err) { + f.log.error(err); + process.exit(1); + } +}; +start(); diff --git a/src/icons/vite.config.js b/src/icons/vite.config.js index 8de6f14..2c172a5 100644 --- a/src/icons/vite.config.js +++ b/src/icons/vite.config.js @@ -1,8 +1,8 @@ -import { defineConfig } from 'vite' -import tsconfigPaths from 'vite-tsconfig-paths' -import nodeExternals from 'rollup-plugin-node-externals' -import path from 'node:path' -import fs from 'node:fs' +import { defineConfig } from 'vite'; +import tsconfigPaths from 'vite-tsconfig-paths'; +import nodeExternals from 'rollup-plugin-node-externals'; +import path from 'node:path'; +import fs from 'node:fs'; function collectDeps(...pkgJsonPaths) { const allDeps = new Set(); @@ -20,27 +20,32 @@ function collectDeps(...pkgJsonPaths) { const externals = collectDeps( './package.json', - '../@shared/package.json' + '../@shared/package.json', ); export default defineConfig({ - root: __dirname, // service root + root: __dirname, + // service root plugins: [tsconfigPaths(), nodeExternals()], build: { ssr: true, outDir: 'dist', emptyOutDir: true, lib: { - entry: path.resolve(__dirname, 'src/run.ts'), // adjust main entry - formats: ['cjs'], // CommonJS for Node.js + entry: path.resolve(__dirname, 'src/run.ts'), + // adjust main entry + formats: ['cjs'], + // CommonJS for Node.js fileName: () => 'index.js', }, rollupOptions: { external: externals, }, - target: 'node22', // or whatever Node version you use + target: 'node22', + // or whatever Node version you use sourcemap: false, - minify: true, // for easier debugging - } -}) + minify: true, + // for easier debugging + }, +}); diff --git a/src/package-lock.json b/src/package-lock.json index dd33633..88a1c01 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -16,7 +16,12 @@ "bindings": "^1.5.0" }, "devDependencies": { - "rimraf": "^5.0.1" + "@eslint/js": "^9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^9.36.0", + "rimraf": "^5.0.1", + "typescript-eslint": "^8.44.1" } }, "@shared": { @@ -537,6 +542,229 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.1.tgz", + "integrity": "sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.36.0.tgz", + "integrity": "sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.15.2", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@fastify/accept-negotiator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@fastify/accept-negotiator/-/accept-negotiator-2.0.1.tgz", @@ -966,6 +1194,58 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.34.3", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", @@ -1431,6 +1711,44 @@ "node": ">=8" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1760,6 +2078,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.17.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.17.0.tgz", @@ -1769,12 +2094,270 @@ "undici-types": "~6.21.0" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.1.tgz", + "integrity": "sha512-molgphGqOBT7t4YKCSkbasmu1tb1MgrZ2szGzHbclF7PNmOkSTQVHy+2jXOSnxvR3+Xe1yySHFZoqMpz3TfQsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/type-utils": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.1.tgz", + "integrity": "sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.1.tgz", + "integrity": "sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.1", + "@typescript-eslint/types": "^8.44.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.1.tgz", + "integrity": "sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.1.tgz", + "integrity": "sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.1.tgz", + "integrity": "sha512-KdEerZqHWXsRNKjF9NYswNISnFzXfXNDfPxoTh7tqohU/PRIbwTmsjGK6V9/RTYWau7NZvfo52lgVk+sJh0K3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.1.tgz", + "integrity": "sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.1.tgz", + "integrity": "sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.44.1", + "@typescript-eslint/tsconfig-utils": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/visitor-keys": "8.44.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.1.tgz", + "integrity": "sha512-DpX5Fp6edTlocMCwA+mHY8Mra+pPjRZ0TfHkXI8QFelIKcbADQz1LUPNtzOFUriBB2UYqw4Pi9+xV4w9ZczHFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.1", + "@typescript-eslint/types": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.1.tgz", + "integrity": "sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.44.1", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", "license": "MIT" }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -1832,6 +2415,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/asn1.js": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", @@ -1955,6 +2545,19 @@ "balanced-match": "^1.0.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1988,6 +2591,16 @@ "node": ">= 0.8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2108,6 +2721,13 @@ "integrity": "sha512-4PIMoPniho+LqXmpS5d3NuGYncG6XWlkBSVGiWycL22dd42OYdUGil2CWuzklaJoNxyxUSpO4MKIBU94viWNAw==", "license": "MIT" }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -2203,6 +2823,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2320,6 +2947,333 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.36.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.36.0.tgz", + "integrity": "sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.1", + "@eslint/core": "^0.15.2", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.36.0", + "@eslint/plugin-kit": "^0.3.5", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2347,6 +3301,43 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-json-stringify": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-6.0.1.tgz", @@ -2386,6 +3377,13 @@ "node": ">=20" } }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-querystring": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", @@ -2564,12 +3562,38 @@ } } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-my-way": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.3.0.tgz", @@ -2596,6 +3620,27 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -2684,6 +3729,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", @@ -2691,6 +3762,13 @@ "dev": true, "license": "MIT" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2758,6 +3836,53 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2800,6 +3925,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2809,6 +3944,29 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/isbinaryfile": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", @@ -2870,6 +4028,26 @@ "node": ">=10" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-ref-resolver": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-2.0.1.tgz", @@ -2895,6 +4073,37 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/light-my-request": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", @@ -2945,6 +4154,13 @@ "node": ">=6" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -2970,6 +4186,43 @@ "node": ">= 0.6" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", @@ -3102,6 +4355,13 @@ "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.75.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.75.0.tgz", @@ -3158,6 +4418,24 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/otp": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/otp/-/otp-1.1.2.tgz", @@ -3209,6 +4487,19 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", @@ -3417,6 +4708,16 @@ "node": ">=10" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-warning": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", @@ -3443,6 +4744,37 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -3646,6 +4978,30 @@ "rollup": "^4.0.0" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -4125,6 +5481,19 @@ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "license": "BSD-3-Clause" }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toad-cache": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", @@ -4143,6 +5512,19 @@ "node": ">=0.6" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsconfck": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", @@ -4183,6 +5565,19 @@ "node": "*" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -4196,6 +5591,45 @@ "node": ">= 0.6" } }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.44.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.1.tgz", + "integrity": "sha512-0ws8uWGrUVTjEeN2OM4K1pLKHK/4NiNP/vz6ns+LjT/6sqpaYzIVFajZb1fj/IDwpsrrHb3Jy0Qm5u9CPcKaeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.44.1", + "@typescript-eslint/parser": "8.44.1", + "@typescript-eslint/typescript-estree": "8.44.1", + "@typescript-eslint/utils": "8.44.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -4211,6 +5645,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4354,6 +5798,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -4468,6 +5922,19 @@ "engines": { "node": ">=12" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/src/package.json b/src/package.json index 8e8f7ea..37b53cf 100644 --- a/src/package.json +++ b/src/package.json @@ -1,5 +1,6 @@ { "name": "workspace", + "type": "module", "version": "0.0.0", "private": true, "workspaces": [ @@ -7,14 +8,27 @@ "./icons", "./auth" ], + "lint-staged": { + "*": [ + "eslint --fix" + ] + }, "scripts": { "build": "npm run build --workspaces --if-present", "fclean": "rimraf \"**/dist\"", "clean": "rimraf \"**/node_modules\"", - "install-all": "npm install" + "install-all": "npm install", + "dev:prepare": "husky" }, "devDependencies": { - "rimraf": "^5.0.1" + "rimraf": "^5.0.1", + "@eslint/js": "^9.36.0", + "@typescript-eslint/eslint-plugin": "^8.44.1", + "@typescript-eslint/parser": "^8.44.1", + "eslint": "^9.36.0", + "lint-staged": "^16.1.5", + "husky": "^9.1.7", + "typescript-eslint": "^8.44.1" }, "dependencies": { "bindings": "^1.5.0"