versacompiler 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,316 @@
1
+ import { readdir, rm, stat, unlink } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import process, { env } from 'node:process';
4
+ import * as chokidar from 'chokidar';
5
+ import { getOutputPath, initCompile, normalizeRuta } from '../compiler/compile.js';
6
+ import { promptUser } from '../utils/promptUser.js';
7
+ import { emitirCambios } from './browserSync.js';
8
+ import { logger } from './logger.js';
9
+ // Lazy loading para chalk
10
+ let chalk;
11
+ async function loadChalk() {
12
+ if (!chalk) {
13
+ chalk = (await import('chalk')).default;
14
+ }
15
+ return chalk;
16
+ }
17
+ class WatchDebouncer {
18
+ pendingChanges = new Map();
19
+ debounceTimer = null;
20
+ DEBOUNCE_DELAY = 300; // 300ms debounce
21
+ BATCH_SIZE = 10; // Máximo archivos por batch
22
+ isProcessing = false;
23
+ browserSyncInstance = null; // ✨ Almacenar referencia a browserSync
24
+ /**
25
+ * Establece la instancia de browserSync
26
+ */
27
+ setBrowserSyncInstance(bs) {
28
+ this.browserSyncInstance = bs;
29
+ }
30
+ /**
31
+ * Añade un cambio al sistema de debouncing
32
+ */
33
+ addChange(filePath, action, extensionAction) {
34
+ // Normalizar ruta para evitar duplicados
35
+ const normalizedPath = normalizeRuta(filePath);
36
+ // Agregar o actualizar el cambio pendiente
37
+ this.pendingChanges.set(normalizedPath, {
38
+ filePath: normalizedPath,
39
+ action,
40
+ timestamp: Date.now(),
41
+ extensionAction,
42
+ });
43
+ // Reiniciar el timer de debounce
44
+ this.resetDebounceTimer();
45
+ }
46
+ /**
47
+ * Reinicia el timer de debounce
48
+ */
49
+ resetDebounceTimer() {
50
+ if (this.debounceTimer) {
51
+ clearTimeout(this.debounceTimer);
52
+ }
53
+ this.debounceTimer = setTimeout(() => {
54
+ this.processPendingChanges();
55
+ }, this.DEBOUNCE_DELAY);
56
+ }
57
+ /**
58
+ * Procesa todos los cambios pendientes en batch
59
+ */
60
+ async processPendingChanges() {
61
+ if (this.isProcessing || this.pendingChanges.size === 0) {
62
+ return;
63
+ }
64
+ this.isProcessing = true;
65
+ const changes = Array.from(this.pendingChanges.values());
66
+ this.pendingChanges.clear();
67
+ try {
68
+ // Agrupar por tipo de acción para optimización
69
+ const deleteChanges = changes.filter(c => c.action === 'unlink');
70
+ const compileChanges = changes.filter(c => c.action === 'add' || c.action === 'change');
71
+ // Procesar eliminaciones primero
72
+ if (deleteChanges.length > 0) {
73
+ await this.processDeleteChanges(deleteChanges);
74
+ }
75
+ // Procesar compilaciones en batches
76
+ if (compileChanges.length > 0) {
77
+ await this.processCompileChanges(compileChanges);
78
+ }
79
+ }
80
+ catch (error) {
81
+ const chalkInstance = await loadChalk();
82
+ logger.error(chalkInstance.red(`🚩 Error procesando cambios en batch: ${error instanceof Error ? error.message : String(error)}`));
83
+ }
84
+ finally {
85
+ this.isProcessing = false;
86
+ // Si hay más cambios pendientes, procesarlos
87
+ if (this.pendingChanges.size > 0) {
88
+ this.resetDebounceTimer();
89
+ }
90
+ }
91
+ }
92
+ /**
93
+ * Procesa cambios de eliminación
94
+ */
95
+ async processDeleteChanges(deleteChanges) {
96
+ for (const change of deleteChanges) {
97
+ logger.info(`\n🗑️ eliminando archivo: ${change.filePath}`);
98
+ const result = await deleteFile(getOutputPath(change.filePath));
99
+ if (result) {
100
+ logger.info(`Archivo eliminado: ${change.filePath}`);
101
+ emitirCambios(this.browserSyncInstance, 'reloadFull', change.filePath);
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Procesa cambios de compilación en paralelo con límite de concurrencia
107
+ */
108
+ async processCompileChanges(compileChanges) {
109
+ const chalkInstance = await loadChalk();
110
+ // Procesar en batches para evitar sobrecarga
111
+ for (let i = 0; i < compileChanges.length; i += this.BATCH_SIZE) {
112
+ const batch = compileChanges.slice(i, i + this.BATCH_SIZE);
113
+ // Mostrar información del batch
114
+ if (batch.length > 1) {
115
+ logger.info(chalkInstance.cyan(`📦 Procesando batch de ${batch.length} archivos (${i + 1}-${Math.min(i + this.BATCH_SIZE, compileChanges.length)} de ${compileChanges.length})`));
116
+ }
117
+ // Procesar batch en paralelo con límite de concurrencia
118
+ const promises = batch.map(change => this.compileFile(change));
119
+ await Promise.allSettled(promises);
120
+ }
121
+ // Emitir cambio global al final del batch
122
+ if (compileChanges.length > 1) {
123
+ logger.info(chalkInstance.green(`✅ Batch completado: ${compileChanges.length} archivos procesados`));
124
+ }
125
+ }
126
+ /**
127
+ * Compila un archivo individual
128
+ */
129
+ async compileFile(change) {
130
+ try {
131
+ const result = await initCompile(change.filePath, true, 'watch');
132
+ if (result.success) {
133
+ let accion = result.action || change.extensionAction;
134
+ accion =
135
+ accion === 'extension' ? change.extensionAction : accion;
136
+ emitirCambios(this.browserSyncInstance, accion || 'reloadFull', result.output);
137
+ }
138
+ }
139
+ catch (error) {
140
+ const chalkInstance = await loadChalk();
141
+ logger.error(chalkInstance.red(`🚩 Error compilando ${change.filePath}: ${error instanceof Error ? error.message : String(error)}`));
142
+ }
143
+ }
144
+ /**
145
+ * Obtiene estadísticas del debouncer
146
+ */
147
+ getStats() {
148
+ return {
149
+ pendingChanges: this.pendingChanges.size,
150
+ isProcessing: this.isProcessing,
151
+ hasTimer: this.debounceTimer !== null,
152
+ };
153
+ }
154
+ }
155
+ // Instancia global del debouncer
156
+ const watchDebouncer = new WatchDebouncer();
157
+ // const cacheImportMap = new Map<string, string[]>();
158
+ // const cacheComponentMap = new Map<string, string[]>();
159
+ export async function cleanOutputDir(outputDir, primerInteraccion = true) {
160
+ try {
161
+ if (!outputDir) {
162
+ throw new Error('El directorio de salida no está definido');
163
+ }
164
+ if (primerInteraccion) {
165
+ const stats = await stat(outputDir).catch(() => null);
166
+ if (!stats || !stats.isDirectory()) {
167
+ logger.error(`🚩 El directorio de salida no existe o no es un directorio: ${outputDir}`);
168
+ return;
169
+ }
170
+ try {
171
+ if (env.yes === 'false') {
172
+ const chalkInstance = await loadChalk();
173
+ const answer = await promptUser('\n\n¿Estás seguro deseas limpiar la carpeta ' +
174
+ chalkInstance.yellow(outputDir) +
175
+ '? (s / N) : ');
176
+ if (answer.toLowerCase() !== 's') {
177
+ logger.info('🛑 Compilación cancelada por el usuario.');
178
+ process.exit(0);
179
+ }
180
+ }
181
+ }
182
+ catch (error) {
183
+ logger.error(`Error en la entrada del usuario: ${error}`);
184
+ process.exit(1);
185
+ }
186
+ }
187
+ const chalkInstance = await loadChalk();
188
+ logger.info(`🗑️ Limpiando directorio de salida: ${chalkInstance.yellow(outputDir)}\n`);
189
+ const items = await readdir(outputDir);
190
+ await Promise.all(items.map(async (item) => {
191
+ const itemPath = path.join(outputDir, item);
192
+ const itemStat = await stat(itemPath);
193
+ if (itemStat.isDirectory()) {
194
+ await rm(itemPath, { recursive: true });
195
+ }
196
+ else {
197
+ await unlink(itemPath);
198
+ }
199
+ }));
200
+ logger.info(`✅ Directorio limpiado: ${outputDir}`);
201
+ }
202
+ catch (error) {
203
+ logger.error(`🚩 Error al limpiar directorio de salida: ${error instanceof Error ? error.message : String(error)}`);
204
+ }
205
+ }
206
+ async function deleteFile(filePath) {
207
+ try {
208
+ await unlink(filePath);
209
+ return true;
210
+ }
211
+ catch (error) {
212
+ logger.error(`🚩 Error eliminando archivo ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
213
+ return false;
214
+ }
215
+ }
216
+ function getAction(ruta, extendsionWatch) {
217
+ const action = extendsionWatch
218
+ .filter((item) => item !== undefined)
219
+ .find(item => item.ext === ruta.split('.').pop())?.action;
220
+ return action || 'reloadFull';
221
+ }
222
+ export async function initChokidar(bs) {
223
+ try {
224
+ if (!env.PATH_SOURCE) {
225
+ logger.error('Error: La variable de entorno PATH_SOURCE no está definida.');
226
+ process.exit(1);
227
+ }
228
+ const watchJS = `${env.PATH_SOURCE}/**/*.js`;
229
+ const watchVue = `${env.PATH_SOURCE}/**/*.vue`;
230
+ const watchTS = `${env.PATH_SOURCE}/**/*.ts`;
231
+ const watchCJS = `${env.PATH_SOURCE}/**/*.cjs`;
232
+ const watchMJS = `${env.PATH_SOURCE}/**/*.mjs`;
233
+ //TODO: agregar watch para CSS
234
+ const watchAditional = JSON.parse(env.aditionalWatch || '[]');
235
+ let fileWatch = [
236
+ watchJS,
237
+ watchVue,
238
+ watchTS,
239
+ watchCJS,
240
+ watchMJS,
241
+ ...watchAditional,
242
+ ];
243
+ //extraer sólo las extesniones de fileWatch
244
+ const accionExtension = {
245
+ vue: 'HRMVue',
246
+ js: 'HRMHelper',
247
+ ts: 'HRMHelper',
248
+ cjs: 'HRMHelper',
249
+ mjs: 'HRMHelper',
250
+ };
251
+ const extendsionWatch = fileWatch.map(item => {
252
+ const ext = item.split('.').pop();
253
+ if (ext) {
254
+ return {
255
+ ext,
256
+ action: accionExtension[ext] ||
257
+ 'reloadFull',
258
+ };
259
+ }
260
+ });
261
+ if (extendsionWatch.length === 0 || extendsionWatch[0] === undefined) {
262
+ throw new Error('No se encontraron extensiones para observar');
263
+ }
264
+ const regExtExtension = new RegExp(`\\.(?!${extendsionWatch
265
+ .filter(item => item !== undefined)
266
+ .map(item => item.ext)
267
+ .join('$|')}$).+$`);
268
+ fileWatch = fileWatch.map(item => item.replace(/\/\*\*\//g, '/'));
269
+ const directories = new Map();
270
+ fileWatch.forEach(item => {
271
+ const dir = item.substring(0, item.lastIndexOf('/'));
272
+ if (!directories.has(dir)) {
273
+ directories.set(dir, []);
274
+ }
275
+ directories.get(dir).push(item);
276
+ });
277
+ const DirWatch = Array.from(directories.keys());
278
+ const watcher = chokidar.watch(DirWatch, {
279
+ persistent: true,
280
+ ignoreInitial: true,
281
+ ignored: regExtExtension,
282
+ });
283
+ watcher.on('ready', async () => {
284
+ const chalkInstance = await loadChalk();
285
+ logger.info(chalkInstance.green(`👀 : Listo para observar \n${fileWatch
286
+ .map((item) => `${item}`)
287
+ .join('\n')}\n`));
288
+ });
289
+ // ✨ CONFIGURAR: Establecer la instancia de browserSync en el debouncer
290
+ watchDebouncer.setBrowserSyncInstance(bs);
291
+ // ✨ OPTIMIZADO: Evento cuando se añade un archivo - Con debouncing
292
+ watcher.on('add', async (ruta) => {
293
+ const action = getAction(ruta, extendsionWatch.filter((item) => item !== undefined));
294
+ // Usar sistema de debouncing en lugar de compilación inmediata
295
+ watchDebouncer.addChange(ruta, 'add', action);
296
+ });
297
+ // ✨ OPTIMIZADO: Evento cuando se modifica un archivo - Con debouncing
298
+ watcher.on('change', async (ruta) => {
299
+ const action = getAction(ruta, extendsionWatch.filter((item) => item !== undefined));
300
+ // Usar sistema de debouncing en lugar de compilación inmediata
301
+ watchDebouncer.addChange(ruta, 'change', action);
302
+ });
303
+ // ✨ OPTIMIZADO: Evento cuando se elimina un archivo - Con debouncing
304
+ watcher.on('unlink', async (ruta) => {
305
+ const action = getAction(ruta, extendsionWatch.filter((item) => item !== undefined));
306
+ // Usar sistema de debouncing para eliminaciones también
307
+ watchDebouncer.addChange(ruta, 'unlink', action);
308
+ });
309
+ return watcher;
310
+ }
311
+ catch (error) {
312
+ logger.error(`🚩 :Error al iniciar watch: ${error instanceof Error ? error.message : String(error)}`);
313
+ process.exit(1);
314
+ }
315
+ }
316
+ //# sourceMappingURL=file-watcher.js.map
@@ -1,3 +1,4 @@
1
+ import * as process from 'node:process';
1
2
  class Logger {
2
3
  constructor() {
3
4
  // Bind console methods
@@ -1,4 +1,5 @@
1
1
  import { normalize, relative, resolve } from 'node:path';
2
+ import * as process from 'node:process';
2
3
  import { env } from 'node:process';
3
4
  import { pathToFileURL } from 'node:url';
4
5
  import { logger } from './logger.js';
@@ -1,8 +1,8 @@
1
1
  // Opción con librería '/node_modules/resolve/index.js' (npm install resolve)
2
2
  import fs, { readFileSync } from 'node:fs';
3
3
  import { dirname, join, relative } from 'node:path';
4
- import { env } from 'node:process';
5
- // import pkg from '/node_modules/enhanced-resolve/index.cjs';
4
+ import { cwd, env } from 'node:process';
5
+ // import pkg from '/node_modules/enhanced-resolve/lib/index.js';
6
6
  // import resolve from '/node_modules/resolve/index.js';
7
7
  import { logger } from '../servicios/logger.js';
8
8
  // Lista de módulos que deben ser excluidos de la resolución automática de rutas
@@ -58,7 +58,7 @@ const EXCLUDED_MODULES = new Set([
58
58
  // return null;
59
59
  // }
60
60
  // }
61
- // Opción con '/node_modules/enhanced-resolve/index.cjs' (webpack's resolver)
61
+ // Opción con '/node_modules/enhanced-resolve/lib/index.js' (webpack's resolver)
62
62
  // npm install enhanced-resolve
63
63
  // const { ResolverFactory } = pkg;
64
64
  // const resolver = ResolverFactory.createResolver({
@@ -128,9 +128,6 @@ function findOptimalESMVersion(moduleDir, entryPoint) {
128
128
  for (const pattern of priorityPatterns) {
129
129
  if (files.includes(pattern)) {
130
130
  const optimizedPath = join(dir, pattern).replace(/\\/g, '/');
131
- if (env.VERBOSE === 'true') {
132
- logger.info(`Versión optimizada encontrada: ${optimizedPath}`);
133
- }
134
131
  return optimizedPath;
135
132
  }
136
133
  } // Buscar archivos que contengan patrones ESM/browser dinámicamente
@@ -154,9 +151,6 @@ function findOptimalESMVersion(moduleDir, entryPoint) {
154
151
  !file.toLowerCase().includes('.min.'));
155
152
  if (devFiles.length > 0 && devFiles[0]) {
156
153
  const optimizedPath = join(dir, devFiles[0]).replace(/\\/g, '/');
157
- if (env.VERBOSE === 'true') {
158
- logger.info(`Versión ESM-Browser dev encontrada: ${optimizedPath}`);
159
- }
160
154
  return optimizedPath;
161
155
  }
162
156
  const prodFiles = esmBrowserCombined.filter(file => file.toLowerCase().includes('.prod.'));
@@ -260,7 +254,7 @@ function findOptimalESMVersion(moduleDir, entryPoint) {
260
254
  // Función mejorada para detectar automáticamente entry points browser-compatible
261
255
  function simpleESMResolver(moduleName) {
262
256
  try {
263
- const nodeModulesPath = join(process.cwd(), 'node_modules', moduleName);
257
+ const nodeModulesPath = join(cwd(), 'node_modules', moduleName);
264
258
  let packagePath;
265
259
  let packageJson;
266
260
  try {
@@ -370,7 +364,8 @@ function simpleESMResolver(moduleName) {
370
364
  if (env.VERBOSE === 'true')
371
365
  logger.info(`Módulo ${moduleName} usa imports privados:`, privateImports.map(m => m[1]));
372
366
  // Si usa imports privados, asegurarnos de que estén disponibles
373
- for (const [, importPath] of privateImports) {
367
+ for (const match of privateImports) {
368
+ const [, importPath] = match;
374
369
  if (!importMap.has(importPath)) {
375
370
  if (env.VERBOSE === 'true')
376
371
  logger.warn(`Import privado no resuelto: ${importPath} en ${moduleName}`);
@@ -381,8 +376,6 @@ function simpleESMResolver(moduleName) {
381
376
  }
382
377
  // Verificar que el archivo existe
383
378
  if (!fs.existsSync(finalPath)) {
384
- if (env.VERBOSE === 'true')
385
- logger.warn(`⚠️ Archivo no existe: ${finalPath}, buscando alternativas...`);
386
379
  // Intentar alternativas comunes
387
380
  const alternatives = [
388
381
  entryPoint,
@@ -422,7 +415,7 @@ function getNodeModulesRelativePath(fullPath, _fromFile) {
422
415
  return '/' + relativePath;
423
416
  }
424
417
  // Para rutas que no están en node_modules, convertir a ruta absoluta desde la raíz
425
- let rel = relative(process.cwd(), fullPath).replace(/\\/g, '/');
418
+ let rel = relative(cwd(), fullPath).replace(/\\/g, '/');
426
419
  if (!rel)
427
420
  rel = '.';
428
421
  // Convertir a ruta absoluta desde la raíz
@@ -434,8 +427,6 @@ function getNodeModulesRelativePath(fullPath, _fromFile) {
434
427
  export function getModulePath(moduleName, fromFile) {
435
428
  // Verificar si el módulo está en la lista de excluidos
436
429
  if (EXCLUDED_MODULES.has(moduleName)) {
437
- if (env.VERBOSE === 'true')
438
- logger.info(`Módulo ${moduleName} está en la lista de excluidos, manteniendo importación original`);
439
430
  return null; // Retornar null para mantener la importación original
440
431
  }
441
432
  return getNodeModulesRelativePath(simpleESMResolver(moduleName), fromFile);
@@ -444,8 +435,6 @@ export function getModulePath(moduleName, fromFile) {
444
435
  export function getModuleSubPath(moduleName, fromFile) {
445
436
  // Verificar si el módulo está en la lista de excluidos
446
437
  if (EXCLUDED_MODULES.has(moduleName)) {
447
- if (env.VERBOSE === 'true')
448
- logger.info(`Módulo ${moduleName} está en la lista de excluidos, manteniendo importación original`);
449
438
  return null; // Retornar null para mantener la importación original
450
439
  } // Si contiene '/', es un subpath
451
440
  if (moduleName.includes('/')) {
@@ -456,7 +445,7 @@ export function getModuleSubPath(moduleName, fromFile) {
456
445
  return null;
457
446
  }
458
447
  try {
459
- const nodeModulesPath = join(process.cwd(), 'node_modules', packageName);
448
+ const nodeModulesPath = join(cwd(), 'node_modules', packageName);
460
449
  const packagePath = join(nodeModulesPath, 'package.json');
461
450
  if (!fs.existsSync(packagePath)) {
462
451
  return null;
@@ -1,4 +1,4 @@
1
- import { stdin as input, stdout as output } from 'node:process';
1
+ import process, { stdin as input, stdout as output } from 'node:process';
2
2
  import * as readline from 'node:readline/promises';
3
3
  import { logger } from '../servicios/logger.js';
4
4
  export async function promptUser(question, timeout = 30000) {
@@ -1,19 +1,38 @@
1
- import { createRequire } from 'node:module';
2
- import path from 'node:path';
1
+ import * as path from 'node:path';
2
+ import * as process from 'node:process';
3
3
  import findRoot from 'find-root';
4
4
  import fs from 'fs-extra';
5
+ // Función helper para resolver módulos sin createRequire
6
+ function resolveModule(moduleName, paths) {
7
+ for (const searchPath of paths) {
8
+ try {
9
+ const nodeModulesPath = path.join(searchPath, 'node_modules', moduleName);
10
+ if (fs.existsSync(nodeModulesPath)) {
11
+ return nodeModulesPath;
12
+ }
13
+ }
14
+ catch {
15
+ // Continuar con el siguiente path
16
+ }
17
+ }
18
+ throw new Error(`Cannot resolve module ${moduleName} from paths: ${paths.join(', ')}`);
19
+ }
5
20
  export function resolveBin(moduleName, { executable = moduleName, paths = [process.cwd()], } = {}) {
6
21
  let rootDir;
7
22
  try {
8
- const customRequire = createRequire(__filename);
9
- const resolved = customRequire.resolve(moduleName, { paths });
23
+ const resolved = resolveModule(moduleName, paths);
10
24
  rootDir = findRoot(resolved);
11
25
  }
12
- catch (e) {
13
- const modJson = require.resolve(`${moduleName}/package.json`, {
14
- paths,
15
- });
16
- rootDir = path.dirname(modJson);
26
+ catch {
27
+ // Intentar resolver package.json directamente
28
+ const basePath = paths[0] || process.cwd();
29
+ const packagePath = path.join(basePath, 'node_modules', moduleName, 'package.json');
30
+ if (fs.existsSync(packagePath)) {
31
+ rootDir = path.dirname(packagePath);
32
+ }
33
+ else {
34
+ throw new Error(`Cannot resolve module ${moduleName}`);
35
+ }
17
36
  }
18
37
  const packageJsonPath = path.join(rootDir, 'package.json');
19
38
  const packageJson = fs.readJsonSync(packageJsonPath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versacompiler",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Una herramienta para compilar y minificar archivos .vue, .js y .ts para proyectos de Vue 3 con soporte para TypeScript.",
5
5
  "main": "dist/main.js",
6
6
  "bin": {
@@ -19,7 +19,7 @@
19
19
  "dev": "tsx --watch src/main.ts --watch --verbose --tailwind",
20
20
  "compile": "tsx src/main.ts --all",
21
21
  "test": "jest --config jest.config.js",
22
- "build": "tsx src/main.ts --all -t --clean -y",
22
+ "build": "tsx src/main.ts --all -t --cc --co -y --verbose",
23
23
  "lint": "oxlint --fix --config .oxlintrc.json",
24
24
  "lint:eslint": "eslint --ext .js,.ts,.vue src/ --fix",
25
25
  "perf": "scripts\\run-performance.bat",
@@ -69,7 +69,7 @@
69
69
  "@types/fs-extra": "^11.0.4",
70
70
  "@types/jest": "^29.5.14",
71
71
  "@types/mocha": "^10.0.10",
72
- "@types/node": "^22.15.30",
72
+ "@types/node": "^24.0.0",
73
73
  "@types/resolve": "^1.20.6",
74
74
  "@types/yargs": "^17.0.33",
75
75
  "@typescript-eslint/eslint-plugin": "^8.34.0",
@@ -80,15 +80,15 @@
80
80
  "eslint": "^9.28.0",
81
81
  "eslint-import-resolver-typescript": "^4.4.3",
82
82
  "eslint-plugin-import": "^2.31.0",
83
- "eslint-plugin-oxlint": "^0.17.0",
83
+ "eslint-plugin-oxlint": "^1.0.0",
84
84
  "eslint-plugin-promise": "^7.2.1",
85
85
  "eslint-plugin-unicorn": "^59.0.1",
86
86
  "eslint-plugin-vue": "^10.2.0",
87
87
  "happy-dom": "^17.6.3",
88
88
  "jest": "^29.7.0",
89
- "jest-environment-jsdom": "30.0.0-beta.3",
90
- "jest-environment-node": "30.0.0-beta.3",
91
- "oxlint": "^0.17.0",
89
+ "jest-environment-jsdom": "30.0.0",
90
+ "jest-environment-node": "30.0.0",
91
+ "oxlint": "^1.0.0",
92
92
  "prettier": "3.5.3",
93
93
  "rimraf": "^6.0.1",
94
94
  "sweetalert2": "^11.22.0",