Compare commits

...

10 Commits

Author SHA1 Message Date
orangecoding
251de1e42d next release version 2025-09-12 13:48:05 +02:00
orangecoding
edc91291b6 fixing telegram 2025-09-12 13:45:54 +02:00
orangecoding
ac0ea64c07 remove unnecessary logging 2025-09-12 13:41:08 +02:00
orangecoding
9f7506a1b3 Merge branch 'master' of github.com:orangecoding/fredy 2025-09-12 13:39:15 +02:00
orangecoding
85cea66051 improving tracking. now using internal tracking 2025-09-12 13:38:53 +02:00
Christian Kellner
05c2df917c Adding link to fredy demo 2025-09-12 13:00:43 +02:00
Christian Kellner
4ad2895eec Update docker command 2025-09-10 11:31:49 +02:00
orangecoding
7372e5313f creating config automagically if missing 2025-09-09 18:41:14 +02:00
orangecoding
637a54e01e upgrading dependencies 2025-09-09 15:17:36 +02:00
orangecoding
04265eaec7 making sure scan interval does not go under 5 2025-09-08 08:30:45 +02:00
12 changed files with 217 additions and 132 deletions

View File

@@ -45,6 +45,11 @@ Fredy is proudly backed by the **JetBrains Open Source Support Program**.
------------------------------------------------------------------------ ------------------------------------------------------------------------
## 👨‍🏫 Demo
You can try out Fredy here: [Fredy Demo](https://fredy-demo.orange-coding.net/)
------------------------------------------------------------------------
## 🚀 Quick Start ## 🚀 Quick Start
### With Docker ### With Docker
@@ -53,7 +58,11 @@ Fredy is proudly backed by the **JetBrains Open Source Support Program**.
> In order to start Fredy, you must provide a config.json. As a start, use the one in this repo: https://github.com/orangecoding/fredy/blob/master/conf/config.json > In order to start Fredy, you must provide a config.json. As a start, use the one in this repo: https://github.com/orangecoding/fredy/blob/master/conf/config.json
``` bash ``` bash
docker run -d --name fredy -v fredy_conf:/conf -p 9998:9998 ghcr.io/orangecoding/fredy:master docker run -d --name fredy \
-v fredy_conf:/conf \
-v fredy_db:/db \
-p 9998:9998 \
ghcr.io/orangecoding/fredy:master
``` ```
Logs: Logs:
@@ -128,7 +137,7 @@ Immoscout has implemented advanced bot detection. In order to work around this,
Fredy is completely free (and will always remain free). However, it would be a huge help if youd allow me to collect some analytical data. Fredy is completely free (and will always remain free). However, it would be a huge help if youd allow me to collect some analytical data.
Before you freak out, let me explain... Before you freak out, let me explain...
If you agree, Fredy will send a ping to my Mixpanel project each time it runs. If you agree, Fredy will send a ping once every 6 hours to my internal tracking project (Will be open sourced soon).
The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The information is entirely anonymous and helps me understand which adapters/providers are most frequently used.</p> The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The information is entirely anonymous and helps me understand which adapters/providers are most frequently used.</p>
**Thanks**🤘 **Thanks**🤘

0
conf/config.json Executable file → Normal file
View File

View File

@@ -3,7 +3,6 @@ import * as jobStorage from '../../services/storage/jobStorage.js';
import * as userStorage from '../../services/storage/userStorage.js'; import * as userStorage from '../../services/storage/userStorage.js';
import { config } from '../../utils.js'; import { config } from '../../utils.js';
import { isAdmin } from '../security.js'; import { isAdmin } from '../security.js';
import { trackDemoJobCreated } from '../../services/tracking/Tracker.js';
const service = restana(); const service = restana();
const jobRouter = service.newRouter(); const jobRouter = service.newRouter();
function doesJobBelongsToUser(job, req) { function doesJobBelongsToUser(job, req) {
@@ -46,11 +45,6 @@ jobRouter.post('/', async (req, res) => {
res.send(new Error(error)); res.send(new Error(error));
console.error(error); console.error(error);
} }
trackDemoJobCreated({
name,
provider,
adapter: notificationAdapter,
});
res.send(); res.send();
}); });
jobRouter.delete('', async (req, res) => { jobRouter.delete('', async (req, res) => {

View File

@@ -27,7 +27,7 @@ loginRouter.post('/', async (req, res) => {
} }
if (user.password === hasher.hash(password)) { if (user.password === hasher.hash(password)) {
if (config.demoMode) { if (config.demoMode) {
trackDemoAccessed(); await trackDemoAccessed();
} }
req.session.currentUser = user.id; req.session.currentUser = user.id;

View File

@@ -63,31 +63,41 @@ export const send = ({ serviceName, newListings, notificationConfig, jobKey }) =
const jobName = job == null ? jobKey : job.name; const jobName = job == null ? jobKey : job.name;
const throttledCall = getThrottled(chatId, async function (endpoint, body) { const throttledCall = getThrottled(chatId, async function (endpoint, body) {
await fetch(`https://api.telegram.org/bot${token}/${endpoint}`, { const res = await fetch(`https://api.telegram.org/bot${token}/${endpoint}`, {
method: 'post', method: 'post',
body: JSON.stringify(body), body: JSON.stringify(body),
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}); });
return res;
}); });
const promises = newListings.map(async (o) => { const promises = newListings.map(async (o) => {
const img = normalizeImageUrl(o.image); const img = normalizeImageUrl(o.image);
const textPayload = {
chat_id: chatId,
text: buildText(jobName, serviceName, o),
parse_mode: 'HTML',
disable_web_page_preview: true,
};
if (img) { if (!img) {
return throttledCall('sendPhoto', { return throttledCall('sendMessage', textPayload);
}
try {
return await throttledCall('sendPhoto', {
chat_id: chatId, chat_id: chatId,
photo: img, photo: img,
caption: buildCaption(jobName, serviceName, o), caption: buildCaption(jobName, serviceName, o),
parse_mode: 'HTML', parse_mode: 'HTML',
}); });
} catch (e) {
// If we see a timeout due to sending an image, try sending it without
if (e && (e.code === 'ETIMEDOUT' || e.errno === 'ETIMEDOUT')) {
return throttledCall('sendMessage', textPayload);
}
throw e;
} }
return throttledCall('sendMessage', {
chat_id: chatId,
text: buildText(jobName, serviceName, o),
parse_mode: 'HTML',
disable_web_page_preview: true,
});
}); });
return Promise.all(promises); return Promise.all(promises);

View File

@@ -66,7 +66,9 @@ export function parse(crawlContainer, crawlFields, text, url) {
if (parsedObject.id != null) { if (parsedObject.id != null) {
result.push(parsedObject); result.push(parsedObject);
} else { } else {
console.warn('ID not found. Not relaying object.'); /* eslint-disable no-console */
console.debug('ID not found. Not relaying object.');
/* eslint-enable no-console */
} }
}); });

View File

@@ -1,65 +1,77 @@
import Mixpanel from 'mixpanel';
import { getJobs } from '../storage/jobStorage.js'; import { getJobs } from '../storage/jobStorage.js';
import { getUniqueId } from './uniqueId.js'; import { getUniqueId } from './uniqueId.js';
import { config, inDevMode } from '../../utils.js'; import { config, inDevMode } from '../../utils.js';
import os from 'os'; import os from 'os';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { packageUp } from 'package-up'; import { packageUp } from 'package-up';
import fetch from 'node-fetch';
const mixpanelTracker = Mixpanel.init('718670ef1c58c0208256c1e408a3d75e'); const deviceId = getUniqueId() || 'N/A';
const distinct_id = getUniqueId() || 'N/A';
const version = await getPackageVersion(); const version = await getPackageVersion();
const FREDY_TRACKING_URL = 'https://fredy.orange-coding.net/tracking';
export const track = function () { let cached = null;
//only send tracking information if the user allowed to do so. let lastSent = 0;
if (config.analyticsEnabled && !inDevMode()) { const SIX_HOURS = 6 * 3_600_000;
const activeProvider = new Set();
const activeAdapter = new Set();
const jobs = getJobs(); export const track = async () => {
try {
if (config.analyticsEnabled && !inDevMode()) {
const activeProvider = new Set();
const activeAdapter = new Set();
if (jobs != null && jobs.length > 0) { const jobs = getJobs();
jobs.forEach((job) => {
job.provider.forEach((provider) => { if (jobs != null && jobs.length > 0) {
activeProvider.add(provider.id); jobs.forEach((job) => {
job.provider.forEach((provider) => activeProvider.add(provider.id));
job.notificationAdapter.forEach((adapter) => activeAdapter.add(adapter.id));
}); });
job.notificationAdapter.forEach((adapter) => {
activeAdapter.add(adapter.id);
});
});
mixpanelTracker.track( const trackingObj = enrichTrackingObject({
'fredy_tracking',
enrichTrackingObject({
adapter: Array.from(activeAdapter), adapter: Array.from(activeAdapter),
provider: Array.from(activeProvider), provider: Array.from(activeProvider),
}), });
);
const stringify = JSON.stringify(trackingObj);
const now = Date.now();
// send if changed OR six hours passed since last send
if (stringify !== cached || now - lastSent >= SIX_HOURS) {
await fetch(`${FREDY_TRACKING_URL}/main`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: stringify,
});
cached = stringify;
lastSent = now;
}
}
} }
} catch (error) {
console.warn('Error sending tracking data', error);
} }
}; };
/** /**
* Note, this will only be used when Fredy runs in demo mode * Note, this will only be used when Fredy runs in demo mode
*/ */
export function trackDemoJobCreated(jobData) { export async function trackDemoAccessed() {
if (config.analyticsEnabled && !inDevMode() && config.demoMode) { if (config.analyticsEnabled && !inDevMode() && config.demoMode) {
mixpanelTracker.track('demoJobCreated', enrichTrackingObject(jobData)); try {
} await fetch(`${FREDY_TRACKING_URL}/demo/accessed`, {
} method: 'POST',
headers: { 'Content-Type': 'application/json' },
/** });
* Note, this will only be used when Fredy runs in demo mode } catch (error) {
*/ console.warn('Error sending tracking data', error);
export function trackDemoAccessed() { }
if (config.analyticsEnabled && !inDevMode() && config.demoMode) {
mixpanelTracker.track('demoAccessed', enrichTrackingObject({}));
} }
} }
function enrichTrackingObject(trackingObject) { function enrichTrackingObject(trackingObject) {
const operating_system = os.platform(); const operatingSystem = os.platform();
const os_version = os.release(); const osVersion = os.release();
const arch = process.arch; const arch = process.arch;
const language = process.env.LANG || 'en'; const language = process.env.LANG || 'en';
const nodeVersion = process.version || 'N/A'; const nodeVersion = process.version || 'N/A';
@@ -67,13 +79,13 @@ function enrichTrackingObject(trackingObject) {
return { return {
...trackingObject, ...trackingObject,
isDemo: config.demoMode, isDemo: config.demoMode,
operating_system, operatingSystem,
os_version, osVersion,
arch, arch,
nodeVersion, nodeVersion,
language, language,
distinct_id, deviceId,
fredy_version: version, version,
}; };
} }

View File

@@ -3,6 +3,12 @@ import { fileURLToPath } from 'node:url';
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { createHash } from 'crypto'; import { createHash } from 'crypto';
import { DEFAULT_CONFIG } from './defaultConfig.js'; import { DEFAULT_CONFIG } from './defaultConfig.js';
import fs from 'fs';
const RE_GT = />/g;
const RE_WEBP = /\/format\/webp/gi;
const RE_EXT = /\.(jpe?g|png|gif)(\?.*)?$/i;
const HTTPS_PREFIX = 'https://';
function inDevMode() { function inDevMode() {
return process.env.NODE_ENV == null || process.env.NODE_ENV !== 'production'; return process.env.NODE_ENV == null || process.env.NODE_ENV !== 'production';
@@ -53,11 +59,14 @@ function buildHash(...inputs) {
} }
let config = {}; let config = {};
export async function readConfigFromStorage() { export async function readConfigFromStorage() {
return JSON.parse(await readFile(new URL('../conf/config.json', import.meta.url))); return JSON.parse(await readFile(new URL('../conf/config.json', import.meta.url)));
} }
export async function refreshConfig() { export async function refreshConfig() {
checkIfConfigExistsAndWriteIfNot();
try { try {
config = await readConfigFromStorage(); config = await readConfigFromStorage();
//backwards compatability... //backwards compatability...
@@ -65,14 +74,20 @@ export async function refreshConfig() {
config.demoMode ??= false; config.demoMode ??= false;
} catch (error) { } catch (error) {
config = { ...DEFAULT_CONFIG }; config = { ...DEFAULT_CONFIG };
console.error('Error reading config file', error); /* eslint-disable no-console */
console.info('Error reading config file.', error);
} }
} }
const RE_GT = />/g; /**
const RE_WEBP = /\/format\/webp/gi; * If the config file does not exist, we will create it.
const RE_EXT = /\.(jpe?g|png|gif)(\?.*)?$/i; */
const HTTPS_PREFIX = 'https://'; const checkIfConfigExistsAndWriteIfNot = () => {
if (!fs.existsSync(`${getDirName()}/../conf/config.json`)) {
console.info('Could not find config file. Will create one with default values now');
fs.writeFileSync(`${getDirName()}/../conf/config.json`, JSON.stringify({ ...DEFAULT_CONFIG }));
}
};
const normalizeImageUrl = (url) => { const normalizeImageUrl = (url) => {
if (typeof url !== 'string' || url.length === 0) return null; if (typeof url !== 'string' || url.length === 0) return null;

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "11.6.0", "version": "11.6.2",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"prepare": "husky", "prepare": "husky",
@@ -70,7 +70,6 @@
"lodash": "4.17.21", "lodash": "4.17.21",
"lowdb": "7.0.1", "lowdb": "7.0.1",
"markdown": "^0.5.0", "markdown": "^0.5.0",
"mixpanel": "^0.18.1",
"nanoid": "5.1.5", "nanoid": "5.1.5",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"node-mailjet": "6.0.9", "node-mailjet": "6.0.9",
@@ -79,7 +78,7 @@
"puppeteer": "^24.19.0", "puppeteer": "^24.19.0",
"puppeteer-extra": "^3.3.6", "puppeteer-extra": "^3.3.6",
"puppeteer-extra-plugin-stealth": "^2.11.2", "puppeteer-extra-plugin-stealth": "^2.11.2",
"query-string": "9.2.2", "query-string": "9.3.0",
"react": "18.3.1", "react": "18.3.1",
"react-dom": "18.3.1", "react-dom": "18.3.1",
"react-redux": "9.2.0", "react-redux": "9.2.0",
@@ -90,16 +89,16 @@
"restana": "5.1.0", "restana": "5.1.0",
"serve-static": "2.2.0", "serve-static": "2.2.0",
"slack": "11.0.2", "slack": "11.0.2",
"vite": "7.1.4", "vite": "7.1.5",
"x-var": "^2.1.0" "x-var": "^2.1.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "7.28.3", "@babel/core": "7.28.4",
"@babel/eslint-parser": "7.28.0", "@babel/eslint-parser": "7.28.4",
"@babel/preset-env": "7.28.3", "@babel/preset-env": "7.28.3",
"@babel/preset-react": "7.27.1", "@babel/preset-react": "7.27.1",
"chai": "6.0.1", "chai": "6.0.1",
"eslint": "9.34.0", "eslint": "9.35.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-react": "7.37.5", "eslint-plugin-react": "7.37.5",
"esmock": "2.7.2", "esmock": "2.7.2",

View File

@@ -43,7 +43,8 @@ export default function TrackingModal() {
</p> </p>
<p> <p>
However, it would be a huge help if youd allow me to collect some analytical data. Wait, before you click However, it would be a huge help if youd allow me to collect some analytical data. Wait, before you click
"no", let me explain. If you agree, Fredy will send a ping to my Mixpanel project each time it runs. "no", let me explain. If you agree, Fredy will send a ping once every 6 hours to my internal tracking project.
(Will be open-sourced soon)
</p> </p>
<p> <p>
The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The The data includes: names of active adapters/providers, OS, architecture, Node version, and language. The

View File

@@ -121,11 +121,11 @@ const GeneralSettings = function GeneralSettings() {
<div> <div>
<SegmentPart <SegmentPart
name="Interval" name="Interval"
helpText="Interval in minutes for running queries against the configured services." helpText="Interval in minutes for running queries against the configured services. Do NOT go under 5 minutes as with a lower interval, your instance might be detected as a bot."
Icon={IconRefresh} Icon={IconRefresh}
> >
<InputNumber <InputNumber
min={0} min={5}
max={1440} max={1440}
placeholder="Interval in minutes" placeholder="Interval in minutes"
value={interval} value={interval}

159
yarn.lock
View File

@@ -33,7 +33,28 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.28.0.tgz#9fc6fd58c2a6a15243cd13983224968392070790"
integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw== integrity sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==
"@babel/core@7.28.3", "@babel/core@^7.28.3": "@babel/core@7.28.4":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.4.tgz#12a550b8794452df4c8b084f95003bce1742d496"
integrity sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.28.3"
"@babel/helper-compilation-targets" "^7.27.2"
"@babel/helper-module-transforms" "^7.28.3"
"@babel/helpers" "^7.28.4"
"@babel/parser" "^7.28.4"
"@babel/template" "^7.27.2"
"@babel/traverse" "^7.28.4"
"@babel/types" "^7.28.4"
"@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.28.3":
version "7.28.3" version "7.28.3"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.28.3.tgz#aceddde69c5d1def69b839d09efa3e3ff59c97cb"
integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ== integrity sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==
@@ -54,10 +75,10 @@
json5 "^2.2.3" json5 "^2.2.3"
semver "^6.3.1" semver "^6.3.1"
"@babel/eslint-parser@7.28.0": "@babel/eslint-parser@7.28.4":
version "7.28.0" version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.0.tgz#c1b3fbba070f5bac32e3d02f244201add4afdd6e" resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.4.tgz#80dd86e0aeaae9704411a044db60e1ae6477d93f"
integrity sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w== integrity sha512-Aa+yDiH87980jR6zvRfFuCR1+dLb00vBydhTL+zI992Rz/wQhSvuxjmOOuJOgO3XmakO6RykRGD2S1mq1AtgHA==
dependencies: dependencies:
"@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1"
eslint-visitor-keys "^2.1.0" eslint-visitor-keys "^2.1.0"
@@ -225,6 +246,14 @@
"@babel/template" "^7.27.2" "@babel/template" "^7.27.2"
"@babel/types" "^7.28.2" "@babel/types" "^7.28.2"
"@babel/helpers@^7.28.4":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.28.4.tgz#fe07274742e95bdf7cf1443593eeb8926ab63827"
integrity sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==
dependencies:
"@babel/template" "^7.27.2"
"@babel/types" "^7.28.4"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3": "@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.27.2", "@babel/parser@^7.28.3":
version "7.28.3" version "7.28.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.3.tgz#d2d25b814621bca5fe9d172bc93792547e7a2a71"
@@ -232,6 +261,13 @@
dependencies: dependencies:
"@babel/types" "^7.28.2" "@babel/types" "^7.28.2"
"@babel/parser@^7.28.4":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.28.4.tgz#da25d4643532890932cc03f7705fe19637e03fa8"
integrity sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==
dependencies:
"@babel/types" "^7.28.4"
"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1": "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.27.1":
version "7.27.1" version "7.27.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz#61dd8a8e61f7eb568268d1b5f129da3eee364bf9"
@@ -873,6 +909,19 @@
"@babel/types" "^7.28.2" "@babel/types" "^7.28.2"
debug "^4.3.1" debug "^4.3.1"
"@babel/traverse@^7.28.4":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.28.4.tgz#8d456101b96ab175d487249f60680221692b958b"
integrity sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==
dependencies:
"@babel/code-frame" "^7.27.1"
"@babel/generator" "^7.28.3"
"@babel/helper-globals" "^7.28.0"
"@babel/parser" "^7.28.4"
"@babel/template" "^7.27.2"
"@babel/types" "^7.28.4"
debug "^4.3.1"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.4.4": "@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.2", "@babel/types@^7.4.4":
version "7.28.2" version "7.28.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.2.tgz#da9db0856a9a88e0a13b019881d7513588cf712b"
@@ -881,6 +930,14 @@
"@babel/helper-string-parser" "^7.27.1" "@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1" "@babel/helper-validator-identifier" "^7.27.1"
"@babel/types@^7.28.4":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.28.4.tgz#0a4e618f4c60a7cd6c11cb2d48060e4dbe38ac3a"
integrity sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==
dependencies:
"@babel/helper-string-parser" "^7.27.1"
"@babel/helper-validator-identifier" "^7.27.1"
"@dnd-kit/accessibility@^3.1.1": "@dnd-kit/accessibility@^3.1.1":
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af" resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz#3b4202bd6bb370a0730f6734867785919beac6af"
@@ -1135,10 +1192,10 @@
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f" resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz#585624dc829cfb6e7c0aa6c3ca7d7e6daa87e34f"
integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ== integrity sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==
"@eslint-community/eslint-utils@^4.2.0": "@eslint-community/eslint-utils@^4.8.0":
version "4.7.0" version "4.9.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz#7308df158e064f0dd8b8fdb58aa14fa2a7f913b3"
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw== integrity sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==
dependencies: dependencies:
eslint-visitor-keys "^3.4.3" eslint-visitor-keys "^3.4.3"
@@ -1183,10 +1240,10 @@
minimatch "^3.1.2" minimatch "^3.1.2"
strip-json-comments "^3.1.1" strip-json-comments "^3.1.1"
"@eslint/js@9.34.0": "@eslint/js@9.35.0":
version "9.34.0" version "9.35.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.34.0.tgz#fc423168b9d10e08dea9088d083788ec6442996b" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.35.0.tgz#ffbc7e13cf1204db18552e9cd9d4a8e17c692d07"
integrity sha512-EoyvqQnBNsV1CWaEJ559rxXL4c8V92gxirbawSmVUOWXlsRxxQXl6LmCpdUblgxgSkDIqKnhzba2SjRTI/A5Rw== integrity sha512-30iXE9whjlILfWobBkNerJo+TXYsgVM5ERQwMcMKCHckHflCmf7wXDAHlARoWnh0s1U72WqlbeyE7iAcCzuCPw==
"@eslint/object-schema@^2.1.6": "@eslint/object-schema@^2.1.6":
version "2.1.6" version "2.1.6"
@@ -1249,6 +1306,14 @@
"@jridgewell/sourcemap-codec" "^1.5.0" "@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24" "@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/remapping@^2.3.5":
version "2.3.5"
resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
dependencies:
"@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.24"
"@jridgewell/resolve-uri@^3.1.0": "@jridgewell/resolve-uri@^3.1.0":
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6"
@@ -1914,13 +1979,6 @@ acorn@^8.0.0, acorn@^8.15.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
agent-base@^7.1.0, agent-base@^7.1.2: agent-base@^7.1.0, agent-base@^7.1.2:
version "7.1.4" version "7.1.4"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
@@ -3273,18 +3331,18 @@ eslint-visitor-keys@^4.2.1:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz#4cfea60fe7dd0ad8e816e1ed026c1d5251b512c1"
integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ== integrity sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==
eslint@9.34.0: eslint@9.35.0:
version "9.34.0" version "9.35.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.34.0.tgz#0ea1f2c1b5d1671db8f01aa6b8ce722302016f7b" resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.35.0.tgz#7a89054b7b9ee1dfd1b62035d8ce75547773f47e"
integrity sha512-RNCHRX5EwdrESy3Jc9o8ie8Bog+PeYvvSR8sDGoZxNFTvZ4dlxUB3WzQ3bQMztFrSRODGrLLj8g6OFuGY/aiQg== integrity sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg==
dependencies: dependencies:
"@eslint-community/eslint-utils" "^4.2.0" "@eslint-community/eslint-utils" "^4.8.0"
"@eslint-community/regexpp" "^4.12.1" "@eslint-community/regexpp" "^4.12.1"
"@eslint/config-array" "^0.21.0" "@eslint/config-array" "^0.21.0"
"@eslint/config-helpers" "^0.3.1" "@eslint/config-helpers" "^0.3.1"
"@eslint/core" "^0.15.2" "@eslint/core" "^0.15.2"
"@eslint/eslintrc" "^3.3.1" "@eslint/eslintrc" "^3.3.1"
"@eslint/js" "9.34.0" "@eslint/js" "9.35.0"
"@eslint/plugin-kit" "^0.3.5" "@eslint/plugin-kit" "^0.3.5"
"@humanfs/node" "^0.16.6" "@humanfs/node" "^0.16.6"
"@humanwhocodes/module-importer" "^1.0.1" "@humanwhocodes/module-importer" "^1.0.1"
@@ -3484,7 +3542,7 @@ fd-slicer@~1.1.0:
dependencies: dependencies:
pend "~1.2.0" pend "~1.2.0"
fdir@^6.4.4, fdir@^6.5.0: fdir@^6.5.0:
version "6.5.0" version "6.5.0"
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350"
integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==
@@ -3991,14 +4049,6 @@ http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1:
agent-base "^7.1.0" agent-base "^7.1.0"
debug "^4.3.4" debug "^4.3.4"
https-proxy-agent@5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
dependencies:
agent-base "6"
debug "4"
https-proxy-agent@^7.0.6: https-proxy-agent@^7.0.6:
version "7.0.6" version "7.0.6"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
@@ -5385,13 +5435,6 @@ mixin-object@^2.0.1:
for-in "^0.1.3" for-in "^0.1.3"
is-extendable "^0.1.1" is-extendable "^0.1.1"
mixpanel@^0.18.1:
version "0.18.1"
resolved "https://registry.yarnpkg.com/mixpanel/-/mixpanel-0.18.1.tgz#beefdce6c260165f4e2059c8cdd34c5c557162f7"
integrity sha512-YD1xfn6WP6ZLQ6Pmgh0KgdXhueJEsrodThMTsHzHMH0VbWa9ck8s+ynDtM83OSgt+yQ61W/SQNrH8Y4wIwocGg==
dependencies:
https-proxy-agent "5.0.0"
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3: mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
version "0.5.3" version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
@@ -5834,7 +5877,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^4.0.2, picomatch@^4.0.3: picomatch@^4.0.3:
version "4.0.3" version "4.0.3"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.3.tgz#796c76136d1eead715db1e7bad785dedd695a042"
integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==
@@ -6062,10 +6105,10 @@ qs@^6.14.0:
dependencies: dependencies:
side-channel "^1.1.0" side-channel "^1.1.0"
query-string@9.2.2: query-string@9.3.0:
version "9.2.2" version "9.3.0"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.2.2.tgz#a0104824edfdd2c1db2f18af71cef7abf6a3b20f" resolved "https://registry.yarnpkg.com/query-string/-/query-string-9.3.0.tgz#f2d60d6b4442cb445f374b5ff749b937b2cccd03"
integrity sha512-pDSIZJ9sFuOp6VnD+5IkakSVf+rICAuuU88Hcsr6AKL0QtxSIfVuKiVP2oahFI7tk3CRSexwV+Ya6MOoTxzg9g== integrity sha512-IQHOQ9aauHAApwAaUYifpEyLHv6fpVGVkMOnwPzcDScLjbLj8tLsILn6unSW79NafOw1llh8oK7Gd0VwmXBFmA==
dependencies: dependencies:
decode-uri-component "^0.4.1" decode-uri-component "^0.4.1"
filter-obj "^5.1.0" filter-obj "^5.1.0"
@@ -7143,13 +7186,13 @@ tiny-json-http@^7.0.2:
resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532" resolved "https://registry.yarnpkg.com/tiny-json-http/-/tiny-json-http-7.5.1.tgz#82efaa190c3edf6f5f2d906a9e88f792d38f8532"
integrity sha512-lB7qkBGpL3HR/8gidBu3MMfgfnDj2mlvK/eYXgSbO06gKphemLKGp/TgRTy/BKVD7nCbgIeCm41lMNayXO1f2w== integrity sha512-lB7qkBGpL3HR/8gidBu3MMfgfnDj2mlvK/eYXgSbO06gKphemLKGp/TgRTy/BKVD7nCbgIeCm41lMNayXO1f2w==
tinyglobby@^0.2.14: tinyglobby@^0.2.15:
version "0.2.14" version "0.2.15"
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.14.tgz#5280b0cf3f972b050e74ae88406c0a6a58f4079d" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ== integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies: dependencies:
fdir "^6.4.4" fdir "^6.5.0"
picomatch "^4.0.2" picomatch "^4.0.3"
to-regex-range@^5.0.1: to-regex-range@^5.0.1:
version "5.0.1" version "5.0.1"
@@ -7464,17 +7507,17 @@ vfile@^6.0.0:
"@types/unist" "^3.0.0" "@types/unist" "^3.0.0"
vfile-message "^4.0.0" vfile-message "^4.0.0"
vite@7.1.4: vite@7.1.5:
version "7.1.4" version "7.1.5"
resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.4.tgz#354944affb55e1aff0157406b74e0d0a3232df9a" resolved "https://registry.yarnpkg.com/vite/-/vite-7.1.5.tgz#4dbcb48c6313116689be540466fc80faa377be38"
integrity sha512-X5QFK4SGynAeeIt+A7ZWnApdUyHYm+pzv/8/A57LqSGcI88U6R6ipOs3uCesdc6yl7nl+zNO0t8LmqAdXcQihw== integrity sha512-4cKBO9wR75r0BeIWWWId9XK9Lj6La5X846Zw9dFfzMRw38IlTk2iCcUt6hsyiDRcPidc55ZParFYDXi0nXOeLQ==
dependencies: dependencies:
esbuild "^0.25.0" esbuild "^0.25.0"
fdir "^6.5.0" fdir "^6.5.0"
picomatch "^4.0.3" picomatch "^4.0.3"
postcss "^8.5.6" postcss "^8.5.6"
rollup "^4.43.0" rollup "^4.43.0"
tinyglobby "^0.2.14" tinyglobby "^0.2.15"
optionalDependencies: optionalDependencies:
fsevents "~2.3.3" fsevents "~2.3.3"