genbox 1.0.12 → 1.0.14
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/commands/config.js +273 -0
- package/dist/commands/destroy.js +170 -6
- package/dist/commands/migrate.js +246 -0
- package/dist/commands/resolve.js +243 -0
- package/dist/commands/scan.js +354 -0
- package/dist/commands/validate.js +529 -0
- package/dist/config-explainer.js +379 -0
- package/dist/detected-config.js +145 -0
- package/dist/index.js +12 -1
- package/dist/migration.js +334 -0
- package/dist/profile-resolver.js +8 -3
- package/dist/schema-v3.js +36 -0
- package/dist/schema-v4.js +54 -0
- package/dist/strict-mode.js +288 -0
- package/package.json +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Config Command
|
|
4
|
+
*
|
|
5
|
+
* Provides configuration inspection and debugging tools:
|
|
6
|
+
* - genbox config show - Show resolved configuration
|
|
7
|
+
* - genbox config show --explain - Show where each value comes from
|
|
8
|
+
* - genbox config diff <a> <b> - Compare two profiles
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.configCommand = void 0;
|
|
48
|
+
const commander_1 = require("commander");
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
const yaml = __importStar(require("js-yaml"));
|
|
51
|
+
const config_loader_1 = require("../config-loader");
|
|
52
|
+
const config_explainer_1 = require("../config-explainer");
|
|
53
|
+
exports.configCommand = new commander_1.Command('config')
|
|
54
|
+
.description('Inspect and debug configuration')
|
|
55
|
+
.addCommand(createShowCommand())
|
|
56
|
+
.addCommand(createDiffCommand());
|
|
57
|
+
function createShowCommand() {
|
|
58
|
+
return new commander_1.Command('show')
|
|
59
|
+
.description('Show resolved configuration')
|
|
60
|
+
.argument('[path]', 'Specific config path to show (e.g., apps.web, profiles.quick)')
|
|
61
|
+
.option('--explain', 'Show where each value comes from')
|
|
62
|
+
.option('--profile <profile>', 'Show configuration for a specific profile')
|
|
63
|
+
.option('--json', 'Output as JSON')
|
|
64
|
+
.option('--yaml', 'Output as YAML (default)')
|
|
65
|
+
.action(async (configPath, options) => {
|
|
66
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
67
|
+
const loadResult = await configLoader.load();
|
|
68
|
+
if (!loadResult.found || !loadResult.config) {
|
|
69
|
+
console.log(chalk_1.default.red('No genbox.yaml found in current directory or parent directories'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const config = loadResult.config;
|
|
73
|
+
if (options.explain) {
|
|
74
|
+
await showWithExplain(config, loadResult, configPath, options.profile);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
await showConfig(config, configPath, options);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function createDiffCommand() {
|
|
82
|
+
return new commander_1.Command('diff')
|
|
83
|
+
.description('Compare two profiles')
|
|
84
|
+
.argument('<profile1>', 'First profile name')
|
|
85
|
+
.argument('<profile2>', 'Second profile name')
|
|
86
|
+
.action(async (profile1, profile2) => {
|
|
87
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
88
|
+
const loadResult = await configLoader.load();
|
|
89
|
+
if (!loadResult.found || !loadResult.config) {
|
|
90
|
+
console.log(chalk_1.default.red('No genbox.yaml found'));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const config = loadResult.config;
|
|
94
|
+
const p1 = configLoader.getProfile(config, profile1);
|
|
95
|
+
const p2 = configLoader.getProfile(config, profile2);
|
|
96
|
+
if (!p1) {
|
|
97
|
+
console.log(chalk_1.default.red(`Profile '${profile1}' not found`));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (!p2) {
|
|
101
|
+
console.log(chalk_1.default.red(`Profile '${profile2}' not found`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
showProfileDiff(profile1, p1, profile2, p2, config);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function showConfig(config, path, options) {
|
|
108
|
+
let data = config;
|
|
109
|
+
// If profile specified, show that profile's resolved config
|
|
110
|
+
if (options.profile) {
|
|
111
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
112
|
+
const profile = configLoader.getProfile(config, options.profile);
|
|
113
|
+
if (!profile) {
|
|
114
|
+
console.log(chalk_1.default.red(`Profile '${options.profile}' not found`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
data = profile;
|
|
118
|
+
}
|
|
119
|
+
// Navigate to specific path if provided
|
|
120
|
+
if (path) {
|
|
121
|
+
const parts = path.split('.');
|
|
122
|
+
let current = data;
|
|
123
|
+
for (const part of parts) {
|
|
124
|
+
if (current && typeof current === 'object' && part in current) {
|
|
125
|
+
current = current[part];
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(chalk_1.default.red(`Path '${path}' not found in configuration`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
data = current;
|
|
133
|
+
}
|
|
134
|
+
// Output
|
|
135
|
+
if (options.json) {
|
|
136
|
+
console.log(JSON.stringify(data, null, 2));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log(yaml.dump(data, { lineWidth: 120, noRefs: true }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function showWithExplain(config, loadResult, path, profileName) {
|
|
143
|
+
const explainer = new config_explainer_1.ConfigExplainer(loadResult);
|
|
144
|
+
console.log(chalk_1.default.bold.cyan('\n📋 Configuration Analysis\n'));
|
|
145
|
+
// Show sources
|
|
146
|
+
console.log(chalk_1.default.bold('Config Sources (priority order):'));
|
|
147
|
+
for (const source of loadResult.sources) {
|
|
148
|
+
const icon = source.type === 'workspace' ? '🏢' : source.type === 'project' ? '📁' : '👤';
|
|
149
|
+
console.log(` ${icon} ${source.type}: ${source.path}`);
|
|
150
|
+
}
|
|
151
|
+
console.log();
|
|
152
|
+
// If path specified, show just that path
|
|
153
|
+
if (path) {
|
|
154
|
+
const explanation = explainer.explain(path, profileName);
|
|
155
|
+
printExplanation(path, explanation);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Show key configuration values with explanations
|
|
159
|
+
console.log(chalk_1.default.bold('Key Values:\n'));
|
|
160
|
+
// Project info
|
|
161
|
+
printExplanation('project.name', explainer.explain('project.name'));
|
|
162
|
+
printExplanation('project.structure', explainer.explain('project.structure'));
|
|
163
|
+
// Apps
|
|
164
|
+
if (config.apps) {
|
|
165
|
+
console.log(chalk_1.default.bold('\nApps:'));
|
|
166
|
+
for (const appName of Object.keys(config.apps)) {
|
|
167
|
+
console.log(chalk_1.default.cyan(`\n ${appName}:`));
|
|
168
|
+
printExplanation(` apps.${appName}.type`, explainer.explain(`apps.${appName}.type`), 4);
|
|
169
|
+
printExplanation(` apps.${appName}.port`, explainer.explain(`apps.${appName}.port`), 4);
|
|
170
|
+
printExplanation(` apps.${appName}.framework`, explainer.explain(`apps.${appName}.framework`), 4);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Profiles
|
|
174
|
+
if (config.profiles && profileName) {
|
|
175
|
+
console.log(chalk_1.default.bold(`\nProfile '${profileName}':`));
|
|
176
|
+
printExplanation(' size', explainer.explain('size', profileName), 4);
|
|
177
|
+
printExplanation(' apps', explainer.explain('apps', profileName), 4);
|
|
178
|
+
printExplanation(' connect_to', explainer.explain('connect_to', profileName), 4);
|
|
179
|
+
printExplanation(' database.mode', explainer.explain('database.mode', profileName), 4);
|
|
180
|
+
}
|
|
181
|
+
// Defaults
|
|
182
|
+
if (config.defaults) {
|
|
183
|
+
console.log(chalk_1.default.bold('\nDefaults:'));
|
|
184
|
+
printExplanation(' defaults.size', explainer.explain('defaults.size'), 4);
|
|
185
|
+
printExplanation(' defaults.branch', explainer.explain('defaults.branch'), 4);
|
|
186
|
+
}
|
|
187
|
+
// Detection warnings
|
|
188
|
+
const warnings = explainer.getDetectionWarnings();
|
|
189
|
+
if (warnings.length > 0) {
|
|
190
|
+
console.log(chalk_1.default.bold.yellow('\n⚠️ Detection Warnings:\n'));
|
|
191
|
+
for (const warning of warnings) {
|
|
192
|
+
console.log(chalk_1.default.yellow(` • ${warning}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
function printExplanation(path, sources, indent = 0) {
|
|
198
|
+
const pad = ' '.repeat(indent);
|
|
199
|
+
if (sources.length === 0) {
|
|
200
|
+
console.log(`${pad}${chalk_1.default.dim(path)}: ${chalk_1.default.dim('(not set)')}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const finalSource = sources.find(s => s.used) || sources[0];
|
|
204
|
+
const valueStr = formatValue(finalSource.value);
|
|
205
|
+
console.log(`${pad}${chalk_1.default.white(path)}: ${chalk_1.default.green(valueStr)}`);
|
|
206
|
+
for (const source of sources) {
|
|
207
|
+
const icon = source.used ? chalk_1.default.green('✓') : chalk_1.default.dim('○');
|
|
208
|
+
const sourceLabel = getSourceLabel(source);
|
|
209
|
+
const value = source.value !== undefined ? formatValue(source.value) : chalk_1.default.dim('(not set)');
|
|
210
|
+
if (source.used) {
|
|
211
|
+
console.log(`${pad} ${icon} ${sourceLabel}: ${value}`);
|
|
212
|
+
if (source.detectedFrom) {
|
|
213
|
+
console.log(`${pad} └─ ${chalk_1.default.dim(`detected from: ${source.detectedFrom}`)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
console.log(`${pad} ${icon} ${chalk_1.default.dim(sourceLabel)}: ${chalk_1.default.dim(String(value))}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function getSourceLabel(source) {
|
|
222
|
+
switch (source.source) {
|
|
223
|
+
case 'cli':
|
|
224
|
+
return 'CLI flag';
|
|
225
|
+
case 'profile':
|
|
226
|
+
return `Profile '${source.profileName || 'unknown'}'`;
|
|
227
|
+
case 'project':
|
|
228
|
+
return 'genbox.yaml (explicit)';
|
|
229
|
+
case 'workspace':
|
|
230
|
+
return 'workspace genbox.yaml';
|
|
231
|
+
case 'detected':
|
|
232
|
+
return 'detected.yaml';
|
|
233
|
+
case 'default':
|
|
234
|
+
return 'schema default';
|
|
235
|
+
case 'inferred':
|
|
236
|
+
return 'inferred';
|
|
237
|
+
default:
|
|
238
|
+
return source.source;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function formatValue(value) {
|
|
242
|
+
if (value === undefined)
|
|
243
|
+
return '(undefined)';
|
|
244
|
+
if (value === null)
|
|
245
|
+
return '(null)';
|
|
246
|
+
if (Array.isArray(value))
|
|
247
|
+
return `[${value.join(', ')}]`;
|
|
248
|
+
if (typeof value === 'object')
|
|
249
|
+
return JSON.stringify(value);
|
|
250
|
+
return String(value);
|
|
251
|
+
}
|
|
252
|
+
function showProfileDiff(name1, profile1, name2, profile2, config) {
|
|
253
|
+
console.log(chalk_1.default.bold.cyan(`\n📊 Comparing profiles: ${name1} vs ${name2}\n`));
|
|
254
|
+
const allKeys = new Set([
|
|
255
|
+
...Object.keys(profile1),
|
|
256
|
+
...Object.keys(profile2),
|
|
257
|
+
]);
|
|
258
|
+
let hasDiff = false;
|
|
259
|
+
for (const key of allKeys) {
|
|
260
|
+
const v1 = profile1[key];
|
|
261
|
+
const v2 = profile2[key];
|
|
262
|
+
if (JSON.stringify(v1) !== JSON.stringify(v2)) {
|
|
263
|
+
hasDiff = true;
|
|
264
|
+
console.log(chalk_1.default.bold(` ${key}:`));
|
|
265
|
+
console.log(chalk_1.default.red(` - ${name1}: ${formatValue(v1)}`));
|
|
266
|
+
console.log(chalk_1.default.green(` + ${name2}: ${formatValue(v2)}`));
|
|
267
|
+
console.log();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!hasDiff) {
|
|
271
|
+
console.log(chalk_1.default.dim(' No differences found'));
|
|
272
|
+
}
|
|
273
|
+
}
|
package/dist/commands/destroy.js
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
@@ -7,21 +40,152 @@ exports.destroyCommand = void 0;
|
|
|
7
40
|
const commander_1 = require("commander");
|
|
8
41
|
const chalk_1 = __importDefault(require("chalk"));
|
|
9
42
|
const confirm_1 = __importDefault(require("@inquirer/confirm"));
|
|
43
|
+
const select_1 = __importDefault(require("@inquirer/select"));
|
|
44
|
+
const prompts = __importStar(require("@inquirer/prompts"));
|
|
10
45
|
const ora_1 = __importDefault(require("ora"));
|
|
11
46
|
const api_1 = require("../api");
|
|
12
47
|
const genbox_selector_1 = require("../genbox-selector");
|
|
13
48
|
const ssh_config_1 = require("../ssh-config");
|
|
49
|
+
/**
|
|
50
|
+
* Format genbox for display in selection list
|
|
51
|
+
*/
|
|
52
|
+
function formatGenboxChoice(g) {
|
|
53
|
+
const statusColor = g.status === 'running' ? chalk_1.default.green :
|
|
54
|
+
g.status === 'terminated' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
55
|
+
const projectInfo = g.project ? chalk_1.default.dim(` [${g.project}]`) : '';
|
|
56
|
+
return {
|
|
57
|
+
name: `${g.name} ${statusColor(`(${g.status})`)} ${chalk_1.default.dim(g.ipAddress || 'No IP')}${projectInfo}`,
|
|
58
|
+
value: g,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Handle bulk delete flow when --all flag is used without a name
|
|
63
|
+
*/
|
|
64
|
+
async function handleBulkDelete(options) {
|
|
65
|
+
const projectName = (0, genbox_selector_1.getProjectContext)();
|
|
66
|
+
// Fetch both project and global genboxes for counts
|
|
67
|
+
const allGenboxes = await (0, genbox_selector_1.getGenboxes)({ all: true });
|
|
68
|
+
const projectGenboxes = projectName
|
|
69
|
+
? allGenboxes.filter(g => g.project === projectName || g.workspace === projectName)
|
|
70
|
+
: [];
|
|
71
|
+
if (allGenboxes.length === 0) {
|
|
72
|
+
console.log(chalk_1.default.yellow('No genboxes found.'));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
let targetGenboxes;
|
|
76
|
+
let scopeLabel;
|
|
77
|
+
// If in project context, ask user to choose scope
|
|
78
|
+
if (projectName && projectGenboxes.length > 0) {
|
|
79
|
+
const scopeChoices = [
|
|
80
|
+
{
|
|
81
|
+
name: `Delete from ${chalk_1.default.cyan(projectName)} project ${chalk_1.default.dim(`(${projectGenboxes.length} genbox${projectGenboxes.length === 1 ? '' : 'es'})`)}`,
|
|
82
|
+
value: 'project',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: `Delete globally ${chalk_1.default.dim(`(${allGenboxes.length} genbox${allGenboxes.length === 1 ? '' : 'es'})`)}`,
|
|
86
|
+
value: 'global',
|
|
87
|
+
},
|
|
88
|
+
];
|
|
89
|
+
const scope = await (0, select_1.default)({
|
|
90
|
+
message: 'Select deletion scope:',
|
|
91
|
+
choices: scopeChoices,
|
|
92
|
+
});
|
|
93
|
+
if (scope === 'project') {
|
|
94
|
+
targetGenboxes = projectGenboxes;
|
|
95
|
+
scopeLabel = `project '${projectName}'`;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
targetGenboxes = allGenboxes;
|
|
99
|
+
scopeLabel = 'all projects';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Not in project context or no project genboxes - show all
|
|
104
|
+
targetGenboxes = allGenboxes;
|
|
105
|
+
scopeLabel = 'all projects';
|
|
106
|
+
console.log(chalk_1.default.dim(`Found ${allGenboxes.length} genbox${allGenboxes.length === 1 ? '' : 'es'} globally.\n`));
|
|
107
|
+
}
|
|
108
|
+
if (targetGenboxes.length === 0) {
|
|
109
|
+
console.log(chalk_1.default.yellow(`No genboxes found in ${scopeLabel}.`));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Show checkbox selection for genboxes
|
|
113
|
+
const choices = targetGenboxes.map(g => ({
|
|
114
|
+
...formatGenboxChoice(g),
|
|
115
|
+
checked: false, // Default to unchecked for safety
|
|
116
|
+
}));
|
|
117
|
+
console.log(''); // Add spacing
|
|
118
|
+
const selectedGenboxes = await prompts.checkbox({
|
|
119
|
+
message: `Select genboxes to destroy from ${scopeLabel}:`,
|
|
120
|
+
choices,
|
|
121
|
+
required: true,
|
|
122
|
+
});
|
|
123
|
+
if (selectedGenboxes.length === 0) {
|
|
124
|
+
console.log(chalk_1.default.yellow('No genboxes selected.'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// Show summary and confirm
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(chalk_1.default.yellow.bold('⚠️ The following genboxes will be PERMANENTLY destroyed:'));
|
|
130
|
+
console.log('');
|
|
131
|
+
selectedGenboxes.forEach(g => {
|
|
132
|
+
const statusColor = g.status === 'running' ? chalk_1.default.green : chalk_1.default.yellow;
|
|
133
|
+
console.log(` ${chalk_1.default.red('•')} ${g.name} ${statusColor(`(${g.status})`)} ${chalk_1.default.dim(g.ipAddress || 'No IP')}`);
|
|
134
|
+
});
|
|
135
|
+
console.log('');
|
|
136
|
+
let confirmed = options.yes;
|
|
137
|
+
if (!confirmed) {
|
|
138
|
+
confirmed = await (0, confirm_1.default)({
|
|
139
|
+
message: `Are you sure you want to destroy ${chalk_1.default.red(selectedGenboxes.length)} genbox${selectedGenboxes.length === 1 ? '' : 'es'}?`,
|
|
140
|
+
default: false,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (!confirmed) {
|
|
144
|
+
console.log(chalk_1.default.dim('Operation cancelled.'));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Delete each genbox
|
|
148
|
+
console.log('');
|
|
149
|
+
let successCount = 0;
|
|
150
|
+
let failCount = 0;
|
|
151
|
+
for (const genbox of selectedGenboxes) {
|
|
152
|
+
const spinner = (0, ora_1.default)(`Destroying ${genbox.name}...`).start();
|
|
153
|
+
try {
|
|
154
|
+
await (0, api_1.fetchApi)(`/genboxes/${genbox._id}`, { method: 'DELETE' });
|
|
155
|
+
(0, ssh_config_1.removeSshConfigEntry)(genbox.name);
|
|
156
|
+
spinner.succeed(chalk_1.default.green(`Destroyed '${genbox.name}'`));
|
|
157
|
+
successCount++;
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
spinner.fail(chalk_1.default.red(`Failed to destroy '${genbox.name}': ${error.message}`));
|
|
161
|
+
failCount++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Summary
|
|
165
|
+
console.log('');
|
|
166
|
+
if (failCount === 0) {
|
|
167
|
+
console.log(chalk_1.default.green.bold(`✓ Successfully destroyed ${successCount} genbox${successCount === 1 ? '' : 'es'}.`));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
console.log(chalk_1.default.yellow(`Completed: ${successCount} destroyed, ${failCount} failed.`));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
14
173
|
exports.destroyCommand = new commander_1.Command('destroy')
|
|
15
174
|
.alias('delete')
|
|
16
|
-
.description('Destroy
|
|
175
|
+
.description('Destroy one or more Genboxes')
|
|
17
176
|
.argument('[name]', 'Name of the Genbox to destroy (optional - will prompt if not provided)')
|
|
18
177
|
.option('-y, --yes', 'Skip confirmation')
|
|
19
|
-
.option('-a, --all', '
|
|
178
|
+
.option('-a, --all', 'Bulk delete mode - select multiple genboxes to destroy')
|
|
20
179
|
.action(async (name, options) => {
|
|
21
180
|
try {
|
|
22
|
-
//
|
|
181
|
+
// Bulk delete mode when --all is used without a specific name
|
|
182
|
+
if (options.all && !name) {
|
|
183
|
+
await handleBulkDelete(options);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
// Single genbox deletion flow
|
|
23
187
|
const { genbox: target, cancelled } = await (0, genbox_selector_1.selectGenbox)(name, {
|
|
24
|
-
all: options.all,
|
|
188
|
+
all: options.all, // If --all with name, search in all genboxes
|
|
25
189
|
selectMessage: 'Select a genbox to destroy:',
|
|
26
190
|
});
|
|
27
191
|
if (cancelled) {
|
|
@@ -31,7 +195,7 @@ exports.destroyCommand = new commander_1.Command('destroy')
|
|
|
31
195
|
if (!target) {
|
|
32
196
|
return;
|
|
33
197
|
}
|
|
34
|
-
//
|
|
198
|
+
// Confirm
|
|
35
199
|
let confirmed = options.yes;
|
|
36
200
|
if (!confirmed) {
|
|
37
201
|
confirmed = await (0, confirm_1.default)({
|
|
@@ -43,7 +207,7 @@ exports.destroyCommand = new commander_1.Command('destroy')
|
|
|
43
207
|
console.log('Operation cancelled.');
|
|
44
208
|
return;
|
|
45
209
|
}
|
|
46
|
-
//
|
|
210
|
+
// Delete
|
|
47
211
|
const spinner = (0, ora_1.default)(`Destroying ${target.name}...`).start();
|
|
48
212
|
await (0, api_1.fetchApi)(`/genboxes/${target._id}`, {
|
|
49
213
|
method: 'DELETE',
|