mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
Emil/db migrations (#58)
* Add users table migration * Add users table migration and update schema * Add users table migration * updated schema, users table and .env.test * added public.users table * Add users table migration * Add users table migration * more fixing * prettier, husky and editorconfig * husky * update actions * Move deps * fixed workflow * regenerate schema * fix generated schema * move to dev dep --------- Co-authored-by: Visuri Emil A <evisuri@cubbli22-gold.local> Co-authored-by: DoebelnAtz <axel.ej.adlercreutz@gmail.com>
This commit is contained in:
co-authored by
Visuri Emil A
DoebelnAtz
parent
2d4ce31cdf
commit
84da650a8f
@@ -0,0 +1,39 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 80
|
||||
charset = utf-8
|
||||
|
||||
[*.yaml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 80
|
||||
charset = utf-8
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
max_line_length = 80
|
||||
charset = utf-8
|
||||
|
||||
[*.sql]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
@@ -26,9 +26,13 @@ jobs:
|
||||
cache-dependency-path: ./gradesa/package-lock.json
|
||||
node-version: 22.6
|
||||
cache: "npm"
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
- name: Install client dependencies
|
||||
run: npm install
|
||||
working-directory: ./gradesa
|
||||
- name: Format
|
||||
run: npm run format:check
|
||||
- name: Download pgmigrate
|
||||
run: |
|
||||
wget --quiet https://github.com/peterldowns/pgmigrate/releases/download/v0.1.0%2Bcommit.2f6a72b/pgmigrate-linux-amd64 -O pgmigrate
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm exec lint-staged
|
||||
@@ -0,0 +1,5 @@
|
||||
dist
|
||||
build
|
||||
node_modules
|
||||
.next
|
||||
.turbo
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"singleQuote": false,
|
||||
"trailingComma": "es5",
|
||||
"semi": true
|
||||
}
|
||||
@@ -14,6 +14,7 @@ This should be all that's needed:
|
||||
- `wget https://github.com/peterldowns/pgmigrate/releases/download/v0.0.6%2Bcommit.4f90829/pgmigrate-linux-amd64 -O /usr/local/bin/pgmigrate`
|
||||
- `chmod +x /usr/local/bin/pgmigrate`
|
||||
- Docker, either of these installation options work (if you're new to docker, I recommend Docker Desktop):
|
||||
|
||||
- `brew install docker` + `brew install colima` (https://github.com/abiosoft/colima)
|
||||
- [Docker Desktop](https://www.docker.com/products/docker-desktop/)
|
||||
- WSL 2: Enabling `systemd` is recommended
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
CREATE TABLE public.users (
|
||||
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
email text UNIQUE NOT NULL,
|
||||
password_hash text NOT NULL
|
||||
);
|
||||
@@ -4,3 +4,9 @@ CREATE TABLE public.pgmigrate_migrations (
|
||||
execution_time_in_millis bigint NOT NULL,
|
||||
applied_at timestamp with time zone NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE public.users (
|
||||
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
|
||||
email text UNIQUE NOT NULL,
|
||||
password_hash text NOT NULL
|
||||
);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ["next", "prettier"],
|
||||
plugins: ["eslint-plugin-react", "react-refresh", "unused-imports"],
|
||||
rules: {
|
||||
"turbo/no-undeclared-env-vars": 0,
|
||||
"react-hooks/exhaustive-deps": 0,
|
||||
"react/no-unescaped-entities": 0,
|
||||
"unused-imports/no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
vars: "all",
|
||||
varsIgnorePattern: "^_",
|
||||
args: "after-used",
|
||||
argsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
},
|
||||
ignorePatterns: ["next.config.js"],
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
||||
|
||||
export default eslintConfig;
|
||||
Generated
+55
@@ -23,6 +23,10 @@
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.5",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-refresh": "^0.4.18",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"jsdom": "^26.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"vitest": "^3.0.4"
|
||||
@@ -3239,6 +3243,7 @@
|
||||
"version": "16.4.7",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
|
||||
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -3609,6 +3614,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz",
|
||||
"integrity": "sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-node": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
|
||||
@@ -3842,6 +3859,15 @@
|
||||
"eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.18",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.18.tgz",
|
||||
"integrity": "sha512-IRGEoFn3OKalm3hjfolEWGqoF/jPqeEYFp+C8B0WMzwGwBMvlRDQd06kghDhF0C61uJ6WfSDhEZE/sAQjduKgw==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"eslint": ">=8.40"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/resolve": {
|
||||
"version": "2.0.0-next.5",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz",
|
||||
@@ -3868,6 +3894,21 @@
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-unused-imports": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-unused-imports/-/eslint-plugin-unused-imports-4.1.4.tgz",
|
||||
"integrity": "sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0",
|
||||
"eslint": "^9.0.0 || ^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-scope": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
|
||||
@@ -7259,6 +7300,20 @@
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz",
|
||||
"integrity": "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
|
||||
@@ -26,6 +26,10 @@
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.1.5",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-react": "^7.37.4",
|
||||
"eslint-plugin-react-refresh": "^0.4.18",
|
||||
"jsdom": "^26.0.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"vitest": "^3.0.4"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { DB } from '../../../backend/db';
|
||||
import { DB } from "../../../backend/db";
|
||||
|
||||
export async function GET(request) {
|
||||
const result = await DB.pool('SELECT now()');
|
||||
const result = await DB.pool("SELECT now()");
|
||||
const query = request.nextUrl.searchParams;
|
||||
const mode = query.get('mode');
|
||||
if (mode === 'error') {
|
||||
return Response.json({ message: 'Error' }, { status: 500 });
|
||||
const mode = query.get("mode");
|
||||
if (mode === "error") {
|
||||
return Response.json({ message: "Error" }, { status: 500 });
|
||||
}
|
||||
const now = result.rows[0].now;
|
||||
return Response.json({ message: 'Hello!', now, mode });
|
||||
return Response.json({ message: "Hello!", now, mode });
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import './auth.css'
|
||||
import "./auth.css";
|
||||
|
||||
export default function AuthLayout({ children }) {
|
||||
return (
|
||||
<div className="auth-container">
|
||||
<div className="auth-box">
|
||||
{children}
|
||||
</div>
|
||||
<div className="auth-box">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
'use client'
|
||||
import { useState } from 'react';
|
||||
"use client";
|
||||
import { useState } from "react";
|
||||
|
||||
export default function Login() {
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
console.log('Email:', email);
|
||||
console.log('Password:', password);
|
||||
}
|
||||
console.log("Email:", email);
|
||||
console.log("Password:", password);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="auth-title">Anmeldung</h1>
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="email">E-Mail-Adresse</label>
|
||||
<label className="form-label" htmlFor="email">
|
||||
E-Mail-Adresse
|
||||
</label>
|
||||
<input
|
||||
className="form-input"
|
||||
type="email"
|
||||
@@ -29,7 +31,9 @@ export default function Login() {
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label className="form-label" htmlFor="password">Passwort</label>
|
||||
<label className="form-label" htmlFor="password">
|
||||
Passwort
|
||||
</label>
|
||||
<input
|
||||
className="form-input"
|
||||
type="password"
|
||||
@@ -41,7 +45,9 @@ export default function Login() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button type="submit" className="form-button">Einloggen</button>
|
||||
<button type="submit" className="form-button">
|
||||
Einloggen
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use client';
|
||||
"use client";
|
||||
import Image from "next/image";
|
||||
import styles from "./page.module.css";
|
||||
import useQuery from "@/shared/hooks/useQuery";
|
||||
import { useState } from "react";
|
||||
export default function Home() {
|
||||
const [mode, setMode] = useState('normal');
|
||||
const [mode, setMode] = useState("normal");
|
||||
|
||||
const { data, error, isLoading } = useQuery('/hello', { mode });
|
||||
const { data, error, isLoading } = useQuery("/hello", { mode });
|
||||
|
||||
return (
|
||||
<div className={styles.page}>
|
||||
@@ -28,8 +28,8 @@ export default function Home() {
|
||||
{data && <p>{data.message}</p>}
|
||||
DB TIME: {isLoading ? <p>Loading...</p> : data && <p>{data.now}</p>}
|
||||
ERROR: {error ? <p>{error}</p> : <p>No error</p>}
|
||||
<button onClick={() => setMode('error')}>Error</button>
|
||||
<button onClick={() => setMode('normal')}>Normal</button>
|
||||
<button onClick={() => setMode("error")}>Error</button>
|
||||
<button onClick={() => setMode("normal")}>Normal</button>
|
||||
<div className={styles.ctas}>
|
||||
<a
|
||||
className={styles.primary}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { config } from 'dotenv';
|
||||
import { config } from "dotenv";
|
||||
|
||||
export const environment = process.env.NODE_ENV;
|
||||
|
||||
export const isTest = environment === 'test';
|
||||
export const isDevelopment = environment === 'development';
|
||||
export const isProduction = environment === 'production';
|
||||
export const isTest = environment === "test";
|
||||
export const isDevelopment = environment === "development";
|
||||
export const isProduction = environment === "production";
|
||||
|
||||
// Loads .env and .env.NODE_ENV (e.g. .env.development)
|
||||
// Shared environment variables are in .env
|
||||
@@ -12,18 +12,17 @@ export const isProduction = environment === 'production';
|
||||
// NextJS defaults to 'development' when running npm run dev
|
||||
export const initConfig = async () => {
|
||||
switch (environment) {
|
||||
case 'test':
|
||||
case 'development':
|
||||
case 'production':
|
||||
case "test":
|
||||
case "development":
|
||||
case "production":
|
||||
config({ path: `${process.cwd()}/.env`, override: true });
|
||||
config({ path: `${process.cwd()}/.env.${environment}`, override: true });
|
||||
break;
|
||||
default:
|
||||
throw new Error('Unsupported environment: ' + env);
|
||||
throw new Error("Unsupported environment: " + env);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// The following is an environment variable helper.
|
||||
// It's used to read environment variables from .env and .env.NODE_ENV
|
||||
// So instead of using process.env.DB_HOST, you can use getConfig().db.host
|
||||
@@ -39,7 +38,6 @@ export const getConfig = () => {
|
||||
return cachedConfig;
|
||||
};
|
||||
|
||||
|
||||
function getFromEnv(key) {
|
||||
const val = process.env[key];
|
||||
if (val === undefined) {
|
||||
@@ -53,15 +51,15 @@ function create() {
|
||||
|
||||
function readString(key) {
|
||||
const val = getFromEnv(key);
|
||||
if (val === undefined || val.trim() === '') {
|
||||
if (val === undefined || val.trim() === "") {
|
||||
errors.push(key);
|
||||
}
|
||||
return val ?? '';
|
||||
}
|
||||
|
||||
function readStringOptional(key, defaultValue) {
|
||||
return (getFromEnv(key)) ?? defaultValue;
|
||||
return val ?? "";
|
||||
}
|
||||
// Unused-vars
|
||||
// function readStringOptional(key, defaultValue) {
|
||||
// return getFromEnv(key) ?? defaultValue;
|
||||
// }
|
||||
|
||||
function readInt(key) {
|
||||
const val = getFromEnv(key);
|
||||
@@ -71,7 +69,7 @@ function create() {
|
||||
}
|
||||
return parseInt(val);
|
||||
}
|
||||
|
||||
// Unused-vars
|
||||
function readIntOptional(key, defaultValue) {
|
||||
const value = getFromEnv(key);
|
||||
return value === undefined ? defaultValue : parseInt(value);
|
||||
@@ -79,16 +77,18 @@ function create() {
|
||||
|
||||
const config = {
|
||||
db: {
|
||||
host: readString('DB_HOST'),
|
||||
port: readInt('DB_PORT'),
|
||||
user: readString('DB_USER'),
|
||||
password: readString('DB_PASSWORD'),
|
||||
database: readString('DB_NAME'),
|
||||
host: readString("DB_HOST"),
|
||||
port: readInt("DB_PORT"),
|
||||
user: readString("DB_USER"),
|
||||
password: readString("DB_PASSWORD"),
|
||||
database: readString("DB_NAME"),
|
||||
},
|
||||
};
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new Error(`Missing or invalid environment variables: ${errors.join(', ')}`);
|
||||
throw new Error(
|
||||
`Missing or invalid environment variables: ${errors.join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
return config;
|
||||
|
||||
+34
-26
@@ -1,26 +1,30 @@
|
||||
import { Pool } from 'pg';
|
||||
import { getConfig } from './config';
|
||||
import { isTest } from './config';
|
||||
import { Pool } from "pg";
|
||||
import { getConfig } from "./config";
|
||||
import { isTest } from "./config";
|
||||
export function dbURL(config) {
|
||||
return `postgres://${config.user}:${config.password}@${config.host}:${config.port}/${config.database}`;
|
||||
}
|
||||
import { logger } from './logging';
|
||||
import { logger } from "./logging";
|
||||
|
||||
let _pool;
|
||||
|
||||
async function connect(pool) {
|
||||
let client
|
||||
let client;
|
||||
try {
|
||||
if (!pool) {
|
||||
throw new Error('Pool not initialized');
|
||||
throw new Error("Pool not initialized");
|
||||
}
|
||||
client = await pool.connect()
|
||||
client = await pool.connect();
|
||||
if (!isTest) {
|
||||
const row = await client.query('SELECT current_database() as database_name, now()');
|
||||
logger.info(`Connected to database ${row.rows[0].database_name} at ${row.rows[0].now.toISOString()}`);
|
||||
const row = await client.query(
|
||||
"SELECT current_database() as database_name, now()"
|
||||
);
|
||||
logger.info(
|
||||
`Connected to database ${row.rows[0].database_name} at ${row.rows[0].now.toISOString()}`
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('Failed to connect to database', err);
|
||||
logger.error("Failed to connect to database", err);
|
||||
} finally {
|
||||
if (client) {
|
||||
await client.release();
|
||||
@@ -38,12 +42,12 @@ async function get() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a database query using the connection pool.
|
||||
*
|
||||
* @param {string} text - The SQL query text to be executed.
|
||||
* @param {Array} [params] - An optional array of parameters to be used in the query.
|
||||
* @returns {Promise<Object>} - A promise that resolves to the result of the query.
|
||||
*/
|
||||
* Executes a database query using the connection pool.
|
||||
*
|
||||
* @param {string} text - The SQL query text to be executed.
|
||||
* @param {Array} [params] - An optional array of parameters to be used in the query.
|
||||
* @returns {Promise<Object>} - A promise that resolves to the result of the query.
|
||||
*/
|
||||
async function pool(text, params) {
|
||||
return (await get()).query(text, params);
|
||||
}
|
||||
@@ -58,12 +62,12 @@ async function transaction(fn) {
|
||||
const db = await get();
|
||||
const client = await db.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
await client.query("BEGIN");
|
||||
const result = await fn(client);
|
||||
await client.query('COMMIT');
|
||||
await client.query("COMMIT");
|
||||
return result;
|
||||
} catch (err) {
|
||||
await client.query('ROLLBACK');
|
||||
await client.query("ROLLBACK");
|
||||
throw err;
|
||||
} finally {
|
||||
await client.release();
|
||||
@@ -72,7 +76,9 @@ async function transaction(fn) {
|
||||
|
||||
async function set(config) {
|
||||
if (!isTest) {
|
||||
throw new Error('Cannot set database in non-test environment, was ' + environment);
|
||||
throw new Error(
|
||||
"Cannot set database in non-test environment, was " + environment
|
||||
);
|
||||
}
|
||||
const newPool = new Pool(config);
|
||||
await connect(newPool);
|
||||
@@ -83,9 +89,9 @@ async function destroy() {
|
||||
if (_pool !== undefined) {
|
||||
if (!_pool.ended && !_pool.ending) {
|
||||
await _pool.end();
|
||||
logger.info('Pool destroyed');
|
||||
logger.info("Pool destroyed");
|
||||
} else {
|
||||
logger.warn('Pool already destroyed');
|
||||
logger.warn("Pool already destroyed");
|
||||
}
|
||||
}
|
||||
_pool = undefined;
|
||||
@@ -96,7 +102,10 @@ async function getClient() {
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
if (!isTest) throw new Error('Cannot reset database in non-test environment, was ' + environment);
|
||||
if (!isTest)
|
||||
throw new Error(
|
||||
"Cannot reset database in non-test environment, was " + environment
|
||||
);
|
||||
if (_pool !== undefined) {
|
||||
await destroy();
|
||||
}
|
||||
@@ -110,6 +119,5 @@ export const DB = {
|
||||
set,
|
||||
destroy,
|
||||
getClient,
|
||||
reset
|
||||
}
|
||||
|
||||
reset,
|
||||
};
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
|
||||
// Printing these strings will change the color of the text in the console
|
||||
const colors = {
|
||||
blue: '\x1b[34m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
green: '\x1b[32m',
|
||||
reset: '\x1b[0m',
|
||||
blue: "\x1b[34m",
|
||||
red: "\x1b[31m",
|
||||
yellow: "\x1b[33m",
|
||||
green: "\x1b[32m",
|
||||
reset: "\x1b[0m",
|
||||
};
|
||||
|
||||
export const logger = {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { DB } from "../db";
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { useTestDatabase } from "./testdb";
|
||||
describe('database', () => {
|
||||
describe("database", () => {
|
||||
useTestDatabase();
|
||||
|
||||
it("Should allow for creation of tables", async () => {
|
||||
@@ -15,7 +15,9 @@ describe('database', () => {
|
||||
});
|
||||
it("should not carry over changes between tests", async () => {
|
||||
const db = await DB.get();
|
||||
expect(async () => await db.query("SELECT * FROM TEST")).rejects.toThrow('relation "test" does not exist');
|
||||
expect(async () => await db.query("SELECT * FROM TEST")).rejects.toThrow(
|
||||
'relation "test" does not exist'
|
||||
);
|
||||
});
|
||||
it("should get a database instance", async () => {
|
||||
const db = await DB.get();
|
||||
@@ -23,7 +25,9 @@ describe('database', () => {
|
||||
});
|
||||
it("should do transactions correctly", async () => {
|
||||
const res = await DB.transaction(async (client) => {
|
||||
await client.query("CREATE table TEST (id SERIAL PRIMARY KEY, value TEXT)");
|
||||
await client.query(
|
||||
"CREATE table TEST (id SERIAL PRIMARY KEY, value TEXT)"
|
||||
);
|
||||
await client.query("INSERT INTO TEST (value) VALUES ('test')");
|
||||
return await client.query("SELECT * FROM TEST");
|
||||
});
|
||||
@@ -36,12 +40,14 @@ describe('database', () => {
|
||||
expect(result.rows.length).toBe(1);
|
||||
|
||||
const res2 = await DB.transaction(async (client) => {
|
||||
await client.query("INSERT INTO TEST (value) VALUES ('should not be inserted')");
|
||||
await client.query(
|
||||
"INSERT INTO TEST (value) VALUES ('should not be inserted')"
|
||||
);
|
||||
await client.query("INSERT INTO TEST (value) VALUES (ERROR)"); // Invalid query
|
||||
return "success";
|
||||
}).catch(() => 'error');
|
||||
}).catch(() => "error");
|
||||
|
||||
expect(res2).toBe('error');
|
||||
expect(res2).toBe("error");
|
||||
|
||||
const result2 = await DB.pool("SELECT * FROM TEST");
|
||||
expect(result2).toBeDefined();
|
||||
@@ -49,4 +55,3 @@ describe('database', () => {
|
||||
expect(result2.rows[0].value).toBe("test");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@ export class Migrator {
|
||||
}
|
||||
|
||||
get migrationsDir() {
|
||||
return path.join(__dirname, "../../../../data", "migrations").replace("dist/", "");
|
||||
return path
|
||||
.join(__dirname, "../../../../data", "migrations")
|
||||
.replace("dist/", "");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@ export function useTestDatabase({
|
||||
// Init new DB
|
||||
baseDB = new Pool({
|
||||
max: 1,
|
||||
...conf
|
||||
})
|
||||
...conf,
|
||||
});
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Failed to connect to test database, is docker-compose running? : ${e}`
|
||||
@@ -45,7 +45,7 @@ export function useTestDatabase({
|
||||
|
||||
// 4. Create testDB
|
||||
// We set the testDB to use the new database
|
||||
await DB.set({ max: 1, ...testDBConfig })
|
||||
await DB.set({ max: 1, ...testDBConfig });
|
||||
testDB = await DB.get();
|
||||
});
|
||||
|
||||
@@ -72,11 +72,7 @@ export function useTestDatabase({
|
||||
|
||||
let template;
|
||||
|
||||
async function getOrCreateTemplate(
|
||||
pool,
|
||||
conf,
|
||||
migrator
|
||||
) {
|
||||
async function getOrCreateTemplate(pool, conf, migrator) {
|
||||
const hash = await migrator.hash();
|
||||
|
||||
if (template !== undefined) {
|
||||
@@ -117,9 +113,11 @@ async function ensureUser(db) {
|
||||
}
|
||||
|
||||
ensureUserCalled = true;
|
||||
await withLock(db, 'testdb-user', async (client) => {
|
||||
await withLock(db, "testdb-user", async (client) => {
|
||||
const roleExists = (
|
||||
await client.query(`SELECT EXISTS (SELECT from pg_catalog.pg_roles WHERE rolname = '${TestUser}')`)
|
||||
await client.query(
|
||||
`SELECT EXISTS (SELECT from pg_catalog.pg_roles WHERE rolname = '${TestUser}')`
|
||||
)
|
||||
).rows[0];
|
||||
|
||||
if (roleExists.exists) {
|
||||
@@ -127,31 +125,30 @@ async function ensureUser(db) {
|
||||
}
|
||||
|
||||
await client.query(`CREATE ROLE ${TestUser}`);
|
||||
await client.query(`ALTER ROLE ${TestUser} WITH LOGIN PASSWORD '${TestPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE`);
|
||||
await client.query(
|
||||
`ALTER ROLE ${TestUser} WITH LOGIN PASSWORD '${TestPassword}' NOSUPERUSER NOCREATEDB NOCREATEROLE`
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// createInstance creates a new database instance using the template database
|
||||
async function createInstance(
|
||||
pool,
|
||||
template
|
||||
) {
|
||||
async function createInstance(pool, template) {
|
||||
const testConf = { ...template.conf };
|
||||
testConf.database = `testdb_tpl_${template.hash}_inst_${randomID()}`;
|
||||
await pool.query(`CREATE DATABASE ${testConf.database} WITH TEMPLATE ${template.conf.database} OWNER ${testConf.user}`);
|
||||
await pool.query(
|
||||
`CREATE DATABASE ${testConf.database} WITH TEMPLATE ${template.conf.database} OWNER ${testConf.user}`
|
||||
);
|
||||
|
||||
return testConf;
|
||||
}
|
||||
|
||||
|
||||
async function ensureTemplate(
|
||||
client,
|
||||
migrator,
|
||||
state
|
||||
) {
|
||||
async function ensureTemplate(client, migrator, state) {
|
||||
// If the template database already exists, and is marked as a template,
|
||||
// there is no more work to be done.
|
||||
const templateExists = await client.query(`SELECT EXISTS (SELECT FROM pg_database WHERE datname = $1 AND datistemplate = true)`, [state.conf.database]);
|
||||
const templateExists = await client.query(
|
||||
`SELECT EXISTS (SELECT FROM pg_database WHERE datname = $1 AND datistemplate = true)`,
|
||||
[state.conf.database]
|
||||
);
|
||||
if (templateExists.rows[0].exists) {
|
||||
return;
|
||||
}
|
||||
@@ -160,13 +157,18 @@ async function ensureTemplate(
|
||||
// template, there was a failure at some point during the creation process
|
||||
// so it needs to be deleted.
|
||||
await client.query(`DROP DATABASE IF EXISTS ${state.conf.database}`);
|
||||
await client.query(`CREATE DATABASE ${state.conf.database} OWNER ${state.conf.user}`)
|
||||
await client.query(
|
||||
`CREATE DATABASE ${state.conf.database} OWNER ${state.conf.user}`
|
||||
);
|
||||
|
||||
await migrator.migrate(state.conf)
|
||||
await migrator.migrate(state.conf);
|
||||
|
||||
// Finalize the creation of the template by marking it as a
|
||||
// template.
|
||||
await client.query(`UPDATE pg_database SET datistemplate = true WHERE datname=$1`, [state.conf.database]);
|
||||
await client.query(
|
||||
`UPDATE pg_database SET datistemplate = true WHERE datname=$1`,
|
||||
[state.conf.database]
|
||||
);
|
||||
}
|
||||
|
||||
const idPrefix = "sessionlock-";
|
||||
@@ -180,14 +182,9 @@ function id(name) {
|
||||
return hexInt | 0; // Convert to 32-bit integer
|
||||
}
|
||||
|
||||
|
||||
// The withLock function ensures that a block of code runs with a database lock.
|
||||
// This prevents other code from running the same block at the same time.
|
||||
async function withLock(
|
||||
pool,
|
||||
lockName,
|
||||
cb
|
||||
) {
|
||||
async function withLock(pool, lockName, cb) {
|
||||
const lockID = id(lockName); // Create a lock ID
|
||||
|
||||
const client = await pool.connect();
|
||||
@@ -195,15 +192,13 @@ async function withLock(
|
||||
await client.query(`SELECT pg_advisory_lock(${lockID})`); // Acquire the lock.
|
||||
await cb(client); // Run the function with the lock.
|
||||
} catch (e) {
|
||||
logger.error('failed to lock', e);
|
||||
logger.error("failed to lock", e);
|
||||
} finally {
|
||||
await client.query(`SELECT pg_advisory_unlock(${lockID})`); // Release the lock.
|
||||
await client.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function randomID() {
|
||||
const hash = createHash("md5");
|
||||
hash.update(randomBytes(4));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
/**
|
||||
* @description This hook is used to check if the component is mounted.
|
||||
|
||||
@@ -1,24 +1,25 @@
|
||||
import axios from 'axios';
|
||||
import { stringify } from 'qs';
|
||||
import { useState, useEffect, useRef, useMemo } from 'react';
|
||||
import { useIsMounted } from './useIsMounted';
|
||||
import axios from "axios";
|
||||
import { stringify } from "qs";
|
||||
import { useState, useEffect, useRef, useMemo } from "react";
|
||||
import { useIsMounted } from "./useIsMounted";
|
||||
|
||||
const baseUrl = process.env.NEXT_ENV === 'production' ? 'https://tbd.com' : 'http://localhost:3000';
|
||||
const baseUrl =
|
||||
process.env.NEXT_ENV === "production"
|
||||
? "https://tbd.com"
|
||||
: "http://localhost:3000";
|
||||
export const axiosInstance = axios.create({
|
||||
baseURL: `${baseUrl}/api`,
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* @description This function converts an object to a query string.
|
||||
* @param {Object} obj - The object to convert to a query string.
|
||||
* @returns {string} - The query string.
|
||||
*/
|
||||
const objectToQueryString = (obj) => {
|
||||
return stringify(obj, { encodeValuesOnly: true, });
|
||||
return stringify(obj, { encodeValuesOnly: true });
|
||||
};
|
||||
|
||||
|
||||
const defaultConfig = {
|
||||
enabled: true,
|
||||
refetchBackoffMax: 10_000,
|
||||
@@ -59,11 +60,13 @@ const useQuery = (url, params, config) => {
|
||||
}
|
||||
setIsLoading(true);
|
||||
const fullUrl = queryString ? `${url}?${queryString}` : url;
|
||||
const response = await axiosInstance.get(fullUrl, { signal: abortCtrl.signal });
|
||||
const response = await axiosInstance.get(fullUrl, {
|
||||
signal: abortCtrl.signal,
|
||||
});
|
||||
if (isMounted && !!response.data) {
|
||||
setData(response.data);
|
||||
} else {
|
||||
throw new Error('Failed to fetch data');
|
||||
throw new Error("Failed to fetch data");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,7 +81,11 @@ const useQuery = (url, params, config) => {
|
||||
const maxBackoff = config.refetchBackoffMax;
|
||||
try {
|
||||
setIsLoading(true);
|
||||
while (currentAttemptRef.current < maxAttempts && isMounted && !abortCtrl.signal.aborted) {
|
||||
while (
|
||||
currentAttemptRef.current < maxAttempts &&
|
||||
isMounted &&
|
||||
!abortCtrl.signal.aborted
|
||||
) {
|
||||
try {
|
||||
await makeGetRequest(url, queryString, abortCtrl);
|
||||
// If succeeded, exit the loop
|
||||
@@ -100,7 +107,9 @@ const useQuery = (url, params, config) => {
|
||||
// backoff equal to the base backoff
|
||||
exponent = memoizedConfig.refetchBackoffExponent;
|
||||
// Wait for the backoff duration
|
||||
await new Promise(resolve => setTimeout(resolve, currentBackoffRef.current));
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, currentBackoffRef.current)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -125,7 +134,12 @@ const useQuery = (url, params, config) => {
|
||||
}
|
||||
let abortCtrl = new AbortController();
|
||||
(async () => {
|
||||
await retryWithBackoff(url, memoizedQueryString, memoizedConfig, abortCtrl);
|
||||
await retryWithBackoff(
|
||||
url,
|
||||
memoizedQueryString,
|
||||
memoizedConfig,
|
||||
abortCtrl
|
||||
);
|
||||
})();
|
||||
return () => {
|
||||
abortCtrl.abort();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { axiosInstance } from './useQuery';
|
||||
import { axiosInstance } from "./useQuery";
|
||||
|
||||
const defaultConfig = {
|
||||
method: 'POST',
|
||||
method: "POST",
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -16,10 +16,11 @@ export const useRequest = () => {
|
||||
* @param {Object} config - The config of the request.
|
||||
* @returns {Object} - The response from the server.
|
||||
*/
|
||||
return (url, body, config) => axiosInstance.request({
|
||||
return (url, body, config) =>
|
||||
axiosInstance.request({
|
||||
url,
|
||||
data: body,
|
||||
...defaultConfig,
|
||||
...config,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { defineConfig } from 'vitest/config'
|
||||
import react from '@vitejs/plugin-react'
|
||||
import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
test: {
|
||||
environment: 'jsdom',
|
||||
environment: "jsdom",
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
Generated
+688
-1
@@ -7,7 +7,694 @@
|
||||
"": {
|
||||
"name": "ohtu-german-learning-website",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC"
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"husky": "^8.0.3",
|
||||
"lint-staged": "^13.3.0",
|
||||
"prettier": "^3.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz",
|
||||
"integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz",
|
||||
"integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"restore-cursor": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
|
||||
"integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"slice-ansi": "^5.0.0",
|
||||
"string-width": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-11.0.0.tgz",
|
||||
"integrity": "sha512-9HMlXtt/BNoYr8ooyjjNRdIilOTkVJXB+GhxMTtOKwk0R4j4lS4NpjuqmRxroBfnfTSHQIHQB7wryHhXarNjmQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
|
||||
"integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
|
||||
"integrity": "sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^6.0.1",
|
||||
"human-signals": "^4.3.0",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^3.0.7",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || ^16.14.0 || >=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
"integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
|
||||
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/husky": {
|
||||
"version": "8.0.3",
|
||||
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.3.tgz",
|
||||
"integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"husky": "lib/bin.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/typicode"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
|
||||
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "13.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-13.3.0.tgz",
|
||||
"integrity": "sha512-mPRtrYnipYYv1FEE134ufbWpeggNTo+O/UPzngoaKzbzHAthvR55am+8GfHTnqNRQVRRrYQLGW9ZyUoD7DsBHQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "5.3.0",
|
||||
"commander": "11.0.0",
|
||||
"debug": "4.3.4",
|
||||
"execa": "7.2.0",
|
||||
"lilconfig": "2.1.0",
|
||||
"listr2": "6.6.1",
|
||||
"micromatch": "4.0.5",
|
||||
"pidtree": "0.6.0",
|
||||
"string-argv": "0.3.2",
|
||||
"yaml": "2.3.1"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^16.14.0 || >=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/lint-staged"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "6.6.1",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-6.6.1.tgz",
|
||||
"integrity": "sha512-+rAXGHh0fkEWdXBmX+L6mmfmXmXvDGEKzkjxO+8mP3+nI/r/CWznVBvsibXdxda9Zz0OW2e2ikphN3OwCT/jSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cli-truncate": "^3.1.0",
|
||||
"colorette": "^2.0.20",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"log-update": "^5.0.1",
|
||||
"rfdc": "^1.3.0",
|
||||
"wrap-ansi": "^8.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"enquirer": ">= 2.3.0 < 3"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"enquirer": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/log-update": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz",
|
||||
"integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^5.0.0",
|
||||
"cli-cursor": "^4.0.0",
|
||||
"slice-ansi": "^5.0.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"wrap-ansi": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
|
||||
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"braces": "^3.0.2",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
|
||||
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pidtree": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
|
||||
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"pidtree": "bin/pidtree.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
|
||||
"integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz",
|
||||
"integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"onetime": "^5.1.0",
|
||||
"signal-exit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/mimic-fn": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/onetime": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"mimic-fn": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
|
||||
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.0.0",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/string-argv": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
||||
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
"emoji-regex": "^9.2.2",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/type-fest": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz",
|
||||
"integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
"string-width": "^5.0.1",
|
||||
"strip-ansi": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz",
|
||||
"integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,32 @@
|
||||
{
|
||||
"name": "ohtu-german-learning-website",
|
||||
"version": "1.0.0",
|
||||
"packageManager": "npm@10.2.4",
|
||||
"description": "GRADESA is a freely accessible website where language learners at language centres as well as university students of German studies can autonomously work on their German grammar skills and knowledge.",
|
||||
"scripts": {
|
||||
"db:migrate": "./data/migrate.sh",
|
||||
"db:reset": "./data/reset-db.sh",
|
||||
"prepare": "husky install",
|
||||
"dev": "npm run --prefix gradesa dev",
|
||||
"format:check": "prettier --check \"**/*.{js,jsx,md,yml}\"",
|
||||
"format:fix": "prettier --write \"**/*.{js,jsx,md,yml}\"",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,jsx,tsx,md,yml}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"lint-staged": "^13.3.0",
|
||||
"prettier": "^3.2.4",
|
||||
"husky": "^8.0.3"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
||||
Reference in New Issue
Block a user