@wundr.io/cli 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/ai/ai-service.d.ts +152 -0
- package/dist/ai/ai-service.d.ts.map +1 -0
- package/dist/ai/ai-service.js +430 -0
- package/dist/ai/ai-service.js.map +1 -0
- package/dist/ai/claude-client.d.ts +130 -0
- package/dist/ai/claude-client.d.ts.map +1 -0
- package/dist/ai/claude-client.js +340 -0
- package/dist/ai/claude-client.js.map +1 -0
- package/dist/ai/conversation-manager.d.ts +164 -0
- package/dist/ai/conversation-manager.d.ts.map +1 -0
- package/dist/ai/conversation-manager.js +614 -0
- package/dist/ai/conversation-manager.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +8 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +36 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +192 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ai.d.ts +89 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +954 -0
- package/dist/commands/ai.js.map +1 -0
- package/dist/commands/alignment.d.ts +78 -0
- package/dist/commands/alignment.d.ts.map +1 -0
- package/dist/commands/alignment.js +817 -0
- package/dist/commands/alignment.js.map +1 -0
- package/dist/commands/analyze-optimized.d.ts +14 -0
- package/dist/commands/analyze-optimized.d.ts.map +1 -0
- package/dist/commands/analyze-optimized.js +609 -0
- package/dist/commands/analyze-optimized.js.map +1 -0
- package/dist/commands/analyze.d.ts +65 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +435 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/batch.d.ts +93 -0
- package/dist/commands/batch.d.ts.map +1 -0
- package/dist/commands/batch.js +854 -0
- package/dist/commands/batch.js.map +1 -0
- package/dist/commands/chat.d.ts +72 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +678 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/claude-init.d.ts +28 -0
- package/dist/commands/claude-init.d.ts.map +1 -0
- package/dist/commands/claude-init.js +591 -0
- package/dist/commands/claude-init.js.map +1 -0
- package/dist/commands/claude-setup.d.ts +119 -0
- package/dist/commands/claude-setup.d.ts.map +1 -0
- package/dist/commands/claude-setup.js +1079 -0
- package/dist/commands/claude-setup.js.map +1 -0
- package/dist/commands/computer-setup.d.ts +8 -0
- package/dist/commands/computer-setup.d.ts.map +1 -0
- package/dist/commands/computer-setup.js +877 -0
- package/dist/commands/computer-setup.js.map +1 -0
- package/dist/commands/create-command.d.ts +7 -0
- package/dist/commands/create-command.d.ts.map +1 -0
- package/dist/commands/create-command.js +158 -0
- package/dist/commands/create-command.js.map +1 -0
- package/dist/commands/create.d.ts +74 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +556 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/dashboard.d.ts +91 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +538 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/govern.d.ts +70 -0
- package/dist/commands/govern.d.ts.map +1 -0
- package/dist/commands/govern.js +481 -0
- package/dist/commands/govern.js.map +1 -0
- package/dist/commands/governance.d.ts +17 -0
- package/dist/commands/governance.d.ts.map +1 -0
- package/dist/commands/governance.js +703 -0
- package/dist/commands/governance.js.map +1 -0
- package/dist/commands/guardian.d.ts +20 -0
- package/dist/commands/guardian.d.ts.map +1 -0
- package/dist/commands/guardian.js +597 -0
- package/dist/commands/guardian.js.map +1 -0
- package/dist/commands/init.d.ts +59 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +650 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/orchestrator.d.ts +7 -0
- package/dist/commands/orchestrator.d.ts.map +1 -0
- package/dist/commands/orchestrator.js +578 -0
- package/dist/commands/orchestrator.js.map +1 -0
- package/dist/commands/performance-optimizer.d.ts +30 -0
- package/dist/commands/performance-optimizer.d.ts.map +1 -0
- package/dist/commands/performance-optimizer.js +650 -0
- package/dist/commands/performance-optimizer.js.map +1 -0
- package/dist/commands/plugins.d.ts +87 -0
- package/dist/commands/plugins.d.ts.map +1 -0
- package/dist/commands/plugins.js +685 -0
- package/dist/commands/plugins.js.map +1 -0
- package/dist/commands/rag.d.ts +7 -0
- package/dist/commands/rag.d.ts.map +1 -0
- package/dist/commands/rag.js +751 -0
- package/dist/commands/rag.js.map +1 -0
- package/dist/commands/session.d.ts +41 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +441 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +24 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +172 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/test-init.d.ts +9 -0
- package/dist/commands/test-init.d.ts.map +1 -0
- package/dist/commands/test-init.js +222 -0
- package/dist/commands/test-init.js.map +1 -0
- package/dist/commands/test.d.ts +25 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +217 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/commands/watch.d.ts +76 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +613 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/commands/worktree.d.ts +63 -0
- package/dist/commands/worktree.d.ts.map +1 -0
- package/dist/commands/worktree.js +774 -0
- package/dist/commands/worktree.js.map +1 -0
- package/dist/context/context-manager.d.ts +155 -0
- package/dist/context/context-manager.d.ts.map +1 -0
- package/dist/context/context-manager.js +383 -0
- package/dist/context/context-manager.js.map +1 -0
- package/dist/context/index.d.ts +3 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +6 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/session-manager.d.ts +207 -0
- package/dist/context/session-manager.d.ts.map +1 -0
- package/dist/context/session-manager.js +686 -0
- package/dist/context/session-manager.js.map +1 -0
- package/dist/framework/command-interface.d.ts +349 -0
- package/dist/framework/command-interface.d.ts.map +1 -0
- package/dist/framework/command-interface.js +101 -0
- package/dist/framework/command-interface.js.map +1 -0
- package/dist/framework/command-registry.d.ts +173 -0
- package/dist/framework/command-registry.d.ts.map +1 -0
- package/dist/framework/command-registry.js +734 -0
- package/dist/framework/command-registry.js.map +1 -0
- package/dist/framework/completion-exporter.d.ts +79 -0
- package/dist/framework/completion-exporter.d.ts.map +1 -0
- package/dist/framework/completion-exporter.js +259 -0
- package/dist/framework/completion-exporter.js.map +1 -0
- package/dist/framework/debug-logger.d.ts +163 -0
- package/dist/framework/debug-logger.d.ts.map +1 -0
- package/dist/framework/debug-logger.js +373 -0
- package/dist/framework/debug-logger.js.map +1 -0
- package/dist/framework/error-handler.d.ts +196 -0
- package/dist/framework/error-handler.d.ts.map +1 -0
- package/dist/framework/error-handler.js +613 -0
- package/dist/framework/error-handler.js.map +1 -0
- package/dist/framework/help-generator.d.ts +78 -0
- package/dist/framework/help-generator.d.ts.map +1 -0
- package/dist/framework/help-generator.js +414 -0
- package/dist/framework/help-generator.js.map +1 -0
- package/dist/framework/index.d.ts +62 -0
- package/dist/framework/index.d.ts.map +1 -0
- package/dist/framework/index.js +95 -0
- package/dist/framework/index.js.map +1 -0
- package/dist/framework/interactive-repl.d.ts +138 -0
- package/dist/framework/interactive-repl.d.ts.map +1 -0
- package/dist/framework/interactive-repl.js +567 -0
- package/dist/framework/interactive-repl.js.map +1 -0
- package/dist/framework/output-formatter.d.ts +274 -0
- package/dist/framework/output-formatter.d.ts.map +1 -0
- package/dist/framework/output-formatter.js +545 -0
- package/dist/framework/output-formatter.js.map +1 -0
- package/dist/framework/progress-manager.d.ts +192 -0
- package/dist/framework/progress-manager.d.ts.map +1 -0
- package/dist/framework/progress-manager.js +408 -0
- package/dist/framework/progress-manager.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +51 -0
- package/dist/index.js.map +1 -0
- package/dist/interactive/interactive-mode.d.ts +76 -0
- package/dist/interactive/interactive-mode.d.ts.map +1 -0
- package/dist/interactive/interactive-mode.js +732 -0
- package/dist/interactive/interactive-mode.js.map +1 -0
- package/dist/nlp/command-mapper.d.ts +174 -0
- package/dist/nlp/command-mapper.d.ts.map +1 -0
- package/dist/nlp/command-mapper.js +624 -0
- package/dist/nlp/command-mapper.js.map +1 -0
- package/dist/nlp/command-parser.d.ts +106 -0
- package/dist/nlp/command-parser.d.ts.map +1 -0
- package/dist/nlp/command-parser.js +417 -0
- package/dist/nlp/command-parser.js.map +1 -0
- package/dist/nlp/index.d.ts +5 -0
- package/dist/nlp/index.d.ts.map +1 -0
- package/dist/nlp/index.js +8 -0
- package/dist/nlp/index.js.map +1 -0
- package/dist/nlp/intent-classifier.d.ts +59 -0
- package/dist/nlp/intent-classifier.d.ts.map +1 -0
- package/dist/nlp/intent-classifier.js +384 -0
- package/dist/nlp/intent-classifier.js.map +1 -0
- package/dist/nlp/intent-parser.d.ts +152 -0
- package/dist/nlp/intent-parser.d.ts.map +1 -0
- package/dist/nlp/intent-parser.js +746 -0
- package/dist/nlp/intent-parser.js.map +1 -0
- package/dist/plugins/plugin-manager.d.ts +121 -0
- package/dist/plugins/plugin-manager.d.ts.map +1 -0
- package/dist/plugins/plugin-manager.js +606 -0
- package/dist/plugins/plugin-manager.js.map +1 -0
- package/dist/types/index.d.ts +224 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/backup-rollback-manager.d.ts +72 -0
- package/dist/utils/backup-rollback-manager.d.ts.map +1 -0
- package/dist/utils/backup-rollback-manager.js +288 -0
- package/dist/utils/backup-rollback-manager.js.map +1 -0
- package/dist/utils/claude-config-installer.d.ts +98 -0
- package/dist/utils/claude-config-installer.d.ts.map +1 -0
- package/dist/utils/claude-config-installer.js +678 -0
- package/dist/utils/claude-config-installer.js.map +1 -0
- package/dist/utils/config-manager.d.ts +73 -0
- package/dist/utils/config-manager.d.ts.map +1 -0
- package/dist/utils/config-manager.js +339 -0
- package/dist/utils/config-manager.js.map +1 -0
- package/dist/utils/error-handler.d.ts +46 -0
- package/dist/utils/error-handler.d.ts.map +1 -0
- package/dist/utils/error-handler.js +169 -0
- package/dist/utils/error-handler.js.map +1 -0
- package/dist/utils/logger.d.ts +25 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +105 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,854 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BatchCommands = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
8
|
+
const listr2_1 = require("listr2");
|
|
9
|
+
const yaml_1 = tslib_1.__importDefault(require("yaml"));
|
|
10
|
+
const error_handler_1 = require("../utils/error-handler");
|
|
11
|
+
const logger_1 = require("../utils/logger");
|
|
12
|
+
/**
|
|
13
|
+
* Batch commands for YAML automation and batch processing
|
|
14
|
+
*/
|
|
15
|
+
class BatchCommands {
|
|
16
|
+
program;
|
|
17
|
+
configManager;
|
|
18
|
+
pluginManager;
|
|
19
|
+
runningJobs = new Map();
|
|
20
|
+
constructor(program, configManager, pluginManager) {
|
|
21
|
+
this.program = program;
|
|
22
|
+
this.configManager = configManager;
|
|
23
|
+
this.pluginManager = pluginManager;
|
|
24
|
+
this.registerCommands();
|
|
25
|
+
}
|
|
26
|
+
registerCommands() {
|
|
27
|
+
const batchCmd = this.program
|
|
28
|
+
.command('batch')
|
|
29
|
+
.description('batch processing and automation with YAML');
|
|
30
|
+
// Run batch job
|
|
31
|
+
batchCmd
|
|
32
|
+
.command('run <file>')
|
|
33
|
+
.description('run batch job from YAML file')
|
|
34
|
+
.option('--dry-run', 'show what would be executed without running')
|
|
35
|
+
.option('--parallel', 'run commands in parallel where possible')
|
|
36
|
+
.option('--continue-on-error', 'continue execution on command failures')
|
|
37
|
+
.option('--vars <vars>', 'variables to pass to batch job (JSON or key=value)')
|
|
38
|
+
.option('--timeout <ms>', 'global timeout for batch job')
|
|
39
|
+
.action(async (file, options) => {
|
|
40
|
+
await this.runBatchJob(file, options);
|
|
41
|
+
});
|
|
42
|
+
// Create batch job
|
|
43
|
+
batchCmd
|
|
44
|
+
.command('create <name>')
|
|
45
|
+
.description('create a new batch job')
|
|
46
|
+
.option('--template <template>', 'batch job template')
|
|
47
|
+
.option('--commands <commands>', 'commands to include (comma-separated)')
|
|
48
|
+
.option('--interactive', 'create job interactively')
|
|
49
|
+
.action(async (name, options) => {
|
|
50
|
+
await this.createBatchJob(name, options);
|
|
51
|
+
});
|
|
52
|
+
// List batch jobs
|
|
53
|
+
batchCmd
|
|
54
|
+
.command('list')
|
|
55
|
+
.alias('ls')
|
|
56
|
+
.description('list available batch jobs')
|
|
57
|
+
.option('--path <path>', 'custom batch jobs directory')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
await this.listBatchJobs(options);
|
|
60
|
+
});
|
|
61
|
+
// Validate batch job
|
|
62
|
+
batchCmd
|
|
63
|
+
.command('validate <file>')
|
|
64
|
+
.description('validate batch job YAML file')
|
|
65
|
+
.action(async (file) => {
|
|
66
|
+
await this.validateBatchJob(file);
|
|
67
|
+
});
|
|
68
|
+
// Stop running job
|
|
69
|
+
batchCmd
|
|
70
|
+
.command('stop <jobId>')
|
|
71
|
+
.description('stop a running batch job')
|
|
72
|
+
.action(async (jobId) => {
|
|
73
|
+
await this.stopBatchJob(jobId);
|
|
74
|
+
});
|
|
75
|
+
// Show job status
|
|
76
|
+
batchCmd
|
|
77
|
+
.command('status [jobId]')
|
|
78
|
+
.description('show batch job status')
|
|
79
|
+
.action(async (jobId) => {
|
|
80
|
+
await this.showJobStatus(jobId);
|
|
81
|
+
});
|
|
82
|
+
// Schedule batch job
|
|
83
|
+
batchCmd
|
|
84
|
+
.command('schedule <file>')
|
|
85
|
+
.description('schedule batch job execution')
|
|
86
|
+
.option('--cron <expression>', 'cron expression for scheduling')
|
|
87
|
+
.option('--interval <ms>', 'interval in milliseconds')
|
|
88
|
+
.option('--once', 'run once after delay')
|
|
89
|
+
.action(async (file, options) => {
|
|
90
|
+
await this.scheduleBatchJob(file, options);
|
|
91
|
+
});
|
|
92
|
+
// Export batch job
|
|
93
|
+
batchCmd
|
|
94
|
+
.command('export <file>')
|
|
95
|
+
.description('export batch job to different formats')
|
|
96
|
+
.option('--format <format>', 'export format (json, shell, dockerfile)', 'json')
|
|
97
|
+
.option('--output <path>', 'output file path')
|
|
98
|
+
.action(async (file, options) => {
|
|
99
|
+
await this.exportBatchJob(file, options);
|
|
100
|
+
});
|
|
101
|
+
// Import batch job
|
|
102
|
+
batchCmd
|
|
103
|
+
.command('import <file>')
|
|
104
|
+
.description('import batch job from different formats')
|
|
105
|
+
.option('--format <format>', 'source format (json, shell, package-scripts)')
|
|
106
|
+
.option('--name <name>', 'batch job name')
|
|
107
|
+
.action(async (file, options) => {
|
|
108
|
+
await this.importBatchJob(file, options);
|
|
109
|
+
});
|
|
110
|
+
// Template management
|
|
111
|
+
batchCmd.command('template').description('manage batch job templates');
|
|
112
|
+
batchCmd
|
|
113
|
+
.command('template list')
|
|
114
|
+
.description('list available templates')
|
|
115
|
+
.action(async () => {
|
|
116
|
+
await this.listTemplates();
|
|
117
|
+
});
|
|
118
|
+
batchCmd
|
|
119
|
+
.command('template create <name>')
|
|
120
|
+
.description('create a new template')
|
|
121
|
+
.option('--from <file>', 'create template from existing batch job')
|
|
122
|
+
.action(async (name, options) => {
|
|
123
|
+
await this.createTemplate(name, options);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Run batch job from YAML file
|
|
128
|
+
*/
|
|
129
|
+
async runBatchJob(file, options) {
|
|
130
|
+
try {
|
|
131
|
+
logger_1.logger.info(`Running batch job: ${chalk_1.default.cyan(file)}`);
|
|
132
|
+
// Load and validate batch job
|
|
133
|
+
const job = await this.loadBatchJob(file);
|
|
134
|
+
await this.validateJobStructure(job);
|
|
135
|
+
// Process variables
|
|
136
|
+
const processedJob = await this.processJobVariables(job, options.vars);
|
|
137
|
+
if (options.dryRun) {
|
|
138
|
+
await this.showDryRun(processedJob);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Create job ID and track execution
|
|
142
|
+
const jobId = `job-${Date.now()}`;
|
|
143
|
+
this.runningJobs.set(jobId, {
|
|
144
|
+
file,
|
|
145
|
+
job: processedJob,
|
|
146
|
+
startTime: Date.now(),
|
|
147
|
+
status: 'running',
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
await this.executeBatchJob(processedJob, jobId, options);
|
|
151
|
+
logger_1.logger.success(`Batch job completed: ${file}`);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
this.runningJobs.delete(jobId);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_RUN_FAILED', 'Failed to run batch job', { file, options }, true);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Create a new batch job
|
|
163
|
+
*/
|
|
164
|
+
async createBatchJob(name, options) {
|
|
165
|
+
try {
|
|
166
|
+
logger_1.logger.info(`Creating batch job: ${chalk_1.default.cyan(name)}`);
|
|
167
|
+
let job;
|
|
168
|
+
if (options.interactive) {
|
|
169
|
+
job = await this.createInteractiveBatchJob(name);
|
|
170
|
+
}
|
|
171
|
+
else if (options.template) {
|
|
172
|
+
job = await this.createJobFromTemplate(name, options.template);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
job = this.createBasicBatchJob(name, options);
|
|
176
|
+
}
|
|
177
|
+
const jobPath = path_1.default.join(process.cwd(), '.wundr', 'batch', `${name}.yaml`);
|
|
178
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(jobPath));
|
|
179
|
+
await fs_extra_1.default.writeFile(jobPath, yaml_1.default.stringify(job));
|
|
180
|
+
logger_1.logger.success(`Batch job created: ${jobPath}`);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_CREATE_FAILED', 'Failed to create batch job', { name, options }, true);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* List available batch jobs
|
|
188
|
+
*/
|
|
189
|
+
async listBatchJobs(options) {
|
|
190
|
+
try {
|
|
191
|
+
const batchDir = options.path || path_1.default.join(process.cwd(), '.wundr', 'batch');
|
|
192
|
+
if (!(await fs_extra_1.default.pathExists(batchDir))) {
|
|
193
|
+
logger_1.logger.info('No batch jobs directory found');
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const files = await fs_extra_1.default.readdir(batchDir);
|
|
197
|
+
const yamlFiles = files.filter(f => f.endsWith('.yaml') || f.endsWith('.yml'));
|
|
198
|
+
if (yamlFiles.length === 0) {
|
|
199
|
+
logger_1.logger.info('No batch jobs found');
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
logger_1.logger.info(`Batch jobs (${yamlFiles.length}):`);
|
|
203
|
+
const jobs = [];
|
|
204
|
+
for (const file of yamlFiles) {
|
|
205
|
+
const filePath = path_1.default.join(batchDir, file);
|
|
206
|
+
try {
|
|
207
|
+
const job = await this.loadBatchJob(filePath);
|
|
208
|
+
jobs.push({
|
|
209
|
+
Name: path_1.default.basename(file, path_1.default.extname(file)),
|
|
210
|
+
Description: job.description || 'No description',
|
|
211
|
+
Commands: job.commands.length,
|
|
212
|
+
Parallel: job.parallel ? '✓' : '✗',
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
jobs.push({
|
|
217
|
+
Name: path_1.default.basename(file, path_1.default.extname(file)),
|
|
218
|
+
Description: 'Invalid YAML',
|
|
219
|
+
Commands: 'N/A',
|
|
220
|
+
Parallel: 'N/A',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
console.table(jobs);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_LIST_FAILED', 'Failed to list batch jobs', { options }, true);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Validate batch job YAML file
|
|
232
|
+
*/
|
|
233
|
+
async validateBatchJob(file) {
|
|
234
|
+
try {
|
|
235
|
+
logger_1.logger.info(`Validating batch job: ${chalk_1.default.cyan(file)}`);
|
|
236
|
+
const job = await this.loadBatchJob(file);
|
|
237
|
+
const validation = await this.validateJobStructure(job);
|
|
238
|
+
if (validation.valid) {
|
|
239
|
+
logger_1.logger.success('Batch job is valid ✓');
|
|
240
|
+
// Show job summary
|
|
241
|
+
console.log(chalk_1.default.blue('\nJob Summary:'));
|
|
242
|
+
console.log(`Name: ${job.name}`);
|
|
243
|
+
console.log(`Description: ${job.description || 'None'}`);
|
|
244
|
+
console.log(`Commands: ${job.commands.length}`);
|
|
245
|
+
console.log(`Parallel execution: ${job.parallel ? 'Yes' : 'No'}`);
|
|
246
|
+
console.log(`Continue on error: ${job.continueOnError ? 'Yes' : 'No'}`);
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
logger_1.logger.error('Batch job validation failed:');
|
|
250
|
+
validation.errors.forEach(error => {
|
|
251
|
+
logger_1.logger.error(` - ${error}`);
|
|
252
|
+
});
|
|
253
|
+
process.exit(1);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_VALIDATE_FAILED', 'Failed to validate batch job', { file }, true);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Stop a running batch job
|
|
262
|
+
*/
|
|
263
|
+
async stopBatchJob(jobId) {
|
|
264
|
+
try {
|
|
265
|
+
const job = this.runningJobs.get(jobId);
|
|
266
|
+
if (!job) {
|
|
267
|
+
logger_1.logger.warn(`Job not found: ${jobId}`);
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
// Stop the job (implementation would depend on how jobs are executed)
|
|
271
|
+
job.status = 'stopped';
|
|
272
|
+
this.runningJobs.delete(jobId);
|
|
273
|
+
logger_1.logger.success(`Batch job stopped: ${jobId}`);
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_STOP_FAILED', 'Failed to stop batch job', { jobId }, true);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Show job status
|
|
281
|
+
*/
|
|
282
|
+
async showJobStatus(jobId) {
|
|
283
|
+
try {
|
|
284
|
+
if (jobId) {
|
|
285
|
+
const job = this.runningJobs.get(jobId);
|
|
286
|
+
if (!job) {
|
|
287
|
+
logger_1.logger.warn(`Job not found: ${jobId}`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
console.log(chalk_1.default.blue(`\nJob Status: ${jobId}`));
|
|
291
|
+
console.log(`File: ${job.file}`);
|
|
292
|
+
console.log(`Status: ${job.status}`);
|
|
293
|
+
console.log(`Started: ${new Date(job.startTime).toLocaleString()}`);
|
|
294
|
+
console.log(`Duration: ${Date.now() - job.startTime}ms`);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
if (this.runningJobs.size === 0) {
|
|
298
|
+
logger_1.logger.info('No running batch jobs');
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
console.log(chalk_1.default.blue(`\nRunning Jobs (${this.runningJobs.size}):`));
|
|
302
|
+
const jobData = Array.from(this.runningJobs.entries()).map(([id, job]) => ({
|
|
303
|
+
ID: id,
|
|
304
|
+
File: path_1.default.basename(job.file),
|
|
305
|
+
Status: job.status,
|
|
306
|
+
Duration: `${Date.now() - job.startTime}ms`,
|
|
307
|
+
}));
|
|
308
|
+
console.table(jobData);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
catch (error) {
|
|
312
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_STATUS_FAILED', 'Failed to show job status', { jobId }, true);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Schedule batch job execution
|
|
317
|
+
*/
|
|
318
|
+
async scheduleBatchJob(file, options) {
|
|
319
|
+
try {
|
|
320
|
+
logger_1.logger.info(`Scheduling batch job: ${chalk_1.default.cyan(file)}`);
|
|
321
|
+
// Validate the job first
|
|
322
|
+
await this.loadBatchJob(file);
|
|
323
|
+
if (options.cron) {
|
|
324
|
+
logger_1.logger.info(`Scheduled with cron: ${options.cron}`);
|
|
325
|
+
// Implementation would use a cron library
|
|
326
|
+
}
|
|
327
|
+
else if (options.interval) {
|
|
328
|
+
logger_1.logger.info(`Scheduled with interval: ${options.interval}ms`);
|
|
329
|
+
// Implementation would use setInterval
|
|
330
|
+
}
|
|
331
|
+
else if (options.once) {
|
|
332
|
+
logger_1.logger.info('Scheduled to run once');
|
|
333
|
+
// Implementation would use setTimeout
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
throw new Error('No scheduling option provided');
|
|
337
|
+
}
|
|
338
|
+
logger_1.logger.success('Batch job scheduled successfully');
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_SCHEDULE_FAILED', 'Failed to schedule batch job', { file, options }, true);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Export batch job to different formats
|
|
346
|
+
*/
|
|
347
|
+
async exportBatchJob(file, options) {
|
|
348
|
+
try {
|
|
349
|
+
logger_1.logger.info(`Exporting batch job: ${chalk_1.default.cyan(file)}`);
|
|
350
|
+
const job = await this.loadBatchJob(file);
|
|
351
|
+
let exportedContent;
|
|
352
|
+
switch (options.format) {
|
|
353
|
+
case 'json':
|
|
354
|
+
exportedContent = JSON.stringify(job, null, 2);
|
|
355
|
+
break;
|
|
356
|
+
case 'shell':
|
|
357
|
+
exportedContent = this.convertToShellScript(job);
|
|
358
|
+
break;
|
|
359
|
+
case 'dockerfile':
|
|
360
|
+
exportedContent = this.convertToDockerfile(job);
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
throw new Error(`Unsupported export format: ${options.format}`);
|
|
364
|
+
}
|
|
365
|
+
const outputPath = options.output ||
|
|
366
|
+
`${path_1.default.basename(file, path_1.default.extname(file))}.${options.format}`;
|
|
367
|
+
await fs_extra_1.default.writeFile(outputPath, exportedContent);
|
|
368
|
+
logger_1.logger.success(`Batch job exported: ${outputPath}`);
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_EXPORT_FAILED', 'Failed to export batch job', { file, options }, true);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Import batch job from different formats
|
|
376
|
+
*/
|
|
377
|
+
async importBatchJob(file, options) {
|
|
378
|
+
try {
|
|
379
|
+
logger_1.logger.info(`Importing batch job: ${chalk_1.default.cyan(file)}`);
|
|
380
|
+
let job;
|
|
381
|
+
switch (options.format) {
|
|
382
|
+
case 'json':
|
|
383
|
+
job = await this.importFromJSON(file, options.name);
|
|
384
|
+
break;
|
|
385
|
+
case 'shell':
|
|
386
|
+
job = await this.importFromShell(file, options.name);
|
|
387
|
+
break;
|
|
388
|
+
case 'package-scripts':
|
|
389
|
+
job = await this.importFromPackageScripts(file, options.name);
|
|
390
|
+
break;
|
|
391
|
+
default:
|
|
392
|
+
throw new Error(`Unsupported import format: ${options.format}`);
|
|
393
|
+
}
|
|
394
|
+
const jobName = options.name || path_1.default.basename(file, path_1.default.extname(file));
|
|
395
|
+
const jobPath = path_1.default.join(process.cwd(), '.wundr', 'batch', `${jobName}.yaml`);
|
|
396
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(jobPath));
|
|
397
|
+
await fs_extra_1.default.writeFile(jobPath, yaml_1.default.stringify(job));
|
|
398
|
+
logger_1.logger.success(`Batch job imported: ${jobPath}`);
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
throw error_handler_1.errorHandler.createError('WUNDR_BATCH_IMPORT_FAILED', 'Failed to import batch job', { file, options }, true);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Helper methods for batch job operations
|
|
406
|
+
*/
|
|
407
|
+
async loadBatchJob(file) {
|
|
408
|
+
if (!(await fs_extra_1.default.pathExists(file))) {
|
|
409
|
+
throw new Error(`Batch job file not found: ${file}`);
|
|
410
|
+
}
|
|
411
|
+
const content = await fs_extra_1.default.readFile(file, 'utf8');
|
|
412
|
+
const ext = path_1.default.extname(file).toLowerCase();
|
|
413
|
+
if (ext === '.yaml' || ext === '.yml') {
|
|
414
|
+
return yaml_1.default.parse(content);
|
|
415
|
+
}
|
|
416
|
+
else if (ext === '.json') {
|
|
417
|
+
return JSON.parse(content);
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
throw new Error(`Unsupported file format: ${ext}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async validateJobStructure(job) {
|
|
424
|
+
const errors = [];
|
|
425
|
+
if (!job.name) {
|
|
426
|
+
errors.push('Job name is required');
|
|
427
|
+
}
|
|
428
|
+
if (!job.commands || job.commands.length === 0) {
|
|
429
|
+
errors.push('At least one command is required');
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
job.commands.forEach((cmd, index) => {
|
|
433
|
+
if (!cmd.command) {
|
|
434
|
+
errors.push(`Command ${index + 1} is missing command property`);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
return { valid: errors.length === 0, errors };
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Regex for valid variable names in batch templates.
|
|
442
|
+
*/
|
|
443
|
+
static VALID_VARIABLE_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
444
|
+
async processJobVariables(job, vars) {
|
|
445
|
+
let variables = {};
|
|
446
|
+
if (vars) {
|
|
447
|
+
try {
|
|
448
|
+
// Try parsing as JSON first
|
|
449
|
+
variables = JSON.parse(vars);
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
// Parse as key=value pairs
|
|
453
|
+
vars.split(',').forEach(pair => {
|
|
454
|
+
const eqIndex = pair.indexOf('=');
|
|
455
|
+
if (eqIndex > 0) {
|
|
456
|
+
const key = pair.slice(0, eqIndex).trim();
|
|
457
|
+
const value = pair.slice(eqIndex + 1).trim();
|
|
458
|
+
if (key) {
|
|
459
|
+
variables[key] = value;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
// Validate variable names to prevent regex injection
|
|
466
|
+
for (const key of Object.keys(variables)) {
|
|
467
|
+
if (!BatchCommands.VALID_VARIABLE_NAME.test(key)) {
|
|
468
|
+
throw new Error(`Invalid variable name "${key}": must match [a-zA-Z_][a-zA-Z0-9_]*`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// SECURITY: Instead of replacing {{var}} in a JSON-stringified string
|
|
472
|
+
// (which allows JSON structure breakout via quotes in values), we walk
|
|
473
|
+
// the job structure and only replace in string leaf values. Values are
|
|
474
|
+
// JSON-escaped to prevent structure injection.
|
|
475
|
+
const processedJob = JSON.parse(JSON.stringify(job));
|
|
476
|
+
const replaceVarsInString = (str) => {
|
|
477
|
+
let result = str;
|
|
478
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
479
|
+
const placeholder = `{{${key}}}`;
|
|
480
|
+
// Only replace exact placeholder matches, not regex patterns
|
|
481
|
+
while (result.includes(placeholder)) {
|
|
482
|
+
result = result.replace(placeholder, String(value));
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return result;
|
|
486
|
+
};
|
|
487
|
+
// Walk commands and replace variables in string fields only
|
|
488
|
+
processedJob.commands = processedJob.commands.map(cmd => ({
|
|
489
|
+
...cmd,
|
|
490
|
+
command: replaceVarsInString(cmd.command),
|
|
491
|
+
args: cmd.args?.map(arg => replaceVarsInString(arg)),
|
|
492
|
+
condition: cmd.condition ? replaceVarsInString(cmd.condition) : undefined,
|
|
493
|
+
}));
|
|
494
|
+
if (processedJob.description) {
|
|
495
|
+
processedJob.description = replaceVarsInString(processedJob.description);
|
|
496
|
+
}
|
|
497
|
+
return processedJob;
|
|
498
|
+
}
|
|
499
|
+
async showDryRun(job) {
|
|
500
|
+
console.log(chalk_1.default.blue('\nDry Run - Commands to be executed:'));
|
|
501
|
+
console.log(chalk_1.default.gray('='.repeat(50)));
|
|
502
|
+
job.commands.forEach((cmd, index) => {
|
|
503
|
+
console.log(`${index + 1}. ${chalk_1.default.cyan(cmd.command)}`);
|
|
504
|
+
if (cmd.args) {
|
|
505
|
+
console.log(` Args: ${cmd.args.join(' ')}`);
|
|
506
|
+
}
|
|
507
|
+
if (cmd.condition) {
|
|
508
|
+
console.log(` Condition: ${cmd.condition}`);
|
|
509
|
+
}
|
|
510
|
+
if (cmd.retry) {
|
|
511
|
+
console.log(` Retry: ${cmd.retry} times`);
|
|
512
|
+
}
|
|
513
|
+
if (cmd.timeout) {
|
|
514
|
+
console.log(` Timeout: ${cmd.timeout}ms`);
|
|
515
|
+
}
|
|
516
|
+
console.log();
|
|
517
|
+
});
|
|
518
|
+
console.log(chalk_1.default.gray('='.repeat(50)));
|
|
519
|
+
console.log(`Total commands: ${job.commands.length}`);
|
|
520
|
+
console.log(`Parallel execution: ${job.parallel ? 'Yes' : 'No'}`);
|
|
521
|
+
console.log(`Continue on error: ${job.continueOnError ? 'Yes' : 'No'}`);
|
|
522
|
+
}
|
|
523
|
+
async executeBatchJob(job, _jobId, _options) {
|
|
524
|
+
const tasks = job.commands.map((cmd, _index) => ({
|
|
525
|
+
title: cmd.command,
|
|
526
|
+
task: async () => {
|
|
527
|
+
await this.executeCommand(cmd, _options);
|
|
528
|
+
},
|
|
529
|
+
retry: cmd.retry || 0,
|
|
530
|
+
timeout: cmd.timeout,
|
|
531
|
+
}));
|
|
532
|
+
const listr = new listr2_1.Listr(tasks, {
|
|
533
|
+
concurrent: job.parallel || _options.parallel,
|
|
534
|
+
exitOnError: !(job.continueOnError || _options.continueOnError),
|
|
535
|
+
rendererOptions: {
|
|
536
|
+
showSubtasks: true,
|
|
537
|
+
},
|
|
538
|
+
});
|
|
539
|
+
await listr.run();
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Shell metacharacters that indicate injection attempts.
|
|
543
|
+
* These characters have special meaning in sh/bash and must not
|
|
544
|
+
* appear in arguments passed to spawn().
|
|
545
|
+
*/
|
|
546
|
+
static SHELL_METACHARACTERS = /[;&|`$(){}[\]<>!#~*?\n\r\\]/;
|
|
547
|
+
/**
|
|
548
|
+
* Tokenize a command string into [binary, ...args] without shell
|
|
549
|
+
* interpretation. Supports simple single/double quoting.
|
|
550
|
+
*/
|
|
551
|
+
tokenizeCommand(command) {
|
|
552
|
+
const tokens = [];
|
|
553
|
+
let current = '';
|
|
554
|
+
let inSingle = false;
|
|
555
|
+
let inDouble = false;
|
|
556
|
+
for (let i = 0; i < command.length; i++) {
|
|
557
|
+
const ch = command[i];
|
|
558
|
+
if (inSingle) {
|
|
559
|
+
if (ch === "'") {
|
|
560
|
+
inSingle = false;
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
current += ch;
|
|
564
|
+
}
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (inDouble) {
|
|
568
|
+
if (ch === '"') {
|
|
569
|
+
inDouble = false;
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
current += ch;
|
|
573
|
+
}
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (ch === "'") {
|
|
577
|
+
inSingle = true;
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
if (ch === '"') {
|
|
581
|
+
inDouble = true;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
if (ch === ' ' || ch === '\t') {
|
|
585
|
+
if (current.length > 0) {
|
|
586
|
+
tokens.push(current);
|
|
587
|
+
current = '';
|
|
588
|
+
}
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
current += ch;
|
|
592
|
+
}
|
|
593
|
+
if (current.length > 0) {
|
|
594
|
+
tokens.push(current);
|
|
595
|
+
}
|
|
596
|
+
if (inSingle || inDouble) {
|
|
597
|
+
throw new Error(`Unterminated quote in command: ${command}`);
|
|
598
|
+
}
|
|
599
|
+
return tokens;
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Validate that no argument contains shell metacharacters.
|
|
603
|
+
* This is a defense-in-depth measure: since we never use shell: true,
|
|
604
|
+
* metacharacters would be treated literally, but their presence strongly
|
|
605
|
+
* suggests a command injection attempt.
|
|
606
|
+
*/
|
|
607
|
+
validateArgs(args) {
|
|
608
|
+
for (const arg of args) {
|
|
609
|
+
if (BatchCommands.SHELL_METACHARACTERS.test(arg)) {
|
|
610
|
+
throw new Error(`Argument contains shell metacharacters (possible injection): "${arg}"`);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async executeCommand(cmd, _options) {
|
|
615
|
+
// Check condition if specified
|
|
616
|
+
if (cmd.condition && !(await this.evaluateCondition(cmd.condition))) {
|
|
617
|
+
logger_1.logger.debug(`Skipping command due to condition: ${cmd.condition}`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const { spawn } = await Promise.resolve().then(() => tslib_1.__importStar(require('child_process')));
|
|
621
|
+
// Tokenize the command string into binary + args without shell
|
|
622
|
+
// interpretation. This prevents command injection via shell metacharacters
|
|
623
|
+
// like ; | & ` $() etc.
|
|
624
|
+
const tokens = this.tokenizeCommand(cmd.command);
|
|
625
|
+
if (tokens.length === 0) {
|
|
626
|
+
throw new Error('Empty command');
|
|
627
|
+
}
|
|
628
|
+
const [command, ...parsedArgs] = tokens;
|
|
629
|
+
const finalArgs = cmd.args ? [...parsedArgs, ...cmd.args] : parsedArgs;
|
|
630
|
+
// Validate arguments for shell metacharacters as defense-in-depth
|
|
631
|
+
this.validateArgs(finalArgs);
|
|
632
|
+
return new Promise((resolve, reject) => {
|
|
633
|
+
// SECURITY: No shell: true. The command is executed directly via
|
|
634
|
+
// execvp(), so shell metacharacters in arguments are treated as
|
|
635
|
+
// literal characters rather than being interpreted by a shell.
|
|
636
|
+
const child = spawn(command ?? 'echo', finalArgs, {
|
|
637
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
638
|
+
});
|
|
639
|
+
let output = '';
|
|
640
|
+
let error = '';
|
|
641
|
+
child.stdout?.on('data', (data) => {
|
|
642
|
+
output += data.toString();
|
|
643
|
+
});
|
|
644
|
+
child.stderr?.on('data', (data) => {
|
|
645
|
+
error += data.toString();
|
|
646
|
+
});
|
|
647
|
+
child.on('exit', (code) => {
|
|
648
|
+
if (code === 0) {
|
|
649
|
+
resolve();
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
reject(new Error(`Command failed with exit code ${code}: ${error}`));
|
|
653
|
+
}
|
|
654
|
+
});
|
|
655
|
+
// Handle timeout
|
|
656
|
+
if (cmd.timeout) {
|
|
657
|
+
setTimeout(() => {
|
|
658
|
+
child.kill();
|
|
659
|
+
reject(new Error(`Command timed out after ${cmd.timeout}ms`));
|
|
660
|
+
}, cmd.timeout);
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
async evaluateCondition(condition) {
|
|
665
|
+
// Simple condition evaluation
|
|
666
|
+
// In a real implementation, this would be more sophisticated
|
|
667
|
+
switch (condition) {
|
|
668
|
+
case 'always':
|
|
669
|
+
return true;
|
|
670
|
+
case 'never':
|
|
671
|
+
return false;
|
|
672
|
+
default:
|
|
673
|
+
// Could evaluate file existence, environment variables, etc.
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async createInteractiveBatchJob(name) {
|
|
678
|
+
const inquirer = await Promise.resolve().then(() => tslib_1.__importStar(require('inquirer')));
|
|
679
|
+
const answers = await inquirer.default.prompt([
|
|
680
|
+
{
|
|
681
|
+
type: 'input',
|
|
682
|
+
name: 'description',
|
|
683
|
+
message: 'Job description:',
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
type: 'confirm',
|
|
687
|
+
name: 'parallel',
|
|
688
|
+
message: 'Run commands in parallel?',
|
|
689
|
+
default: false,
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
type: 'confirm',
|
|
693
|
+
name: 'continueOnError',
|
|
694
|
+
message: 'Continue on command failure?',
|
|
695
|
+
default: false,
|
|
696
|
+
},
|
|
697
|
+
]);
|
|
698
|
+
const commands = [];
|
|
699
|
+
let addMore = true;
|
|
700
|
+
while (addMore) {
|
|
701
|
+
const cmdAnswers = await inquirer.default.prompt([
|
|
702
|
+
{
|
|
703
|
+
type: 'input',
|
|
704
|
+
name: 'command',
|
|
705
|
+
message: 'Command:',
|
|
706
|
+
validate: input => input.length > 0 || 'Command is required',
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
type: 'input',
|
|
710
|
+
name: 'condition',
|
|
711
|
+
message: 'Condition (optional):',
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
type: 'number',
|
|
715
|
+
name: 'retry',
|
|
716
|
+
message: 'Retry count:',
|
|
717
|
+
default: 0,
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
type: 'confirm',
|
|
721
|
+
name: 'addMore',
|
|
722
|
+
message: 'Add another command?',
|
|
723
|
+
default: true,
|
|
724
|
+
},
|
|
725
|
+
]);
|
|
726
|
+
commands.push({
|
|
727
|
+
command: cmdAnswers.command,
|
|
728
|
+
condition: cmdAnswers.condition || undefined,
|
|
729
|
+
retry: cmdAnswers.retry || undefined,
|
|
730
|
+
});
|
|
731
|
+
addMore = cmdAnswers.addMore;
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
name,
|
|
735
|
+
description: answers.description,
|
|
736
|
+
commands,
|
|
737
|
+
parallel: answers.parallel,
|
|
738
|
+
continueOnError: answers.continueOnError,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
createBasicBatchJob(name, options) {
|
|
742
|
+
const commands = options.commands
|
|
743
|
+
? options.commands
|
|
744
|
+
.split(',')
|
|
745
|
+
.map((cmd) => ({ command: cmd.trim() }))
|
|
746
|
+
: [];
|
|
747
|
+
return {
|
|
748
|
+
name,
|
|
749
|
+
description: `Batch job: ${name}`,
|
|
750
|
+
commands,
|
|
751
|
+
parallel: false,
|
|
752
|
+
continueOnError: false,
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
async createJobFromTemplate(name, template) {
|
|
756
|
+
// Load template and create job
|
|
757
|
+
const templatePath = path_1.default.join(__dirname, '../../templates/batch', `${template}.yaml`);
|
|
758
|
+
if (await fs_extra_1.default.pathExists(templatePath)) {
|
|
759
|
+
const templateJob = await this.loadBatchJob(templatePath);
|
|
760
|
+
return { ...templateJob, name };
|
|
761
|
+
}
|
|
762
|
+
throw new Error(`Template not found: ${template}`);
|
|
763
|
+
}
|
|
764
|
+
// Format conversion methods
|
|
765
|
+
convertToShellScript(job) {
|
|
766
|
+
let script = `#!/bin/bash\n# Generated from batch job: ${job.name}\n\n`;
|
|
767
|
+
if (job.description) {
|
|
768
|
+
script += `# ${job.description}\n\n`;
|
|
769
|
+
}
|
|
770
|
+
script += 'set -e\n\n'; // Exit on error unless continueOnError is true
|
|
771
|
+
job.commands.forEach(cmd => {
|
|
772
|
+
script += `echo "Executing: ${cmd.command}"\n`;
|
|
773
|
+
script += `${cmd.command}\n\n`;
|
|
774
|
+
});
|
|
775
|
+
return script;
|
|
776
|
+
}
|
|
777
|
+
convertToDockerfile(job) {
|
|
778
|
+
let dockerfile = `# Generated from batch job: ${job.name}\n`;
|
|
779
|
+
dockerfile += 'FROM node:18-alpine\n\n';
|
|
780
|
+
if (job.description) {
|
|
781
|
+
dockerfile += `# ${job.description}\n`;
|
|
782
|
+
}
|
|
783
|
+
dockerfile += 'WORKDIR /app\n';
|
|
784
|
+
dockerfile += 'COPY . .\n\n';
|
|
785
|
+
job.commands.forEach(cmd => {
|
|
786
|
+
dockerfile += `RUN ${cmd.command}\n`;
|
|
787
|
+
});
|
|
788
|
+
return dockerfile;
|
|
789
|
+
}
|
|
790
|
+
async importFromJSON(file, name) {
|
|
791
|
+
const content = await fs_extra_1.default.readJson(file);
|
|
792
|
+
return { ...content, name: name || content.name };
|
|
793
|
+
}
|
|
794
|
+
async importFromShell(file, name) {
|
|
795
|
+
const content = await fs_extra_1.default.readFile(file, 'utf8');
|
|
796
|
+
const commands = content
|
|
797
|
+
.split('\n')
|
|
798
|
+
.filter(line => line.trim() && !line.startsWith('#'))
|
|
799
|
+
.map(line => ({ command: line.trim() }));
|
|
800
|
+
return {
|
|
801
|
+
name: name || path_1.default.basename(file, path_1.default.extname(file)),
|
|
802
|
+
description: `Imported from shell script: ${file}`,
|
|
803
|
+
commands,
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
async importFromPackageScripts(file, name) {
|
|
807
|
+
const packageJson = await fs_extra_1.default.readJson(file);
|
|
808
|
+
const scripts = packageJson.scripts || {};
|
|
809
|
+
const commands = Object.entries(scripts).map(([script, command]) => ({
|
|
810
|
+
command: `npm run ${script}`,
|
|
811
|
+
args: [],
|
|
812
|
+
condition: undefined,
|
|
813
|
+
}));
|
|
814
|
+
return {
|
|
815
|
+
name: name || 'package-scripts',
|
|
816
|
+
description: 'Imported from package.json scripts',
|
|
817
|
+
commands,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
async listTemplates() {
|
|
821
|
+
const templatesDir = path_1.default.join(__dirname, '../../templates/batch');
|
|
822
|
+
if (await fs_extra_1.default.pathExists(templatesDir)) {
|
|
823
|
+
const templates = await fs_extra_1.default.readdir(templatesDir);
|
|
824
|
+
const yamlTemplates = templates.filter(t => t.endsWith('.yaml') || t.endsWith('.yml'));
|
|
825
|
+
if (yamlTemplates.length > 0) {
|
|
826
|
+
logger_1.logger.info('Available templates:');
|
|
827
|
+
yamlTemplates.forEach(template => {
|
|
828
|
+
console.log(` - ${path_1.default.basename(template, path_1.default.extname(template))}`);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
else {
|
|
832
|
+
logger_1.logger.info('No templates available');
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
else {
|
|
836
|
+
logger_1.logger.info('No templates directory found');
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
async createTemplate(name, options) {
|
|
840
|
+
logger_1.logger.info(`Creating template: ${name}`);
|
|
841
|
+
if (options.from) {
|
|
842
|
+
const job = await this.loadBatchJob(options.from);
|
|
843
|
+
const templatePath = path_1.default.join(__dirname, '../../templates/batch', `${name}.yaml`);
|
|
844
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(templatePath));
|
|
845
|
+
await fs_extra_1.default.writeFile(templatePath, yaml_1.default.stringify(job));
|
|
846
|
+
logger_1.logger.success(`Template created: ${templatePath}`);
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
logger_1.logger.info('Template creation from scratch not yet implemented');
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
exports.BatchCommands = BatchCommands;
|
|
854
|
+
//# sourceMappingURL=batch.js.map
|