vtex-css-sanitizer-cli 1.0.2 → 1.0.5

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,24 @@
1
+ appId: com.elilab.vtex-css-sanitizer
2
+ productName: VTEX CSS Sanitizer
3
+ directories:
4
+ buildResources: build
5
+ output: release
6
+ files:
7
+ - '!**/.vscode/*'
8
+ - '!src/*'
9
+ - '!electron.vite.config.*'
10
+ - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
11
+ - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
12
+ linux:
13
+ target:
14
+ - AppImage
15
+ category: Development
16
+ icon: build/icon.png
17
+ win:
18
+ target:
19
+ - nsis
20
+ icon: build/icon.png
21
+ nsis:
22
+ oneClick: false
23
+ allowToChangeInstallationDirectory: true
24
+ shortcutName: VTEX CSS Sanitizer
@@ -0,0 +1,21 @@
1
+ import { resolve } from 'path'
2
+ import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
3
+ import react from '@vitejs/plugin-react'
4
+ import tailwindcss from '@tailwindcss/vite'
5
+
6
+ export default defineConfig({
7
+ main: {
8
+ plugins: [externalizeDepsPlugin()]
9
+ },
10
+ preload: {
11
+ plugins: [externalizeDepsPlugin()]
12
+ },
13
+ renderer: {
14
+ resolve: {
15
+ alias: {
16
+ '@renderer': resolve('src/renderer/src')
17
+ }
18
+ },
19
+ plugins: [react(), tailwindcss()]
20
+ }
21
+ })
@@ -0,0 +1,536 @@
1
+ "use strict";
2
+ const electron = require("electron");
3
+ const path = require("path");
4
+ const fs = require("fs/promises");
5
+ const postcss = require("postcss");
6
+ const glob = require("glob");
7
+ const jsoncParser = require("jsonc-parser");
8
+ const is = {
9
+ dev: !electron.app.isPackaged
10
+ };
11
+ const platform = {
12
+ isWindows: process.platform === "win32",
13
+ isMacOS: process.platform === "darwin",
14
+ isLinux: process.platform === "linux"
15
+ };
16
+ const electronApp = {
17
+ setAppUserModelId(id) {
18
+ if (platform.isWindows)
19
+ electron.app.setAppUserModelId(is.dev ? process.execPath : id);
20
+ },
21
+ setAutoLaunch(auto) {
22
+ if (platform.isLinux)
23
+ return false;
24
+ const isOpenAtLogin = () => {
25
+ return electron.app.getLoginItemSettings().openAtLogin;
26
+ };
27
+ if (isOpenAtLogin() !== auto) {
28
+ electron.app.setLoginItemSettings({
29
+ openAtLogin: auto,
30
+ path: process.execPath
31
+ });
32
+ return isOpenAtLogin() === auto;
33
+ } else {
34
+ return true;
35
+ }
36
+ },
37
+ skipProxy() {
38
+ return electron.session.defaultSession.setProxy({ mode: "direct" });
39
+ }
40
+ };
41
+ const optimizer = {
42
+ watchWindowShortcuts(window, shortcutOptions) {
43
+ if (!window)
44
+ return;
45
+ const { webContents } = window;
46
+ const { escToCloseWindow = false, zoom = false } = shortcutOptions || {};
47
+ webContents.on("before-input-event", (event, input) => {
48
+ if (input.type === "keyDown") {
49
+ if (!is.dev) {
50
+ if (input.code === "KeyR" && (input.control || input.meta))
51
+ event.preventDefault();
52
+ } else {
53
+ if (input.code === "F12") {
54
+ if (webContents.isDevToolsOpened()) {
55
+ webContents.closeDevTools();
56
+ } else {
57
+ webContents.openDevTools({ mode: "undocked" });
58
+ console.log("Open dev tool...");
59
+ }
60
+ }
61
+ }
62
+ if (escToCloseWindow) {
63
+ if (input.code === "Escape" && input.key !== "Process") {
64
+ window.close();
65
+ event.preventDefault();
66
+ }
67
+ }
68
+ if (!zoom) {
69
+ if (input.code === "Minus" && (input.control || input.meta))
70
+ event.preventDefault();
71
+ if (input.code === "Equal" && input.shift && (input.control || input.meta))
72
+ event.preventDefault();
73
+ }
74
+ }
75
+ });
76
+ },
77
+ registerFramelessWindowIpc() {
78
+ electron.ipcMain.on("win:invoke", (event, action) => {
79
+ const win = electron.BrowserWindow.fromWebContents(event.sender);
80
+ if (win) {
81
+ if (action === "show") {
82
+ win.show();
83
+ } else if (action === "showInactive") {
84
+ win.showInactive();
85
+ } else if (action === "min") {
86
+ win.minimize();
87
+ } else if (action === "max") {
88
+ const isMaximized = win.isMaximized();
89
+ if (isMaximized) {
90
+ win.unmaximize();
91
+ } else {
92
+ win.maximize();
93
+ }
94
+ } else if (action === "close") {
95
+ win.close();
96
+ }
97
+ }
98
+ });
99
+ }
100
+ };
101
+ function toGlobPath(p) {
102
+ return p.split(path.sep).join("/");
103
+ }
104
+ function findJsonFiles(projectPath) {
105
+ const storePath = path.join(projectPath, "store");
106
+ return glob.sync(`${toGlobPath(storePath)}/**/*.{json,jsonc}`);
107
+ }
108
+ function findCssFiles(projectPath) {
109
+ const stylesPath = path.join(projectPath, "styles/css");
110
+ const allCssFiles = glob.sync(`${toGlobPath(stylesPath)}/**/*.css`);
111
+ const nativeVtexCssFiles = allCssFiles.filter((filePath) => {
112
+ const fileName = path.basename(filePath);
113
+ return fileName.startsWith("vtex.");
114
+ });
115
+ return nativeVtexCssFiles;
116
+ }
117
+ async function extractBlockClasses(jsonFiles) {
118
+ const declaredClasses = /* @__PURE__ */ new Map();
119
+ for (const filePath of jsonFiles) {
120
+ try {
121
+ const content = await fs.readFile(filePath, "utf-8");
122
+ const parsedJson = jsoncParser.parse(content);
123
+ if (typeof parsedJson !== "object" || parsedJson === null) continue;
124
+ for (const blockName in parsedJson) {
125
+ if (!Object.prototype.hasOwnProperty.call(parsedJson, blockName)) continue;
126
+ const blockDef = parsedJson[blockName];
127
+ const blockClassValue = blockDef?.props?.blockClass;
128
+ let classesFound = [];
129
+ if (typeof blockClassValue === "string") {
130
+ classesFound = blockClassValue.split(" ").filter(Boolean);
131
+ } else if (Array.isArray(blockClassValue)) {
132
+ classesFound = blockClassValue.filter(
133
+ (cls) => typeof cls === "string" && cls.length > 0
134
+ );
135
+ }
136
+ if (classesFound.length > 0) {
137
+ for (const cls of classesFound) {
138
+ const locations = declaredClasses.get(cls) || [];
139
+ locations.push({ filePath, blockName });
140
+ declaredClasses.set(cls, locations);
141
+ }
142
+ }
143
+ }
144
+ } catch (error) {
145
+ console.error(`Error procesando el archivo JSON ${filePath}:`, error);
146
+ }
147
+ }
148
+ return declaredClasses;
149
+ }
150
+ const VTEX_CLASS_REGEX = /\.[\w-]+(?:--[\w-]+)+/g;
151
+ function getPrimarySuffix(vtexClassName) {
152
+ const parts = vtexClassName.split("--");
153
+ if (parts.length > 1) {
154
+ return parts[1];
155
+ }
156
+ return null;
157
+ }
158
+ async function extractCssSuffixes(cssFiles) {
159
+ const usedSuffixes = /* @__PURE__ */ new Map();
160
+ for (const filePath of cssFiles) {
161
+ try {
162
+ const content = await fs.readFile(filePath, "utf-8");
163
+ const root = postcss.parse(content);
164
+ root.walkRules((rule) => {
165
+ rule.selectors.forEach((selector) => {
166
+ const matches = [...selector.matchAll(VTEX_CLASS_REGEX)];
167
+ for (const match of matches) {
168
+ const fullClassName = match[0].substring(1);
169
+ const primarySuffix = getPrimarySuffix(fullClassName);
170
+ if (primarySuffix) {
171
+ const locations = usedSuffixes.get(primarySuffix) || [];
172
+ locations.push({ filePath, selector });
173
+ usedSuffixes.set(primarySuffix, locations);
174
+ }
175
+ }
176
+ });
177
+ });
178
+ } catch (error) {
179
+ console.error(`Error procesando el archivo CSS ${filePath}:`, error);
180
+ }
181
+ }
182
+ return usedSuffixes;
183
+ }
184
+ function identifyRulesForDeletion(root, unusedSuffixes) {
185
+ const candidates = [];
186
+ root.walkRules((rule) => {
187
+ const ruleSelectors = rule.selectors;
188
+ let allSelectorsAreUnused = ruleSelectors.length > 0;
189
+ for (const selector of ruleSelectors) {
190
+ const matches = [...selector.matchAll(VTEX_CLASS_REGEX)];
191
+ if (matches.length === 0) {
192
+ allSelectorsAreUnused = false;
193
+ break;
194
+ }
195
+ const hasAtLeastOneUsedSuffix = matches.some((match) => {
196
+ const primarySuffix = getPrimarySuffix(match[0].substring(1));
197
+ return primarySuffix && !unusedSuffixes.has(primarySuffix);
198
+ });
199
+ if (hasAtLeastOneUsedSuffix) {
200
+ allSelectorsAreUnused = false;
201
+ break;
202
+ }
203
+ }
204
+ if (allSelectorsAreUnused) {
205
+ candidates.push(rule);
206
+ }
207
+ });
208
+ return candidates;
209
+ }
210
+ async function ensureReportDirectory(projectPath) {
211
+ const reportDir = path.join(projectPath, ".sanitizer-reports");
212
+ await fs.mkdir(reportDir, { recursive: true });
213
+ return reportDir;
214
+ }
215
+ function getFormattedDate() {
216
+ const date = /* @__PURE__ */ new Date();
217
+ const year = date.getFullYear();
218
+ const month = (date.getMonth() + 1).toString().padStart(2, "0");
219
+ const day = date.getDate().toString().padStart(2, "0");
220
+ return `${year}-${month}-${day}`;
221
+ }
222
+ async function generateAnalysisReport(projectPath, unusedCss, unusedBlockClasses, usedCssSuffixesMap, declaredBlockClassesMap) {
223
+ const reportDir = await ensureReportDirectory(projectPath);
224
+ const reportPath = path.join(reportDir, `analysis-report-${getFormattedDate()}.md`);
225
+ let markdownContent = `# ✅ Informe de Análisis de CSS - ${getFormattedDate()}
226
+
227
+ `;
228
+ markdownContent += `Este informe detalla las clases CSS y declaraciones \`blockClass\` que podrían estar sin uso en el proyecto.
229
+
230
+ `;
231
+ markdownContent += `---
232
+
233
+ `;
234
+ markdownContent += `## 🔴 Sufijos CSS sin \`blockClass\` correspondiente (${unusedCss.length} encontrados)
235
+
236
+ `;
237
+ if (unusedCss.length > 0) {
238
+ unusedCss.forEach((suffix) => {
239
+ markdownContent += `### \`--${suffix}\`
240
+
241
+ `;
242
+ const locations = usedCssSuffixesMap.get(suffix) || [];
243
+ locations.slice(0, 5).forEach((loc) => {
244
+ markdownContent += `* **Usado en:** \`${path.relative(projectPath, loc.filePath)}\`
245
+ `;
246
+ markdownContent += `* **Selector:** \`${loc.selector}\`
247
+
248
+ `;
249
+ });
250
+ });
251
+ } else {
252
+ markdownContent += `¡Excelente! No se encontraron sufijos CSS sin uso.
253
+
254
+ `;
255
+ }
256
+ markdownContent += `---
257
+
258
+ `;
259
+ markdownContent += `## 🟡 \`blockClass\` sin estilos CSS asociados (${unusedBlockClasses.length} encontrados)
260
+
261
+ `;
262
+ if (unusedBlockClasses.length > 0) {
263
+ unusedBlockClasses.forEach((cls) => {
264
+ markdownContent += `### \`"${cls}"\`
265
+
266
+ `;
267
+ const locations = declaredBlockClassesMap.get(cls) || [];
268
+ locations.slice(0, 5).forEach((loc) => {
269
+ markdownContent += `* **Declarado en:** \`${path.relative(projectPath, loc.filePath)}\`
270
+ `;
271
+ markdownContent += `* **Bloque:** \`${loc.blockName}\`
272
+
273
+ `;
274
+ });
275
+ });
276
+ } else {
277
+ markdownContent += `¡Genial! Todas las \`blockClass\` declaradas se están utilizando.
278
+
279
+ `;
280
+ }
281
+ await fs.writeFile(reportPath, markdownContent);
282
+ return reportPath;
283
+ }
284
+ async function generateFixReport(projectPath, deletedRules, keptRules) {
285
+ const reportDir = await ensureReportDirectory(projectPath);
286
+ const reportPath = path.join(reportDir, `fix-report-${getFormattedDate()}.md`);
287
+ let markdownContent = `# 🛠️ Informe de Limpieza de CSS - ${getFormattedDate()}
288
+
289
+ `;
290
+ markdownContent += `Este informe detalla las acciones realizadas durante el proceso de limpieza.
291
+
292
+ `;
293
+ markdownContent += `---
294
+
295
+ `;
296
+ markdownContent += `## 🗑️ Reglas Eliminadas (${deletedRules.length})
297
+
298
+ `;
299
+ if (deletedRules.length > 0) {
300
+ deletedRules.forEach((entry) => {
301
+ markdownContent += `* **Archivo:** \`${path.relative(projectPath, entry.filePath)}\`
302
+ `;
303
+ markdownContent += ` \`\`\`css
304
+ ${entry.rule}
305
+ \`\`\`
306
+
307
+ `;
308
+ });
309
+ } else {
310
+ markdownContent += `No se eliminó ninguna regla durante esta sesión.
311
+
312
+ `;
313
+ }
314
+ markdownContent += `---
315
+
316
+ `;
317
+ markdownContent += `## 👍 Reglas Conservadas (${keptRules.length})
318
+
319
+ `;
320
+ if (keptRules.length > 0) {
321
+ keptRules.forEach((entry) => {
322
+ markdownContent += `* **Archivo:** \`${path.relative(projectPath, entry.filePath)}\`
323
+ `;
324
+ markdownContent += ` \`\`\`css
325
+ ${entry.rule}
326
+ \`\`\`
327
+
328
+ `;
329
+ });
330
+ } else {
331
+ markdownContent += `No se conservó ninguna regla candidata durante esta sesión.
332
+
333
+ `;
334
+ }
335
+ await fs.writeFile(reportPath, markdownContent);
336
+ return reportPath;
337
+ }
338
+ async function analyzeProject(projectPath) {
339
+ const jsonFiles = findJsonFiles(projectPath);
340
+ const cssFiles = findCssFiles(projectPath);
341
+ if (jsonFiles.length === 0 && cssFiles.length === 0) {
342
+ return {
343
+ unusedCss: [],
344
+ unusedBlockClasses: [],
345
+ stats: { jsonFiles: 0, cssFiles: 0, uniqueBlockClasses: 0, uniqueCssSuffixes: 0 }
346
+ };
347
+ }
348
+ const declaredBlockClassesMap = await extractBlockClasses(jsonFiles);
349
+ const usedCssSuffixesMap = await extractCssSuffixes(cssFiles);
350
+ const declaredBlockClasses = new Set(declaredBlockClassesMap.keys());
351
+ const usedCssSuffixes = new Set(usedCssSuffixesMap.keys());
352
+ const unusedCssSuffixes = [...usedCssSuffixes].filter(
353
+ (suffix) => !declaredBlockClasses.has(suffix)
354
+ );
355
+ const unusedCss = unusedCssSuffixes.map((suffix) => ({
356
+ suffix,
357
+ locations: (usedCssSuffixesMap.get(suffix) || []).map((loc) => ({
358
+ filePath: path.relative(projectPath, loc.filePath),
359
+ selector: loc.selector
360
+ }))
361
+ }));
362
+ const unusedBlockClassNames = [...declaredBlockClasses].filter(
363
+ (cls) => !usedCssSuffixes.has(cls)
364
+ );
365
+ const unusedBlockClasses = unusedBlockClassNames.map((cls) => ({
366
+ blockClass: cls,
367
+ locations: (declaredBlockClassesMap.get(cls) || []).map((loc) => ({
368
+ filePath: path.relative(projectPath, loc.filePath),
369
+ blockName: loc.blockName
370
+ }))
371
+ }));
372
+ try {
373
+ await generateAnalysisReport(
374
+ projectPath,
375
+ unusedCssSuffixes,
376
+ unusedBlockClassNames,
377
+ usedCssSuffixesMap,
378
+ declaredBlockClassesMap
379
+ );
380
+ } catch (err) {
381
+ console.error("Error generando reporte de análisis:", err);
382
+ }
383
+ return {
384
+ unusedCss,
385
+ unusedBlockClasses,
386
+ stats: {
387
+ jsonFiles: jsonFiles.length,
388
+ cssFiles: cssFiles.length,
389
+ uniqueBlockClasses: declaredBlockClasses.size,
390
+ uniqueCssSuffixes: usedCssSuffixes.size
391
+ }
392
+ };
393
+ }
394
+ async function getCandidateRules(projectPath) {
395
+ const jsonFiles = findJsonFiles(projectPath);
396
+ const cssFiles = findCssFiles(projectPath);
397
+ const declaredBlockClassesMap = await extractBlockClasses(jsonFiles);
398
+ const usedCssSuffixesMap = await extractCssSuffixes(cssFiles);
399
+ const declaredBlockClasses = new Set(declaredBlockClassesMap.keys());
400
+ const usedCssSuffixes = new Set(usedCssSuffixesMap.keys());
401
+ const unusedSuffixes = new Set(
402
+ [...usedCssSuffixes].filter((suffix) => !declaredBlockClasses.has(suffix))
403
+ );
404
+ if (unusedSuffixes.size === 0) return [];
405
+ const candidates = [];
406
+ let idCounter = 0;
407
+ for (const filePath of cssFiles) {
408
+ const content = await fs.readFile(filePath, "utf-8");
409
+ const root = postcss.parse(content);
410
+ const rules = identifyRulesForDeletion(root, unusedSuffixes);
411
+ for (const rule of rules) {
412
+ candidates.push({
413
+ id: `rule-${idCounter++}`,
414
+ filePath,
415
+ relativeFilePath: path.relative(projectPath, filePath),
416
+ ruleText: rule.toString(),
417
+ selector: rule.selectors.join(", ")
418
+ });
419
+ }
420
+ }
421
+ return candidates;
422
+ }
423
+ async function applyFix(projectPath, rulesToDelete) {
424
+ const rulesByFile = /* @__PURE__ */ new Map();
425
+ for (const rule of rulesToDelete) {
426
+ const existing = rulesByFile.get(rule.filePath) || [];
427
+ existing.push(rule);
428
+ rulesByFile.set(rule.filePath, existing);
429
+ }
430
+ let totalDeleted = 0;
431
+ const deletedEntries = [];
432
+ for (const [filePath, rules] of rulesByFile.entries()) {
433
+ const content = await fs.readFile(filePath, "utf-8");
434
+ const root = postcss.parse(content);
435
+ const ruleTexts = new Set(rules.map((r) => r.ruleText));
436
+ root.walkRules((cssRule) => {
437
+ if (ruleTexts.has(cssRule.toString())) {
438
+ deletedEntries.push({ rule: cssRule.toString(), filePath });
439
+ cssRule.remove();
440
+ totalDeleted++;
441
+ }
442
+ });
443
+ const newContent = root.toString();
444
+ await fs.writeFile(filePath, newContent, "utf-8");
445
+ }
446
+ let reportPath = "";
447
+ try {
448
+ reportPath = await generateFixReport(projectPath, deletedEntries, []);
449
+ } catch (err) {
450
+ console.error("Error generando reporte de fix:", err);
451
+ }
452
+ return { deletedCount: totalDeleted, reportPath };
453
+ }
454
+ function createWindow() {
455
+ const mainWindow = new electron.BrowserWindow({
456
+ width: 1100,
457
+ height: 750,
458
+ minWidth: 900,
459
+ minHeight: 600,
460
+ show: false,
461
+ autoHideMenuBar: true,
462
+ title: "VTEX CSS Sanitizer",
463
+ webPreferences: {
464
+ preload: path.join(__dirname, "../preload/index.js"),
465
+ sandbox: false
466
+ }
467
+ });
468
+ mainWindow.on("ready-to-show", () => {
469
+ mainWindow.show();
470
+ });
471
+ mainWindow.webContents.setWindowOpenHandler((details) => {
472
+ electron.shell.openExternal(details.url);
473
+ return { action: "deny" };
474
+ });
475
+ if (is.dev && process.env["ELECTRON_RENDERER_URL"]) {
476
+ mainWindow.loadURL(process.env["ELECTRON_RENDERER_URL"]);
477
+ } else {
478
+ mainWindow.loadFile(path.join(__dirname, "../renderer/index.html"));
479
+ }
480
+ }
481
+ function registerIpcHandlers() {
482
+ electron.ipcMain.handle("dialog:selectFolder", async () => {
483
+ const result = await electron.dialog.showOpenDialog({
484
+ properties: ["openDirectory"],
485
+ title: "Seleccionar proyecto VTEX IO"
486
+ });
487
+ if (result.canceled || result.filePaths.length === 0) return null;
488
+ return result.filePaths[0];
489
+ });
490
+ electron.ipcMain.handle("analysis:run", async (_event, projectPath) => {
491
+ try {
492
+ return await analyzeProject(projectPath);
493
+ } catch (error) {
494
+ console.error("Error en análisis:", error);
495
+ throw error;
496
+ }
497
+ });
498
+ electron.ipcMain.handle("fix:getCandidates", async (_event, projectPath) => {
499
+ try {
500
+ return await getCandidateRules(projectPath);
501
+ } catch (error) {
502
+ console.error("Error obteniendo candidatos:", error);
503
+ throw error;
504
+ }
505
+ });
506
+ electron.ipcMain.handle(
507
+ "fix:apply",
508
+ async (_event, projectPath, rulesToDelete) => {
509
+ try {
510
+ return await applyFix(projectPath, rulesToDelete);
511
+ } catch (error) {
512
+ console.error("Error aplicando fix:", error);
513
+ throw error;
514
+ }
515
+ }
516
+ );
517
+ electron.ipcMain.handle("report:open", async (_event, reportPath) => {
518
+ electron.shell.showItemInFolder(reportPath);
519
+ });
520
+ }
521
+ electron.app.whenReady().then(() => {
522
+ electronApp.setAppUserModelId("com.elilab.vtex-css-sanitizer");
523
+ electron.app.on("browser-window-created", (_, window) => {
524
+ optimizer.watchWindowShortcuts(window);
525
+ });
526
+ registerIpcHandlers();
527
+ createWindow();
528
+ electron.app.on("activate", () => {
529
+ if (electron.BrowserWindow.getAllWindows().length === 0) createWindow();
530
+ });
531
+ });
532
+ electron.app.on("window-all-closed", () => {
533
+ if (process.platform !== "darwin") {
534
+ electron.app.quit();
535
+ }
536
+ });
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ const electron = require("electron");
3
+ const electronAPI = {
4
+ ipcRenderer: {
5
+ send(channel, ...args) {
6
+ electron.ipcRenderer.send(channel, ...args);
7
+ },
8
+ sendTo(webContentsId, channel, ...args) {
9
+ const electronVer = process.versions.electron;
10
+ const electronMajorVer = electronVer ? parseInt(electronVer.split(".")[0]) : 0;
11
+ if (electronMajorVer >= 28) {
12
+ throw new Error('"sendTo" method has been removed since Electron 28.');
13
+ } else {
14
+ electron.ipcRenderer.sendTo(webContentsId, channel, ...args);
15
+ }
16
+ },
17
+ sendSync(channel, ...args) {
18
+ return electron.ipcRenderer.sendSync(channel, ...args);
19
+ },
20
+ sendToHost(channel, ...args) {
21
+ electron.ipcRenderer.sendToHost(channel, ...args);
22
+ },
23
+ postMessage(channel, message, transfer) {
24
+ electron.ipcRenderer.postMessage(channel, message, transfer);
25
+ },
26
+ invoke(channel, ...args) {
27
+ return electron.ipcRenderer.invoke(channel, ...args);
28
+ },
29
+ on(channel, listener) {
30
+ electron.ipcRenderer.on(channel, listener);
31
+ return () => {
32
+ electron.ipcRenderer.removeListener(channel, listener);
33
+ };
34
+ },
35
+ once(channel, listener) {
36
+ electron.ipcRenderer.once(channel, listener);
37
+ return () => {
38
+ electron.ipcRenderer.removeListener(channel, listener);
39
+ };
40
+ },
41
+ removeListener(channel, listener) {
42
+ electron.ipcRenderer.removeListener(channel, listener);
43
+ return this;
44
+ },
45
+ removeAllListeners(channel) {
46
+ electron.ipcRenderer.removeAllListeners(channel);
47
+ }
48
+ },
49
+ webFrame: {
50
+ insertCSS(css) {
51
+ return electron.webFrame.insertCSS(css);
52
+ },
53
+ setZoomFactor(factor) {
54
+ if (typeof factor === "number" && factor > 0) {
55
+ electron.webFrame.setZoomFactor(factor);
56
+ }
57
+ },
58
+ setZoomLevel(level) {
59
+ if (typeof level === "number") {
60
+ electron.webFrame.setZoomLevel(level);
61
+ }
62
+ }
63
+ },
64
+ webUtils: {
65
+ getPathForFile(file) {
66
+ return electron.webUtils.getPathForFile(file);
67
+ }
68
+ },
69
+ process: {
70
+ get platform() {
71
+ return process.platform;
72
+ },
73
+ get versions() {
74
+ return process.versions;
75
+ },
76
+ get env() {
77
+ return { ...process.env };
78
+ }
79
+ }
80
+ };
81
+ const api = {
82
+ selectFolder: () => electron.ipcRenderer.invoke("dialog:selectFolder"),
83
+ runAnalysis: (projectPath) => electron.ipcRenderer.invoke("analysis:run", projectPath),
84
+ getCandidates: (projectPath) => electron.ipcRenderer.invoke("fix:getCandidates", projectPath),
85
+ applyFix: (projectPath, rulesToDelete) => electron.ipcRenderer.invoke("fix:apply", projectPath, rulesToDelete),
86
+ openReport: (reportPath) => electron.ipcRenderer.invoke("report:open", reportPath)
87
+ };
88
+ if (process.contextIsolated) {
89
+ try {
90
+ electron.contextBridge.exposeInMainWorld("electron", electronAPI);
91
+ electron.contextBridge.exposeInMainWorld("api", api);
92
+ } catch (error) {
93
+ console.error(error);
94
+ }
95
+ } else {
96
+ window.electron = electronAPI;
97
+ window.api = api;
98
+ }