@meltstudio/meltctl 2.1.0 โ 2.2.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/README.md +12 -0
- package/dist/commands/project/clean.d.ts +5 -0
- package/dist/commands/project/clean.js +239 -0
- package/dist/index.js +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,18 @@ Updates your project templates to the latest version. This command:
|
|
|
70
70
|
- Verifies .melt/ workspace migration status
|
|
71
71
|
- Handles version compatibility automatically
|
|
72
72
|
|
|
73
|
+
### Clean Project
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
meltctl project clean
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Safely removes all Melt-generated files from your project while preserving user-created content. This command:
|
|
80
|
+
- Removes the entire `.melt/` directory (all Melt-generated content)
|
|
81
|
+
- Selectively removes only Melt commands from `.cursor/commands/`
|
|
82
|
+
- Preserves user-created files in `.cursor/commands/`
|
|
83
|
+
- Provides interactive confirmation before deletion
|
|
84
|
+
|
|
73
85
|
## ๐ ๏ธ Requirements
|
|
74
86
|
|
|
75
87
|
- Node.js 22+ (works with Node.js 18+ but 22+ recommended)
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { intro, outro, confirm, spinner } from '@clack/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
// List of Melt-generated command files to remove
|
|
6
|
+
const MELT_COMMAND_FILES = [
|
|
7
|
+
'melt-plan.md',
|
|
8
|
+
'melt-test-plan.md',
|
|
9
|
+
'melt-docs.md',
|
|
10
|
+
'melt-implement.md',
|
|
11
|
+
'melt-pr.md',
|
|
12
|
+
'melt-review.md',
|
|
13
|
+
'melt-complete.md',
|
|
14
|
+
'melt-debug.md',
|
|
15
|
+
];
|
|
16
|
+
export async function cleanCommand(_options = {}) {
|
|
17
|
+
intro(chalk.blue('๐งน Melt Project - Clean'));
|
|
18
|
+
const currentDir = process.cwd();
|
|
19
|
+
const meltDir = path.join(currentDir, '.melt');
|
|
20
|
+
const cursorCommandsDir = path.join(currentDir, '.cursor', 'commands');
|
|
21
|
+
// Check if this is a Melt workspace
|
|
22
|
+
const hasMeltWorkspace = await fs.pathExists(meltDir);
|
|
23
|
+
const hasCursorCommands = await fs.pathExists(cursorCommandsDir);
|
|
24
|
+
if (!hasMeltWorkspace && !hasCursorCommands) {
|
|
25
|
+
console.log(chalk.yellow('โ ๏ธ No Melt workspace found in this directory.'));
|
|
26
|
+
console.log("This project doesn't appear to have Melt tools installed.");
|
|
27
|
+
outro(chalk.gray('Nothing to clean.'));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Analyze what will be cleaned
|
|
31
|
+
const analysisResult = await analyzeCleanupTarget(currentDir);
|
|
32
|
+
if (analysisResult.removedFiles.length === 0 && analysisResult.removedDirs.length === 0) {
|
|
33
|
+
console.log(chalk.yellow('โ ๏ธ No Melt files found to clean.'));
|
|
34
|
+
outro(chalk.gray('Nothing to clean.'));
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
// Show what will be cleaned
|
|
38
|
+
displayCleanupPlan(analysisResult);
|
|
39
|
+
// Get confirmation
|
|
40
|
+
const shouldProceed = await confirm({
|
|
41
|
+
message: 'Do you want to proceed with cleaning? This action cannot be undone.',
|
|
42
|
+
});
|
|
43
|
+
if (!shouldProceed) {
|
|
44
|
+
outro(chalk.gray('Clean operation cancelled.'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Perform the cleanup
|
|
48
|
+
const s = spinner();
|
|
49
|
+
s.start('Cleaning Melt workspace...');
|
|
50
|
+
try {
|
|
51
|
+
const cleanResult = await performCleanup(currentDir);
|
|
52
|
+
s.stop('โ
Cleanup completed!');
|
|
53
|
+
displayCleanupResults(cleanResult);
|
|
54
|
+
outro(chalk.green('๐ Melt workspace cleaned successfully!'));
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
s.stop('โ Cleanup failed');
|
|
58
|
+
console.error(chalk.red('Error during cleanup:'), error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function analyzeCleanupTarget(baseDir) {
|
|
63
|
+
const result = {
|
|
64
|
+
removedFiles: [],
|
|
65
|
+
removedDirs: [],
|
|
66
|
+
preservedFiles: [],
|
|
67
|
+
errors: [],
|
|
68
|
+
};
|
|
69
|
+
const meltDir = path.join(baseDir, '.melt');
|
|
70
|
+
const cursorCommandsDir = path.join(baseDir, '.cursor', 'commands');
|
|
71
|
+
try {
|
|
72
|
+
// Check .melt directory (remove entirely)
|
|
73
|
+
if (await fs.pathExists(meltDir)) {
|
|
74
|
+
result.removedDirs.push('.melt/');
|
|
75
|
+
}
|
|
76
|
+
// Check .cursor/commands directory (selective removal)
|
|
77
|
+
if (await fs.pathExists(cursorCommandsDir)) {
|
|
78
|
+
const commandFiles = await fs.readdir(cursorCommandsDir);
|
|
79
|
+
for (const file of commandFiles) {
|
|
80
|
+
const filePath = path.join(cursorCommandsDir, file);
|
|
81
|
+
const stat = await fs.stat(filePath);
|
|
82
|
+
if (stat.isFile()) {
|
|
83
|
+
if (MELT_COMMAND_FILES.includes(file)) {
|
|
84
|
+
result.removedFiles.push(path.join('.cursor/commands', file));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
result.preservedFiles.push(path.join('.cursor/commands', file));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Check if .cursor/commands will be empty after cleaning
|
|
92
|
+
const meltFiles = commandFiles.filter(file => MELT_COMMAND_FILES.includes(file));
|
|
93
|
+
const nonMeltFiles = commandFiles.filter(file => !MELT_COMMAND_FILES.includes(file));
|
|
94
|
+
if (meltFiles.length > 0 && nonMeltFiles.length === 0) {
|
|
95
|
+
result.removedDirs.push('.cursor/commands/');
|
|
96
|
+
// Also check if .cursor itself would be empty
|
|
97
|
+
const cursorDir = path.join(baseDir, '.cursor');
|
|
98
|
+
const cursorContents = await fs.readdir(cursorDir);
|
|
99
|
+
if (cursorContents.length === 1 && cursorContents[0] === 'commands') {
|
|
100
|
+
result.removedDirs.push('.cursor/');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
result.errors.push(`Failed to analyze cleanup target: ${error instanceof Error ? error.message : String(error)}`);
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
async function performCleanup(baseDir) {
|
|
111
|
+
const result = {
|
|
112
|
+
removedFiles: [],
|
|
113
|
+
removedDirs: [],
|
|
114
|
+
preservedFiles: [],
|
|
115
|
+
errors: [],
|
|
116
|
+
};
|
|
117
|
+
const meltDir = path.join(baseDir, '.melt');
|
|
118
|
+
const cursorCommandsDir = path.join(baseDir, '.cursor', 'commands');
|
|
119
|
+
try {
|
|
120
|
+
// Remove .melt directory entirely
|
|
121
|
+
if (await fs.pathExists(meltDir)) {
|
|
122
|
+
await fs.remove(meltDir);
|
|
123
|
+
result.removedDirs.push('.melt/');
|
|
124
|
+
}
|
|
125
|
+
// Remove Melt command files from .cursor/commands
|
|
126
|
+
if (await fs.pathExists(cursorCommandsDir)) {
|
|
127
|
+
const commandFiles = await fs.readdir(cursorCommandsDir);
|
|
128
|
+
let hasNonMeltFiles = false;
|
|
129
|
+
for (const file of commandFiles) {
|
|
130
|
+
const filePath = path.join(cursorCommandsDir, file);
|
|
131
|
+
const stat = await fs.stat(filePath);
|
|
132
|
+
if (stat.isFile()) {
|
|
133
|
+
if (MELT_COMMAND_FILES.includes(file)) {
|
|
134
|
+
try {
|
|
135
|
+
await fs.remove(filePath);
|
|
136
|
+
result.removedFiles.push(path.join('.cursor/commands', file));
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
result.errors.push(`Failed to remove ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
hasNonMeltFiles = true;
|
|
144
|
+
result.preservedFiles.push(path.join('.cursor/commands', file));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Remove empty directories if no user files remain
|
|
149
|
+
if (!hasNonMeltFiles) {
|
|
150
|
+
try {
|
|
151
|
+
await fs.remove(cursorCommandsDir);
|
|
152
|
+
result.removedDirs.push('.cursor/commands/');
|
|
153
|
+
// Check if .cursor directory is now empty
|
|
154
|
+
const cursorDir = path.join(baseDir, '.cursor');
|
|
155
|
+
if (await fs.pathExists(cursorDir)) {
|
|
156
|
+
const cursorContents = await fs.readdir(cursorDir);
|
|
157
|
+
if (cursorContents.length === 0) {
|
|
158
|
+
await fs.remove(cursorDir);
|
|
159
|
+
result.removedDirs.push('.cursor/');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
result.errors.push(`Failed to remove empty directories: ${error instanceof Error ? error.message : String(error)}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
result.errors.push(`Cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
}
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
function displayCleanupPlan(result) {
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(chalk.cyan('๐ Cleanup Plan:'));
|
|
177
|
+
console.log();
|
|
178
|
+
if (result.removedDirs.length > 0) {
|
|
179
|
+
console.log(chalk.yellow('Directories to be removed:'));
|
|
180
|
+
result.removedDirs.forEach(dir => {
|
|
181
|
+
console.log(` โข ${chalk.red(dir)}`);
|
|
182
|
+
});
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
if (result.removedFiles.length > 0) {
|
|
186
|
+
console.log(chalk.yellow('Files to be removed:'));
|
|
187
|
+
result.removedFiles.forEach(file => {
|
|
188
|
+
console.log(` โข ${chalk.red(file)}`);
|
|
189
|
+
});
|
|
190
|
+
console.log();
|
|
191
|
+
}
|
|
192
|
+
if (result.preservedFiles.length > 0) {
|
|
193
|
+
console.log(chalk.green('Files to be preserved:'));
|
|
194
|
+
result.preservedFiles.forEach(file => {
|
|
195
|
+
console.log(` โข ${chalk.cyan(file)}`);
|
|
196
|
+
});
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
if (result.errors.length > 0) {
|
|
200
|
+
console.log(chalk.red('Analysis errors:'));
|
|
201
|
+
result.errors.forEach(error => {
|
|
202
|
+
console.log(` โ ๏ธ ${error}`);
|
|
203
|
+
});
|
|
204
|
+
console.log();
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function displayCleanupResults(result) {
|
|
208
|
+
console.log();
|
|
209
|
+
console.log(chalk.green('๐ฏ Cleanup Summary:'));
|
|
210
|
+
console.log();
|
|
211
|
+
if (result.removedDirs.length > 0) {
|
|
212
|
+
console.log(chalk.green(`โ
Removed ${result.removedDirs.length} directories:`));
|
|
213
|
+
result.removedDirs.forEach(dir => {
|
|
214
|
+
console.log(` โข ${dir}`);
|
|
215
|
+
});
|
|
216
|
+
console.log();
|
|
217
|
+
}
|
|
218
|
+
if (result.removedFiles.length > 0) {
|
|
219
|
+
console.log(chalk.green(`โ
Removed ${result.removedFiles.length} files:`));
|
|
220
|
+
result.removedFiles.forEach(file => {
|
|
221
|
+
console.log(` โข ${file}`);
|
|
222
|
+
});
|
|
223
|
+
console.log();
|
|
224
|
+
}
|
|
225
|
+
if (result.preservedFiles.length > 0) {
|
|
226
|
+
console.log(chalk.cyan(`๐ Preserved ${result.preservedFiles.length} user files:`));
|
|
227
|
+
result.preservedFiles.forEach(file => {
|
|
228
|
+
console.log(` โข ${file}`);
|
|
229
|
+
});
|
|
230
|
+
console.log();
|
|
231
|
+
}
|
|
232
|
+
if (result.errors.length > 0) {
|
|
233
|
+
console.log(chalk.red(`โ Errors encountered (${result.errors.length}):`));
|
|
234
|
+
result.errors.forEach(error => {
|
|
235
|
+
console.log(` โข ${error}`);
|
|
236
|
+
});
|
|
237
|
+
console.log();
|
|
238
|
+
}
|
|
239
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { join, dirname } from 'path';
|
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { initCommand } from './commands/project/init.js';
|
|
7
7
|
import { updateCommand } from './commands/project/update.js';
|
|
8
|
+
import { cleanCommand } from './commands/project/clean.js';
|
|
8
9
|
// Read version from package.json
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
@@ -34,4 +35,10 @@ projectCommand
|
|
|
34
35
|
.command('update')
|
|
35
36
|
.description('Update project configurations to latest version')
|
|
36
37
|
.action(updateCommand);
|
|
38
|
+
projectCommand
|
|
39
|
+
.command('clean')
|
|
40
|
+
.description('Remove all Melt-generated files from the project')
|
|
41
|
+
.action(() => {
|
|
42
|
+
return cleanCommand();
|
|
43
|
+
});
|
|
37
44
|
program.parse(process.argv);
|
package/package.json
CHANGED