@teambit/ripple 0.0.0-00ad897e742f2a0c76f3f48e46d69e13bfc07a2e

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.
@@ -0,0 +1,635 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.RippleStopCmd = exports.RippleRetryCmd = exports.RippleLogCmd = exports.RippleListCmd = exports.RippleErrorsCmd = exports.RippleCmd = void 0;
7
+ function _chalk() {
8
+ const data = _interopRequireDefault(require("chalk"));
9
+ _chalk = function () {
10
+ return data;
11
+ };
12
+ return data;
13
+ }
14
+ function _cliTable() {
15
+ const data = _interopRequireDefault(require("cli-table"));
16
+ _cliTable = function () {
17
+ return data;
18
+ };
19
+ return data;
20
+ }
21
+ function _rippleUtils() {
22
+ const data = require("./ripple-utils");
23
+ _rippleUtils = function () {
24
+ return data;
25
+ };
26
+ return data;
27
+ }
28
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
29
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
30
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
31
+ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
32
+ /**
33
+ * a last-export entry matches the target context when its saved lane equals the current lane
34
+ * (or both are undefined / "main"). this prevents using a stale last-export from a different lane
35
+ * after the user has switched lanes.
36
+ */
37
+ function lastExportMatchesTarget(last, targetLane) {
38
+ const lastLaneStr = last.lane ? `${last.lane.scope}/${last.lane.name}` : undefined;
39
+ return lastLaneStr === targetLane;
40
+ }
41
+ function lastExportHeader(lastExport, job) {
42
+ const age = (0, _rippleUtils().formatAge)(lastExport.timestamp);
43
+ const target = lastExport.lane ? `lane "${lastExport.lane.scope}/${lastExport.lane.name}"` : 'main';
44
+ const phase = job.status?.phase?.toUpperCase();
45
+ if (phase === 'SUCCESS') {
46
+ return _chalk().default.green(`✓ Your last export (${age}) to ${target} succeeded.`);
47
+ }
48
+ if ((0, _rippleUtils().isFailedPhase)(phase)) {
49
+ return _chalk().default.red(`✗ Your last export (${age}) to ${target} failed.`);
50
+ }
51
+ return _chalk().default.gray(`Your last export (${age}) to ${target} — status: ${(0, _rippleUtils().colorPhase)(phase)}.`);
52
+ }
53
+ class RippleCmd {
54
+ constructor() {
55
+ _defineProperty(this, "name", 'ripple <sub-command>');
56
+ _defineProperty(this, "description", 'manage Ripple CI jobs on bit.cloud');
57
+ _defineProperty(this, "extendedDescription", 'view, retry, and manage Ripple CI jobs that build your components in the cloud after export.');
58
+ _defineProperty(this, "group", 'collaborate');
59
+ _defineProperty(this, "skipWorkspace", true);
60
+ _defineProperty(this, "remoteOp", true);
61
+ _defineProperty(this, "options", []);
62
+ _defineProperty(this, "commands", []);
63
+ }
64
+ async report() {
65
+ return {
66
+ code: 1,
67
+ data: '[ripple] please specify a subcommand. See --help for available commands.'
68
+ };
69
+ }
70
+ }
71
+ exports.RippleCmd = RippleCmd;
72
+ class RippleListCmd {
73
+ constructor(ripple) {
74
+ this.ripple = ripple;
75
+ _defineProperty(this, "name", 'list');
76
+ _defineProperty(this, "description", 'list recent Ripple CI jobs (filtered by workspace owner by default)');
77
+ _defineProperty(this, "skipWorkspace", true);
78
+ _defineProperty(this, "remoteOp", true);
79
+ _defineProperty(this, "alias", '');
80
+ _defineProperty(this, "options", [['', 'all', 'show jobs from all owners, not just the workspace owner'], ['o', 'owner <owner>', 'filter by organization (default: detected from workspace defaultScope)'], ['s', 'scope <scope>', 'filter by scope (e.g. "teambit.cloud")'], ['', 'lane <lane>', 'filter by lane ID (e.g. "scope/lane-name")'], ['u', 'user <user>', 'filter by username'], ['', 'status <status>', 'filter by status (e.g. SUCCESS, FAILURE, RUNNING)'], ['l', 'limit <limit>', 'max number of jobs to show (default: 20)'], ['j', 'json', 'return the output as JSON']]);
81
+ }
82
+ async report(args, flags) {
83
+ const {
84
+ jobs,
85
+ ownerUsed
86
+ } = await this.getFilteredJobs(flags);
87
+ if (!jobs || jobs.length === 0) {
88
+ let hint = '';
89
+ if (flags.lane) hint = ` for lane "${flags.lane}"`;else if (flags.scope) hint = ` for scope "${flags.scope}"`;else if (ownerUsed) hint = ` for owner "${ownerUsed}"`;
90
+ const tip = ownerUsed ? ' Use --all to see jobs from all owners.' : '';
91
+ return _chalk().default.yellow(`No Ripple CI jobs found${hint}.${tip}`);
92
+ }
93
+ const table = new (_cliTable().default)({
94
+ head: [_chalk().default.cyan('Job ID'), _chalk().default.cyan('Name'), _chalk().default.cyan('Scope'), _chalk().default.cyan('Status'), _chalk().default.cyan('User'), _chalk().default.cyan('Started'), _chalk().default.cyan('Duration')],
95
+ chars: {
96
+ top: '',
97
+ 'top-mid': '',
98
+ 'top-left': '',
99
+ 'top-right': '',
100
+ bottom: '',
101
+ 'bottom-mid': '',
102
+ 'bottom-left': '',
103
+ 'bottom-right': '',
104
+ left: '',
105
+ 'left-mid': '',
106
+ mid: '',
107
+ 'mid-mid': '',
108
+ right: '',
109
+ 'right-mid': '',
110
+ middle: ' '
111
+ },
112
+ style: {
113
+ 'padding-left': 1,
114
+ 'padding-right': 1
115
+ }
116
+ });
117
+ for (const job of jobs) {
118
+ table.push([job.id, truncate(job.name || '-', 40), getScopeFromLaneId(job.laneId), (0, _rippleUtils().colorPhase)(job.status?.phase), job.user?.username || '-', formatDate(job.status?.startedAt), formatDuration(job.status?.startedAt, job.status?.finishedAt)]);
119
+ }
120
+ const header = ownerUsed ? _chalk().default.gray(`showing jobs for owner "${ownerUsed}" (use --all for all jobs)`) : '';
121
+ return [header, table.toString()].filter(Boolean).join('\n');
122
+ }
123
+ async json(args, flags) {
124
+ const {
125
+ jobs
126
+ } = await this.getFilteredJobs(flags);
127
+ return {
128
+ jobs: jobs || []
129
+ };
130
+ }
131
+ async getFilteredJobs(flags) {
132
+ const requestedLimit = flags.limit ? parseInt(flags.limit, 10) : 20;
133
+ if (!Number.isFinite(requestedLimit) || requestedLimit < 1) {
134
+ throw new Error(`Invalid --limit value "${flags.limit}". Expected a positive integer.`);
135
+ }
136
+
137
+ // build server-side filters
138
+ const filters = {};
139
+
140
+ // determine owner for default filtering (skip when --lane or --scope is given explicitly)
141
+ let ownerUsed;
142
+ if (!flags.all && !flags.lane && !flags.scope) {
143
+ ownerUsed = flags.owner || this.ripple.getDefaultOwner();
144
+ } else if (flags.owner) {
145
+ ownerUsed = flags.owner;
146
+ }
147
+ if (ownerUsed) filters.owners = [ownerUsed];
148
+ if (flags.lane) filters.lanes = [flags.lane];
149
+ if (flags.scope) filters.scopes = [flags.scope];
150
+ if (flags.status) filters.status = flags.status.toUpperCase();
151
+
152
+ // user filter is not supported server-side, so overfetch if needed
153
+ const needsClientFilter = !!flags.user;
154
+ const fetchLimit = needsClientFilter ? Math.max(requestedLimit * 5, 100) : requestedLimit;
155
+ let jobs = await this.ripple.listJobs({
156
+ filters,
157
+ limit: fetchLimit
158
+ });
159
+
160
+ // apply client-side filters for fields not supported by FilterOptions
161
+ if (flags.user) {
162
+ const userFilter = flags.user.toLowerCase();
163
+ jobs = jobs.filter(j => j.user?.username?.toLowerCase().includes(userFilter));
164
+ }
165
+ jobs = jobs.slice(0, requestedLimit);
166
+ return {
167
+ jobs,
168
+ ownerUsed
169
+ };
170
+ }
171
+ }
172
+ exports.RippleListCmd = RippleListCmd;
173
+ class RippleLogCmd {
174
+ constructor(ripple) {
175
+ this.ripple = ripple;
176
+ _defineProperty(this, "name", 'log [job-id]');
177
+ _defineProperty(this, "description", 'show job details and component build task summaries (auto-detects current lane, or your last export when on main)');
178
+ _defineProperty(this, "skipWorkspace", true);
179
+ _defineProperty(this, "remoteOp", true);
180
+ _defineProperty(this, "alias", '');
181
+ _defineProperty(this, "options", [['', 'lane <lane>', 'lane ID to find the latest job for (default: detected from .bitmap)'], ['c', 'component <component>', 'show build tasks for a specific component (full component ID)'], ['j', 'json', 'return the output as JSON']]);
182
+ _defineProperty(this, "arguments", [{
183
+ name: 'job-id',
184
+ description: 'the Ripple CI job ID (optional — auto-detects from current lane, or your last export when on main)'
185
+ }]);
186
+ }
187
+ async resolveJob(jobId, flags) {
188
+ const resolved = await (0, _rippleUtils().resolveJobId)(this.ripple, jobId, flags);
189
+ if ('error' in resolved) return {
190
+ job: null,
191
+ error: resolved.error,
192
+ source: undefined,
193
+ lastExport: undefined
194
+ };
195
+ // when resolved from last-export, the resolver already fetched the full job — reuse it
196
+ const job = resolved.job ?? (await this.ripple.getJob(resolved.id));
197
+ return {
198
+ job,
199
+ error: undefined,
200
+ source: resolved.source,
201
+ lastExport: resolved.lastExport
202
+ };
203
+ }
204
+ async report([jobId], flags) {
205
+ const {
206
+ job,
207
+ error,
208
+ source,
209
+ lastExport
210
+ } = await this.resolveJob(jobId, flags);
211
+ if (!job) {
212
+ if (jobId) return _chalk().default.red(`Job "${jobId}" not found.`);
213
+ return _chalk().default.red(error || 'Could not find a Ripple CI job.');
214
+ }
215
+ const lines = [];
216
+ if (source === 'last-export' && lastExport) {
217
+ lines.push(lastExportHeader(lastExport, job));
218
+ lines.push('');
219
+ }
220
+ lines.push(_chalk().default.bold('Job Details'));
221
+ lines.push(` ${_chalk().default.cyan('ID:')} ${job.id}`);
222
+ if (job.name) lines.push(` ${_chalk().default.cyan('Name:')} ${job.name}`);
223
+ lines.push(` ${_chalk().default.cyan('Status:')} ${(0, _rippleUtils().colorPhase)(job.status?.phase)}`);
224
+ if (job.laneId) lines.push(` ${_chalk().default.cyan('Lane:')} ${job.laneId}`);
225
+ if (job.user?.displayName) lines.push(` ${_chalk().default.cyan('User:')} ${job.user.displayName} (${job.user.username})`);
226
+ if (job.status?.startedAt) lines.push(` ${_chalk().default.cyan('Started:')} ${new Date(job.status.startedAt).toLocaleString()}`);
227
+ if (job.status?.finishedAt) lines.push(` ${_chalk().default.cyan('Finished:')} ${new Date(job.status.finishedAt).toLocaleString()}`);
228
+ const jobUrl = this.ripple.getJobUrl(job);
229
+ lines.push(` ${_chalk().default.cyan('URL:')} ${jobUrl}`);
230
+ if (flags.component) {
231
+ await this.appendComponentDetail(lines, job.id, flags.component);
232
+ } else {
233
+ this.appendComponentList(lines, job);
234
+ }
235
+ return lines.join('\n');
236
+ }
237
+ async appendComponentDetail(lines, jobId, componentId) {
238
+ const summary = await this.ripple.getComponentBuildSummary(jobId, componentId);
239
+ if (!summary) {
240
+ lines.push('');
241
+ lines.push(_chalk().default.yellow(`No build summary found for component "${componentId}" in this job.`));
242
+ return;
243
+ }
244
+ lines.push('');
245
+ lines.push(_chalk().default.bold(`Build Tasks for ${summary.name || componentId}`));
246
+ if (!summary.tasks || summary.tasks.length === 0) {
247
+ lines.push(_chalk().default.gray(' No build tasks found.'));
248
+ return;
249
+ }
250
+ const table = new (_cliTable().default)({
251
+ head: [_chalk().default.cyan('Task'), _chalk().default.cyan('Status'), _chalk().default.cyan('Started'), _chalk().default.cyan('Warnings')],
252
+ chars: {
253
+ top: '',
254
+ 'top-mid': '',
255
+ 'top-left': '',
256
+ 'top-right': '',
257
+ bottom: '',
258
+ 'bottom-mid': '',
259
+ 'bottom-left': '',
260
+ 'bottom-right': '',
261
+ left: '',
262
+ 'left-mid': '',
263
+ mid: '',
264
+ 'mid-mid': '',
265
+ right: '',
266
+ 'right-mid': '',
267
+ middle: ' '
268
+ },
269
+ style: {
270
+ 'padding-left': 1,
271
+ 'padding-right': 1
272
+ }
273
+ });
274
+ for (const task of summary.tasks) {
275
+ table.push([task.name || '-', (0, _rippleUtils().colorPhase)(task.status?.status), task.startTime ? new Date(task.startTime).toLocaleString() : '-', task.status?.warnings ? _chalk().default.yellow(String(task.status.warnings)) : '0']);
276
+ }
277
+ lines.push(table.toString());
278
+ }
279
+ appendComponentList(lines, job) {
280
+ const ciNodes = this.ripple.getCiGraphNodes(job);
281
+ if (ciNodes.length === 0) return;
282
+ const totalComponents = ciNodes.reduce((sum, n) => sum + n.componentIds.length, 0);
283
+ lines.push('');
284
+ lines.push(_chalk().default.bold(`Components (${totalComponents})`));
285
+ lines.push(_chalk().default.gray(' Use --component <id> to see build tasks for a specific component'));
286
+ let shown = 0;
287
+ for (const node of ciNodes) {
288
+ if (shown >= 30) {
289
+ lines.push(_chalk().default.gray(` ... and ${totalComponents - shown} more`));
290
+ break;
291
+ }
292
+ for (const compId of node.componentIds) {
293
+ if (shown >= 30) break;
294
+ const icon = (0, _rippleUtils().isFailedPhase)(node.phase) ? _chalk().default.red('✗') : node.phase === 'SUCCESS' ? _chalk().default.green('✓') : _chalk().default.yellow('○');
295
+ lines.push(` ${icon} ${compId}`);
296
+ shown++;
297
+ }
298
+ }
299
+ }
300
+ async json([jobId], flags) {
301
+ const {
302
+ job,
303
+ source,
304
+ lastExport
305
+ } = await this.resolveJob(jobId, flags);
306
+ if (flags.component && job) {
307
+ const summary = await this.ripple.getComponentBuildSummary(job.id, flags.component);
308
+ return {
309
+ job,
310
+ componentBuild: summary,
311
+ source,
312
+ lastExport
313
+ };
314
+ }
315
+ return {
316
+ job,
317
+ source,
318
+ lastExport
319
+ };
320
+ }
321
+ }
322
+ exports.RippleLogCmd = RippleLogCmd;
323
+ class RippleErrorsCmd {
324
+ constructor(ripple) {
325
+ this.ripple = ripple;
326
+ _defineProperty(this, "name", 'errors [job-id]');
327
+ _defineProperty(this, "description", 'show build errors for a Ripple CI job (auto-detects current lane, or your last export when on main)');
328
+ _defineProperty(this, "skipWorkspace", true);
329
+ _defineProperty(this, "remoteOp", true);
330
+ _defineProperty(this, "alias", '');
331
+ _defineProperty(this, "options", [['', 'lane <lane>', 'lane ID to find the latest job for (default: detected from .bitmap)'], ['', 'log', 'show full build log for failed containers (not just the error summary)'], ['j', 'json', 'return the output as JSON']]);
332
+ _defineProperty(this, "arguments", [{
333
+ name: 'job-id',
334
+ description: 'the Ripple CI job ID (optional — auto-detects from current lane, or your last export when on main)'
335
+ }]);
336
+ }
337
+ async report([jobId], flags) {
338
+ const {
339
+ job,
340
+ ciNodes,
341
+ source,
342
+ lastExport,
343
+ error
344
+ } = await this.getErrors(jobId, flags);
345
+ if (!job) {
346
+ if (jobId) {
347
+ return _chalk().default.red(`Job "${jobId}" not found.`);
348
+ }
349
+ const laneId = flags.lane || this.ripple.getCurrentLaneId();
350
+ if (laneId) {
351
+ return _chalk().default.red(`No Ripple CI job found for lane "${laneId}".`);
352
+ }
353
+ return _chalk().default.red(error || 'Could not find a Ripple CI job. Provide a job ID, use --lane, or run from a workspace with a recent export.');
354
+ }
355
+ const lines = [];
356
+ if (source === 'last-export' && lastExport) {
357
+ lines.push(lastExportHeader(lastExport, job));
358
+ lines.push('');
359
+ }
360
+ lines.push(_chalk().default.bold(`Ripple CI Errors — ${job.name || job.id}`));
361
+ lines.push(` ${_chalk().default.cyan('Job ID:')} ${job.id}`);
362
+ lines.push(` ${_chalk().default.cyan('Status:')} ${(0, _rippleUtils().colorPhase)(job.status?.phase)}`);
363
+ if (job.laneId) lines.push(` ${_chalk().default.cyan('Lane:')} ${job.laneId}`);
364
+ lines.push(` ${_chalk().default.cyan('URL:')} ${this.ripple.getJobUrl(job)}`);
365
+ if (ciNodes.length === 0) {
366
+ lines.push('');
367
+ lines.push(_chalk().default.yellow('Could not determine which components are in this job.'));
368
+ return lines.join('\n');
369
+ }
370
+ const failedNodes = ciNodes.filter(n => (0, _rippleUtils().isFailedPhase)(n.phase));
371
+ const succeededNodes = ciNodes.filter(n => n.phase === 'SUCCESS');
372
+ const otherNodes = ciNodes.filter(n => !(0, _rippleUtils().isFailedPhase)(n.phase) && n.phase !== 'SUCCESS');
373
+ const totalComponents = ciNodes.reduce((sum, n) => sum + n.componentIds.length, 0);
374
+ const failedComponents = failedNodes.flatMap(n => n.componentIds);
375
+ const blockedComponents = otherNodes.flatMap(n => n.componentIds);
376
+ if (failedComponents.length === 0) {
377
+ if (job.status?.phase?.toUpperCase() === 'FAILURE') {
378
+ lines.push('');
379
+ lines.push(_chalk().default.yellow(`${totalComponents} component(s) in this job — no individual component failures found.`));
380
+ lines.push(_chalk().default.yellow('The failure may be in a pipeline-level step. Check the Ripple CI URL above.'));
381
+ } else {
382
+ lines.push('');
383
+ lines.push(_chalk().default.green(`All ${totalComponents} component(s) built successfully.`));
384
+ }
385
+ return lines.join('\n');
386
+ }
387
+ lines.push('');
388
+ lines.push(_chalk().default.red.bold(`${failedComponents.length} component(s) with build failures:`));
389
+
390
+ // fetch all build logs in parallel
391
+ const containerNames = failedNodes.map(n => n.containerName);
392
+ const logMap = await this.ripple.getContainerLogs(job.id, containerNames);
393
+ for (const node of failedNodes) {
394
+ const compList = node.componentIds.join(', ');
395
+ lines.push('');
396
+ lines.push(_chalk().default.bold(` ${compList}`));
397
+ const logMessages = logMap.get(node.containerName);
398
+ if (logMessages && logMessages.length > 0) {
399
+ const errorLines = flags.log ? logMessages : this.ripple.extractErrorsFromLog(logMessages);
400
+ if (errorLines.length > 0) {
401
+ for (const msg of errorLines) {
402
+ const clean = (0, _rippleUtils().stripAnsi)(msg);
403
+ // skip stack trace lines (noisy) unless --log is used
404
+ if (!flags.log && /^\s+at\s/.test(clean)) continue;
405
+ if (clean.length > 0) {
406
+ lines.push(` ${clean}`);
407
+ }
408
+ }
409
+ } else {
410
+ lines.push(_chalk().default.gray(' No error details found in build log.'));
411
+ }
412
+ } else {
413
+ lines.push(_chalk().default.gray(' Build log not available.'));
414
+ }
415
+ }
416
+ if (blockedComponents.length > 0) {
417
+ lines.push('');
418
+ lines.push(_chalk().default.yellow(`${blockedComponents.length} component(s) not built (blocked by failure):`));
419
+ for (const compId of blockedComponents) {
420
+ lines.push(` ${_chalk().default.yellow('○')} ${compId}`);
421
+ }
422
+ }
423
+ if (succeededNodes.length > 0) {
424
+ const succeededCount = succeededNodes.reduce((sum, n) => sum + n.componentIds.length, 0);
425
+ lines.push('');
426
+ lines.push(_chalk().default.green(`${succeededCount} component(s) built successfully.`));
427
+ }
428
+ return lines.join('\n');
429
+ }
430
+ async json([jobId], flags) {
431
+ const {
432
+ job,
433
+ ciNodes,
434
+ source,
435
+ lastExport,
436
+ error
437
+ } = await this.getErrors(jobId, flags);
438
+ if (!job) {
439
+ return {
440
+ error: error || 'No job found',
441
+ job: null,
442
+ ciNodes: [],
443
+ containerLogs: {},
444
+ source,
445
+ lastExport
446
+ };
447
+ }
448
+
449
+ // fetch error logs for failed containers in parallel
450
+ const failedNodes = ciNodes.filter(n => (0, _rippleUtils().isFailedPhase)(n.phase));
451
+ const containerNames = failedNodes.map(n => n.containerName);
452
+ const logMap = await this.ripple.getContainerLogs(job.id, containerNames);
453
+ const containerLogs = {};
454
+ for (const [name, messages] of logMap) {
455
+ containerLogs[name] = flags.log ? messages : this.ripple.extractErrorsFromLog(messages);
456
+ }
457
+ return {
458
+ job,
459
+ ciNodes,
460
+ containerLogs,
461
+ source,
462
+ lastExport
463
+ };
464
+ }
465
+ async getErrors(jobId, flags) {
466
+ let job = null;
467
+ let source;
468
+ let lastExport;
469
+ if (jobId) {
470
+ job = await this.ripple.getJob(jobId);
471
+ source = 'arg';
472
+ } else {
473
+ const laneId = flags.lane || this.ripple.getCurrentLaneId();
474
+ const last = await this.ripple.getLastExport();
475
+ if (last?.rippleJobs?.length && lastExportMatchesTarget(last, laneId)) {
476
+ const slug = last.rippleJobs[last.rippleJobs.length - 1];
477
+ job = await this.ripple.getJobBySlug(slug);
478
+ source = 'last-export';
479
+ lastExport = last;
480
+ if (!job) {
481
+ return {
482
+ job: null,
483
+ ciNodes: [],
484
+ source: 'last-export',
485
+ lastExport: last,
486
+ error: `Could not find Ripple CI job for your last export "${slug}".`
487
+ };
488
+ }
489
+ } else if (laneId) {
490
+ const found = await this.ripple.findLatestJobForLane(laneId);
491
+ job = found ? await this.ripple.getJob(found.id) : null;
492
+ source = 'lane';
493
+ }
494
+ }
495
+ if (!job) {
496
+ return {
497
+ job: null,
498
+ ciNodes: [],
499
+ source,
500
+ lastExport
501
+ };
502
+ }
503
+
504
+ // use ciGraph (internal graph) for job-specific build status per container/component
505
+ const ciNodes = this.ripple.getCiGraphNodes(job);
506
+ return {
507
+ job,
508
+ ciNodes,
509
+ source,
510
+ lastExport
511
+ };
512
+ }
513
+ }
514
+ exports.RippleErrorsCmd = RippleErrorsCmd;
515
+ class RippleRetryCmd {
516
+ constructor(ripple) {
517
+ this.ripple = ripple;
518
+ _defineProperty(this, "name", 'retry [job-id]');
519
+ _defineProperty(this, "description", 'retry a failed Ripple CI job (auto-detects current lane when no job-id given)');
520
+ _defineProperty(this, "skipWorkspace", true);
521
+ _defineProperty(this, "remoteOp", true);
522
+ _defineProperty(this, "alias", '');
523
+ _defineProperty(this, "options", [['', 'lane <lane>', 'lane ID to find the latest job for (default: detected from .bitmap)'], ['j', 'json', 'return the output as JSON']]);
524
+ _defineProperty(this, "arguments", [{
525
+ name: 'job-id',
526
+ description: 'the Ripple CI job ID to retry (optional — auto-detects from current lane)'
527
+ }]);
528
+ }
529
+ async report([jobId], flags) {
530
+ const resolved = await (0, _rippleUtils().resolveJobId)(this.ripple, jobId, flags, {
531
+ allowedPhases: ['FAILURE', 'FAILED'],
532
+ actionVerb: 'retry'
533
+ });
534
+ if ('error' in resolved) return _chalk().default.red(resolved.error);
535
+ const result = await this.ripple.retryJob(resolved.id);
536
+ if (!result) {
537
+ return _chalk().default.red(`Failed to retry job "${resolved.id}". Make sure the job exists and has failed.`);
538
+ }
539
+ const lines = [];
540
+ lines.push(_chalk().default.green(`Successfully retried job "${resolved.id}".`));
541
+ if (result.id) lines.push(` ${_chalk().default.cyan('New Job ID:')} ${result.id}`);
542
+ if (result.status?.phase) lines.push(` ${_chalk().default.cyan('Status:')} ${result.status.phase}`);
543
+ const jobUrl = this.ripple.getJobUrl(result);
544
+ lines.push(` ${_chalk().default.cyan('URL:')} ${jobUrl}`);
545
+ return lines.join('\n');
546
+ }
547
+ async json([jobId], flags) {
548
+ const resolved = await (0, _rippleUtils().resolveJobId)(this.ripple, jobId, flags, {
549
+ allowedPhases: ['FAILURE', 'FAILED'],
550
+ actionVerb: 'retry'
551
+ });
552
+ if ('error' in resolved) return {
553
+ error: resolved.error
554
+ };
555
+ const result = await this.ripple.retryJob(resolved.id);
556
+ return {
557
+ job: result
558
+ };
559
+ }
560
+ }
561
+ exports.RippleRetryCmd = RippleRetryCmd;
562
+ class RippleStopCmd {
563
+ constructor(ripple) {
564
+ this.ripple = ripple;
565
+ _defineProperty(this, "name", 'stop [job-id]');
566
+ _defineProperty(this, "description", 'stop a running Ripple CI job (auto-detects current lane when no job-id given)');
567
+ _defineProperty(this, "skipWorkspace", true);
568
+ _defineProperty(this, "remoteOp", true);
569
+ _defineProperty(this, "alias", '');
570
+ _defineProperty(this, "options", [['', 'lane <lane>', 'lane ID to find the latest job for (default: detected from .bitmap)'], ['j', 'json', 'return the output as JSON']]);
571
+ _defineProperty(this, "arguments", [{
572
+ name: 'job-id',
573
+ description: 'the Ripple CI job ID to stop (optional — auto-detects from current lane)'
574
+ }]);
575
+ }
576
+ async report([jobId], flags) {
577
+ const resolved = await (0, _rippleUtils().resolveJobId)(this.ripple, jobId, flags, {
578
+ allowedPhases: ['RUNNING', 'IN_PROGRESS', 'PROCESSING'],
579
+ actionVerb: 'stop'
580
+ });
581
+ if ('error' in resolved) return _chalk().default.red(resolved.error);
582
+ const result = await this.ripple.stopJob(resolved.id);
583
+ if (!result) {
584
+ return _chalk().default.red(`Failed to stop job "${resolved.id}". Make sure the job exists and is currently running.`);
585
+ }
586
+ return _chalk().default.green(`Successfully stopped job "${resolved.id}".`);
587
+ }
588
+ async json([jobId], flags) {
589
+ const resolved = await (0, _rippleUtils().resolveJobId)(this.ripple, jobId, flags, {
590
+ allowedPhases: ['RUNNING', 'IN_PROGRESS', 'PROCESSING'],
591
+ actionVerb: 'stop'
592
+ });
593
+ if ('error' in resolved) return {
594
+ error: resolved.error
595
+ };
596
+ const result = await this.ripple.stopJob(resolved.id);
597
+ return {
598
+ job: result
599
+ };
600
+ }
601
+ }
602
+ exports.RippleStopCmd = RippleStopCmd;
603
+ function formatDate(dateStr) {
604
+ if (!dateStr) return '-';
605
+ try {
606
+ return new Date(dateStr).toLocaleString();
607
+ } catch {
608
+ return dateStr;
609
+ }
610
+ }
611
+ function formatDuration(startedAt, finishedAt) {
612
+ if (!startedAt) return '-';
613
+ const start = new Date(startedAt).getTime();
614
+ const end = finishedAt ? new Date(finishedAt).getTime() : Date.now();
615
+ const ms = end - start;
616
+ if (ms < 0) return '-';
617
+ const seconds = Math.floor(ms / 1000);
618
+ if (seconds < 60) return `${seconds}s`;
619
+ const minutes = Math.floor(seconds / 60);
620
+ const remainingSeconds = seconds % 60;
621
+ if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
622
+ const hours = Math.floor(minutes / 60);
623
+ const remainingMinutes = minutes % 60;
624
+ return `${hours}h ${remainingMinutes}m`;
625
+ }
626
+ function getScopeFromLaneId(laneId) {
627
+ if (!laneId) return '-';
628
+ return laneId.split('/')[0] || '-';
629
+ }
630
+ function truncate(str, max) {
631
+ if (str.length <= max) return str;
632
+ return `${str.substring(0, max - 1)}…`;
633
+ }
634
+
635
+ //# sourceMappingURL=ripple.cmd.js.map