mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
251de1e42d | ||
|
|
edc91291b6 | ||
|
|
ac0ea64c07 | ||
|
|
9f7506a1b3 | ||
|
|
85cea66051 | ||
|
|
05c2df917c | ||
|
|
4ad2895eec | ||
|
|
7372e5313f | ||
|
|
637a54e01e | ||
|
|
04265eaec7 | ||
|
|
fa76821f7d | ||
|
|
09c6ce1d0b | ||
|
|
7fa9a265ef | ||
|
|
f201090b56 | ||
|
|
dda5b5fbcb | ||
|
|
a93c7ffee5 | ||
|
|
79a2d967e8 | ||
|
|
c264e11c26 | ||
|
|
9f8d189f47 |
25
README.md
25
README.md
@@ -16,6 +16,7 @@ same listing twice.
|
|||||||

|

|
||||||
[](https://github.com/orangecoding/fredy/actions/workflows/docker.yml)
|
[](https://github.com/orangecoding/fredy/actions/workflows/docker.yml)
|
||||||

|

|
||||||
|

|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -39,10 +40,13 @@ same listing twice.
|
|||||||
I maintain Fredy and other open-source projects in my free time.\
|
I maintain Fredy and other open-source projects in my free time.\
|
||||||
If you find it useful, consider supporting the project 💙
|
If you find it useful, consider supporting the project 💙
|
||||||
|
|
||||||
|
Fredy is proudly backed by the **JetBrains Open Source Support Program**.
|
||||||
[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains" width="120"/>](https://jb.gg/OpenSourceSupport)
|
[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jetbrains.svg" alt="JetBrains" width="120"/>](https://jb.gg/OpenSourceSupport)
|
||||||
|
|
||||||
Fredy is proudly supported by the **JetBrains Open Source Support
|
------------------------------------------------------------------------
|
||||||
Program**.
|
|
||||||
|
## 👨🏫 Demo
|
||||||
|
You can try out Fredy here: [Fredy Demo](https://fredy-demo.orange-coding.net/)
|
||||||
|
|
||||||
------------------------------------------------------------------------
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -50,10 +54,15 @@ Program**.
|
|||||||
|
|
||||||
### With Docker
|
### With Docker
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> 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 pull ghcr.io/orangecoding/fredy:master
|
docker run -d --name fredy \
|
||||||
docker create --name fredy -v /path/to/your/conf/:/conf -p 9998:9998 fredy/fredy
|
-v fredy_conf:/conf \
|
||||||
docker start fredy
|
-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 you’d allow me to collect some analytical data.
|
Fredy is completely free (and will always remain free). However, it would be a huge help if you’d 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**🤘
|
||||||
@@ -188,9 +197,7 @@ flowchart TD
|
|||||||
|
|
||||||
Thanks to everyone who has contributed!
|
Thanks to everyone who has contributed!
|
||||||
|
|
||||||
`<a href="https://github.com/orangecoding/fredy/graphs/contributors">`{=html}
|
<a href="https://github.com/orangecoding/fredy/graphs/contributors"><img src="https://contrib.rocks/image?repo=orangecoding/fredy" /></a>
|
||||||
`<img src="https://contrib.rocks/image?repo=orangecoding/fredy" />`{=html}
|
|
||||||
`</a>`{=html}
|
|
||||||
|
|
||||||
See the [Contributing
|
See the [Contributing
|
||||||
Guide](https://github.com/orangecoding/fredy/blob/master/CONTRIBUTING.md).
|
Guide](https://github.com/orangecoding/fredy/blob/master/CONTRIBUTING.md).
|
||||||
|
|||||||
0
conf/config.json
Executable file → Normal file
0
conf/config.json
Executable file → Normal file
@@ -102,15 +102,15 @@ class FredyRuntime {
|
|||||||
|
|
||||||
_filterBySimilarListings(listings) {
|
_filterBySimilarListings(listings) {
|
||||||
const filteredList = listings.filter((listing) => {
|
const filteredList = listings.filter((listing) => {
|
||||||
const similar = this._similarityCache.hasSimilarEntries(this._jobKey, listing.title);
|
const similar = this._similarityCache.hasSimilarEntries(listing.title, listing.address);
|
||||||
if (similar) {
|
if (similar) {
|
||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
console.debug(`Filtering similar entry for job with id ${this._jobKey} with title: `, listing.title);
|
console.debug(`Filtering similar entry for title: ${listing.title} and address ${listing.address}`);
|
||||||
/* eslint-enable no-console */
|
/* eslint-enable no-console */
|
||||||
}
|
}
|
||||||
return !similar;
|
return !similar;
|
||||||
});
|
});
|
||||||
filteredList.forEach((filter) => this._similarityCache.addCacheEntry(this._jobKey, filter.title));
|
filteredList.forEach((filter) => this._similarityCache.addCacheEntry(filter.title, listings.address));
|
||||||
return filteredList;
|
return filteredList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 */
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
import stringSimilarity from 'string-similarity';
|
|
||||||
//if the score is higher than this, it will be considered a match
|
|
||||||
const MAX_DICE_INDEX = 0.7;
|
|
||||||
export default (class SimilarityCacheEntry {
|
|
||||||
constructor(time) {
|
|
||||||
this.time = time;
|
|
||||||
this.values = [];
|
|
||||||
}
|
|
||||||
setCacheEntry = (entry) => {
|
|
||||||
this.values.push(entry);
|
|
||||||
};
|
|
||||||
getTime = () => {
|
|
||||||
return this.time;
|
|
||||||
};
|
|
||||||
hasSimilarEntries = (value) => {
|
|
||||||
if (this.values.length > 0) {
|
|
||||||
for (let i = 0; i < this.values.length; i++) {
|
|
||||||
const index = stringSimilarity.compareTwoStrings(value, this.values[i]);
|
|
||||||
if (index >= MAX_DICE_INDEX) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
@@ -1,40 +1,116 @@
|
|||||||
import SimilarityCacheEntry from './SimilarityCacheEntry.js';
|
import crypto from 'crypto';
|
||||||
import { config } from '../../utils.js';
|
|
||||||
//5 minutes
|
const retention = 60 * 60 * 1000;
|
||||||
let retention = 5 * 60 * 1000;
|
|
||||||
const intervalInMs = config.interval * 60 * 1000;
|
|
||||||
//an interval below 5 mins sounds crazy, but there are ppl out there doing crazy shit.
|
|
||||||
if (intervalInMs <= retention) {
|
|
||||||
retention = Math.floor(intervalInMs / 2);
|
|
||||||
}
|
|
||||||
//jobid -> SimilarityCacheEntry
|
|
||||||
const cache = {};
|
|
||||||
let intervalId;
|
|
||||||
/**
|
/**
|
||||||
* cleanup
|
* Internal cache storage.
|
||||||
|
* Maps a SHA-256 hash (string) to its expiry timestamp (number in ms).
|
||||||
|
* @type {Map<string, number>}
|
||||||
*/
|
*/
|
||||||
intervalId = setInterval(() => {
|
const entries = new Map();
|
||||||
const keysToBeRemoved = [];
|
|
||||||
|
/**
|
||||||
|
* Reference to the currently scheduled cleanup timer.
|
||||||
|
* @type {NodeJS.Timeout | null}
|
||||||
|
*/
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a SHA-256 hash from a list of input strings.
|
||||||
|
* Null or undefined values are ignored.
|
||||||
|
*
|
||||||
|
* @param {...(string|null|undefined)} strings - Input values to hash
|
||||||
|
* @returns {string} Hexadecimal hash
|
||||||
|
*/
|
||||||
|
function toHash(...strings) {
|
||||||
|
return crypto.createHash('sha256').update(strings.filter(Boolean).join('|')).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup expired cache entries and schedule the next cleanup run.
|
||||||
|
* This function is invoked automatically by scheduled timers.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function runCleanup() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
Object.keys(cache).forEach((key) => {
|
for (const [hash, expiry] of entries) {
|
||||||
if (cache[key].getTime() + retention < now) {
|
if (expiry <= now) entries.delete(hash);
|
||||||
keysToBeRemoved.push(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (keysToBeRemoved.length > 0) {
|
|
||||||
keysToBeRemoved.forEach((key) => delete cache[key]);
|
|
||||||
}
|
}
|
||||||
}, 10000);
|
scheduleNext();
|
||||||
export const addCacheEntry = (jobId, value) => {
|
}
|
||||||
cache[jobId] = cache[jobId] || new SimilarityCacheEntry(Date.now());
|
|
||||||
cache[jobId].setCacheEntry(value);
|
/**
|
||||||
};
|
* Find the soonest expiry timestamp among all cache entries
|
||||||
export const hasSimilarEntries = (jobId, value) => {
|
* and schedule a one-shot timer that will trigger at that time.
|
||||||
if (cache[jobId] == null) {
|
* Cancels any existing timer before scheduling a new one.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function scheduleNext() {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
let next = Infinity;
|
||||||
|
const now = Date.now();
|
||||||
|
for (const expiry of entries.values()) {
|
||||||
|
if (expiry > now && expiry < next) next = expiry;
|
||||||
|
}
|
||||||
|
if (next !== Infinity) {
|
||||||
|
timer = setTimeout(runCleanup, Math.max(0, next - now));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add or refresh a cache entry for the given title and address.
|
||||||
|
* The entry will automatically expire after the configured retention window.
|
||||||
|
*
|
||||||
|
* @param {string} title - The title used to build the cache key
|
||||||
|
* @param {string} address - The address used to build the cache key
|
||||||
|
*/
|
||||||
|
export function addCacheEntry(title, address) {
|
||||||
|
const hash = toHash(title, address);
|
||||||
|
const expiry = Date.now() + retention;
|
||||||
|
entries.set(hash, expiry);
|
||||||
|
scheduleNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a cache entry with the same title and address exists
|
||||||
|
* and is still valid (not expired).
|
||||||
|
*
|
||||||
|
* @param {string} title - The title used to build the cache key
|
||||||
|
* @param {string} address - The address used to build the cache key
|
||||||
|
* @returns {boolean} True if a valid cache entry exists, false otherwise
|
||||||
|
*/
|
||||||
|
export function hasSimilarEntries(title, address) {
|
||||||
|
const hash = toHash(title, address);
|
||||||
|
const expiry = entries.get(hash);
|
||||||
|
if (expiry == null) return false;
|
||||||
|
if (expiry <= Date.now()) {
|
||||||
|
entries.delete(hash);
|
||||||
|
scheduleNext();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return cache[jobId].hasSimilarEntries(value);
|
return true;
|
||||||
};
|
}
|
||||||
export const stopCacheCleanup = () => {
|
|
||||||
clearInterval(intervalId);
|
/**
|
||||||
};
|
* Stop any scheduled cleanup timers and prevent further automatic cleanup.
|
||||||
|
* Entries that are already in the cache will remain until removed manually
|
||||||
|
* or until cleanup is started again by adding new entries.
|
||||||
|
*/
|
||||||
|
export function stopCacheCleanup() {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this is only for test purposes
|
||||||
|
*/
|
||||||
|
export function invalidateAllForTest() {
|
||||||
|
for (const key of entries.keys()) {
|
||||||
|
entries.set(key, 0);
|
||||||
|
}
|
||||||
|
runCleanup();
|
||||||
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
25
lib/utils.js
25
lib/utils.js
@@ -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;
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fredy",
|
"name": "fredy",
|
||||||
"version": "11.5.1",
|
"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,17 +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",
|
||||||
"string-similarity": "^4.0.4",
|
"vite": "7.1.5",
|
||||||
"vite": "7.1.4",
|
|
||||||
"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",
|
||||||
|
|||||||
@@ -1,40 +1,30 @@
|
|||||||
import SimilarityCacheEntry from '../../lib/services/similarity-check/SimilarityCacheEntry.js';
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
|
import * as similarityCache from '../../lib/services/similarity-check/similarityCache.js';
|
||||||
|
|
||||||
describe('similarityCheck', () => {
|
describe('similarityCheck', () => {
|
||||||
describe('#similarityCheck()', () => {
|
it('should return true on duplicate', () => {
|
||||||
it('should be false', () => {
|
similarityCache.addCacheEntry('Hello World', 'Test');
|
||||||
const check = new SimilarityCacheEntry(0);
|
expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.true;
|
||||||
check.setCacheEntry('Hallo');
|
});
|
||||||
expect(check.hasSimilarEntries('Welt')).to.be.false;
|
|
||||||
});
|
it('should return true even if one value is null', () => {
|
||||||
it('should be true', () => {
|
similarityCache.addCacheEntry('Hello World', null);
|
||||||
const check = new SimilarityCacheEntry(0);
|
expect(similarityCache.hasSimilarEntries('Hello World', null)).to.be.true;
|
||||||
check.setCacheEntry('Hallo');
|
});
|
||||||
expect(check.hasSimilarEntries('hallo')).to.be.true;
|
|
||||||
});
|
it('should return true even if one value is an obj', () => {
|
||||||
it('should be true', () => {
|
similarityCache.addCacheEntry('Hello World', [{ TR: 'OLOLO' }]);
|
||||||
const check = new SimilarityCacheEntry(0);
|
expect(similarityCache.hasSimilarEntries('Hello World', [{ TR: 'OLOLO' }])).to.be.true;
|
||||||
check.setCacheEntry('Selling an incredible house in san francisco');
|
});
|
||||||
expect(check.hasSimilarEntries('incredible house in san francisco for sale')).to.be.true;
|
|
||||||
});
|
it('should return false when no duplicate', () => {
|
||||||
it('should be true', () => {
|
similarityCache.addCacheEntry('Hello World__', 'Test');
|
||||||
const check = new SimilarityCacheEntry(0);
|
expect(similarityCache.hasSimilarEntries('Hello World___', 'Test')).to.be.false;
|
||||||
check.setCacheEntry('a');
|
});
|
||||||
check.setCacheEntry('b');
|
|
||||||
check.setCacheEntry('c');
|
it('should return false when no duplicate', () => {
|
||||||
check.setCacheEntry('d');
|
expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.true;
|
||||||
expect(check.hasSimilarEntries('b')).to.be.true;
|
similarityCache.invalidateAllForTest();
|
||||||
});
|
expect(similarityCache.hasSimilarEntries('Hello World', 'Test')).to.be.false;
|
||||||
it('should be false', () => {
|
|
||||||
const check = new SimilarityCacheEntry(0);
|
|
||||||
check.setCacheEntry(
|
|
||||||
'The index is known by several other names, especially Sørensen–Dice index,[3] Sørensen index and Dice\'s coefficient. Other variations include the "similarity coefficient" or "index", such as Dice similarity coefficient (DSC). Common alternate spellings for Sørensen are Sorenson, Soerenson and Sörenson, and all three can also be seen with the –sen ending.',
|
|
||||||
);
|
|
||||||
check.setCacheEntry(
|
|
||||||
'where |X| and |Y| are the cardinalities of the two sets (i.e. the number of elements in each set). The Sørensen index equals twice the number of elements common to both sets divided by the sum of the number of elements in each set.',
|
|
||||||
);
|
|
||||||
expect(check.hasSimilarEntries('unrelated text')).to.be.false;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -43,7 +43,8 @@ export default function TrackingModal() {
|
|||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
However, it would be a huge help if you’d allow me to collect some analytical data. Wait, before you click
|
However, it would be a huge help if you’d 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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
164
yarn.lock
164
yarn.lock
@@ -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"
|
||||||
@@ -6899,11 +6942,6 @@ string-argv@^0.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.2.tgz#2b6d0ef24b656274d957d54e0a4bbf6153dc02b6"
|
||||||
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
integrity sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==
|
||||||
|
|
||||||
string-similarity@^4.0.4:
|
|
||||||
version "4.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b"
|
|
||||||
integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ==
|
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0":
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
@@ -7148,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"
|
||||||
@@ -7469,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"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user