swynx-lite 1.0.0
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.
- package/README.md +113 -0
- package/bin/swynx-lite +3 -0
- package/package.json +47 -0
- package/src/clean.mjs +280 -0
- package/src/cli.mjs +264 -0
- package/src/config.mjs +121 -0
- package/src/output/console.mjs +298 -0
- package/src/output/json.mjs +76 -0
- package/src/output/progress.mjs +57 -0
- package/src/scan.mjs +143 -0
- package/src/security.mjs +62 -0
- package/src/shared/fixer/barrel-cleaner.mjs +192 -0
- package/src/shared/fixer/import-cleaner.mjs +237 -0
- package/src/shared/fixer/quarantine.mjs +218 -0
- package/src/shared/scanner/analysers/buildSystems.mjs +647 -0
- package/src/shared/scanner/analysers/configParsers.mjs +1086 -0
- package/src/shared/scanner/analysers/deadcode.mjs +6194 -0
- package/src/shared/scanner/analysers/entryPointDetector.mjs +634 -0
- package/src/shared/scanner/analysers/generatedCode.mjs +297 -0
- package/src/shared/scanner/analysers/imports.mjs +60 -0
- package/src/shared/scanner/discovery.mjs +240 -0
- package/src/shared/scanner/parse-worker.mjs +82 -0
- package/src/shared/scanner/parsers/assets.mjs +44 -0
- package/src/shared/scanner/parsers/csharp.mjs +400 -0
- package/src/shared/scanner/parsers/css.mjs +60 -0
- package/src/shared/scanner/parsers/go.mjs +445 -0
- package/src/shared/scanner/parsers/java.mjs +364 -0
- package/src/shared/scanner/parsers/javascript.mjs +823 -0
- package/src/shared/scanner/parsers/kotlin.mjs +350 -0
- package/src/shared/scanner/parsers/python.mjs +497 -0
- package/src/shared/scanner/parsers/registry.mjs +233 -0
- package/src/shared/scanner/parsers/rust.mjs +427 -0
- package/src/shared/scanner/scan-dead-code.mjs +316 -0
- package/src/shared/security/patterns.mjs +349 -0
- package/src/shared/security/proximity.mjs +84 -0
- package/src/shared/security/scanner.mjs +269 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// src/fixer/import-cleaner.mjs
|
|
2
|
+
// Clean up import statements in live files that reference deleted files
|
|
3
|
+
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
5
|
+
import { join, dirname, relative, basename, extname } from 'path';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Find and clean imports that reference deleted files
|
|
9
|
+
* @param {string} projectPath - Project root
|
|
10
|
+
* @param {string[]} deletedFiles - List of deleted file paths (relative)
|
|
11
|
+
* @param {string[]} liveFiles - List of live files to check (relative)
|
|
12
|
+
* @param {object} options - Options
|
|
13
|
+
*/
|
|
14
|
+
export async function cleanDeadImports(projectPath, deletedFiles, liveFiles, options = {}) {
|
|
15
|
+
const { dryRun = false } = options;
|
|
16
|
+
|
|
17
|
+
const result = {
|
|
18
|
+
filesModified: [],
|
|
19
|
+
importsRemoved: [],
|
|
20
|
+
errors: []
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// Build a set of deleted file patterns (without extensions for matching)
|
|
24
|
+
const deletedPatterns = new Set();
|
|
25
|
+
for (const file of deletedFiles) {
|
|
26
|
+
// Add full path
|
|
27
|
+
deletedPatterns.add(file);
|
|
28
|
+
// Add without extension
|
|
29
|
+
const ext = extname(file);
|
|
30
|
+
if (ext) {
|
|
31
|
+
deletedPatterns.add(file.slice(0, -ext.length));
|
|
32
|
+
}
|
|
33
|
+
// Add just the basename without extension
|
|
34
|
+
const base = basename(file, ext);
|
|
35
|
+
deletedPatterns.add(base);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Process each live file
|
|
39
|
+
for (const liveFile of liveFiles) {
|
|
40
|
+
const fullPath = join(projectPath, liveFile);
|
|
41
|
+
|
|
42
|
+
if (!existsSync(fullPath)) continue;
|
|
43
|
+
|
|
44
|
+
// Only process JS/TS files
|
|
45
|
+
const ext = extname(liveFile).toLowerCase();
|
|
46
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue'].includes(ext)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync(fullPath, 'utf-8');
|
|
52
|
+
const { modified, changes } = cleanImportsInFile(content, liveFile, deletedFiles, deletedPatterns);
|
|
53
|
+
|
|
54
|
+
if (changes.length > 0) {
|
|
55
|
+
if (!dryRun) {
|
|
56
|
+
writeFileSync(fullPath, modified, 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
result.filesModified.push(liveFile);
|
|
60
|
+
result.importsRemoved.push({
|
|
61
|
+
file: liveFile,
|
|
62
|
+
imports: changes,
|
|
63
|
+
dryRun
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
result.errors.push({ file: liveFile, error: error.message });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Clean imports in a single file's content
|
|
76
|
+
*/
|
|
77
|
+
function cleanImportsInFile(content, filePath, deletedFiles, deletedPatterns) {
|
|
78
|
+
const lines = content.split('\n');
|
|
79
|
+
const changes = [];
|
|
80
|
+
const newLines = [];
|
|
81
|
+
const fileDir = dirname(filePath);
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < lines.length; i++) {
|
|
84
|
+
const line = lines[i];
|
|
85
|
+
const importMatch = matchImportStatement(line);
|
|
86
|
+
|
|
87
|
+
if (importMatch) {
|
|
88
|
+
const { importPath, fullMatch } = importMatch;
|
|
89
|
+
|
|
90
|
+
// Resolve the import path relative to the file
|
|
91
|
+
const resolvedPath = resolveImportPath(fileDir, importPath, deletedFiles);
|
|
92
|
+
|
|
93
|
+
if (resolvedPath && isDeletedImport(resolvedPath, deletedFiles, deletedPatterns)) {
|
|
94
|
+
changes.push({
|
|
95
|
+
line: i + 1,
|
|
96
|
+
removed: line.trim(),
|
|
97
|
+
importPath
|
|
98
|
+
});
|
|
99
|
+
// Skip this line (don't add to newLines)
|
|
100
|
+
// Also skip empty lines that follow import blocks
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
newLines.push(line);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Clean up consecutive empty lines that might be left
|
|
109
|
+
const cleaned = cleanEmptyLines(newLines);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
modified: cleaned.join('\n'),
|
|
113
|
+
changes
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Match import/require statements
|
|
119
|
+
*/
|
|
120
|
+
function matchImportStatement(line) {
|
|
121
|
+
const trimmed = line.trim();
|
|
122
|
+
|
|
123
|
+
// ES6 import
|
|
124
|
+
// import X from 'path'
|
|
125
|
+
// import { X } from 'path'
|
|
126
|
+
// import * as X from 'path'
|
|
127
|
+
// import 'path'
|
|
128
|
+
const esImportMatch = trimmed.match(/^import\s+(?:.*\s+from\s+)?['"]([^'"]+)['"]/);
|
|
129
|
+
if (esImportMatch) {
|
|
130
|
+
return { importPath: esImportMatch[1], fullMatch: esImportMatch[0] };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Dynamic import (inline - harder to remove safely, skip for now)
|
|
134
|
+
// const X = await import('path')
|
|
135
|
+
|
|
136
|
+
// CommonJS require
|
|
137
|
+
// const X = require('path')
|
|
138
|
+
// require('path')
|
|
139
|
+
const requireMatch = trimmed.match(/(?:const|let|var)?\s*\w*\s*=?\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/);
|
|
140
|
+
if (requireMatch) {
|
|
141
|
+
return { importPath: requireMatch[1], fullMatch: requireMatch[0] };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Resolve an import path relative to the importing file
|
|
149
|
+
*/
|
|
150
|
+
function resolveImportPath(fileDir, importPath, deletedFiles) {
|
|
151
|
+
// Skip node_modules and absolute imports
|
|
152
|
+
if (!importPath.startsWith('.') && !importPath.startsWith('/')) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Resolve relative path
|
|
157
|
+
const resolved = join(fileDir, importPath);
|
|
158
|
+
|
|
159
|
+
// Try with common extensions
|
|
160
|
+
const extensions = ['', '.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.vue', '/index.js', '/index.ts'];
|
|
161
|
+
|
|
162
|
+
for (const ext of extensions) {
|
|
163
|
+
const withExt = resolved + ext;
|
|
164
|
+
// Normalize the path
|
|
165
|
+
const normalized = withExt.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
166
|
+
|
|
167
|
+
for (const deleted of deletedFiles) {
|
|
168
|
+
const deletedNorm = deleted.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
169
|
+
if (normalized === deletedNorm) {
|
|
170
|
+
return normalized;
|
|
171
|
+
}
|
|
172
|
+
// Check without extension
|
|
173
|
+
const deletedNoExt = deletedNorm.replace(/\.[^/.]+$/, '');
|
|
174
|
+
const resolvedNoExt = normalized.replace(/\.[^/.]+$/, '');
|
|
175
|
+
if (resolvedNoExt === deletedNoExt) {
|
|
176
|
+
return normalized;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return resolved;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Check if an import path matches a deleted file
|
|
186
|
+
*/
|
|
187
|
+
function isDeletedImport(resolvedPath, deletedFiles, deletedPatterns) {
|
|
188
|
+
const normalized = resolvedPath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
189
|
+
|
|
190
|
+
// Direct match
|
|
191
|
+
if (deletedPatterns.has(normalized)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Without extension match
|
|
196
|
+
const withoutExt = normalized.replace(/\.[^/.]+$/, '');
|
|
197
|
+
if (deletedPatterns.has(withoutExt)) {
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Check against full deleted paths
|
|
202
|
+
for (const deleted of deletedFiles) {
|
|
203
|
+
const deletedNorm = deleted.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
204
|
+
const deletedNoExt = deletedNorm.replace(/\.[^/.]+$/, '');
|
|
205
|
+
|
|
206
|
+
if (normalized === deletedNorm || withoutExt === deletedNoExt) {
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Clean up consecutive empty lines
|
|
216
|
+
*/
|
|
217
|
+
function cleanEmptyLines(lines) {
|
|
218
|
+
const result = [];
|
|
219
|
+
let lastWasEmpty = false;
|
|
220
|
+
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
const isEmpty = line.trim() === '';
|
|
223
|
+
|
|
224
|
+
if (isEmpty && lastWasEmpty) {
|
|
225
|
+
continue; // Skip consecutive empty lines
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
result.push(line);
|
|
229
|
+
lastWasEmpty = isEmpty;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return result;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export default {
|
|
236
|
+
cleanDeadImports
|
|
237
|
+
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// src/fixer/quarantine.mjs
|
|
2
|
+
// Quarantine manager for safe file removal
|
|
3
|
+
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, unlinkSync, rmSync, readdirSync, statSync } from 'fs';
|
|
5
|
+
import { join, dirname, relative } from 'path';
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
|
|
8
|
+
const QUARANTINE_DIR = '.swynx-quarantine';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a new quarantine session
|
|
12
|
+
*/
|
|
13
|
+
export function createSession(projectPath, reason = 'manual') {
|
|
14
|
+
const sessionId = `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
15
|
+
const sessionDir = join(projectPath, QUARANTINE_DIR, sessionId);
|
|
16
|
+
|
|
17
|
+
mkdirSync(sessionDir, { recursive: true });
|
|
18
|
+
|
|
19
|
+
const manifest = {
|
|
20
|
+
sessionId,
|
|
21
|
+
reason,
|
|
22
|
+
createdAt: new Date().toISOString(),
|
|
23
|
+
projectPath,
|
|
24
|
+
files: [],
|
|
25
|
+
status: 'active',
|
|
26
|
+
fileCount: 0,
|
|
27
|
+
totalSize: 0
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
writeFileSync(join(sessionDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
31
|
+
|
|
32
|
+
return { sessionId, sessionDir, manifest };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Quarantine a file (move to quarantine directory)
|
|
37
|
+
*/
|
|
38
|
+
export function quarantineFile(projectPath, sessionId, filePath) {
|
|
39
|
+
const sessionDir = join(projectPath, QUARANTINE_DIR, sessionId);
|
|
40
|
+
const manifestPath = join(sessionDir, 'manifest.json');
|
|
41
|
+
|
|
42
|
+
if (!existsSync(manifestPath)) {
|
|
43
|
+
throw new Error(`Quarantine session ${sessionId} not found`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
47
|
+
|
|
48
|
+
// Get relative path from project root
|
|
49
|
+
const relativePath = relative(projectPath, filePath);
|
|
50
|
+
const quarantinePath = join(sessionDir, 'files', relativePath);
|
|
51
|
+
|
|
52
|
+
// Ensure directory exists
|
|
53
|
+
mkdirSync(dirname(quarantinePath), { recursive: true });
|
|
54
|
+
|
|
55
|
+
// Get file size before moving
|
|
56
|
+
let fileSize = 0;
|
|
57
|
+
if (existsSync(filePath)) {
|
|
58
|
+
fileSize = statSync(filePath).size;
|
|
59
|
+
// Copy file to quarantine
|
|
60
|
+
copyFileSync(filePath, quarantinePath);
|
|
61
|
+
// Delete original
|
|
62
|
+
unlinkSync(filePath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Update manifest
|
|
66
|
+
manifest.files.push({
|
|
67
|
+
originalPath: relativePath,
|
|
68
|
+
quarantinePath: relative(sessionDir, quarantinePath),
|
|
69
|
+
quarantinedAt: new Date().toISOString(),
|
|
70
|
+
size: fileSize
|
|
71
|
+
});
|
|
72
|
+
manifest.fileCount = manifest.files.length;
|
|
73
|
+
manifest.totalSize = manifest.files.reduce((sum, f) => sum + (f.size || 0), 0);
|
|
74
|
+
|
|
75
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
76
|
+
|
|
77
|
+
return { quarantinePath, relativePath };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* List all quarantine sessions for a project
|
|
82
|
+
*/
|
|
83
|
+
export function listSessions(projectPath) {
|
|
84
|
+
const quarantineDir = join(projectPath, QUARANTINE_DIR);
|
|
85
|
+
|
|
86
|
+
if (!existsSync(quarantineDir)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const sessions = [];
|
|
91
|
+
const entries = readdirSync(quarantineDir, { withFileTypes: true });
|
|
92
|
+
|
|
93
|
+
for (const entry of entries) {
|
|
94
|
+
if (entry.isDirectory()) {
|
|
95
|
+
const manifestPath = join(quarantineDir, entry.name, 'manifest.json');
|
|
96
|
+
if (existsSync(manifestPath)) {
|
|
97
|
+
try {
|
|
98
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
99
|
+
sessions.push(manifest);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
// Skip invalid sessions
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Sort by creation date (newest first)
|
|
108
|
+
return sessions.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get a specific session
|
|
113
|
+
*/
|
|
114
|
+
export function getSession(projectPath, sessionId) {
|
|
115
|
+
const manifestPath = join(projectPath, QUARANTINE_DIR, sessionId, 'manifest.json');
|
|
116
|
+
|
|
117
|
+
if (!existsSync(manifestPath)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Restore files from quarantine (undo)
|
|
126
|
+
*/
|
|
127
|
+
export function restoreSession(projectPath, sessionId) {
|
|
128
|
+
const sessionDir = join(projectPath, QUARANTINE_DIR, sessionId);
|
|
129
|
+
const manifestPath = join(sessionDir, 'manifest.json');
|
|
130
|
+
|
|
131
|
+
if (!existsSync(manifestPath)) {
|
|
132
|
+
throw new Error(`Quarantine session ${sessionId} not found`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
136
|
+
const restored = [];
|
|
137
|
+
const errors = [];
|
|
138
|
+
|
|
139
|
+
for (const file of manifest.files) {
|
|
140
|
+
try {
|
|
141
|
+
const quarantinePath = join(sessionDir, file.quarantinePath || join('files', file.originalPath));
|
|
142
|
+
const originalPath = join(projectPath, file.originalPath);
|
|
143
|
+
|
|
144
|
+
if (existsSync(quarantinePath)) {
|
|
145
|
+
// Ensure directory exists
|
|
146
|
+
mkdirSync(dirname(originalPath), { recursive: true });
|
|
147
|
+
// Copy back
|
|
148
|
+
copyFileSync(quarantinePath, originalPath);
|
|
149
|
+
restored.push(file.originalPath);
|
|
150
|
+
} else {
|
|
151
|
+
errors.push({ file: file.originalPath, error: 'Quarantine file not found' });
|
|
152
|
+
}
|
|
153
|
+
} catch (error) {
|
|
154
|
+
errors.push({ file: file.originalPath, error: error.message });
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Update manifest status
|
|
159
|
+
manifest.status = 'restored';
|
|
160
|
+
manifest.restoredAt = new Date().toISOString();
|
|
161
|
+
manifest.restoredFiles = restored.length;
|
|
162
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
sessionId,
|
|
167
|
+
restored,
|
|
168
|
+
errors: errors.length > 0 ? errors : undefined,
|
|
169
|
+
message: `Restored ${restored.length} file(s)`
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Permanently delete quarantined files
|
|
175
|
+
*/
|
|
176
|
+
export function purgeSession(projectPath, sessionId) {
|
|
177
|
+
const sessionDir = join(projectPath, QUARANTINE_DIR, sessionId);
|
|
178
|
+
|
|
179
|
+
if (!existsSync(sessionDir)) {
|
|
180
|
+
throw new Error(`Quarantine session ${sessionId} not found`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Read manifest for logging
|
|
184
|
+
const manifestPath = join(sessionDir, 'manifest.json');
|
|
185
|
+
let fileCount = 0;
|
|
186
|
+
if (existsSync(manifestPath)) {
|
|
187
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
188
|
+
fileCount = manifest.fileCount || manifest.files?.length || 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Remove entire session directory
|
|
192
|
+
rmSync(sessionDir, { recursive: true, force: true });
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
success: true,
|
|
196
|
+
sessionId,
|
|
197
|
+
purgedFiles: fileCount,
|
|
198
|
+
message: `Permanently deleted quarantine session with ${fileCount} file(s)`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get total quarantine size for a project
|
|
204
|
+
*/
|
|
205
|
+
export function getQuarantineSize(projectPath) {
|
|
206
|
+
const sessions = listSessions(projectPath);
|
|
207
|
+
return sessions.reduce((sum, s) => sum + (s.totalSize || 0), 0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export default {
|
|
211
|
+
createSession,
|
|
212
|
+
quarantineFile,
|
|
213
|
+
listSessions,
|
|
214
|
+
getSession,
|
|
215
|
+
restoreSession,
|
|
216
|
+
purgeSession,
|
|
217
|
+
getQuarantineSize
|
|
218
|
+
};
|