appclean 1.8.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/.github/workflows/publish.yml +41 -0
- package/.github/workflows/test.yml +37 -0
- package/ACTION_CHECKLIST.md +342 -0
- package/APPCLEAN_SUMMARY.md +309 -0
- package/CHANGELOG.md +205 -0
- package/CODE_OF_CONDUCT.md +49 -0
- package/CODE_REVIEW_REPORT.md +447 -0
- package/COMMUNITY_POSTS.md +307 -0
- package/CONTRIBUTING.md +121 -0
- package/DEPLOYMENT_GUIDE.md +345 -0
- package/DEPLOYMENT_STATUS.md +182 -0
- package/EXECUTIVE_REPORT.md +393 -0
- package/GITHUB_OPTIMIZATION.md +383 -0
- package/INDEX.md +165 -0
- package/LICENSE +21 -0
- package/MARKETING_SUMMARY.md +352 -0
- package/NPM_PACKAGE_OPTIMIZATION.md +281 -0
- package/NPM_PUBLISH.md +116 -0
- package/PROJECT_SUMMARY.txt +249 -0
- package/QUICKSTART.md +219 -0
- package/README.md +548 -0
- package/SECURITY.md +104 -0
- package/SETUP_GITHUB.md +237 -0
- package/TESTING_SUMMARY.md +379 -0
- package/dist/core/appUpdateChecker.d.ts +23 -0
- package/dist/core/appUpdateChecker.d.ts.map +1 -0
- package/dist/core/appUpdateChecker.js +159 -0
- package/dist/core/appUpdateChecker.js.map +1 -0
- package/dist/core/detector.d.ts +13 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +99 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/duplicateFileFinder.d.ts +14 -0
- package/dist/core/duplicateFileFinder.d.ts.map +1 -0
- package/dist/core/duplicateFileFinder.js +80 -0
- package/dist/core/duplicateFileFinder.js.map +1 -0
- package/dist/core/orphanedDependencyDetector.d.ts +19 -0
- package/dist/core/orphanedDependencyDetector.d.ts.map +1 -0
- package/dist/core/orphanedDependencyDetector.js +148 -0
- package/dist/core/orphanedDependencyDetector.js.map +1 -0
- package/dist/core/performanceOptimizer.d.ts +37 -0
- package/dist/core/performanceOptimizer.d.ts.map +1 -0
- package/dist/core/performanceOptimizer.js +128 -0
- package/dist/core/performanceOptimizer.js.map +1 -0
- package/dist/core/permissionHandler.d.ts +9 -0
- package/dist/core/permissionHandler.d.ts.map +1 -0
- package/dist/core/permissionHandler.js +89 -0
- package/dist/core/permissionHandler.js.map +1 -0
- package/dist/core/pluginSystem.d.ts +39 -0
- package/dist/core/pluginSystem.d.ts.map +1 -0
- package/dist/core/pluginSystem.js +120 -0
- package/dist/core/pluginSystem.js.map +1 -0
- package/dist/core/removalRecorder.d.ts +32 -0
- package/dist/core/removalRecorder.d.ts.map +1 -0
- package/dist/core/removalRecorder.js +79 -0
- package/dist/core/removalRecorder.js.map +1 -0
- package/dist/core/remover.d.ts +15 -0
- package/dist/core/remover.d.ts.map +1 -0
- package/dist/core/remover.js +225 -0
- package/dist/core/remover.js.map +1 -0
- package/dist/core/reportGenerator.d.ts +9 -0
- package/dist/core/reportGenerator.d.ts.map +1 -0
- package/dist/core/reportGenerator.js +328 -0
- package/dist/core/reportGenerator.js.map +1 -0
- package/dist/core/scheduledCleanup.d.ts +38 -0
- package/dist/core/scheduledCleanup.d.ts.map +1 -0
- package/dist/core/scheduledCleanup.js +127 -0
- package/dist/core/scheduledCleanup.js.map +1 -0
- package/dist/core/serviceFileDetector.d.ts +18 -0
- package/dist/core/serviceFileDetector.d.ts.map +1 -0
- package/dist/core/serviceFileDetector.js +136 -0
- package/dist/core/serviceFileDetector.js.map +1 -0
- package/dist/core/verificationModule.d.ts +14 -0
- package/dist/core/verificationModule.d.ts.map +1 -0
- package/dist/core/verificationModule.js +102 -0
- package/dist/core/verificationModule.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +333 -0
- package/dist/index.js.map +1 -0
- package/dist/managers/brewManager.d.ts +10 -0
- package/dist/managers/brewManager.d.ts.map +1 -0
- package/dist/managers/brewManager.js +130 -0
- package/dist/managers/brewManager.js.map +1 -0
- package/dist/managers/customManager.d.ts +8 -0
- package/dist/managers/customManager.d.ts.map +1 -0
- package/dist/managers/customManager.js +139 -0
- package/dist/managers/customManager.js.map +1 -0
- package/dist/managers/linuxManager.d.ts +10 -0
- package/dist/managers/linuxManager.d.ts.map +1 -0
- package/dist/managers/linuxManager.js +191 -0
- package/dist/managers/linuxManager.js.map +1 -0
- package/dist/managers/npmManager.d.ts +10 -0
- package/dist/managers/npmManager.d.ts.map +1 -0
- package/dist/managers/npmManager.js +119 -0
- package/dist/managers/npmManager.js.map +1 -0
- package/dist/types/index.d.ts +44 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/ui/guiServer.d.ts +10 -0
- package/dist/ui/guiServer.d.ts.map +1 -0
- package/dist/ui/guiServer.js +134 -0
- package/dist/ui/guiServer.js.map +1 -0
- package/dist/ui/menu.d.ts +6 -0
- package/dist/ui/menu.d.ts.map +1 -0
- package/dist/ui/menu.js +93 -0
- package/dist/ui/menu.js.map +1 -0
- package/dist/ui/prompts.d.ts +13 -0
- package/dist/ui/prompts.d.ts.map +1 -0
- package/dist/ui/prompts.js +161 -0
- package/dist/ui/prompts.js.map +1 -0
- package/dist/utils/filesystem.d.ts +13 -0
- package/dist/utils/filesystem.d.ts.map +1 -0
- package/dist/utils/filesystem.js +152 -0
- package/dist/utils/filesystem.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +49 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/platform.d.ts +9 -0
- package/dist/utils/platform.d.ts.map +1 -0
- package/dist/utils/platform.js +75 -0
- package/dist/utils/platform.js.map +1 -0
- package/jest.config.js +20 -0
- package/logo.svg +60 -0
- package/package.json +55 -0
- package/setup-github.sh +125 -0
- package/src/core/appUpdateChecker.ts +220 -0
- package/src/core/detector.ts +133 -0
- package/src/core/duplicateFileFinder.ts +113 -0
- package/src/core/orphanedDependencyDetector.ts +195 -0
- package/src/core/performanceOptimizer.ts +209 -0
- package/src/core/permissionHandler.ts +121 -0
- package/src/core/pluginSystem.ts +194 -0
- package/src/core/removalRecorder.ts +146 -0
- package/src/core/remover.ts +280 -0
- package/src/core/reportGenerator.ts +354 -0
- package/src/core/scheduledCleanup.ts +204 -0
- package/src/core/serviceFileDetector.ts +181 -0
- package/src/core/verificationModule.ts +140 -0
- package/src/index.ts +449 -0
- package/src/managers/brewManager.ts +149 -0
- package/src/managers/customManager.ts +167 -0
- package/src/managers/linuxManager.ts +210 -0
- package/src/managers/npmManager.ts +137 -0
- package/src/types/index.ts +59 -0
- package/src/ui/guiServer.ts +155 -0
- package/src/ui/menu.ts +100 -0
- package/src/ui/prompts.ts +177 -0
- package/src/utils/filesystem.ts +145 -0
- package/src/utils/logger.ts +48 -0
- package/src/utils/platform.ts +75 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getHomeDir } from '../utils/platform';
|
|
3
|
+
import {
|
|
4
|
+
pathExists,
|
|
5
|
+
listDirectory,
|
|
6
|
+
readFile,
|
|
7
|
+
listDirectoryDeep,
|
|
8
|
+
} from '../utils/filesystem';
|
|
9
|
+
import { InstalledApp, ArtifactPath } from '../types';
|
|
10
|
+
|
|
11
|
+
export class CustomManager {
|
|
12
|
+
async findCustomInstalledApps(): Promise<InstalledApp[]> {
|
|
13
|
+
const apps: InstalledApp[] = [];
|
|
14
|
+
const home = getHomeDir();
|
|
15
|
+
|
|
16
|
+
const customBinPaths = [
|
|
17
|
+
'/usr/local/bin',
|
|
18
|
+
'/usr/bin',
|
|
19
|
+
`${home}/.local/bin`,
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const foundBinaries = new Set<string>();
|
|
23
|
+
|
|
24
|
+
for (const binPath of customBinPaths) {
|
|
25
|
+
if (!pathExists(binPath)) continue;
|
|
26
|
+
|
|
27
|
+
const binaries = listDirectory(binPath);
|
|
28
|
+
for (const binary of binaries) {
|
|
29
|
+
const fullPath = path.join(binPath, binary);
|
|
30
|
+
|
|
31
|
+
// Skip if already found
|
|
32
|
+
if (foundBinaries.has(binary)) continue;
|
|
33
|
+
|
|
34
|
+
// Try to detect installation method from the binary
|
|
35
|
+
const method = await this.detectInstallMethod(fullPath);
|
|
36
|
+
|
|
37
|
+
// Only add if it looks like a custom install (not from system)
|
|
38
|
+
if (method === 'custom') {
|
|
39
|
+
foundBinaries.add(binary);
|
|
40
|
+
apps.push({
|
|
41
|
+
name: binary,
|
|
42
|
+
version: 'unknown',
|
|
43
|
+
installMethod: 'custom',
|
|
44
|
+
mainPath: fullPath,
|
|
45
|
+
installedDate: undefined,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return apps;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private async detectInstallMethod(binaryPath: string): Promise<string> {
|
|
55
|
+
try {
|
|
56
|
+
const content = readFile(binaryPath);
|
|
57
|
+
if (!content) return 'unknown';
|
|
58
|
+
|
|
59
|
+
// Check for common shebangs/markers
|
|
60
|
+
if (content.includes('node') || content.includes('#!/usr/bin/env node')) {
|
|
61
|
+
return 'custom'; // Custom node script
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (content.includes('python') || content.includes('#!/usr/bin/env python')) {
|
|
65
|
+
return 'custom'; // Custom python script
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (content.startsWith('#!/bin/bash') || content.startsWith('#!/bin/sh')) {
|
|
69
|
+
return 'custom'; // Shell script
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return 'custom';
|
|
73
|
+
} catch {
|
|
74
|
+
return 'unknown';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async findArtifacts(appName: string): Promise<ArtifactPath[]> {
|
|
79
|
+
const artifacts: ArtifactPath[] = [];
|
|
80
|
+
const home = getHomeDir();
|
|
81
|
+
|
|
82
|
+
// Search in common binary locations
|
|
83
|
+
const binPaths = [
|
|
84
|
+
`/usr/local/bin/${appName}`,
|
|
85
|
+
`/usr/bin/${appName}`,
|
|
86
|
+
`${home}/.local/bin/${appName}`,
|
|
87
|
+
];
|
|
88
|
+
|
|
89
|
+
for (const binPath of binPaths) {
|
|
90
|
+
if (pathExists(binPath)) {
|
|
91
|
+
artifacts.push({
|
|
92
|
+
path: binPath,
|
|
93
|
+
type: 'binary',
|
|
94
|
+
size: 0,
|
|
95
|
+
description: 'Custom binary',
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Search for config files
|
|
101
|
+
const configPatterns = [
|
|
102
|
+
path.join(home, `.config`, appName),
|
|
103
|
+
path.join(home, `.${appName}`),
|
|
104
|
+
path.join(home, `.${appName}rc`),
|
|
105
|
+
path.join(home, `.${appName}rc.json`),
|
|
106
|
+
];
|
|
107
|
+
|
|
108
|
+
for (const configPath of configPatterns) {
|
|
109
|
+
if (pathExists(configPath)) {
|
|
110
|
+
artifacts.push({
|
|
111
|
+
path: configPath,
|
|
112
|
+
type: 'config',
|
|
113
|
+
size: 0,
|
|
114
|
+
description: 'Configuration',
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Search for data files
|
|
120
|
+
const dataPath = path.join(home, `.local`, `share`, appName);
|
|
121
|
+
if (pathExists(dataPath)) {
|
|
122
|
+
artifacts.push({
|
|
123
|
+
path: dataPath,
|
|
124
|
+
type: 'data',
|
|
125
|
+
size: 0,
|
|
126
|
+
description: 'Data directory',
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Search for cache
|
|
131
|
+
const cachePath = path.join(home, `.cache`, appName);
|
|
132
|
+
if (pathExists(cachePath)) {
|
|
133
|
+
artifacts.push({
|
|
134
|
+
path: cachePath,
|
|
135
|
+
type: 'cache',
|
|
136
|
+
size: 0,
|
|
137
|
+
description: 'Cache directory',
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return artifacts;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async searchByName(query: string): Promise<string[]> {
|
|
145
|
+
const results: string[] = [];
|
|
146
|
+
const home = getHomeDir();
|
|
147
|
+
|
|
148
|
+
const binPaths = [
|
|
149
|
+
'/usr/local/bin',
|
|
150
|
+
'/usr/bin',
|
|
151
|
+
`${home}/.local/bin`,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
for (const binPath of binPaths) {
|
|
155
|
+
if (!pathExists(binPath)) continue;
|
|
156
|
+
|
|
157
|
+
const binaries = listDirectory(binPath);
|
|
158
|
+
for (const binary of binaries) {
|
|
159
|
+
if (binary.toLowerCase().includes(query.toLowerCase())) {
|
|
160
|
+
results.push(binary);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return results;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getHomeDir } from '../utils/platform';
|
|
4
|
+
import { pathExists, listDirectory } from '../utils/filesystem';
|
|
5
|
+
import { InstalledApp, ArtifactPath } from '../types';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
export class LinuxManager {
|
|
9
|
+
private packageManager: 'apt' | 'yum' | 'dnf' | 'unknown' = 'unknown';
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.packageManager = this.detectPackageManager();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private detectPackageManager(): 'apt' | 'yum' | 'dnf' | 'unknown' {
|
|
16
|
+
try {
|
|
17
|
+
execSync('which apt', { stdio: 'ignore' });
|
|
18
|
+
return 'apt';
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
execSync('which dnf', { stdio: 'ignore' });
|
|
23
|
+
return 'dnf';
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
execSync('which yum', { stdio: 'ignore' });
|
|
28
|
+
return 'yum';
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
return 'unknown';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async getInstalledPackages(): Promise<InstalledApp[]> {
|
|
35
|
+
const packages: InstalledApp[] = [];
|
|
36
|
+
|
|
37
|
+
if (this.packageManager === 'unknown') {
|
|
38
|
+
return packages;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
let output: string;
|
|
43
|
+
|
|
44
|
+
if (this.packageManager === 'apt') {
|
|
45
|
+
output = execSync('apt list --installed 2>/dev/null').toString();
|
|
46
|
+
const lines = output.split('\n').slice(1); // Skip header
|
|
47
|
+
|
|
48
|
+
for (const line of lines) {
|
|
49
|
+
if (!line.trim()) continue;
|
|
50
|
+
const parts = line.split('/');
|
|
51
|
+
if (parts[0]) {
|
|
52
|
+
packages.push({
|
|
53
|
+
name: parts[0].trim(),
|
|
54
|
+
version: 'unknown',
|
|
55
|
+
installMethod: 'apt',
|
|
56
|
+
mainPath: '',
|
|
57
|
+
installedDate: undefined,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
// yum or dnf
|
|
63
|
+
try {
|
|
64
|
+
output = execSync(`${this.packageManager} list installed`).toString();
|
|
65
|
+
const lines = output.split('\n');
|
|
66
|
+
|
|
67
|
+
for (const line of lines) {
|
|
68
|
+
if (!line.trim()) continue;
|
|
69
|
+
const parts = line.split(/\s+/);
|
|
70
|
+
if (parts[0]) {
|
|
71
|
+
packages.push({
|
|
72
|
+
name: parts[0].split('.')[0],
|
|
73
|
+
version: parts[1] || 'unknown',
|
|
74
|
+
installMethod: this.packageManager as 'yum' | 'dnf',
|
|
75
|
+
mainPath: '',
|
|
76
|
+
installedDate: undefined,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
Logger.debug(`Failed to query ${this.packageManager}: ` + (error as Error).message);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch (error) {
|
|
85
|
+
Logger.debug('Failed to get system packages: ' + (error as Error).message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return packages;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async findArtifacts(appName: string): Promise<ArtifactPath[]> {
|
|
92
|
+
const artifacts: ArtifactPath[] = [];
|
|
93
|
+
const home = getHomeDir();
|
|
94
|
+
|
|
95
|
+
// Binaries
|
|
96
|
+
const binPaths = [
|
|
97
|
+
`/usr/bin/${appName}`,
|
|
98
|
+
`/usr/local/bin/${appName}`,
|
|
99
|
+
`${home}/.local/bin/${appName}`,
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
for (const binPath of binPaths) {
|
|
103
|
+
if (pathExists(binPath)) {
|
|
104
|
+
artifacts.push({
|
|
105
|
+
path: binPath,
|
|
106
|
+
type: 'binary',
|
|
107
|
+
size: 0,
|
|
108
|
+
description: 'Binary executable',
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Config files
|
|
114
|
+
const configPaths = [
|
|
115
|
+
path.join(home, `.config`, appName),
|
|
116
|
+
path.join(home, `.${appName}rc`),
|
|
117
|
+
path.join(home, `.${appName}`),
|
|
118
|
+
`/etc/${appName}`,
|
|
119
|
+
`/etc/${appName}.conf`,
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const configPath of configPaths) {
|
|
123
|
+
if (pathExists(configPath)) {
|
|
124
|
+
artifacts.push({
|
|
125
|
+
path: configPath,
|
|
126
|
+
type: 'config',
|
|
127
|
+
size: 0,
|
|
128
|
+
description: 'Configuration directory/file',
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Cache files
|
|
134
|
+
const cachePaths = [
|
|
135
|
+
path.join(home, `.cache`, appName),
|
|
136
|
+
path.join(home, `.${appName}_cache`),
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
for (const cachePath of cachePaths) {
|
|
140
|
+
if (pathExists(cachePath)) {
|
|
141
|
+
artifacts.push({
|
|
142
|
+
path: cachePath,
|
|
143
|
+
type: 'cache',
|
|
144
|
+
size: 0,
|
|
145
|
+
description: 'Cache directory',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Data files
|
|
151
|
+
const dataPath = path.join(home, `.local`, `share`, appName);
|
|
152
|
+
if (pathExists(dataPath)) {
|
|
153
|
+
artifacts.push({
|
|
154
|
+
path: dataPath,
|
|
155
|
+
type: 'data',
|
|
156
|
+
size: 0,
|
|
157
|
+
description: 'Data directory',
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Log files
|
|
162
|
+
const logPaths = [
|
|
163
|
+
`/var/log/${appName}`,
|
|
164
|
+
`/var/log/${appName}.log`,
|
|
165
|
+
path.join(home, `.local`, `share`, `log`, appName),
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
for (const logPath of logPaths) {
|
|
169
|
+
if (pathExists(logPath)) {
|
|
170
|
+
artifacts.push({
|
|
171
|
+
path: logPath,
|
|
172
|
+
type: 'log',
|
|
173
|
+
size: 0,
|
|
174
|
+
description: 'Log file/directory',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Systemd services
|
|
180
|
+
const systemdPath = `/etc/systemd/system/${appName}.service`;
|
|
181
|
+
if (pathExists(systemdPath)) {
|
|
182
|
+
artifacts.push({
|
|
183
|
+
path: systemdPath,
|
|
184
|
+
type: 'service',
|
|
185
|
+
size: 0,
|
|
186
|
+
description: 'Systemd service file',
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return artifacts;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async removePackage(appName: string): Promise<boolean> {
|
|
194
|
+
if (this.packageManager === 'unknown') {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const command = this.packageManager === 'apt'
|
|
200
|
+
? `apt-get remove -y ${appName}`
|
|
201
|
+
: `${this.packageManager} remove -y ${appName}`;
|
|
202
|
+
|
|
203
|
+
execSync(command, { stdio: 'pipe' });
|
|
204
|
+
return true;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
Logger.debug(`Failed to uninstall ${appName}: ` + (error as Error).message);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { getHomeDir } from '../utils/platform';
|
|
4
|
+
import { pathExists, listDirectory, readFile } from '../utils/filesystem';
|
|
5
|
+
import { InstalledApp, ArtifactPath } from '../types';
|
|
6
|
+
import { Logger } from '../utils/logger';
|
|
7
|
+
|
|
8
|
+
export class NpmManager {
|
|
9
|
+
private globalNpmPath: string;
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
this.globalNpmPath = this.getNpmGlobalPath();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private getNpmGlobalPath(): string {
|
|
16
|
+
try {
|
|
17
|
+
return execSync('npm config get prefix').toString().trim();
|
|
18
|
+
} catch {
|
|
19
|
+
return path.join(getHomeDir(), '.npm-global');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async getInstalledPackages(): Promise<InstalledApp[]> {
|
|
24
|
+
const packages: InstalledApp[] = [];
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const output = execSync('npm list -g --json --depth=0').toString();
|
|
28
|
+
const data = JSON.parse(output);
|
|
29
|
+
const dependencies = data.dependencies || {};
|
|
30
|
+
|
|
31
|
+
for (const [name, pkg] of Object.entries(dependencies)) {
|
|
32
|
+
const typedPkg = pkg as any;
|
|
33
|
+
packages.push({
|
|
34
|
+
name,
|
|
35
|
+
version: typedPkg.version || 'unknown',
|
|
36
|
+
installMethod: 'npm',
|
|
37
|
+
mainPath: path.join(this.globalNpmPath, 'lib', 'node_modules', name),
|
|
38
|
+
installedDate: undefined,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
Logger.debug('Failed to get npm packages: ' + (error as Error).message);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return packages;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async findArtifacts(appName: string): Promise<ArtifactPath[]> {
|
|
49
|
+
const artifacts: ArtifactPath[] = [];
|
|
50
|
+
const home = getHomeDir();
|
|
51
|
+
|
|
52
|
+
// Main package directory
|
|
53
|
+
const packagePath = path.join(
|
|
54
|
+
this.globalNpmPath,
|
|
55
|
+
'lib',
|
|
56
|
+
'node_modules',
|
|
57
|
+
appName
|
|
58
|
+
);
|
|
59
|
+
if (pathExists(packagePath)) {
|
|
60
|
+
artifacts.push({
|
|
61
|
+
path: packagePath,
|
|
62
|
+
type: 'other',
|
|
63
|
+
size: 0,
|
|
64
|
+
description: 'Package directory',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Binaries in bin directory
|
|
69
|
+
const binPath = path.join(this.globalNpmPath, 'bin', appName);
|
|
70
|
+
if (pathExists(binPath)) {
|
|
71
|
+
artifacts.push({
|
|
72
|
+
path: binPath,
|
|
73
|
+
type: 'binary',
|
|
74
|
+
size: 0,
|
|
75
|
+
description: 'Executable binary',
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Config files
|
|
80
|
+
const configPaths = [
|
|
81
|
+
path.join(home, `.${appName}rc`),
|
|
82
|
+
path.join(home, `.${appName}rc.json`),
|
|
83
|
+
path.join(home, `.config`, appName),
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
for (const configPath of configPaths) {
|
|
87
|
+
if (pathExists(configPath)) {
|
|
88
|
+
artifacts.push({
|
|
89
|
+
path: configPath,
|
|
90
|
+
type: 'config',
|
|
91
|
+
size: 0,
|
|
92
|
+
description: `${appName} configuration`,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Cache directories
|
|
98
|
+
const cachePaths = [
|
|
99
|
+
path.join(home, `.cache`, appName),
|
|
100
|
+
path.join(home, `Library`, `Caches`, appName),
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
for (const cachePath of cachePaths) {
|
|
104
|
+
if (pathExists(cachePath)) {
|
|
105
|
+
artifacts.push({
|
|
106
|
+
path: cachePath,
|
|
107
|
+
type: 'cache',
|
|
108
|
+
size: 0,
|
|
109
|
+
description: `${appName} cache`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Data directories
|
|
115
|
+
const dataPath = path.join(home, `.local`, `share`, appName);
|
|
116
|
+
if (pathExists(dataPath)) {
|
|
117
|
+
artifacts.push({
|
|
118
|
+
path: dataPath,
|
|
119
|
+
type: 'data',
|
|
120
|
+
size: 0,
|
|
121
|
+
description: `${appName} data files`,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return artifacts;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async removePackage(appName: string): Promise<boolean> {
|
|
129
|
+
try {
|
|
130
|
+
execSync(`npm uninstall -g ${appName}`);
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
Logger.debug('Failed to uninstall npm package: ' + (error as Error).message);
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export type Platform = 'darwin' | 'linux' | 'win32';
|
|
2
|
+
|
|
3
|
+
export type InstallMethod =
|
|
4
|
+
| 'npm'
|
|
5
|
+
| 'yarn'
|
|
6
|
+
| 'pnpm'
|
|
7
|
+
| 'brew'
|
|
8
|
+
| 'apt'
|
|
9
|
+
| 'yum'
|
|
10
|
+
| 'dnf'
|
|
11
|
+
| 'custom'
|
|
12
|
+
| 'unknown';
|
|
13
|
+
|
|
14
|
+
export interface InstalledApp {
|
|
15
|
+
name: string;
|
|
16
|
+
version: string;
|
|
17
|
+
installMethod: InstallMethod;
|
|
18
|
+
mainPath: string;
|
|
19
|
+
installedDate?: Date;
|
|
20
|
+
size?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ArtifactPath {
|
|
24
|
+
path: string;
|
|
25
|
+
type: 'binary' | 'config' | 'cache' | 'log' | 'data' | 'service' | 'other';
|
|
26
|
+
size: number;
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface AppAnalysis {
|
|
31
|
+
app: InstalledApp;
|
|
32
|
+
artifacts: ArtifactPath[];
|
|
33
|
+
totalSize: number;
|
|
34
|
+
dependents?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface RemovalOptions {
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
createBackup?: boolean;
|
|
40
|
+
backupPath?: string;
|
|
41
|
+
force?: boolean;
|
|
42
|
+
userConsent?: boolean;
|
|
43
|
+
reportFormat?: 'html' | 'text';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RemovalResult {
|
|
47
|
+
success: boolean;
|
|
48
|
+
appName: string;
|
|
49
|
+
removedFiles: number;
|
|
50
|
+
freedSpace: number;
|
|
51
|
+
backupPath?: string;
|
|
52
|
+
errors?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface SearchOptions {
|
|
56
|
+
query?: string;
|
|
57
|
+
installMethod?: InstallMethod;
|
|
58
|
+
sortBy?: 'name' | 'date' | 'size';
|
|
59
|
+
}
|