@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/dist/index.js
ADDED
|
@@ -0,0 +1,752 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
|
+
};
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
const commander_1 = require("commander");
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const ora_1 = __importDefault(require("ora"));
|
|
43
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
44
|
+
const figlet_1 = __importDefault(require("figlet"));
|
|
45
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
46
|
+
const path_1 = __importDefault(require("path"));
|
|
47
|
+
const os_1 = __importDefault(require("os"));
|
|
48
|
+
const ConfigManager_1 = __importDefault(require("./core/ConfigManager"));
|
|
49
|
+
const Scanner_1 = __importDefault(require("./core/Scanner"));
|
|
50
|
+
const Cleaner_1 = __importDefault(require("./core/Cleaner"));
|
|
51
|
+
const Builder_1 = __importDefault(require("./core/Builder"));
|
|
52
|
+
const Archiver_1 = __importDefault(require("./core/Archiver"));
|
|
53
|
+
const Prompt_1 = __importStar(require("./core/Prompt"));
|
|
54
|
+
// Register the custom prompt to handle ESC key
|
|
55
|
+
inquirer_1.default.registerPrompt('list', Prompt_1.default);
|
|
56
|
+
inquirer_1.default.registerPrompt('checkbox', Prompt_1.EscapableCheckbox);
|
|
57
|
+
inquirer_1.default.registerPrompt('confirm', Prompt_1.EscapableConfirm);
|
|
58
|
+
// ============================================================
|
|
59
|
+
// CORE LOGIC (Shared between CLI and Interactive)
|
|
60
|
+
// ============================================================
|
|
61
|
+
function showLogo() {
|
|
62
|
+
const logoText = figlet_1.default.textSync('CRYO', { font: 'Slant', horizontalLayout: 'fitted' });
|
|
63
|
+
const lines = logoText.split('\n');
|
|
64
|
+
console.log('');
|
|
65
|
+
lines.forEach((line, i) => {
|
|
66
|
+
let coloredLine = '';
|
|
67
|
+
if (i < 2)
|
|
68
|
+
coloredLine = chalk_1.default.cyan.bold(line);
|
|
69
|
+
else if (i < 4)
|
|
70
|
+
coloredLine = chalk_1.default.blue.bold(line);
|
|
71
|
+
else
|
|
72
|
+
coloredLine = chalk_1.default.blueBright.bold(line);
|
|
73
|
+
if (i === lines.length - 2) {
|
|
74
|
+
coloredLine += chalk_1.default.cyan(' v1.0.0');
|
|
75
|
+
}
|
|
76
|
+
console.log(coloredLine);
|
|
77
|
+
});
|
|
78
|
+
console.log('');
|
|
79
|
+
}
|
|
80
|
+
function showHeader(breadcrumb) {
|
|
81
|
+
console.clear();
|
|
82
|
+
showLogo();
|
|
83
|
+
console.log(' ' + chalk_1.default.blue(' ') + chalk_1.default.dim(breadcrumb));
|
|
84
|
+
//console.log(' ' + chalk.gray('━'.repeat(breadcrumb.length + 3)));
|
|
85
|
+
console.log('');
|
|
86
|
+
}
|
|
87
|
+
async function resolveTarget(target) {
|
|
88
|
+
// 1. Try as a Profile Name
|
|
89
|
+
const profiles = ConfigManager_1.default.listProfiles();
|
|
90
|
+
const safeTarget = target.replace(/[^a-z0-9]/gi, '-').toLowerCase();
|
|
91
|
+
if (profiles.includes(safeTarget)) {
|
|
92
|
+
const config = ConfigManager_1.default.getProfile(safeTarget);
|
|
93
|
+
console.log(chalk_1.default.blue(`Loading profile: ${target}`));
|
|
94
|
+
const projects = await Scanner_1.default.scan(config.scanPaths);
|
|
95
|
+
return { projects, config };
|
|
96
|
+
}
|
|
97
|
+
// 2. Try as a File Path
|
|
98
|
+
const resolvedPath = path_1.default.resolve(target);
|
|
99
|
+
if (fs_extra_1.default.existsSync(resolvedPath)) {
|
|
100
|
+
console.log(chalk_1.default.blue(`Scanning path: ${resolvedPath}`));
|
|
101
|
+
const projects = await Scanner_1.default.scan([resolvedPath]);
|
|
102
|
+
return { projects };
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Target "${target}" is neither a valid profile nor a valid path.`);
|
|
105
|
+
}
|
|
106
|
+
async function runScan(target) {
|
|
107
|
+
const { projects } = await resolveTarget(target);
|
|
108
|
+
console.log(chalk_1.default.green(`\nFound ${projects.length} projects:`));
|
|
109
|
+
projects.forEach(p => {
|
|
110
|
+
console.log(` - ${chalk_1.default.bold(p.name)} (${p.type}) at ${chalk_1.default.gray(p.path)}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
async function runClean(target, options) {
|
|
114
|
+
const { projects } = await resolveTarget(target);
|
|
115
|
+
if (projects.length === 0) {
|
|
116
|
+
console.log(chalk_1.default.yellow('No projects found.'));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const isDryRun = options.dryRun === true;
|
|
120
|
+
if (!options.force && !isDryRun) {
|
|
121
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
122
|
+
type: 'confirm',
|
|
123
|
+
name: 'confirm',
|
|
124
|
+
message: `Are you sure you want to clean ${projects.length} projects?`,
|
|
125
|
+
default: false
|
|
126
|
+
}]);
|
|
127
|
+
if (!confirm) {
|
|
128
|
+
console.log(chalk_1.default.yellow('Operation cancelled.'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (isDryRun) {
|
|
133
|
+
for (const p of projects) {
|
|
134
|
+
await Cleaner_1.default.clean(p, { dryRun: true });
|
|
135
|
+
}
|
|
136
|
+
console.log(chalk_1.default.blue('\n[DRY RUN] Simulation complete. No files were deleted.'));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const spinner = (0, ora_1.default)('Cleaning...').start();
|
|
140
|
+
for (const p of projects) {
|
|
141
|
+
spinner.text = `Cleaning ${p.name}...`;
|
|
142
|
+
await Cleaner_1.default.clean(p);
|
|
143
|
+
}
|
|
144
|
+
spinner.succeed(`Cleaned ${projects.length} projects.`);
|
|
145
|
+
}
|
|
146
|
+
async function runBuild(target) {
|
|
147
|
+
const { projects } = await resolveTarget(target);
|
|
148
|
+
if (projects.length === 0) {
|
|
149
|
+
console.log(chalk_1.default.yellow('No projects found.'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
console.log(chalk_1.default.yellow('\nStarting build process...'));
|
|
153
|
+
const results = { success: [], fail: [] };
|
|
154
|
+
for (const p of projects) {
|
|
155
|
+
console.log(chalk_1.default.blue.bold(`\nBuilding: ${p.name}`));
|
|
156
|
+
try {
|
|
157
|
+
await Builder_1.default.build(p);
|
|
158
|
+
results.success.push(p.name);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
console.log(chalk_1.default.red(`Failed to build ${p.name}`));
|
|
162
|
+
results.fail.push(p.name);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
console.log(chalk_1.default.bold.underline('\nBuild Results:'));
|
|
166
|
+
if (results.success.length > 0)
|
|
167
|
+
console.log(chalk_1.default.green(`Success: ${results.success.join(', ')}`));
|
|
168
|
+
if (results.fail.length > 0)
|
|
169
|
+
console.log(chalk_1.default.red(`Failed: ${results.fail.join(', ')}`));
|
|
170
|
+
}
|
|
171
|
+
async function runBackup(target, options) {
|
|
172
|
+
const { projects, config } = await resolveTarget(target);
|
|
173
|
+
if (projects.length === 0) {
|
|
174
|
+
console.log(chalk_1.default.yellow('No projects found.'));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
// Determine output directory: CLI Flag -> Profile Config -> Default
|
|
178
|
+
let archiveDir = options.out;
|
|
179
|
+
let ignores = [];
|
|
180
|
+
if (!archiveDir && config) {
|
|
181
|
+
archiveDir = config.archivePath;
|
|
182
|
+
ignores = config.ignoredPaths || [];
|
|
183
|
+
}
|
|
184
|
+
if (!archiveDir) {
|
|
185
|
+
archiveDir = path_1.default.join(os_1.default.homedir(), 'Cryo_Archives');
|
|
186
|
+
}
|
|
187
|
+
const spinner = (0, ora_1.default)('Backing up...').start();
|
|
188
|
+
for (const p of projects) {
|
|
189
|
+
spinner.text = `Archiving ${p.name}...`;
|
|
190
|
+
try {
|
|
191
|
+
await Archiver_1.default.freeze(p, archiveDir, ignores);
|
|
192
|
+
}
|
|
193
|
+
catch (e) {
|
|
194
|
+
spinner.fail(`Failed to backup ${p.name}: ${e.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
spinner.succeed(`Backup completed at: ${archiveDir}`);
|
|
198
|
+
}
|
|
199
|
+
// ============================================================
|
|
200
|
+
// INTERACTIVE HELPER FUNCTIONS (Wrapper around Core Logic)
|
|
201
|
+
// ============================================================
|
|
202
|
+
// Helper to select multiple projects via checkbox
|
|
203
|
+
async function selectProjects(projects, actionName, profileName) {
|
|
204
|
+
showHeader(`${actionName} Projects > ${profileName}`);
|
|
205
|
+
const choices = projects.map(p => ({
|
|
206
|
+
name: `${chalk_1.default.bold(p.name)} (${p.type}) - ${chalk_1.default.gray(p.lastModified.toLocaleDateString())}`,
|
|
207
|
+
value: p,
|
|
208
|
+
checked: false
|
|
209
|
+
}));
|
|
210
|
+
const { selected } = await inquirer_1.default.prompt([{
|
|
211
|
+
type: 'checkbox',
|
|
212
|
+
name: 'selected',
|
|
213
|
+
message: 'Select projects:',
|
|
214
|
+
choices: choices,
|
|
215
|
+
pageSize: 20
|
|
216
|
+
}]);
|
|
217
|
+
return selected;
|
|
218
|
+
}
|
|
219
|
+
// Common function to Select Profile -> Scan Projects
|
|
220
|
+
// Returns null if the user goes back or cancels
|
|
221
|
+
async function prepareAndScan(actionLabel) {
|
|
222
|
+
const profiles = ConfigManager_1.default.listProfiles();
|
|
223
|
+
if (profiles.length === 0) {
|
|
224
|
+
console.log(chalk_1.default.red('No profiles found. Please create one in "Profile Settings".'));
|
|
225
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter to continue...' }]);
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
showHeader(actionLabel);
|
|
229
|
+
const { profileName } = await inquirer_1.default.prompt([{
|
|
230
|
+
type: 'list',
|
|
231
|
+
name: 'profileName',
|
|
232
|
+
message: 'Select Profile:',
|
|
233
|
+
choices: [
|
|
234
|
+
{ name: 'Back to Main Menu', value: 'back' },
|
|
235
|
+
new inquirer_1.default.Separator(),
|
|
236
|
+
...profiles,
|
|
237
|
+
new inquirer_1.default.Separator(),
|
|
238
|
+
{ name: 'Back to Main Menu', value: 'back' }
|
|
239
|
+
],
|
|
240
|
+
default: 'default',
|
|
241
|
+
pageSize: 20
|
|
242
|
+
}]);
|
|
243
|
+
if (profileName === 'back') {
|
|
244
|
+
return null; // Go back to main loop
|
|
245
|
+
}
|
|
246
|
+
const config = ConfigManager_1.default.getProfile(profileName);
|
|
247
|
+
const spinner = (0, ora_1.default)(`Scanning: ${config.scanPaths}`).start();
|
|
248
|
+
try {
|
|
249
|
+
const projects = await Scanner_1.default.scan(config.scanPaths);
|
|
250
|
+
spinner.stop();
|
|
251
|
+
if (projects.length === 0) {
|
|
252
|
+
console.log(chalk_1.default.yellow('No projects found in this profile.'));
|
|
253
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter to continue...' }]);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
return { projects, config };
|
|
257
|
+
}
|
|
258
|
+
catch (e) {
|
|
259
|
+
spinner.fail('Scan failed.');
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// ============================================================
|
|
264
|
+
// SINGLE PROJECT MANAGER
|
|
265
|
+
// ============================================================
|
|
266
|
+
async function manageProject(project, config) {
|
|
267
|
+
while (true) {
|
|
268
|
+
showHeader(`List Projects > ${config.profileName} > ${project.name}`);
|
|
269
|
+
console.log(chalk_1.default.gray(` Path: ${project.path}`));
|
|
270
|
+
console.log(chalk_1.default.gray(' ' + '-'.repeat(40)));
|
|
271
|
+
const { action } = await inquirer_1.default.prompt([{
|
|
272
|
+
type: 'list',
|
|
273
|
+
name: 'action',
|
|
274
|
+
message: 'What do you want to do with this project?',
|
|
275
|
+
choices: [
|
|
276
|
+
{ name: 'Clean (Delete Build Folders)', value: 'clean' },
|
|
277
|
+
{ name: 'Preview Clean (Simulation)', value: 'dryrun' },
|
|
278
|
+
{ name: 'Build (Rebuild)', value: 'build' },
|
|
279
|
+
{ name: 'Backup (Archive)', value: 'backup' },
|
|
280
|
+
new inquirer_1.default.Separator(),
|
|
281
|
+
{ name: 'Back to List', value: 'back' }
|
|
282
|
+
]
|
|
283
|
+
}]);
|
|
284
|
+
if (action === 'back')
|
|
285
|
+
break;
|
|
286
|
+
if (action === 'dryrun') {
|
|
287
|
+
await Cleaner_1.default.clean(project, { dryRun: true });
|
|
288
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: '\nPress Enter to continue...' }]);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (action === 'clean') {
|
|
292
|
+
const spinner = (0, ora_1.default)('Cleaning...').start();
|
|
293
|
+
await Cleaner_1.default.clean(project);
|
|
294
|
+
spinner.succeed('Cleaned.');
|
|
295
|
+
project.buildInfo = { exists: false };
|
|
296
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
297
|
+
}
|
|
298
|
+
if (action === 'build') {
|
|
299
|
+
console.log(chalk_1.default.yellow(`\n Starting build process for ${project.name}...`));
|
|
300
|
+
console.log(chalk_1.default.gray('--------------------------------------------------'));
|
|
301
|
+
try {
|
|
302
|
+
await Builder_1.default.build(project);
|
|
303
|
+
console.log(chalk_1.default.gray('--------------------------------------------------'));
|
|
304
|
+
console.log(chalk_1.default.green(`\n ${project.name} built successfully!`));
|
|
305
|
+
project.buildInfo = { exists: true, lastBuilt: new Date(), path: 'new' };
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
console.log(chalk_1.default.gray('--------------------------------------------------'));
|
|
309
|
+
console.log(chalk_1.default.red(`\n Build failed.`));
|
|
310
|
+
}
|
|
311
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter...' }]);
|
|
312
|
+
}
|
|
313
|
+
if (action === 'backup') {
|
|
314
|
+
const spinner = (0, ora_1.default)('Backing up...').start();
|
|
315
|
+
try {
|
|
316
|
+
await Archiver_1.default.freeze(project, config.archivePath, config.ignoredPaths);
|
|
317
|
+
spinner.succeed(`Backup created at: ${config.archivePath}`);
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
spinner.fail(`Backup failed: ${e.message}`);
|
|
321
|
+
}
|
|
322
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter...' }]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// ============================================================
|
|
327
|
+
// INTERACTIVE ACTIONS
|
|
328
|
+
// ============================================================
|
|
329
|
+
async function actionList(projects, config) {
|
|
330
|
+
// Column Widths
|
|
331
|
+
const colDate = 12;
|
|
332
|
+
const colBuild = 30;
|
|
333
|
+
const colType = 12;
|
|
334
|
+
while (true) {
|
|
335
|
+
showHeader(`List Projects > ${config.profileName}`);
|
|
336
|
+
// Header row for the list
|
|
337
|
+
console.log(' ' +
|
|
338
|
+
chalk_1.default.gray('Date'.padEnd(colDate)) +
|
|
339
|
+
chalk_1.default.gray('Build Status'.padEnd(colBuild)) +
|
|
340
|
+
chalk_1.default.gray('Type'.padEnd(colType)) +
|
|
341
|
+
chalk_1.default.gray('Project Name'));
|
|
342
|
+
console.log(chalk_1.default.gray(' ' + '-'.repeat(70)));
|
|
343
|
+
// 1. Create Project List
|
|
344
|
+
const projectChoices = projects.map(p => {
|
|
345
|
+
const dateStr = p.lastModified.toLocaleDateString().padEnd(colDate);
|
|
346
|
+
let buildStr = 'None'.padEnd(colBuild);
|
|
347
|
+
if (p.buildInfo?.exists && p.buildInfo.lastBuilt) {
|
|
348
|
+
const bDate = p.buildInfo.lastBuilt.toLocaleDateString();
|
|
349
|
+
buildStr = `${bDate} (${p.buildInfo.path})`.padEnd(colBuild);
|
|
350
|
+
}
|
|
351
|
+
// Create plain text first for alignment, then colorize
|
|
352
|
+
const typeStr = p.type.padEnd(colType);
|
|
353
|
+
// Apply colors to the padded strings
|
|
354
|
+
const coloredType = p.type === 'nodejs' ? chalk_1.default.cyan(typeStr) :
|
|
355
|
+
p.type === 'flutter' ? chalk_1.default.blue(typeStr) :
|
|
356
|
+
p.type.includes('java') ? chalk_1.default.magenta(typeStr) : chalk_1.default.white(typeStr);
|
|
357
|
+
const coloredBuild = buildStr.trim() === 'None' ? chalk_1.default.red(buildStr) : chalk_1.default.green(buildStr);
|
|
358
|
+
return {
|
|
359
|
+
name: `${chalk_1.default.gray(dateStr)}${coloredBuild}${coloredType}${chalk_1.default.bold(p.name)} ${chalk_1.default.gray('(' + p.path + ')')}`,
|
|
360
|
+
value: p
|
|
361
|
+
};
|
|
362
|
+
});
|
|
363
|
+
// 2. Merge Options (Back at top and bottom)
|
|
364
|
+
const allChoices = [
|
|
365
|
+
{ name: ' .. Back to Main Menu', value: 'back' },
|
|
366
|
+
new inquirer_1.default.Separator(),
|
|
367
|
+
...projectChoices,
|
|
368
|
+
new inquirer_1.default.Separator(),
|
|
369
|
+
{ name: ' .. Back to Main Menu', value: 'back' }
|
|
370
|
+
];
|
|
371
|
+
const { selectedProject } = await inquirer_1.default.prompt([{
|
|
372
|
+
type: 'list',
|
|
373
|
+
name: 'selectedProject',
|
|
374
|
+
message: 'Select a project to manage (or Go Back):',
|
|
375
|
+
choices: allChoices,
|
|
376
|
+
pageSize: 20,
|
|
377
|
+
}]);
|
|
378
|
+
if (selectedProject === 'back') {
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
await manageProject(selectedProject, config);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
async function actionClean(projects, profileName) {
|
|
385
|
+
const selected = await selectProjects(projects, 'Clean', profileName);
|
|
386
|
+
if (selected.length === 0)
|
|
387
|
+
return;
|
|
388
|
+
const { mode } = await inquirer_1.default.prompt([{
|
|
389
|
+
type: 'list',
|
|
390
|
+
name: 'mode',
|
|
391
|
+
message: `You selected ${selected.length} projects. How to proceed?`,
|
|
392
|
+
choices: [
|
|
393
|
+
{ name: '🚀 Clean Projects NOW', value: 'real' },
|
|
394
|
+
{ name: '🔍 Preview Simulation (Dry Run)', value: 'dry' },
|
|
395
|
+
{ name: '❌ Cancel', value: 'cancel' }
|
|
396
|
+
]
|
|
397
|
+
}]);
|
|
398
|
+
if (mode === 'cancel')
|
|
399
|
+
return;
|
|
400
|
+
if (mode === 'dry') {
|
|
401
|
+
showHeader(`Clean Projects > ${profileName} > Preview`);
|
|
402
|
+
for (const p of selected) {
|
|
403
|
+
await Cleaner_1.default.clean(p, { dryRun: true });
|
|
404
|
+
}
|
|
405
|
+
console.log(chalk_1.default.blue('\n[DRY RUN] Simulation complete. No files were deleted.'));
|
|
406
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter to continue...' }]);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const spinner = (0, ora_1.default)('Cleaning...').start();
|
|
410
|
+
for (const p of selected) {
|
|
411
|
+
spinner.text = `Cleaning ${p.name}...`;
|
|
412
|
+
await Cleaner_1.default.clean(p);
|
|
413
|
+
}
|
|
414
|
+
spinner.succeed('Cleaning completed.');
|
|
415
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
416
|
+
}
|
|
417
|
+
async function actionBuild(projects, profileName) {
|
|
418
|
+
const selected = await selectProjects(projects, 'Build', profileName);
|
|
419
|
+
if (selected.length === 0)
|
|
420
|
+
return;
|
|
421
|
+
console.log(chalk_1.default.yellow('\n Streaming build output to terminal...\n'));
|
|
422
|
+
const results = { success: [], fail: [] };
|
|
423
|
+
for (const p of selected) {
|
|
424
|
+
console.log(chalk_1.default.blue.bold(`\n Building: ${p.name}`));
|
|
425
|
+
console.log(chalk_1.default.gray(`Path: ${p.path}`));
|
|
426
|
+
console.log(chalk_1.default.gray('...'));
|
|
427
|
+
try {
|
|
428
|
+
await Builder_1.default.build(p);
|
|
429
|
+
results.success.push(p.name);
|
|
430
|
+
}
|
|
431
|
+
catch (e) {
|
|
432
|
+
console.log(chalk_1.default.red(` Failed to build ${p.name}`));
|
|
433
|
+
results.fail.push(p.name);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
console.log(chalk_1.default.bold.underline('\n Build Results:'));
|
|
437
|
+
if (results.success.length > 0)
|
|
438
|
+
console.log(chalk_1.default.green(` Success: `) + results.success.join(', '));
|
|
439
|
+
if (results.fail.length > 0)
|
|
440
|
+
console.log(chalk_1.default.red(` Failed: `) + results.fail.join(', '));
|
|
441
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'enter', message: 'Press Enter to continue...' }]);
|
|
442
|
+
}
|
|
443
|
+
async function actionBackup(projects, profileName, archivePath, ignoredPaths) {
|
|
444
|
+
const selected = await selectProjects(projects, 'Backup', profileName);
|
|
445
|
+
if (selected.length === 0)
|
|
446
|
+
return;
|
|
447
|
+
const spinner = (0, ora_1.default)('Backing up...').start();
|
|
448
|
+
const archiveDir = archivePath;
|
|
449
|
+
const ignores = ignoredPaths;
|
|
450
|
+
for (const p of selected) {
|
|
451
|
+
spinner.text = `Archiving ${p.name}...`;
|
|
452
|
+
try {
|
|
453
|
+
await Archiver_1.default.freeze(p, archiveDir, ignores);
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
spinner.fail(`Failed to backup ${p.name}: ${e.message}`);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
spinner.succeed(`Backup completed at: ${archiveDir}`);
|
|
460
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
461
|
+
}
|
|
462
|
+
async function runListProfiles() {
|
|
463
|
+
const profiles = ConfigManager_1.default.listProfiles();
|
|
464
|
+
console.log(chalk_1.default.blue('\nSaved Profiles:'));
|
|
465
|
+
if (profiles.length === 0) {
|
|
466
|
+
console.log(chalk_1.default.gray(' - No profiles found.'));
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
profiles.forEach(p => {
|
|
470
|
+
console.log(` ${chalk_1.default.blue('')} ${p}`);
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
console.log('');
|
|
474
|
+
}
|
|
475
|
+
async function runShowProfile(name) {
|
|
476
|
+
try {
|
|
477
|
+
const config = ConfigManager_1.default.getProfile(name);
|
|
478
|
+
console.log(chalk_1.default.blue(`\nProfile: ${chalk_1.default.bold(config.profileName)}`));
|
|
479
|
+
console.log(chalk_1.default.gray(' ' + '-'.repeat(20)));
|
|
480
|
+
console.log(chalk_1.default.white(` Scan Paths: `) + chalk_1.default.cyan(config.scanPaths.join(', ')));
|
|
481
|
+
console.log(chalk_1.default.white(` Ignored Paths: `) + chalk_1.default.cyan(config.ignoredPaths.length > 0 ? config.ignoredPaths.join(', ') : 'None'));
|
|
482
|
+
console.log(chalk_1.default.white(` Archive Path: `) + chalk_1.default.cyan(config.archivePath));
|
|
483
|
+
console.log('');
|
|
484
|
+
}
|
|
485
|
+
catch (e) {
|
|
486
|
+
console.error(chalk_1.default.red(`Error: ${e.message}`));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
async function runCreateProfile(name, options) {
|
|
490
|
+
const scanPaths = options.paths.split(',').map(p => path_1.default.resolve(p.trim()));
|
|
491
|
+
const ignoredPaths = options.ignore ? options.ignore.split(',').map(i => i.trim()) : [];
|
|
492
|
+
const archivePath = options.out ? path_1.default.resolve(options.out) : path_1.default.join(os_1.default.homedir(), 'Cryo_Archives');
|
|
493
|
+
const config = {
|
|
494
|
+
profileName: name,
|
|
495
|
+
scanPaths,
|
|
496
|
+
ignoredPaths,
|
|
497
|
+
archivePath
|
|
498
|
+
};
|
|
499
|
+
ConfigManager_1.default.saveProfile(name, config);
|
|
500
|
+
}
|
|
501
|
+
async function runDeleteProfile(name) {
|
|
502
|
+
const { confirm } = await inquirer_1.default.prompt([{
|
|
503
|
+
type: 'confirm',
|
|
504
|
+
name: 'confirm',
|
|
505
|
+
message: `Are you sure you want to delete profile "${name}"?`,
|
|
506
|
+
default: false
|
|
507
|
+
}]);
|
|
508
|
+
if (!confirm) {
|
|
509
|
+
console.log(chalk_1.default.yellow('Cancelled.'));
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
if (ConfigManager_1.default.deleteProfile(name)) {
|
|
513
|
+
console.log(chalk_1.default.green(`Profile "${name}" deleted.`));
|
|
514
|
+
}
|
|
515
|
+
else {
|
|
516
|
+
console.log(chalk_1.default.red(`Profile "${name}" not found.`));
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
// ============================================================
|
|
520
|
+
// PROFILE MANAGEMENT MENU
|
|
521
|
+
// ============================================================
|
|
522
|
+
async function menuProfileOperations() {
|
|
523
|
+
while (true) {
|
|
524
|
+
showHeader('Profile Settings');
|
|
525
|
+
const { op } = await inquirer_1.default.prompt([{
|
|
526
|
+
type: 'list',
|
|
527
|
+
name: 'op',
|
|
528
|
+
message: 'Main Menu > Profile Settings',
|
|
529
|
+
choices: [
|
|
530
|
+
{ name: ' List & View Profiles', value: 'list' },
|
|
531
|
+
{ name: ' Create New Profile', value: 'create' },
|
|
532
|
+
{ name: ' Delete Profile', value: 'delete' },
|
|
533
|
+
new inquirer_1.default.Separator(),
|
|
534
|
+
{ name: ' Back to Main Menu', value: 'back' }
|
|
535
|
+
],
|
|
536
|
+
pageSize: 20
|
|
537
|
+
}]);
|
|
538
|
+
if (op === 'back')
|
|
539
|
+
break;
|
|
540
|
+
if (op === 'list') {
|
|
541
|
+
const profiles = ConfigManager_1.default.listProfiles();
|
|
542
|
+
const { viewTarget } = await inquirer_1.default.prompt([{
|
|
543
|
+
type: 'list',
|
|
544
|
+
name: 'viewTarget',
|
|
545
|
+
message: 'Select a profile to view details:',
|
|
546
|
+
choices: [
|
|
547
|
+
{ name: ' Back', value: 'back' },
|
|
548
|
+
new inquirer_1.default.Separator(),
|
|
549
|
+
...profiles,
|
|
550
|
+
new inquirer_1.default.Separator(),
|
|
551
|
+
{ name: ' Back', value: 'back' }
|
|
552
|
+
],
|
|
553
|
+
pageSize: 20
|
|
554
|
+
}]);
|
|
555
|
+
if (viewTarget !== 'back') {
|
|
556
|
+
const config = ConfigManager_1.default.getProfile(viewTarget);
|
|
557
|
+
console.log(chalk_1.default.bold.cyan(`\n Profile Details: [${viewTarget}]`));
|
|
558
|
+
console.log(chalk_1.default.gray('--------------------------------------------------'));
|
|
559
|
+
console.log(JSON.stringify(config, null, 2));
|
|
560
|
+
console.log(chalk_1.default.gray('--------------------------------------------------'));
|
|
561
|
+
await inquirer_1.default.prompt([{ type: 'input', name: 'wait', message: 'Press Enter to continue...' }]);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (op === 'create') {
|
|
565
|
+
const answers = await inquirer_1.default.prompt([
|
|
566
|
+
{ type: 'input', name: 'name', message: 'Profile Name:', validate: i => i.length > 0 || 'Required' },
|
|
567
|
+
{ type: 'input', name: 'path', message: 'Scan Path (Full Path):', default: process.cwd(), validate: i => fs_extra_1.default.existsSync(i) || 'Folder not found.' },
|
|
568
|
+
{ type: 'input', name: 'ignores', message: 'Ignore patterns for backup:', default: '' }
|
|
569
|
+
]);
|
|
570
|
+
const ignoreList = answers.ignores.split(',').map((item) => item.trim()).filter((item) => item.length > 0);
|
|
571
|
+
const newConfig = { profileName: answers.name, scanPaths: [answers.path], ignoredPaths: ignoreList, archivePath: path_1.default.join(os_1.default.homedir(), 'Cryo_Archives') };
|
|
572
|
+
ConfigManager_1.default.saveProfile(answers.name, newConfig);
|
|
573
|
+
console.log(chalk_1.default.green('Profile created successfully.'));
|
|
574
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
575
|
+
}
|
|
576
|
+
if (op === 'delete') {
|
|
577
|
+
const profiles = ConfigManager_1.default.listProfiles();
|
|
578
|
+
const { target } = await inquirer_1.default.prompt([{
|
|
579
|
+
type: 'list',
|
|
580
|
+
name: 'target',
|
|
581
|
+
message: 'Select profile to delete:',
|
|
582
|
+
choices: [
|
|
583
|
+
{ name: ' Back', value: 'back' },
|
|
584
|
+
new inquirer_1.default.Separator(),
|
|
585
|
+
...profiles,
|
|
586
|
+
new inquirer_1.default.Separator(),
|
|
587
|
+
{ name: ' Back', value: 'back' }
|
|
588
|
+
],
|
|
589
|
+
pageSize: 20
|
|
590
|
+
}]);
|
|
591
|
+
if (target === 'back') {
|
|
592
|
+
// Do nothing, loop back
|
|
593
|
+
}
|
|
594
|
+
else if (target === 'default') {
|
|
595
|
+
console.log(chalk_1.default.red('Default profile cannot be deleted!'));
|
|
596
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
const pPath = path_1.default.join(os_1.default.homedir(), '.cryo', 'profiles', `${target}.json`);
|
|
600
|
+
await fs_extra_1.default.remove(pPath);
|
|
601
|
+
console.log(chalk_1.default.green('Profile deleted.'));
|
|
602
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
// ============================================================
|
|
608
|
+
// MAIN INTERACTIVE LOOP
|
|
609
|
+
// ============================================================
|
|
610
|
+
async function interactiveMain() {
|
|
611
|
+
while (true) {
|
|
612
|
+
console.clear();
|
|
613
|
+
showLogo();
|
|
614
|
+
const { mainMenuAction } = await inquirer_1.default.prompt([{
|
|
615
|
+
type: 'list',
|
|
616
|
+
name: 'mainMenuAction',
|
|
617
|
+
message: 'Main Menu',
|
|
618
|
+
choices: [
|
|
619
|
+
{ name: ' List Projects', value: 'list' },
|
|
620
|
+
{ name: ' Clean Projects', value: 'clean' },
|
|
621
|
+
{ name: ' Build Projects', value: 'build' },
|
|
622
|
+
{ name: ' Backup Projects', value: 'backup' },
|
|
623
|
+
{ name: ' Profile Settings', value: 'profiles' },
|
|
624
|
+
new inquirer_1.default.Separator(),
|
|
625
|
+
{ name: ' Exit', value: 'exit' }
|
|
626
|
+
]
|
|
627
|
+
}]);
|
|
628
|
+
if (mainMenuAction === 'exit' || mainMenuAction === 'back') {
|
|
629
|
+
console.log(chalk_1.default.gray('Goodbye! '));
|
|
630
|
+
process.exit(0);
|
|
631
|
+
}
|
|
632
|
+
if (mainMenuAction === 'profiles') {
|
|
633
|
+
await menuProfileOperations();
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const labels = {
|
|
637
|
+
'list': 'List Projects',
|
|
638
|
+
'clean': 'Clean Projects',
|
|
639
|
+
'build': 'Build Projects',
|
|
640
|
+
'backup': 'Backup Projects'
|
|
641
|
+
};
|
|
642
|
+
const data = await prepareAndScan(labels[mainMenuAction]);
|
|
643
|
+
if (!data)
|
|
644
|
+
continue;
|
|
645
|
+
const { projects, config } = data;
|
|
646
|
+
switch (mainMenuAction) {
|
|
647
|
+
case 'list':
|
|
648
|
+
await actionList(projects, config);
|
|
649
|
+
break;
|
|
650
|
+
case 'clean':
|
|
651
|
+
await actionClean(projects, config.profileName);
|
|
652
|
+
break;
|
|
653
|
+
case 'build':
|
|
654
|
+
await actionBuild(projects, config.profileName);
|
|
655
|
+
break;
|
|
656
|
+
case 'backup':
|
|
657
|
+
await actionBackup(projects, config.profileName, config.archivePath, config.ignoredPaths);
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
// ============================================================
|
|
663
|
+
// COMMANDER CONFIGURATION
|
|
664
|
+
// ============================================================
|
|
665
|
+
const program = new commander_1.Command();
|
|
666
|
+
program
|
|
667
|
+
.name('cryo')
|
|
668
|
+
.description('Smart project cleaner and archiver')
|
|
669
|
+
.version('1.0.0');
|
|
670
|
+
// Default Action (Interactive Mode)
|
|
671
|
+
program
|
|
672
|
+
.action(() => {
|
|
673
|
+
interactiveMain();
|
|
674
|
+
});
|
|
675
|
+
// CLI Commands
|
|
676
|
+
const profileCmd = program.command('profiles')
|
|
677
|
+
.description('Manage saved profiles');
|
|
678
|
+
profileCmd.command('list')
|
|
679
|
+
.description('List all saved profiles')
|
|
680
|
+
.action(async () => {
|
|
681
|
+
await runListProfiles();
|
|
682
|
+
});
|
|
683
|
+
profileCmd.command('show <name>')
|
|
684
|
+
.description('Show profile details')
|
|
685
|
+
.action(async (name) => {
|
|
686
|
+
await runShowProfile(name);
|
|
687
|
+
});
|
|
688
|
+
profileCmd.command('create <name>')
|
|
689
|
+
.description('Create a new profile')
|
|
690
|
+
.requiredOption('-p, --paths <paths>', 'Comma-separated scan paths')
|
|
691
|
+
.option('-i, --ignore <patterns>', 'Comma-separated ignore patterns')
|
|
692
|
+
.option('-o, --out <path>', 'Custom archive path')
|
|
693
|
+
.action(async (name, options) => {
|
|
694
|
+
await runCreateProfile(name, options);
|
|
695
|
+
});
|
|
696
|
+
profileCmd.command('delete <name>')
|
|
697
|
+
.description('Delete a profile')
|
|
698
|
+
.action(async (name) => {
|
|
699
|
+
await runDeleteProfile(name);
|
|
700
|
+
});
|
|
701
|
+
program.command('scan')
|
|
702
|
+
.argument('<target>', 'Profile name or File path')
|
|
703
|
+
.description('Scan and list projects in target')
|
|
704
|
+
.action(async (target) => {
|
|
705
|
+
try {
|
|
706
|
+
await runScan(target);
|
|
707
|
+
}
|
|
708
|
+
catch (e) {
|
|
709
|
+
console.error(chalk_1.default.red(e.message));
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
program.command('clean')
|
|
714
|
+
.argument('<target>', 'Profile name or File path')
|
|
715
|
+
.option('-f, --force', 'Skip confirmation', false)
|
|
716
|
+
.option('-d, --dry-run', 'Simulation mode (no deletion)', false)
|
|
717
|
+
.description('Clean projects in target')
|
|
718
|
+
.action(async (target, options) => {
|
|
719
|
+
try {
|
|
720
|
+
await runClean(target, options);
|
|
721
|
+
}
|
|
722
|
+
catch (e) {
|
|
723
|
+
console.error(chalk_1.default.red(e.message));
|
|
724
|
+
process.exit(1);
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
program.command('build')
|
|
728
|
+
.argument('<target>', 'Profile name or File path')
|
|
729
|
+
.description('Build projects in target')
|
|
730
|
+
.action(async (target) => {
|
|
731
|
+
try {
|
|
732
|
+
await runBuild(target);
|
|
733
|
+
}
|
|
734
|
+
catch (e) {
|
|
735
|
+
console.error(chalk_1.default.red(e.message));
|
|
736
|
+
process.exit(1);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
program.command('backup')
|
|
740
|
+
.argument('<target>', 'Profile name or File path')
|
|
741
|
+
.option('-o, --out <path>', 'Output directory for archives')
|
|
742
|
+
.description('Backup projects in target')
|
|
743
|
+
.action(async (target, options) => {
|
|
744
|
+
try {
|
|
745
|
+
await runBackup(target, options);
|
|
746
|
+
}
|
|
747
|
+
catch (e) {
|
|
748
|
+
console.error(chalk_1.default.red(e.message));
|
|
749
|
+
process.exit(1);
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
program.parse(process.argv);
|