lsh-framework 0.8.2 ā 0.9.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 +75 -1
- package/dist/cli.js +14 -10
- package/dist/daemon/lshd.js +23 -13
- package/dist/lib/api-error-handler.js +16 -14
- package/dist/lib/base-command-registrar.js +6 -5
- package/dist/lib/daemon-client.js +13 -9
- package/dist/lib/database-persistence.js +8 -8
- package/dist/lib/env-validator.js +0 -3
- package/dist/lib/logger.js +0 -1
- package/dist/lib/secrets-manager.js +254 -153
- package/dist/lib/zsh-import-manager.js +17 -9
- package/dist/pipeline/job-tracker.js +1 -1
- package/dist/pipeline/mcli-bridge.js +11 -5
- package/dist/pipeline/workflow-engine.js +10 -7
- package/dist/services/cron/cron-registrar.js +27 -22
- package/dist/services/daemon/daemon-registrar.js +27 -13
- package/dist/services/secrets/secrets.js +37 -28
- package/dist/services/supabase/supabase-registrar.js +40 -33
- package/package.json +2 -1
|
@@ -140,12 +140,14 @@ export class MCLIBridge extends EventEmitter {
|
|
|
140
140
|
// Webhook handler for MCLI callbacks
|
|
141
141
|
async handleWebhook(payload) {
|
|
142
142
|
const { job_id, status, result, error, metrics, artifacts } = payload;
|
|
143
|
+
const jobIdStr = job_id;
|
|
143
144
|
// Get pipeline job ID
|
|
144
|
-
let pipelineJobId = this.jobMapping.get(
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
let pipelineJobId = this.jobMapping.get(jobIdStr);
|
|
146
|
+
const metadata = payload.metadata;
|
|
147
|
+
if (!pipelineJobId && metadata?.pipeline_job_id) {
|
|
148
|
+
pipelineJobId = metadata.pipeline_job_id;
|
|
147
149
|
if (pipelineJobId) {
|
|
148
|
-
this.jobMapping.set(
|
|
150
|
+
this.jobMapping.set(jobIdStr, pipelineJobId);
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
if (!pipelineJobId) {
|
|
@@ -169,7 +171,8 @@ export class MCLIBridge extends EventEmitter {
|
|
|
169
171
|
break;
|
|
170
172
|
case 'failed':
|
|
171
173
|
case 'error':
|
|
172
|
-
|
|
174
|
+
const errorObj = error;
|
|
175
|
+
await this.jobTracker.failExecution(execution.id, errorObj?.message || 'Job failed in MCLI', error);
|
|
173
176
|
break;
|
|
174
177
|
case 'cancelled':
|
|
175
178
|
await this.jobTracker.updateJobStatus(pipelineJobId, JobStatus.CANCELLED);
|
|
@@ -258,11 +261,13 @@ export class MCLIBridge extends EventEmitter {
|
|
|
258
261
|
// Helper methods
|
|
259
262
|
async updateJobExternalId(jobId, externalId) {
|
|
260
263
|
// This would be implemented in JobTracker, but for now we'll use raw SQL
|
|
264
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
261
265
|
const pool = this.jobTracker.pool;
|
|
262
266
|
await pool.query('UPDATE pipeline_jobs SET external_id = $1 WHERE id = $2', [externalId, jobId]);
|
|
263
267
|
}
|
|
264
268
|
async getLatestExecution(jobId) {
|
|
265
269
|
// This would be implemented in JobTracker
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
266
271
|
const pool = this.jobTracker.pool;
|
|
267
272
|
const result = await pool.query(`SELECT * FROM job_executions
|
|
268
273
|
WHERE job_id = $1
|
|
@@ -271,6 +276,7 @@ export class MCLIBridge extends EventEmitter {
|
|
|
271
276
|
if (result.rows.length === 0) {
|
|
272
277
|
return null;
|
|
273
278
|
}
|
|
279
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
274
280
|
return this.jobTracker.parseExecutionRow(result.rows[0]);
|
|
275
281
|
}
|
|
276
282
|
// Health check
|
|
@@ -208,22 +208,23 @@ export class WorkflowEngine extends EventEmitter {
|
|
|
208
208
|
}
|
|
209
209
|
async executeJobNode(execution, node) {
|
|
210
210
|
// Create job from node configuration
|
|
211
|
+
const config = node.config;
|
|
211
212
|
const jobConfig = {
|
|
212
213
|
name: `${execution.runId}-${node.name}`,
|
|
213
|
-
type:
|
|
214
|
+
type: config.type || 'workflow_job',
|
|
214
215
|
sourceSystem: 'workflow',
|
|
215
|
-
targetSystem:
|
|
216
|
+
targetSystem: config.targetSystem || 'mcli',
|
|
216
217
|
status: JobStatus.PENDING,
|
|
217
|
-
priority:
|
|
218
|
+
priority: config.priority || JobPriority.NORMAL,
|
|
218
219
|
config: {
|
|
219
|
-
...
|
|
220
|
+
...config,
|
|
220
221
|
workflowExecutionId: execution.id,
|
|
221
222
|
workflowNodeId: node.id,
|
|
222
223
|
workflowRunId: execution.runId
|
|
223
224
|
},
|
|
224
225
|
parameters: {
|
|
225
226
|
...execution.parameters,
|
|
226
|
-
...
|
|
227
|
+
...(config.parameters || {})
|
|
227
228
|
},
|
|
228
229
|
owner: execution.triggeredBy,
|
|
229
230
|
tags: [`workflow:${execution.workflowId}`, `run:${execution.runId}`]
|
|
@@ -262,7 +263,8 @@ export class WorkflowEngine extends EventEmitter {
|
|
|
262
263
|
await this.checkAndContinueExecution(execution);
|
|
263
264
|
}
|
|
264
265
|
async executeWaitNode(execution, node) {
|
|
265
|
-
const
|
|
266
|
+
const config = node.config;
|
|
267
|
+
const waitMs = config.waitMs || 1000;
|
|
266
268
|
setTimeout(async () => {
|
|
267
269
|
const nodeState = execution.nodeStates[node.id];
|
|
268
270
|
nodeState.status = NodeStatus.COMPLETED;
|
|
@@ -300,7 +302,8 @@ export class WorkflowEngine extends EventEmitter {
|
|
|
300
302
|
nodeState.durationMs = nodeState.completedAt.getTime() - nodeState.startedAt.getTime();
|
|
301
303
|
}
|
|
302
304
|
if (status === 'failed') {
|
|
303
|
-
|
|
305
|
+
const errorData = data;
|
|
306
|
+
nodeState.error = errorData.errorMessage || 'Job failed';
|
|
304
307
|
// Check retry policy
|
|
305
308
|
const workflow = await this.getWorkflow(targetExecution.workflowId);
|
|
306
309
|
const node = workflow?.nodes.find(n => n.id === targetNodeId);
|
|
@@ -48,22 +48,23 @@ export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
|
48
48
|
{ flags: '-p, --priority <priority>', description: 'Priority (0-10)', defaultValue: '5' }
|
|
49
49
|
],
|
|
50
50
|
action: async (templateId, options) => {
|
|
51
|
+
const opts = options;
|
|
51
52
|
const result = await this.withCronManager(async (manager) => {
|
|
52
53
|
const customizations = {};
|
|
53
|
-
if (
|
|
54
|
-
customizations.name =
|
|
55
|
-
if (
|
|
56
|
-
customizations.command =
|
|
57
|
-
if (
|
|
58
|
-
customizations.schedule = { cron:
|
|
59
|
-
if (
|
|
60
|
-
customizations.workingDirectory =
|
|
61
|
-
if (
|
|
62
|
-
customizations.environment = this.parseJSON(
|
|
63
|
-
if (
|
|
64
|
-
customizations.tags = this.parseTags(
|
|
65
|
-
if (
|
|
66
|
-
customizations.priority = parseInt(
|
|
54
|
+
if (opts.name)
|
|
55
|
+
customizations.name = opts.name;
|
|
56
|
+
if (opts.command)
|
|
57
|
+
customizations.command = opts.command;
|
|
58
|
+
if (opts.schedule)
|
|
59
|
+
customizations.schedule = { cron: opts.schedule };
|
|
60
|
+
if (opts.workingDir)
|
|
61
|
+
customizations.workingDirectory = opts.workingDir;
|
|
62
|
+
if (opts.env)
|
|
63
|
+
customizations.environment = this.parseJSON(opts.env, 'environment variables');
|
|
64
|
+
if (opts.tags)
|
|
65
|
+
customizations.tags = this.parseTags(opts.tags);
|
|
66
|
+
if (opts.priority)
|
|
67
|
+
customizations.priority = parseInt(opts.priority);
|
|
67
68
|
return await manager.createJobFromTemplate(templateId, customizations);
|
|
68
69
|
});
|
|
69
70
|
this.logSuccess('Job created from template:');
|
|
@@ -84,8 +85,9 @@ export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
|
84
85
|
{ flags: '-f, --filter <filter>', description: 'Filter by status' }
|
|
85
86
|
],
|
|
86
87
|
action: async (options) => {
|
|
88
|
+
const opts = options;
|
|
87
89
|
const jobs = await this.withCronManager(async (manager) => {
|
|
88
|
-
return await manager.listJobs(
|
|
90
|
+
return await manager.listJobs(opts.filter ? { status: opts.filter } : undefined);
|
|
89
91
|
});
|
|
90
92
|
this.logInfo(`Cron Jobs (${jobs.length} total):`);
|
|
91
93
|
jobs.forEach(job => {
|
|
@@ -146,10 +148,11 @@ export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
|
146
148
|
{ flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
|
|
147
149
|
],
|
|
148
150
|
action: async (jobId, options) => {
|
|
151
|
+
const opts = options;
|
|
149
152
|
await this.withCronManager(async (manager) => {
|
|
150
|
-
await manager.stopJob(jobId,
|
|
153
|
+
await manager.stopJob(jobId, opts.signal);
|
|
151
154
|
});
|
|
152
|
-
this.logSuccess(`Job ${jobId} stopped with signal ${
|
|
155
|
+
this.logSuccess(`Job ${jobId} stopped with signal ${opts.signal}`);
|
|
153
156
|
}
|
|
154
157
|
});
|
|
155
158
|
// Remove job
|
|
@@ -161,8 +164,9 @@ export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
|
161
164
|
{ flags: '-f, --force', description: 'Force removal', defaultValue: false }
|
|
162
165
|
],
|
|
163
166
|
action: async (jobId, options) => {
|
|
167
|
+
const opts = options;
|
|
164
168
|
await this.withCronManager(async (manager) => {
|
|
165
|
-
await manager.removeJob(jobId,
|
|
169
|
+
await manager.removeJob(jobId, opts.force);
|
|
166
170
|
});
|
|
167
171
|
this.logSuccess(`Job ${jobId} removed`);
|
|
168
172
|
}
|
|
@@ -218,13 +222,14 @@ export class CronCommandRegistrar extends BaseCommandRegistrar {
|
|
|
218
222
|
{ flags: '-o, --output <file>', description: 'Output file path' }
|
|
219
223
|
],
|
|
220
224
|
action: async (options) => {
|
|
225
|
+
const opts = options;
|
|
221
226
|
const data = await this.withCronManager(async (manager) => {
|
|
222
|
-
return await manager.exportJobData(
|
|
227
|
+
return await manager.exportJobData(opts.format);
|
|
223
228
|
});
|
|
224
|
-
if (
|
|
229
|
+
if (opts.output) {
|
|
225
230
|
const fs = await import('fs');
|
|
226
|
-
fs.writeFileSync(
|
|
227
|
-
this.logSuccess(`Data exported to ${
|
|
231
|
+
fs.writeFileSync(opts.output, data);
|
|
232
|
+
this.logSuccess(`Data exported to ${opts.output}`);
|
|
228
233
|
}
|
|
229
234
|
else {
|
|
230
235
|
this.logInfo(data);
|
|
@@ -29,8 +29,12 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
29
29
|
this.logInfo('Daemon Status:');
|
|
30
30
|
this.logInfo(` PID: ${status.pid}`);
|
|
31
31
|
this.logInfo(` Uptime: ${Math.floor(status.uptime / 60)} minutes`);
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
if (status.memoryUsage) {
|
|
33
|
+
this.logInfo(` Memory: ${Math.round(status.memoryUsage.heapUsed / 1024 / 1024)} MB`);
|
|
34
|
+
}
|
|
35
|
+
if (status.jobs) {
|
|
36
|
+
this.logInfo(` Jobs: ${status.jobs.total} total, ${status.jobs.running} running`);
|
|
37
|
+
}
|
|
34
38
|
}
|
|
35
39
|
});
|
|
36
40
|
// Start command
|
|
@@ -95,7 +99,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
95
99
|
{ flags: '-f, --force', description: 'Force cleanup without prompts', defaultValue: false }
|
|
96
100
|
],
|
|
97
101
|
action: async (options) => {
|
|
98
|
-
|
|
102
|
+
const opts = options;
|
|
103
|
+
await this.cleanupDaemon(opts.force);
|
|
99
104
|
}
|
|
100
105
|
});
|
|
101
106
|
}
|
|
@@ -122,11 +127,13 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
122
127
|
{ flags: '--no-database-sync', description: 'Disable database synchronization' }
|
|
123
128
|
],
|
|
124
129
|
action: async (options) => {
|
|
125
|
-
|
|
130
|
+
const opts = options;
|
|
131
|
+
if (!opts.name || !opts.command || (!opts.schedule && !opts.interval)) {
|
|
126
132
|
throw new Error('Missing required options: --name, --command, and (--schedule or --interval)');
|
|
127
133
|
}
|
|
128
|
-
const jobSpec = this.createJobSpec(
|
|
134
|
+
const jobSpec = this.createJobSpec(opts);
|
|
129
135
|
await this.withDaemonAction(async (client) => {
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
130
137
|
await client.createDatabaseCronJob(jobSpec);
|
|
131
138
|
});
|
|
132
139
|
this.logSuccess('Job created successfully:');
|
|
@@ -145,8 +152,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
145
152
|
{ flags: '-f, --filter <filter>', description: 'Filter jobs by status' }
|
|
146
153
|
],
|
|
147
154
|
action: async (options) => {
|
|
155
|
+
const opts = options;
|
|
148
156
|
const jobs = await this.withDaemonAction(async (client) => {
|
|
149
|
-
return await client.listJobs(
|
|
157
|
+
return await client.listJobs(opts.filter ? { status: opts.filter } : undefined);
|
|
150
158
|
});
|
|
151
159
|
this.displayJobs(jobs);
|
|
152
160
|
}
|
|
@@ -185,8 +193,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
185
193
|
{ flags: '-f, --filter <status>', description: 'Filter by job status', defaultValue: 'created' }
|
|
186
194
|
],
|
|
187
195
|
action: async (options) => {
|
|
196
|
+
const opts = options;
|
|
188
197
|
await this.withDaemonAction(async (client) => {
|
|
189
|
-
const jobs = await client.listJobs({ status:
|
|
198
|
+
const jobs = await client.listJobs({ status: opts.filter });
|
|
190
199
|
this.logInfo(`Triggering ${jobs.length} jobs...`);
|
|
191
200
|
for (const job of jobs) {
|
|
192
201
|
try {
|
|
@@ -215,10 +224,11 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
215
224
|
{ flags: '-s, --signal <signal>', description: 'Signal to send', defaultValue: 'SIGTERM' }
|
|
216
225
|
],
|
|
217
226
|
action: async (jobId, options) => {
|
|
227
|
+
const opts = options;
|
|
218
228
|
await this.withDaemonAction(async (client) => {
|
|
219
|
-
await client.stopJob(jobId,
|
|
229
|
+
await client.stopJob(jobId, opts.signal);
|
|
220
230
|
});
|
|
221
|
-
this.logSuccess(`Job ${jobId} stopped with signal ${
|
|
231
|
+
this.logSuccess(`Job ${jobId} stopped with signal ${opts.signal}`);
|
|
222
232
|
}
|
|
223
233
|
});
|
|
224
234
|
// Remove job
|
|
@@ -230,8 +240,9 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
230
240
|
{ flags: '-f, --force', description: 'Force removal', defaultValue: false }
|
|
231
241
|
],
|
|
232
242
|
action: async (jobId, options) => {
|
|
243
|
+
const opts = options;
|
|
233
244
|
await this.withDaemonAction(async (client) => {
|
|
234
|
-
await client.removeJob(jobId,
|
|
245
|
+
await client.removeJob(jobId, opts.force);
|
|
235
246
|
});
|
|
236
247
|
this.logSuccess(`Job ${jobId} removed`);
|
|
237
248
|
}
|
|
@@ -275,7 +286,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
275
286
|
{ flags: '-l, --limit <limit>', description: 'Limit number of results', defaultValue: '50' }
|
|
276
287
|
],
|
|
277
288
|
action: async (options) => {
|
|
278
|
-
const
|
|
289
|
+
const opts = options;
|
|
290
|
+
const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(opts.jobId, parseInt(opts.limit)), { forUser: true, requireRunning: false });
|
|
279
291
|
this.logInfo(`Job History (${jobs.length} records):`);
|
|
280
292
|
jobs.forEach(job => {
|
|
281
293
|
const started = new Date(job.started_at).toLocaleString();
|
|
@@ -297,7 +309,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
297
309
|
{ flags: '-j, --job-id <jobId>', description: 'Filter by job ID' }
|
|
298
310
|
],
|
|
299
311
|
action: async (options) => {
|
|
300
|
-
const
|
|
312
|
+
const opts = options;
|
|
313
|
+
const stats = await this.withDaemonAction(async (client) => await client.getJobStatistics(opts.jobId), { forUser: true, requireRunning: false });
|
|
301
314
|
this.logInfo('Job Statistics:');
|
|
302
315
|
this.logInfo(` Total Jobs: ${stats.totalJobs}`);
|
|
303
316
|
this.logInfo(` Success Rate: ${stats.successRate.toFixed(1)}%`);
|
|
@@ -316,7 +329,8 @@ export class DaemonCommandRegistrar extends BaseCommandRegistrar {
|
|
|
316
329
|
{ flags: '-l, --limit <limit>', description: 'Number of recent executions to show', defaultValue: '5' }
|
|
317
330
|
],
|
|
318
331
|
action: async (options) => {
|
|
319
|
-
const
|
|
332
|
+
const opts = options;
|
|
333
|
+
const jobs = await this.withDaemonAction(async (client) => await client.getJobHistory(undefined, parseInt(opts.limit)), { forUser: true });
|
|
320
334
|
this.logInfo(`Recent Job Executions (${jobs.length} records):`);
|
|
321
335
|
jobs.forEach((job, index) => {
|
|
322
336
|
const started = new Date(job.started_at).toLocaleString();
|
|
@@ -7,27 +7,26 @@ import * as fs from 'fs';
|
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
9
|
export async function init_secrets(program) {
|
|
10
|
-
const secretsCmd = program
|
|
11
|
-
.command('secrets')
|
|
12
|
-
.description('Manage environment secrets across machines');
|
|
13
10
|
// Push secrets to cloud
|
|
14
|
-
|
|
11
|
+
program
|
|
15
12
|
.command('push')
|
|
16
13
|
.description('Push local .env to encrypted cloud storage')
|
|
17
14
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
18
15
|
.option('-e, --env <name>', 'Environment name (dev/staging/prod)', 'dev')
|
|
16
|
+
.option('--force', 'Force push even if destructive changes detected')
|
|
19
17
|
.action(async (options) => {
|
|
20
18
|
try {
|
|
21
19
|
const manager = new SecretsManager();
|
|
22
|
-
await manager.push(options.file, options.env);
|
|
20
|
+
await manager.push(options.file, options.env, options.force);
|
|
23
21
|
}
|
|
24
22
|
catch (error) {
|
|
25
|
-
|
|
23
|
+
const err = error;
|
|
24
|
+
console.error('ā Failed to push secrets:', err.message);
|
|
26
25
|
process.exit(1);
|
|
27
26
|
}
|
|
28
27
|
});
|
|
29
28
|
// Pull secrets from cloud
|
|
30
|
-
|
|
29
|
+
program
|
|
31
30
|
.command('pull')
|
|
32
31
|
.description('Pull .env from encrypted cloud storage')
|
|
33
32
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -39,12 +38,13 @@ export async function init_secrets(program) {
|
|
|
39
38
|
await manager.pull(options.file, options.env, options.force);
|
|
40
39
|
}
|
|
41
40
|
catch (error) {
|
|
42
|
-
|
|
41
|
+
const err = error;
|
|
42
|
+
console.error('ā Failed to pull secrets:', err.message);
|
|
43
43
|
process.exit(1);
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
// List environments
|
|
47
|
-
|
|
47
|
+
program
|
|
48
48
|
.command('list [environment]')
|
|
49
49
|
.alias('ls')
|
|
50
50
|
.description('List all stored environments or show secrets for specific environment')
|
|
@@ -56,7 +56,7 @@ export async function init_secrets(program) {
|
|
|
56
56
|
if (options.allFiles) {
|
|
57
57
|
const files = await manager.listAllFiles();
|
|
58
58
|
if (files.length === 0) {
|
|
59
|
-
console.log('No .env files found. Push your first file with: lsh
|
|
59
|
+
console.log('No .env files found. Push your first file with: lsh push --file <filename>');
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
62
|
console.log('\nš¦ Tracked .env files:\n');
|
|
@@ -74,7 +74,7 @@ export async function init_secrets(program) {
|
|
|
74
74
|
// Otherwise, list all environments
|
|
75
75
|
const envs = await manager.listEnvironments();
|
|
76
76
|
if (envs.length === 0) {
|
|
77
|
-
console.log('No environments found. Push your first .env with: lsh
|
|
77
|
+
console.log('No environments found. Push your first .env with: lsh push');
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
console.log('\nš¦ Available environments:\n');
|
|
@@ -84,12 +84,13 @@ export async function init_secrets(program) {
|
|
|
84
84
|
console.log();
|
|
85
85
|
}
|
|
86
86
|
catch (error) {
|
|
87
|
-
|
|
87
|
+
const err = error;
|
|
88
|
+
console.error('ā Failed to list environments:', err.message);
|
|
88
89
|
process.exit(1);
|
|
89
90
|
}
|
|
90
91
|
});
|
|
91
92
|
// Show secrets (masked)
|
|
92
|
-
|
|
93
|
+
program
|
|
93
94
|
.command('show')
|
|
94
95
|
.description('Show secrets for an environment (masked)')
|
|
95
96
|
.option('-e, --env <name>', 'Environment name', 'dev')
|
|
@@ -99,12 +100,13 @@ export async function init_secrets(program) {
|
|
|
99
100
|
await manager.show(options.env);
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
|
-
|
|
103
|
+
const err = error;
|
|
104
|
+
console.error('ā Failed to show secrets:', err.message);
|
|
103
105
|
process.exit(1);
|
|
104
106
|
}
|
|
105
107
|
});
|
|
106
108
|
// Generate encryption key
|
|
107
|
-
|
|
109
|
+
program
|
|
108
110
|
.command('key')
|
|
109
111
|
.description('Generate a new encryption key')
|
|
110
112
|
.action(async () => {
|
|
@@ -116,7 +118,7 @@ export async function init_secrets(program) {
|
|
|
116
118
|
console.log(' Never commit it to git!\n');
|
|
117
119
|
});
|
|
118
120
|
// Create .env file
|
|
119
|
-
|
|
121
|
+
program
|
|
120
122
|
.command('create')
|
|
121
123
|
.description('Create a new .env file')
|
|
122
124
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -165,12 +167,13 @@ API_KEY=
|
|
|
165
167
|
console.log('');
|
|
166
168
|
}
|
|
167
169
|
catch (error) {
|
|
168
|
-
|
|
170
|
+
const err = error;
|
|
171
|
+
console.error('ā Failed to create .env file:', err.message);
|
|
169
172
|
process.exit(1);
|
|
170
173
|
}
|
|
171
174
|
});
|
|
172
175
|
// Sync command - automatically set up and synchronize secrets
|
|
173
|
-
|
|
176
|
+
program
|
|
174
177
|
.command('sync')
|
|
175
178
|
.description('Automatically set up and synchronize secrets (smart mode)')
|
|
176
179
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -178,6 +181,7 @@ API_KEY=
|
|
|
178
181
|
.option('--dry-run', 'Show what would be done without executing')
|
|
179
182
|
.option('--legacy', 'Use legacy sync mode (suggestions only)')
|
|
180
183
|
.option('--load', 'Output eval-able export commands for loading secrets')
|
|
184
|
+
.option('--force', 'Force sync even if destructive changes detected')
|
|
181
185
|
.action(async (options) => {
|
|
182
186
|
try {
|
|
183
187
|
const manager = new SecretsManager();
|
|
@@ -187,16 +191,17 @@ API_KEY=
|
|
|
187
191
|
}
|
|
188
192
|
else {
|
|
189
193
|
// Use new smart sync (auto-execute)
|
|
190
|
-
await manager.smartSync(options.file, options.env, !options.dryRun, options.load);
|
|
194
|
+
await manager.smartSync(options.file, options.env, !options.dryRun, options.load, options.force);
|
|
191
195
|
}
|
|
192
196
|
}
|
|
193
197
|
catch (error) {
|
|
194
|
-
|
|
198
|
+
const err = error;
|
|
199
|
+
console.error('ā Failed to sync:', err.message);
|
|
195
200
|
process.exit(1);
|
|
196
201
|
}
|
|
197
202
|
});
|
|
198
203
|
// Status command - get detailed status info
|
|
199
|
-
|
|
204
|
+
program
|
|
200
205
|
.command('status')
|
|
201
206
|
.description('Get detailed secrets status (JSON output)')
|
|
202
207
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -208,12 +213,13 @@ API_KEY=
|
|
|
208
213
|
console.log(JSON.stringify(status, null, 2));
|
|
209
214
|
}
|
|
210
215
|
catch (error) {
|
|
211
|
-
|
|
216
|
+
const err = error;
|
|
217
|
+
console.error('ā Failed to get status:', err.message);
|
|
212
218
|
process.exit(1);
|
|
213
219
|
}
|
|
214
220
|
});
|
|
215
221
|
// Get a specific secret value
|
|
216
|
-
|
|
222
|
+
program
|
|
217
223
|
.command('get <key>')
|
|
218
224
|
.description('Get a specific secret value from .env file')
|
|
219
225
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -245,12 +251,13 @@ API_KEY=
|
|
|
245
251
|
process.exit(1);
|
|
246
252
|
}
|
|
247
253
|
catch (error) {
|
|
248
|
-
|
|
254
|
+
const err = error;
|
|
255
|
+
console.error('ā Failed to get secret:', err.message);
|
|
249
256
|
process.exit(1);
|
|
250
257
|
}
|
|
251
258
|
});
|
|
252
259
|
// Set a specific secret value
|
|
253
|
-
|
|
260
|
+
program
|
|
254
261
|
.command('set <key> <value>')
|
|
255
262
|
.description('Set a specific secret value in .env file')
|
|
256
263
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -297,12 +304,13 @@ API_KEY=
|
|
|
297
304
|
console.log(`ā
Set ${key} in ${options.file}`);
|
|
298
305
|
}
|
|
299
306
|
catch (error) {
|
|
300
|
-
|
|
307
|
+
const err = error;
|
|
308
|
+
console.error('ā Failed to set secret:', err.message);
|
|
301
309
|
process.exit(1);
|
|
302
310
|
}
|
|
303
311
|
});
|
|
304
312
|
// Delete .env file with confirmation
|
|
305
|
-
|
|
313
|
+
program
|
|
306
314
|
.command('delete')
|
|
307
315
|
.description('Delete .env file (requires confirmation)')
|
|
308
316
|
.option('-f, --file <path>', 'Path to .env file', '.env')
|
|
@@ -351,7 +359,8 @@ API_KEY=
|
|
|
351
359
|
console.log('');
|
|
352
360
|
}
|
|
353
361
|
catch (error) {
|
|
354
|
-
|
|
362
|
+
const err = error;
|
|
363
|
+
console.error('ā Failed to delete .env file:', err.message);
|
|
355
364
|
process.exit(1);
|
|
356
365
|
}
|
|
357
366
|
});
|