@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/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);