packagepurge 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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +125 -0
  3. package/core/Cargo.lock +1093 -0
  4. package/core/Cargo.toml +22 -0
  5. package/core/src/arc_lfu.rs +91 -0
  6. package/core/src/cache.rs +205 -0
  7. package/core/src/lockfiles.rs +112 -0
  8. package/core/src/main.rs +125 -0
  9. package/core/src/ml.rs +188 -0
  10. package/core/src/optimization.rs +314 -0
  11. package/core/src/safety.rs +103 -0
  12. package/core/src/scanner.rs +136 -0
  13. package/core/src/symlink.rs +223 -0
  14. package/core/src/types.rs +87 -0
  15. package/core/src/usage_tracker.rs +107 -0
  16. package/dist/cli/index.d.ts +2 -0
  17. package/dist/cli/index.d.ts.map +1 -0
  18. package/dist/cli/index.js +249 -0
  19. package/dist/cli/index.js.map +1 -0
  20. package/dist/core/bindings.d.ts +33 -0
  21. package/dist/core/bindings.d.ts.map +1 -0
  22. package/dist/core/bindings.js +172 -0
  23. package/dist/core/bindings.js.map +1 -0
  24. package/dist/managers/base-manager.d.ts +33 -0
  25. package/dist/managers/base-manager.d.ts.map +1 -0
  26. package/dist/managers/base-manager.js +122 -0
  27. package/dist/managers/base-manager.js.map +1 -0
  28. package/dist/managers/index.d.ts +12 -0
  29. package/dist/managers/index.d.ts.map +1 -0
  30. package/dist/managers/index.js +37 -0
  31. package/dist/managers/index.js.map +1 -0
  32. package/dist/managers/npm-manager.d.ts +14 -0
  33. package/dist/managers/npm-manager.d.ts.map +1 -0
  34. package/dist/managers/npm-manager.js +128 -0
  35. package/dist/managers/npm-manager.js.map +1 -0
  36. package/dist/managers/pnpm-manager.d.ts +14 -0
  37. package/dist/managers/pnpm-manager.d.ts.map +1 -0
  38. package/dist/managers/pnpm-manager.js +137 -0
  39. package/dist/managers/pnpm-manager.js.map +1 -0
  40. package/dist/managers/yarn-manager.d.ts +14 -0
  41. package/dist/managers/yarn-manager.d.ts.map +1 -0
  42. package/dist/managers/yarn-manager.js +141 -0
  43. package/dist/managers/yarn-manager.js.map +1 -0
  44. package/dist/types/index.d.ts +85 -0
  45. package/dist/types/index.d.ts.map +1 -0
  46. package/dist/types/index.js +13 -0
  47. package/dist/types/index.js.map +1 -0
  48. package/dist/utils/logger.d.ts +18 -0
  49. package/dist/utils/logger.d.ts.map +1 -0
  50. package/dist/utils/logger.js +50 -0
  51. package/dist/utils/logger.js.map +1 -0
  52. package/package.json +64 -0
  53. package/src/cli/index.ts +212 -0
  54. package/src/core/bindings.ts +157 -0
  55. package/src/managers/base-manager.ts +117 -0
  56. package/src/managers/index.ts +32 -0
  57. package/src/managers/npm-manager.ts +96 -0
  58. package/src/managers/pnpm-manager.ts +107 -0
  59. package/src/managers/yarn-manager.ts +112 -0
  60. package/src/types/index.ts +97 -0
  61. package/src/utils/logger.ts +50 -0
  62. package/tsconfig.json +22 -0
@@ -0,0 +1,96 @@
1
+ /**
2
+ * npm package manager integration
3
+ */
4
+ import { BasePackageManager } from './base-manager';
5
+ import { PackageManager, PackageInfo } from '../types';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs-extra';
9
+
10
+ export class NpmManager extends BasePackageManager {
11
+ readonly manager = PackageManager.NPM;
12
+ readonly lockFileName = 'package-lock.json';
13
+
14
+ async getCachePath(): Promise<string> {
15
+ // npm cache path: ~/.npm on Unix, %AppData%/npm-cache on Windows
16
+ if (process.platform === 'win32') {
17
+ return path.join(os.homedir(), 'AppData', 'Roaming', 'npm-cache');
18
+ }
19
+ return path.join(os.homedir(), '.npm');
20
+ }
21
+
22
+ async parseLockFile(lockFilePath: string): Promise<Map<string, string>> {
23
+ const dependencies = new Map<string, string>();
24
+
25
+ try {
26
+ const lockFile = await fs.readJson(lockFilePath);
27
+
28
+ // Parse package-lock.json structure
29
+ function extractDependencies(obj: any, prefix: string = ''): void {
30
+ if (obj.dependencies) {
31
+ for (const [name, dep] of Object.entries(obj.dependencies as Record<string, any>)) {
32
+ const fullName = prefix ? `${prefix}/${name}` : name;
33
+ if (dep.version) {
34
+ dependencies.set(fullName, dep.version);
35
+ }
36
+ if (dep.dependencies) {
37
+ extractDependencies(dep, fullName);
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ extractDependencies(lockFile);
44
+ } catch (error) {
45
+ // If lock file parsing fails, return empty map
46
+ }
47
+
48
+ return dependencies;
49
+ }
50
+
51
+ protected async analyzePackage(packagePath: string): Promise<PackageInfo | null> {
52
+ try {
53
+ const stat = await fs.stat(packagePath);
54
+ const packageJsonPath = path.join(packagePath, 'package.json');
55
+
56
+ if (!(await fs.pathExists(packageJsonPath))) {
57
+ return null;
58
+ }
59
+
60
+ const packageJson = await fs.readJson(packageJsonPath);
61
+ const size = await this.calculateDirectorySize(packagePath);
62
+
63
+ return {
64
+ name: packageJson.name || path.basename(packagePath),
65
+ version: packageJson.version || 'unknown',
66
+ path: packagePath,
67
+ lastAccessed: stat.atime,
68
+ size,
69
+ manager: this.manager,
70
+ projectPaths: [],
71
+ };
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ private async calculateDirectorySize(dirPath: string): Promise<number> {
78
+ let size = 0;
79
+ try {
80
+ const entries = await fs.readdir(dirPath);
81
+ for (const entry of entries) {
82
+ const entryPath = path.join(dirPath, entry);
83
+ const stat = await fs.stat(entryPath);
84
+ if (stat.isDirectory()) {
85
+ size += await this.calculateDirectorySize(entryPath);
86
+ } else {
87
+ size += stat.size;
88
+ }
89
+ }
90
+ } catch {
91
+ // Ignore errors
92
+ }
93
+ return size;
94
+ }
95
+ }
96
+
@@ -0,0 +1,107 @@
1
+ /**
2
+ * pnpm package manager integration
3
+ */
4
+ import { BasePackageManager } from './base-manager';
5
+ import { PackageManager, PackageInfo } from '../types';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs-extra';
9
+
10
+ export class PnpmManager extends BasePackageManager {
11
+ readonly manager = PackageManager.PNPM;
12
+ readonly lockFileName = 'pnpm-lock.yaml';
13
+
14
+ async getCachePath(): Promise<string> {
15
+ // pnpm store path: ~/.pnpm-store on Unix, %LOCALAPPDATA%/pnpm/store on Windows
16
+ if (process.platform === 'win32') {
17
+ return path.join(os.homedir(), 'AppData', 'Local', 'pnpm', 'store');
18
+ }
19
+ return path.join(os.homedir(), '.pnpm-store');
20
+ }
21
+
22
+ async parseLockFile(lockFilePath: string): Promise<Map<string, string>> {
23
+ const dependencies = new Map<string, string>();
24
+
25
+ try {
26
+ const lockFileContent = await fs.readFile(lockFilePath, 'utf-8');
27
+
28
+ // Parse pnpm-lock.yaml format (YAML)
29
+ const lines = lockFileContent.split('\n');
30
+ let currentPackage: string | null = null;
31
+ let currentVersion: string | null = null;
32
+
33
+ for (const line of lines) {
34
+ const trimmed = line.trim();
35
+
36
+ // Package entry: "package-name:"
37
+ if (trimmed.endsWith(':') && !trimmed.startsWith(' ') && !trimmed.startsWith('#')) {
38
+ const match = trimmed.match(/^(.+?):$/);
39
+ if (match && !match[1].startsWith('lockfile')) {
40
+ currentPackage = match[1];
41
+ }
42
+ }
43
+
44
+ // Version field
45
+ if (trimmed.startsWith('version:') && currentPackage) {
46
+ const match = trimmed.match(/version:\s*(.+?)$/);
47
+ if (match) {
48
+ currentVersion = match[1].replace(/["']/g, '');
49
+ dependencies.set(currentPackage, currentVersion);
50
+ currentPackage = null;
51
+ currentVersion = null;
52
+ }
53
+ }
54
+ }
55
+ } catch (error) {
56
+ // If lock file parsing fails, return empty map
57
+ }
58
+
59
+ return dependencies;
60
+ }
61
+
62
+ protected async analyzePackage(packagePath: string): Promise<PackageInfo | null> {
63
+ try {
64
+ const stat = await fs.stat(packagePath);
65
+ const packageJsonPath = path.join(packagePath, 'package.json');
66
+
67
+ if (!(await fs.pathExists(packageJsonPath))) {
68
+ return null;
69
+ }
70
+
71
+ const packageJson = await fs.readJson(packageJsonPath);
72
+ const size = await this.calculateDirectorySize(packagePath);
73
+
74
+ return {
75
+ name: packageJson.name || path.basename(packagePath),
76
+ version: packageJson.version || 'unknown',
77
+ path: packagePath,
78
+ lastAccessed: stat.atime,
79
+ size,
80
+ manager: this.manager,
81
+ projectPaths: [],
82
+ };
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ private async calculateDirectorySize(dirPath: string): Promise<number> {
89
+ let size = 0;
90
+ try {
91
+ const entries = await fs.readdir(dirPath);
92
+ for (const entry of entries) {
93
+ const entryPath = path.join(dirPath, entry);
94
+ const stat = await fs.stat(entryPath);
95
+ if (stat.isDirectory()) {
96
+ size += await this.calculateDirectorySize(entryPath);
97
+ } else {
98
+ size += stat.size;
99
+ }
100
+ }
101
+ } catch {
102
+ // Ignore errors
103
+ }
104
+ return size;
105
+ }
106
+ }
107
+
@@ -0,0 +1,112 @@
1
+ /**
2
+ * yarn package manager integration
3
+ */
4
+ import { BasePackageManager } from './base-manager';
5
+ import { PackageManager, PackageInfo } from '../types';
6
+ import * as os from 'os';
7
+ import * as path from 'path';
8
+ import * as fs from 'fs-extra';
9
+
10
+ export class YarnManager extends BasePackageManager {
11
+ readonly manager = PackageManager.YARN;
12
+ readonly lockFileName = 'yarn.lock';
13
+
14
+ async getCachePath(): Promise<string> {
15
+ // yarn cache path: ~/.yarn/cache on Unix, %LOCALAPPDATA%/Yarn/Cache on Windows
16
+ if (process.platform === 'win32') {
17
+ return path.join(os.homedir(), 'AppData', 'Local', 'Yarn', 'Cache');
18
+ }
19
+ return path.join(os.homedir(), '.yarn', 'cache');
20
+ }
21
+
22
+ async parseLockFile(lockFilePath: string): Promise<Map<string, string>> {
23
+ const dependencies = new Map<string, string>();
24
+
25
+ try {
26
+ const lockFileContent = await fs.readFile(lockFilePath, 'utf-8');
27
+
28
+ // Parse yarn.lock format (YAML-like format)
29
+ const lines = lockFileContent.split('\n');
30
+ let currentPackage: string | null = null;
31
+ let currentVersion: string | null = null;
32
+
33
+ for (const line of lines) {
34
+ const trimmed = line.trim();
35
+
36
+ // Package declaration: "package-name@version:"
37
+ if (trimmed.endsWith(':') && !trimmed.startsWith(' ')) {
38
+ const match = trimmed.match(/^"?(.+?)@(.+?)"?:$/);
39
+ if (match) {
40
+ currentPackage = match[1];
41
+ currentVersion = match[2];
42
+ }
43
+ }
44
+
45
+ // Version field
46
+ if (trimmed.startsWith('version') && currentPackage) {
47
+ const match = trimmed.match(/version\s+"?(.+?)"?$/);
48
+ if (match) {
49
+ currentVersion = match[1];
50
+ }
51
+ }
52
+
53
+ // Store dependency
54
+ if (currentPackage && currentVersion && trimmed.startsWith('resolved')) {
55
+ dependencies.set(currentPackage, currentVersion);
56
+ currentPackage = null;
57
+ currentVersion = null;
58
+ }
59
+ }
60
+ } catch (error) {
61
+ // If lock file parsing fails, return empty map
62
+ }
63
+
64
+ return dependencies;
65
+ }
66
+
67
+ protected async analyzePackage(packagePath: string): Promise<PackageInfo | null> {
68
+ try {
69
+ const stat = await fs.stat(packagePath);
70
+ const packageJsonPath = path.join(packagePath, 'package.json');
71
+
72
+ if (!(await fs.pathExists(packageJsonPath))) {
73
+ return null;
74
+ }
75
+
76
+ const packageJson = await fs.readJson(packageJsonPath);
77
+ const size = await this.calculateDirectorySize(packagePath);
78
+
79
+ return {
80
+ name: packageJson.name || path.basename(packagePath),
81
+ version: packageJson.version || 'unknown',
82
+ path: packagePath,
83
+ lastAccessed: stat.atime,
84
+ size,
85
+ manager: this.manager,
86
+ projectPaths: [],
87
+ };
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+
93
+ private async calculateDirectorySize(dirPath: string): Promise<number> {
94
+ let size = 0;
95
+ try {
96
+ const entries = await fs.readdir(dirPath);
97
+ for (const entry of entries) {
98
+ const entryPath = path.join(dirPath, entry);
99
+ const stat = await fs.stat(entryPath);
100
+ if (stat.isDirectory()) {
101
+ size += await this.calculateDirectorySize(entryPath);
102
+ } else {
103
+ size += stat.size;
104
+ }
105
+ }
106
+ } catch {
107
+ // Ignore errors
108
+ }
109
+ return size;
110
+ }
111
+ }
112
+
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Core type definitions for PackagePurge service
3
+ */
4
+
5
+ export enum PackageManager {
6
+ NPM = 'npm',
7
+ YARN = 'yarn',
8
+ PNPM = 'pnpm',
9
+ }
10
+
11
+ export interface PackageInfo {
12
+ name: string;
13
+ version: string;
14
+ path: string;
15
+ lastAccessed: Date;
16
+ size: number; // in bytes
17
+ manager: PackageManager;
18
+ projectPaths: string[]; // projects that use this package
19
+ }
20
+
21
+ export interface ProjectInfo {
22
+ path: string;
23
+ manager: PackageManager;
24
+ dependencies: Map<string, string>; // package -> version
25
+ lastModified: Date;
26
+ lockFilePath?: string;
27
+ }
28
+
29
+ export interface CleanupStrategy {
30
+ name: string;
31
+ rules: CleanupRule[];
32
+ mlEnabled: boolean;
33
+ }
34
+
35
+ export interface CleanupRule {
36
+ name: string;
37
+ condition: (packageInfo: PackageInfo) => boolean;
38
+ priority: number;
39
+ }
40
+
41
+ export interface CleanupResult {
42
+ packagesDeleted: PackageInfo[];
43
+ spaceSaved: number; // in bytes
44
+ rollbackId?: string;
45
+ timestamp: Date;
46
+ }
47
+
48
+ export interface BackupInfo {
49
+ id: string;
50
+ timestamp: Date;
51
+ packages: PackageInfo[];
52
+ totalSize: number;
53
+ archivePath: string;
54
+ }
55
+
56
+ export interface Analytics {
57
+ totalSpaceSaved: number; // in bytes
58
+ totalRollbacks: number;
59
+ totalReinstalls: number;
60
+ savingsToRiskRatio: number;
61
+ cacheHitRate: number;
62
+ projectsAnalyzed: number;
63
+ lastCleanup: Date | null;
64
+ }
65
+
66
+ export interface OptimizationConfig {
67
+ preserveDays: number;
68
+ keepVersions: number;
69
+ enableML: boolean;
70
+ enableSymlinking: boolean;
71
+ backupEnabled: boolean;
72
+ managers: PackageManager[];
73
+ dryRun: boolean;
74
+ lruMaxPackages?: number;
75
+ lruMaxSizeBytes?: number;
76
+ }
77
+
78
+ export interface OptimizeResult {
79
+ items: Array<{
80
+ target_path: string;
81
+ estimated_size_bytes: number;
82
+ reason: string;
83
+ }>;
84
+ total_estimated_bytes: number;
85
+ }
86
+
87
+ export interface SymlinkResult {
88
+ status: string;
89
+ symlinked_count: number;
90
+ }
91
+
92
+ export interface DependencyGraph {
93
+ nodes: Map<string, PackageInfo>;
94
+ edges: Map<string, string[]>; // package -> dependencies
95
+ rootProjects: ProjectInfo[];
96
+ }
97
+
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Logger utility for PackagePurge
3
+ */
4
+ import chalk from 'chalk';
5
+
6
+ export enum LogLevel {
7
+ DEBUG = 0,
8
+ INFO = 1,
9
+ WARN = 2,
10
+ ERROR = 3,
11
+ }
12
+
13
+ class Logger {
14
+ private level: LogLevel = LogLevel.INFO;
15
+
16
+ setLevel(level: LogLevel): void {
17
+ this.level = level;
18
+ }
19
+
20
+ debug(message: string, ...args: any[]): void {
21
+ if (this.level <= LogLevel.DEBUG) {
22
+ console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
23
+ }
24
+ }
25
+
26
+ info(message: string, ...args: any[]): void {
27
+ if (this.level <= LogLevel.INFO) {
28
+ console.log(chalk.blue(`[INFO] ${message}`), ...args);
29
+ }
30
+ }
31
+
32
+ warn(message: string, ...args: any[]): void {
33
+ if (this.level <= LogLevel.WARN) {
34
+ console.warn(chalk.yellow(`[WARN] ${message}`), ...args);
35
+ }
36
+ }
37
+
38
+ error(message: string, ...args: any[]): void {
39
+ if (this.level <= LogLevel.ERROR) {
40
+ console.error(chalk.red(`[ERROR] ${message}`), ...args);
41
+ }
42
+ }
43
+
44
+ success(message: string, ...args: any[]): void {
45
+ console.log(chalk.green(`[SUCCESS] ${message}`), ...args);
46
+ }
47
+ }
48
+
49
+ export const logger = new Logger();
50
+
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true,
16
+ "moduleResolution": "node",
17
+ "types": ["node", "jest"]
18
+ },
19
+ "include": ["src/**/*"],
20
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
21
+ }
22
+