@utarid/cryo 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/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/cryo.js +2 -0
- package/dist/core/Archiver.js +79 -0
- package/dist/core/Builder.js +112 -0
- package/dist/core/Cleaner.js +112 -0
- package/dist/core/ConfigManager.js +115 -0
- package/dist/core/Prompt.js +77 -0
- package/dist/core/Scanner.js +150 -0
- package/dist/index.js +752 -0
- package/dist/types.js +3 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cryo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# cryo
|
|
2
|
+
|
|
3
|
+
**cryo** is an intelligent CLI tool designed for polyglot developers. It scans your workspaces, identifies forgotten projects, cleans up heavy build artifacts (like `node_modules`), and archives your source code securely.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
* **Smart Scanning:** Automatically detects **Node.js, Flutter, Java, Python, Rust, .NET, PHP, and Swift** projects.
|
|
8
|
+
* **Deep Cleaning:** Frees up disk space by deleting build artifacts with surgical precision.
|
|
9
|
+
* **Backup:** Archives your source code into `.tar.gz` files. Capture full snapshots or lightweight source-only backups.
|
|
10
|
+
* **Rebuild Capability:** Triggers build commands directly from the dashboard.
|
|
11
|
+
* **Insightful Dashboard:** Tracks modification dates vs. build status.
|
|
12
|
+
* **Multi-Profile System:** Manage separate configurations for Work, Personal, or Clients.
|
|
13
|
+
|
|
14
|
+
## Supported Frameworks & Tools
|
|
15
|
+
|
|
16
|
+
cryo automatically detects the following project types and handles their specific build/clean operations:
|
|
17
|
+
|
|
18
|
+
| Framework | Detection File | Clean Target | Build Command |
|
|
19
|
+
| :--- | :--- | :--- | :--- |
|
|
20
|
+
| **Node.js** | `package.json` | `node_modules`, `dist`, `.next` | `npm/yarn/pnpm install && build` |
|
|
21
|
+
| **Flutter** | `pubspec.yaml` | `build`, `.dart_tool` | `flutter pub get` |
|
|
22
|
+
| **Java (Maven)** | `pom.xml` | `target` | `mvn clean install` |
|
|
23
|
+
| **Java (Gradle)**| `build.gradle` | `build`, `.gradle` | `./gradlew build` |
|
|
24
|
+
| **Python** | `requirements.txt` | `venv`, `__pycache__`, `dist` | `pip/pipenv install` |
|
|
25
|
+
| **Rust** | `Cargo.toml` | `target` | `cargo build` |
|
|
26
|
+
| **.NET** | `*.csproj`, `*.sln` | `bin`, `obj` | `dotnet build` |
|
|
27
|
+
| **PHP** | `composer.json` | `vendor` | `composer install` |
|
|
28
|
+
| **Swift/iOS** | `Podfile` | `Pods`, `build`, `DerivedData` | `pod install` / `swift build` |
|
|
29
|
+
|
|
30
|
+
## ๐ฆ Installation
|
|
31
|
+
|
|
32
|
+
### Option 1: Run from Source
|
|
33
|
+
|
|
34
|
+
1. Clone the repository:
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/utariddev/cryo.git
|
|
37
|
+
cd cryo
|
|
38
|
+
```
|
|
39
|
+
2. Install dependencies:
|
|
40
|
+
```bash
|
|
41
|
+
npm install
|
|
42
|
+
```
|
|
43
|
+
3. Run the CLI:
|
|
44
|
+
```bash
|
|
45
|
+
npx ts-node src/index.ts
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Option 2: Install Globally
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install -g @utarid/cryo
|
|
52
|
+
cryo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## ๐ Usage Guide
|
|
56
|
+
|
|
57
|
+
Just type `cryo` (or the start command) to launch the interactive menu.
|
|
58
|
+
|
|
59
|
+
### ๐ CLI Commands
|
|
60
|
+
|
|
61
|
+
You can use cryo directly from your terminal without entering the interactive menu.
|
|
62
|
+
|
|
63
|
+
| Command | Description | Example |
|
|
64
|
+
| :--- | :--- | :--- |
|
|
65
|
+
| `cryo profiles list` | List all saved profiles. | `cryo profiles list` |
|
|
66
|
+
| `cryo profiles show <name>` | Show specific profile details. | `cryo profiles show Work` |
|
|
67
|
+
| `cryo profiles create <name> -p <paths>` | Create a new profile. | `cryo profiles create Work -p ./src,./apps` |
|
|
68
|
+
| `cryo profiles delete <name>` | Delete a profile. | `cryo profiles delete Tmp` |
|
|
69
|
+
| `cryo scan <target>` | Scan and list projects in the target path or profile. | `cryo scan ./projects` |
|
|
70
|
+
| `cryo clean <target> [-f] [-d]` | Clean build folders. `-f`: Force, `-d`: Dry-run. | `cryo clean Work -d` |
|
|
71
|
+
| `cryo build <target>` | Rebuild all projects in the target. | `cryo build ./my-app` |
|
|
72
|
+
| `cryo backup <target> [-o <path>]` | Backup projects to a zip archive. | `cryo backup Work -o ./backups` |
|
|
73
|
+
|
|
74
|
+
* **target**: Can be a valid system path (e.g., `./src`) OR a saved profile name (e.g., `Work`).
|
|
75
|
+
|
|
76
|
+
### ๐ ๏ธ Options & Flags
|
|
77
|
+
|
|
78
|
+
| Flag | Long Name | Description |
|
|
79
|
+
| :--- | :--- | :--- |
|
|
80
|
+
| `-f` | `--force` | Skips the confirmation prompt. Dangerous but useful for scripts. |
|
|
81
|
+
| `-d` | `--dry-run` | Simulation mode. Shows what would happen without deleting anything. |
|
|
82
|
+
| `-p` | `--paths` | Comma-separated list of paths (used in `profiles create`). |
|
|
83
|
+
| `-i` | `--ignore` | Comma-separated list of ignore patterns (used in `profiles create`). |
|
|
84
|
+
| `-o` | `--out` | Custom output directory for backups or profiles. |
|
|
85
|
+
|
|
86
|
+
### 1. Main Menu
|
|
87
|
+
The central hub where you can manage projects or configure profiles.
|
|
88
|
+
|
|
89
|
+
* **๐ List Projects:** View all projects in the active profile, sortable by date.
|
|
90
|
+
* **๐งน Clean Projects:** Select multiple projects to wipe their build folders.
|
|
91
|
+
* **๐จ Build Projects:** Re-compile selected projects (streams output to terminal).
|
|
92
|
+
* **๐ฆ Backup Projects:** Archive source code to your configured Backup Path.
|
|
93
|
+
|
|
94
|
+
> [!IMPORTANT]
|
|
95
|
+
> **Raw Snapshot Philosophy:** By default, cryo archives **everything** (including `node_modules`, `build`, etc.) to ensure a perfect point-in-time snapshot.
|
|
96
|
+
>
|
|
97
|
+
> If you want a **lightweight source-only backup**, simply run the **Clean** command first, then perform the **Backup**.
|
|
98
|
+
|
|
99
|
+
### 2. Project Explorer
|
|
100
|
+
When you select **List**, cryo shows a clean, aligned table using breadcrumbs for context:
|
|
101
|
+
|
|
102
|
+
```text
|
|
103
|
+
๎ฑ List Projects > Work
|
|
104
|
+
-----------------------
|
|
105
|
+
|
|
106
|
+
Date Build Status Type Project Name
|
|
107
|
+
------------------------------------------------------------
|
|
108
|
+
18.01.2026 19.01.2026 (dist) nodejs cryo (/Users/ben/works/cryo)
|
|
109
|
+
15.01.2026 None flutter mobile_app (/Users/ben/works/mobile_app)
|
|
110
|
+
```
|
|
111
|
+
* **Date:** The last time you modified the source code (`src`, `lib`, etc.).
|
|
112
|
+
* **Build Status:** Shows the last build date and the artifact folder. Red "None" means no build found.
|
|
113
|
+
|
|
114
|
+
### 3. Profiles
|
|
115
|
+
You don't need to scan your whole disk every time. Create profiles for specific workflows:
|
|
116
|
+
|
|
117
|
+
* **Default:** Scans your current working directory.
|
|
118
|
+
* **Work:** Scans `~/Work` and `~/Clients`.
|
|
119
|
+
* **Playground:** Scans `~/Tmp` and `~/Tests`.
|
|
120
|
+
|
|
121
|
+
## โ๏ธ Configuration
|
|
122
|
+
|
|
123
|
+
cryo stores its configuration in `~/.cryo/profiles/`. You can edit them via the CLI or manually.
|
|
124
|
+
|
|
125
|
+
**Example Profile (`default.json`):**
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"profileName": "Work",
|
|
129
|
+
"scanPaths": ["/Users/me/projects"],
|
|
130
|
+
"ignoredPaths": ["**/*.log", "**/tmp/**"],
|
|
131
|
+
"archivePath": "/Users/me/cryo_Archives"
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## ๐ค Contributing
|
|
136
|
+
|
|
137
|
+
Contributions are welcome!
|
|
138
|
+
1. Fork the project.
|
|
139
|
+
2. Create your feature branch (`git checkout -b feature/AmazingFeature`).
|
|
140
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`).
|
|
141
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`).
|
|
142
|
+
5. Open a Pull Request.
|
|
143
|
+
|
|
144
|
+
## ๐ License
|
|
145
|
+
|
|
146
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Made with โค๏ธ and [<img src="https://img.shields.io/badge/-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white" height="30">](https://www.typescriptlang.org/) in [<img src="https://upload.wikimedia.org/wikipedia/commons/b/b1/Flag_of_Lower_Austria_%28state%29.svg" height="30">](https://en.wikipedia.org/wiki/Lower_Austria)
|
|
151
|
+
|
package/bin/cryo.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/Archiver.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Archiver = void 0;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const archiver_1 = __importDefault(require("archiver"));
|
|
11
|
+
class Archiver {
|
|
12
|
+
constructor() {
|
|
13
|
+
// Fixed Ignore List
|
|
14
|
+
this.defaultIgnores = [
|
|
15
|
+
'**/.DS_Store'
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Archives the project in .tar.gz format
|
|
20
|
+
*/
|
|
21
|
+
async freeze(project, archiveDir, userIgnores = []) {
|
|
22
|
+
// 1. Prepare ignore list
|
|
23
|
+
const finalIgnoreList = [...this.defaultIgnores, ...userIgnores];
|
|
24
|
+
// 2. Create directory
|
|
25
|
+
await fs_extra_1.default.ensureDir(archiveDir);
|
|
26
|
+
const date = new Date().toISOString().split('T')[0];
|
|
27
|
+
const fileName = `${project.name}_${date}.tar.gz`;
|
|
28
|
+
const outputPath = path_1.default.join(archiveDir, fileName);
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
// Output file (Stream)
|
|
31
|
+
const output = fs_extra_1.default.createWriteStream(outputPath);
|
|
32
|
+
// Archive object
|
|
33
|
+
const archive = (0, archiver_1.default)('tar', {
|
|
34
|
+
gzip: true,
|
|
35
|
+
zlib: { level: 9 }
|
|
36
|
+
});
|
|
37
|
+
// --- EVENT LISTENERS (ERROR HANDLING) ---
|
|
38
|
+
// When process finishes successfully
|
|
39
|
+
output.on('close', () => {
|
|
40
|
+
resolve(outputPath);
|
|
41
|
+
});
|
|
42
|
+
output.on('error', (err) => {
|
|
43
|
+
reject(err);
|
|
44
|
+
});
|
|
45
|
+
// If critical error occurs
|
|
46
|
+
archive.on('error', (err) => {
|
|
47
|
+
reject(err);
|
|
48
|
+
});
|
|
49
|
+
// If warning occurs (e.g. No permission to read file but can continue)
|
|
50
|
+
archive.on('warning', (err) => {
|
|
51
|
+
if (err.code === 'ENOENT') {
|
|
52
|
+
// File not found warning, not critical, log and continue
|
|
53
|
+
console.warn('Archive warning:', err.message);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
reject(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Pipe stream
|
|
60
|
+
archive.pipe(output);
|
|
61
|
+
// --- ADD FILES ---
|
|
62
|
+
try {
|
|
63
|
+
archive.glob('**/*', {
|
|
64
|
+
cwd: project.path,
|
|
65
|
+
ignore: finalIgnoreList,
|
|
66
|
+
dot: true // Include files like .env
|
|
67
|
+
});
|
|
68
|
+
// Finalize process
|
|
69
|
+
archive.finalize();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
// If a synchronous error occurs during glob, reject promise
|
|
73
|
+
reject(err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
exports.Archiver = Archiver;
|
|
79
|
+
exports.default = new Archiver();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/Builder.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Builder = void 0;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
class Builder {
|
|
12
|
+
async build(project) {
|
|
13
|
+
// Java/Gradle Permission Fix
|
|
14
|
+
// On Linux/Mac, ensure gradlew is executable
|
|
15
|
+
if (process.platform !== 'win32' && project.type === 'java-gradle') {
|
|
16
|
+
const gradlewPath = path_1.default.join(project.path, 'gradlew');
|
|
17
|
+
try {
|
|
18
|
+
if (await fs_extra_1.default.pathExists(gradlewPath))
|
|
19
|
+
await fs_extra_1.default.chmod(gradlewPath, 0o755);
|
|
20
|
+
}
|
|
21
|
+
catch (e) { }
|
|
22
|
+
}
|
|
23
|
+
const steps = this.getBuildSteps(project);
|
|
24
|
+
if (steps.length === 0)
|
|
25
|
+
return;
|
|
26
|
+
for (const step of steps) {
|
|
27
|
+
await this.runStep(project.path, step.cmd, step.args);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
runStep(cwd, cmd, args) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const child = (0, child_process_1.spawn)(cmd, args, {
|
|
33
|
+
cwd,
|
|
34
|
+
shell: false, // Security improvement
|
|
35
|
+
stdio: 'inherit'
|
|
36
|
+
});
|
|
37
|
+
child.on('close', (code) => {
|
|
38
|
+
if (code === 0)
|
|
39
|
+
resolve();
|
|
40
|
+
else
|
|
41
|
+
reject(new Error(`Command "${cmd}" exited with code ${code}`));
|
|
42
|
+
});
|
|
43
|
+
child.on('error', (err) => reject(err));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
getBuildSteps(project) {
|
|
47
|
+
const isWin = process.platform === 'win32';
|
|
48
|
+
const fail = [];
|
|
49
|
+
// Helper for script based tools on Windows
|
|
50
|
+
const cmd = (name) => isWin ? `${name}.cmd` : name;
|
|
51
|
+
const bat = (name) => isWin ? `${name}.bat` : name;
|
|
52
|
+
switch (project.type) {
|
|
53
|
+
// Node.js
|
|
54
|
+
case 'nodejs': return this.getNodeSteps(project.path, isWin);
|
|
55
|
+
// Flutter
|
|
56
|
+
case 'flutter':
|
|
57
|
+
// flutter is usually a batch file on windows or shell script on linux/mac
|
|
58
|
+
// but spawn('flutter') might need '.bat' on windows if shell=false
|
|
59
|
+
return [{ cmd: isWin ? 'flutter.bat' : 'flutter', args: ['pub', 'get'] }];
|
|
60
|
+
// Java
|
|
61
|
+
case 'java-maven':
|
|
62
|
+
return [{ cmd: cmd('mvn'), args: ['clean', 'install', '-DskipTests'] }];
|
|
63
|
+
case 'java-gradle':
|
|
64
|
+
const gradlew = isWin ? 'gradlew.bat' : './gradlew';
|
|
65
|
+
return [{ cmd: gradlew, args: ['build', '-x', 'test'] }];
|
|
66
|
+
// Python
|
|
67
|
+
case 'python':
|
|
68
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'requirements.txt'))) {
|
|
69
|
+
// pip is usually exe on windows
|
|
70
|
+
return [{ cmd: 'pip', args: ['install', '-r', 'requirements.txt'] }];
|
|
71
|
+
}
|
|
72
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'Pipfile'))) {
|
|
73
|
+
return [{ cmd: 'pipenv', args: ['install'] }];
|
|
74
|
+
}
|
|
75
|
+
return fail;
|
|
76
|
+
// Rust
|
|
77
|
+
case 'rust': return [{ cmd: 'cargo', args: ['build'] }];
|
|
78
|
+
// .NET
|
|
79
|
+
case 'dotnet': return [{ cmd: 'dotnet', args: ['build'] }];
|
|
80
|
+
// PHP
|
|
81
|
+
case 'php': return [{ cmd: cmd('composer'), args: ['install'] }];
|
|
82
|
+
// Swift
|
|
83
|
+
case 'swift':
|
|
84
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(project.path, 'Podfile'))) {
|
|
85
|
+
return [{ cmd: 'pod', args: ['install'] }];
|
|
86
|
+
}
|
|
87
|
+
return [{ cmd: 'swift', args: ['build'] }];
|
|
88
|
+
default: return fail;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
getNodeSteps(projectPath, isWin) {
|
|
92
|
+
const cmd = (name) => isWin ? `${name}.cmd` : name;
|
|
93
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(projectPath, 'pnpm-lock.yaml'))) {
|
|
94
|
+
return [
|
|
95
|
+
{ cmd: cmd('pnpm'), args: ['install'] },
|
|
96
|
+
{ cmd: cmd('pnpm'), args: ['build'] }
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(projectPath, 'yarn.lock'))) {
|
|
100
|
+
return [
|
|
101
|
+
{ cmd: cmd('yarn'), args: ['install'] },
|
|
102
|
+
{ cmd: cmd('yarn'), args: ['build'] }
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
return [
|
|
106
|
+
{ cmd: cmd('npm'), args: ['install'] },
|
|
107
|
+
{ cmd: cmd('npm'), args: ['run', 'build'] }
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.Builder = Builder;
|
|
112
|
+
exports.default = new Builder();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/Cleaner.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.Cleaner = void 0;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const util_1 = __importDefault(require("util"));
|
|
13
|
+
const execAsync = util_1.default.promisify(child_process_1.exec);
|
|
14
|
+
class Cleaner {
|
|
15
|
+
constructor() {
|
|
16
|
+
// Define what to delete
|
|
17
|
+
this.targetFolders = {
|
|
18
|
+
// Old
|
|
19
|
+
'nodejs': ['node_modules', 'dist', '.next', '.nuxt', 'coverage', '.turbo'],
|
|
20
|
+
'flutter': ['build', '.dart_tool', 'android/.gradle', 'ios/Pods'],
|
|
21
|
+
'java-maven': ['target'],
|
|
22
|
+
'java-gradle': ['build', '.gradle'],
|
|
23
|
+
// Newly Added
|
|
24
|
+
'python': ['__pycache__', 'venv', '.venv', 'env', 'build', 'dist', '.pytest_cache', '*.egg-info'],
|
|
25
|
+
'rust': ['target'],
|
|
26
|
+
'dotnet': ['bin', 'obj'],
|
|
27
|
+
'php': ['vendor'],
|
|
28
|
+
'swift': ['Pods', 'build', 'DerivedData', '.build']
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async calculateSize(project) {
|
|
32
|
+
const targets = this.targetFolders[project.type] || [];
|
|
33
|
+
let totalSize = 0;
|
|
34
|
+
for (const folder of targets) {
|
|
35
|
+
const folderPath = path_1.default.join(project.path, folder);
|
|
36
|
+
if (fs_extra_1.default.existsSync(folderPath)) {
|
|
37
|
+
totalSize += await this.getFolderSize(folderPath);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return parseFloat((totalSize / 1024 / 1024).toFixed(2));
|
|
41
|
+
}
|
|
42
|
+
async getFolderSize(dirPath) {
|
|
43
|
+
// Optimization: Use 'du' on Linux/macOS
|
|
44
|
+
if (process.platform !== 'win32') {
|
|
45
|
+
try {
|
|
46
|
+
// -s: summary, -k: kilobytes (1024 bytes blocks)
|
|
47
|
+
const { stdout } = await execAsync(`du -sk "${dirPath}"`);
|
|
48
|
+
const match = stdout.match(/^(\d+)/);
|
|
49
|
+
if (match && match[1]) {
|
|
50
|
+
// Convert KB to Bytes
|
|
51
|
+
return parseInt(match[1], 10) * 1024;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
// Fallback to recursive method if 'du' fails
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return this.getFolderSizeRecursive(dirPath);
|
|
59
|
+
}
|
|
60
|
+
async getFolderSizeRecursive(dirPath) {
|
|
61
|
+
let size = 0;
|
|
62
|
+
try {
|
|
63
|
+
const files = await fs_extra_1.default.readdir(dirPath);
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
const filePath = path_1.default.join(dirPath, file);
|
|
66
|
+
const stats = await fs_extra_1.default.stat(filePath);
|
|
67
|
+
if (stats.isDirectory()) {
|
|
68
|
+
size += await this.getFolderSizeRecursive(filePath);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
size += stats.size;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
console.warn(chalk_1.default.yellow(`\n[WARNING] Clean warning (Size Calc): ${dirPath} - ${e.message}`));
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
return size;
|
|
80
|
+
}
|
|
81
|
+
async clean(project, options = {}) {
|
|
82
|
+
const targets = this.targetFolders[project.type];
|
|
83
|
+
if (!targets)
|
|
84
|
+
return false;
|
|
85
|
+
const isDryRun = options.dryRun === true;
|
|
86
|
+
if (isDryRun) {
|
|
87
|
+
console.log(chalk_1.default.blue(`\n[DRY RUN] Simulating clean for ${project.name}:`));
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
for (const folder of targets) {
|
|
91
|
+
const targetPath = path_1.default.join(project.path, folder);
|
|
92
|
+
if (fs_extra_1.default.existsSync(targetPath)) {
|
|
93
|
+
if (isDryRun) {
|
|
94
|
+
const size = await this.getFolderSize(targetPath);
|
|
95
|
+
const sizeStr = (size / 1024 / 1024).toFixed(2);
|
|
96
|
+
console.log(chalk_1.default.gray(` - Would delete: ${folder} (~${sizeStr} MB)`));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
await fs_extra_1.default.remove(targetPath);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
console.error(chalk_1.default.red(`Clean error (${project.name}):`), error);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.Cleaner = Cleaner;
|
|
112
|
+
exports.default = new Cleaner();
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/core/ConfigManager.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.ConfigManager = void 0;
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const os_1 = __importDefault(require("os"));
|
|
11
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
+
const child_process_1 = require("child_process");
|
|
13
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.cryo');
|
|
14
|
+
const PROFILES_DIR = path_1.default.join(CONFIG_DIR, 'profiles');
|
|
15
|
+
class ConfigManager {
|
|
16
|
+
constructor() {
|
|
17
|
+
// Ensure directories exist
|
|
18
|
+
fs_extra_1.default.ensureDirSync(PROFILES_DIR);
|
|
19
|
+
}
|
|
20
|
+
getSafeName(name) {
|
|
21
|
+
return name.replace(/[^a-z0-9]/gi, '-').toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
getProfile(profileName = 'default') {
|
|
24
|
+
const safeName = this.getSafeName(profileName);
|
|
25
|
+
const profilePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
|
|
26
|
+
if (!fs_extra_1.default.existsSync(profilePath)) {
|
|
27
|
+
if (profileName === 'default') {
|
|
28
|
+
return this.createDefaultProfile();
|
|
29
|
+
}
|
|
30
|
+
throw new Error(`Profile not found: ${profileName}`);
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return fs_extra_1.default.readJsonSync(profilePath);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
console.error(chalk_1.default.red('Config file unreachable or corrupted!'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
createDefaultProfile() {
|
|
41
|
+
const defaultConfig = {
|
|
42
|
+
profileName: "Default",
|
|
43
|
+
scanPaths: [process.cwd()],
|
|
44
|
+
ignoredPaths: [],
|
|
45
|
+
archivePath: path_1.default.join(os_1.default.homedir(), 'Cryo_Archives')
|
|
46
|
+
};
|
|
47
|
+
const filePath = path_1.default.join(PROFILES_DIR, 'default.json');
|
|
48
|
+
fs_extra_1.default.writeJsonSync(filePath, defaultConfig, { spaces: 2 });
|
|
49
|
+
console.log(chalk_1.default.green(' Default profile (~/.cryo/profiles/default.json) created.'));
|
|
50
|
+
return defaultConfig;
|
|
51
|
+
}
|
|
52
|
+
listProfiles() {
|
|
53
|
+
if (!fs_extra_1.default.existsSync(PROFILES_DIR))
|
|
54
|
+
return [];
|
|
55
|
+
return fs_extra_1.default.readdirSync(PROFILES_DIR)
|
|
56
|
+
.filter(file => file.endsWith('.json'))
|
|
57
|
+
.map(file => file.replace('.json', ''));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Saves a new profile to disk.
|
|
61
|
+
*/
|
|
62
|
+
saveProfile(profileName, config) {
|
|
63
|
+
// Clean up the file name (replace spaces with hyphens, remove invalid characters)
|
|
64
|
+
const safeName = this.getSafeName(profileName);
|
|
65
|
+
const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
|
|
66
|
+
try {
|
|
67
|
+
fs_extra_1.default.writeJsonSync(filePath, config, { spaces: 2 });
|
|
68
|
+
console.log(chalk_1.default.green(` Profile created successfully: ${chalk_1.default.bold(filePath)}`));
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(chalk_1.default.red(`Error saving profile: ${error.message}`));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
editProfile(profileName) {
|
|
75
|
+
const safeName = this.getSafeName(profileName);
|
|
76
|
+
const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
|
|
77
|
+
if (!fs_extra_1.default.existsSync(filePath)) {
|
|
78
|
+
console.log(chalk_1.default.red(`Error: Profile ${profileName} not found.`));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk_1.default.yellow(` Opening config file: ${filePath}`));
|
|
82
|
+
// Command based on OS
|
|
83
|
+
let command;
|
|
84
|
+
let args = [filePath];
|
|
85
|
+
switch (process.platform) {
|
|
86
|
+
case 'win32': // Windows
|
|
87
|
+
command = 'notepad';
|
|
88
|
+
break;
|
|
89
|
+
case 'darwin': // Mac
|
|
90
|
+
command = 'open'; // Default editor
|
|
91
|
+
args = ['-t', filePath]; // -t: force text editor
|
|
92
|
+
break;
|
|
93
|
+
default: // Linux
|
|
94
|
+
command = 'xdg-open'; // Default opener
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
// Run command in background
|
|
98
|
+
const child = (0, child_process_1.spawn)(command, args, {
|
|
99
|
+
detached: true,
|
|
100
|
+
stdio: 'ignore'
|
|
101
|
+
});
|
|
102
|
+
child.unref();
|
|
103
|
+
}
|
|
104
|
+
deleteProfile(profileName) {
|
|
105
|
+
const safeName = this.getSafeName(profileName);
|
|
106
|
+
const filePath = path_1.default.join(PROFILES_DIR, `${safeName}.json`);
|
|
107
|
+
if (fs_extra_1.default.existsSync(filePath)) {
|
|
108
|
+
fs_extra_1.default.removeSync(filePath);
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.ConfigManager = ConfigManager;
|
|
115
|
+
exports.default = new ConfigManager();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.EscapableConfirm = exports.EscapableCheckbox = void 0;
|
|
7
|
+
// We need to import the internal ListPrompt class.
|
|
8
|
+
// Since no types are available for internal files, we ignore TS errors.
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
const list_js_1 = __importDefault(require("inquirer/lib/prompts/list.js"));
|
|
11
|
+
class EscapableList extends list_js_1.default {
|
|
12
|
+
_run(cb) {
|
|
13
|
+
// Listen for raw data to get instant ESC detection (bypassing readline's 500ms timeout)
|
|
14
|
+
// @ts-ignore
|
|
15
|
+
const onData = (chunk) => {
|
|
16
|
+
if (chunk[0] === 0x1b && chunk.length === 1) {
|
|
17
|
+
// Remove listener to prevent memory leaks/double triggers
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
this.rl.input.removeListener('data', onData);
|
|
20
|
+
// When ESC is pressed, submit 'back' immediately.
|
|
21
|
+
// @ts-ignore
|
|
22
|
+
this.onSubmit('back');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
this.rl.input.on('data', onData);
|
|
27
|
+
// Also cleanup on finish
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
this.rl.on('close', () => this.rl.input.removeListener('data', onData));
|
|
30
|
+
return super._run(cb);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.default = EscapableList;
|
|
34
|
+
// @ts-ignore
|
|
35
|
+
const checkbox_js_1 = __importDefault(require("inquirer/lib/prompts/checkbox.js"));
|
|
36
|
+
class EscapableCheckbox extends checkbox_js_1.default {
|
|
37
|
+
_run(cb) {
|
|
38
|
+
// @ts-ignore
|
|
39
|
+
const onData = (chunk) => {
|
|
40
|
+
if (chunk[0] === 0x1b && chunk.length === 1) {
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
this.rl.input.removeListener('data', onData);
|
|
43
|
+
// @ts-ignore
|
|
44
|
+
this.onSubmit([]);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
// @ts-ignore
|
|
48
|
+
this.rl.input.on('data', onData);
|
|
49
|
+
// @ts-ignore
|
|
50
|
+
this.rl.on('close', () => this.rl.input.removeListener('data', onData));
|
|
51
|
+
// @ts-ignore
|
|
52
|
+
return super._run(cb);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.EscapableCheckbox = EscapableCheckbox;
|
|
56
|
+
// @ts-ignore
|
|
57
|
+
const confirm_js_1 = __importDefault(require("inquirer/lib/prompts/confirm.js"));
|
|
58
|
+
class EscapableConfirm extends confirm_js_1.default {
|
|
59
|
+
_run(cb) {
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
const onData = (chunk) => {
|
|
62
|
+
if (chunk[0] === 0x1b && chunk.length === 1) {
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
this.rl.input.removeListener('data', onData);
|
|
65
|
+
// @ts-ignore
|
|
66
|
+
this.onSubmit(false);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
this.rl.input.on('data', onData);
|
|
71
|
+
// @ts-ignore
|
|
72
|
+
this.rl.on('close', () => this.rl.input.removeListener('data', onData));
|
|
73
|
+
// @ts-ignore
|
|
74
|
+
return super._run(cb);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.EscapableConfirm = EscapableConfirm;
|