start-command 0.25.3 → 0.25.4

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # start-command
2
2
 
3
+ ## 0.25.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 038b6db: Add `currentTime` to `--status` output when an execution is still `executing`, so users can see the query time alongside `startTime` and compute how long a command has been running.
8
+
3
9
  ## 0.25.3
4
10
 
5
11
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "start-command",
3
- "version": "0.25.3",
3
+ "version": "0.25.4",
4
4
  "description": "Gamification of coding, execute any command with ability to auto-report issues on GitHub",
5
5
  "main": "src/bin/cli.js",
6
6
  "exports": {
@@ -126,6 +126,45 @@ function enrichDetachedStatus(record) {
126
126
  return enriched;
127
127
  }
128
128
 
129
+ /**
130
+ * Wrap a record so its serialized form includes a `currentTime` field when
131
+ * the status is "executing". This reflects the moment `--status` was invoked,
132
+ * making it easy to compute how long a command has been running.
133
+ * The original record is not mutated.
134
+ * @param {Object} record - Execution record
135
+ * @returns {Object} Record-like object with `toObject()` augmented when executing
136
+ */
137
+ function attachCurrentTime(record) {
138
+ if (!record || record.status !== 'executing') {
139
+ return record;
140
+ }
141
+
142
+ const currentTime = new Date().toISOString();
143
+ const wrapped = Object.create(Object.getPrototypeOf(record));
144
+ Object.assign(wrapped, record);
145
+ wrapped.currentTime = currentTime;
146
+ wrapped.toObject = function () {
147
+ const base =
148
+ typeof record.toObject === 'function'
149
+ ? record.toObject.call(this)
150
+ : { ...this };
151
+ // Insert currentTime right after startTime for readability
152
+ const ordered = {};
153
+ for (const [key, value] of Object.entries(base)) {
154
+ ordered[key] = value;
155
+ if (key === 'startTime') {
156
+ ordered.currentTime = currentTime;
157
+ }
158
+ }
159
+ if (!('currentTime' in ordered)) {
160
+ ordered.currentTime = currentTime;
161
+ }
162
+ return ordered;
163
+ };
164
+
165
+ return wrapped;
166
+ }
167
+
129
168
  /**
130
169
  * Format execution record as Links Notation (indented style)
131
170
  * Uses nested Links notation for object values (like options) instead of JSON
@@ -192,9 +231,14 @@ function formatRecordAsText(record) {
192
231
  `Shell: ${obj.shell}`,
193
232
  `Platform: ${obj.platform}`,
194
233
  `Start Time: ${obj.startTime}`,
195
- `End Time: ${obj.endTime || 'N/A'}`,
196
- `Log Path: ${obj.logPath}`,
197
234
  ];
235
+ if (obj.currentTime) {
236
+ lines.push(`Current Time: ${obj.currentTime}`);
237
+ }
238
+ lines.push(
239
+ `End Time: ${obj.endTime || 'N/A'}`,
240
+ `Log Path: ${obj.logPath}`
241
+ );
198
242
 
199
243
  // Format options as nested list instead of JSON
200
244
  const optionEntries = Object.entries(obj.options || {}).filter(
@@ -250,9 +294,11 @@ function queryStatus(store, identifier, outputFormat) {
250
294
  try {
251
295
  // Enrich detached execution status with live session check
252
296
  const enrichedRecord = enrichDetachedStatus(record);
297
+ // Attach currentTime so callers can see how long an executing command has been running
298
+ const withCurrentTime = attachCurrentTime(enrichedRecord);
253
299
  return {
254
300
  success: true,
255
- output: formatRecord(enrichedRecord, outputFormat || 'links-notation'),
301
+ output: formatRecord(withCurrentTime, outputFormat || 'links-notation'),
256
302
  };
257
303
  } catch (err) {
258
304
  return { success: false, error: err.message };
@@ -266,4 +312,5 @@ module.exports = {
266
312
  queryStatus,
267
313
  isDetachedSessionAlive,
268
314
  enrichDetachedStatus,
315
+ attachCurrentTime,
269
316
  };
@@ -18,6 +18,7 @@ const {
18
18
  queryStatus,
19
19
  isDetachedSessionAlive,
20
20
  enrichDetachedStatus,
21
+ attachCurrentTime,
21
22
  } = require('../src/lib/status-formatter');
22
23
 
23
24
  // Use temp directory for tests
@@ -308,4 +309,72 @@ describe('Issue #101: Detached status enrichment', () => {
308
309
  });
309
310
  });
310
311
 
312
+ describe('Issue #105: attachCurrentTime for executing status', () => {
313
+ it('should add currentTime to serialization when status is executing', () => {
314
+ const record = new ExecutionRecord({
315
+ command: 'sleep 60',
316
+ pid: 12345,
317
+ logPath: '/tmp/test.log',
318
+ });
319
+
320
+ const before = Date.now();
321
+ const wrapped = attachCurrentTime(record);
322
+ const obj = wrapped.toObject();
323
+ const after = Date.now();
324
+
325
+ expect(obj.currentTime).toBeDefined();
326
+ const currentTimeMs = new Date(obj.currentTime).getTime();
327
+ expect(Number.isNaN(currentTimeMs)).toBe(false);
328
+ expect(currentTimeMs).toBeGreaterThanOrEqual(before - 1);
329
+ expect(currentTimeMs).toBeLessThanOrEqual(after + 1);
330
+ });
331
+
332
+ it('should not add currentTime when status is executed', () => {
333
+ const record = new ExecutionRecord({
334
+ command: 'echo hello',
335
+ pid: 12345,
336
+ logPath: '/tmp/test.log',
337
+ });
338
+ record.complete(0);
339
+
340
+ const wrapped = attachCurrentTime(record);
341
+ // attachCurrentTime should return the original record unchanged
342
+ expect(wrapped).toBe(record);
343
+ const obj = wrapped.toObject();
344
+ expect(obj.currentTime).toBeUndefined();
345
+ });
346
+
347
+ it('should not mutate the original record', () => {
348
+ const record = new ExecutionRecord({
349
+ command: 'sleep 60',
350
+ pid: 12345,
351
+ logPath: '/tmp/test.log',
352
+ });
353
+
354
+ const wrapped = attachCurrentTime(record);
355
+ expect(wrapped).not.toBe(record);
356
+ // The original record's toObject output should not include currentTime
357
+ const originalObj = record.toObject();
358
+ expect(originalObj.currentTime).toBeUndefined();
359
+ });
360
+
361
+ it('should place currentTime right after startTime in serialization order', () => {
362
+ const record = new ExecutionRecord({
363
+ command: 'sleep 60',
364
+ pid: 12345,
365
+ logPath: '/tmp/test.log',
366
+ });
367
+
368
+ const wrapped = attachCurrentTime(record);
369
+ const keys = Object.keys(wrapped.toObject());
370
+ const startIndex = keys.indexOf('startTime');
371
+ expect(startIndex).toBeGreaterThanOrEqual(0);
372
+ expect(keys[startIndex + 1]).toBe('currentTime');
373
+ });
374
+
375
+ it('should handle null record gracefully', () => {
376
+ expect(attachCurrentTime(null)).toBeNull();
377
+ });
378
+ });
379
+
311
380
  console.log('=== Session Name Status Tests (Issue #101) ===');
@@ -191,6 +191,102 @@ describe('--status query functionality', () => {
191
191
  expect(parsed.exitCode).toBeNull();
192
192
  expect(parsed.endTime).toBeNull();
193
193
  });
194
+
195
+ it('should include currentTime for executing commands (JSON)', () => {
196
+ const beforeQuery = Date.now();
197
+ const executingRecord = new ExecutionRecord({
198
+ command: 'sleep 100',
199
+ pid: 99999,
200
+ logPath: '/tmp/executing.log',
201
+ });
202
+ store.save(executingRecord);
203
+
204
+ const result = runCli([
205
+ '--status',
206
+ executingRecord.uuid,
207
+ '--output-format',
208
+ 'json',
209
+ ]);
210
+
211
+ expect(result.exitCode).toBe(0);
212
+ const parsed = JSON.parse(result.stdout);
213
+ expect(parsed.currentTime).toBeDefined();
214
+ // currentTime should be a valid ISO timestamp at or after the query started
215
+ const currentTimeMs = new Date(parsed.currentTime).getTime();
216
+ expect(Number.isNaN(currentTimeMs)).toBe(false);
217
+ expect(currentTimeMs).toBeGreaterThanOrEqual(beforeQuery - 1);
218
+ expect(currentTimeMs).toBeLessThanOrEqual(Date.now() + 1);
219
+ // Should be >= startTime
220
+ expect(currentTimeMs).toBeGreaterThanOrEqual(
221
+ new Date(parsed.startTime).getTime()
222
+ );
223
+ });
224
+
225
+ it('should not include currentTime for completed commands (JSON)', () => {
226
+ // testRecord from beforeEach is already completed
227
+ const result = runCli([
228
+ '--status',
229
+ testRecord.uuid,
230
+ '--output-format',
231
+ 'json',
232
+ ]);
233
+
234
+ expect(result.exitCode).toBe(0);
235
+ const parsed = JSON.parse(result.stdout);
236
+ expect(parsed.status).toBe('executed');
237
+ expect(parsed.currentTime).toBeUndefined();
238
+ });
239
+
240
+ it('should include currentTime in links-notation for executing commands', () => {
241
+ const executingRecord = new ExecutionRecord({
242
+ command: 'sleep 100',
243
+ pid: 99999,
244
+ logPath: '/tmp/executing.log',
245
+ });
246
+ store.save(executingRecord);
247
+
248
+ const result = runCli(['--status', executingRecord.uuid]);
249
+
250
+ expect(result.exitCode).toBe(0);
251
+ expect(result.stdout).toContain('status executing');
252
+ // currentTime should appear as an indented property with an ISO timestamp value
253
+ expect(result.stdout).toMatch(
254
+ /\n {2}currentTime "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
255
+ );
256
+ });
257
+
258
+ it('should include Current Time in text format for executing commands', () => {
259
+ const executingRecord = new ExecutionRecord({
260
+ command: 'sleep 100',
261
+ pid: 99999,
262
+ logPath: '/tmp/executing.log',
263
+ });
264
+ store.save(executingRecord);
265
+
266
+ const result = runCli([
267
+ '--status',
268
+ executingRecord.uuid,
269
+ '--output-format',
270
+ 'text',
271
+ ]);
272
+
273
+ expect(result.exitCode).toBe(0);
274
+ expect(result.stdout).toContain('Status:');
275
+ expect(result.stdout).toContain('executing');
276
+ expect(result.stdout).toContain('Current Time:');
277
+ });
278
+
279
+ it('should not include Current Time in text format for completed commands', () => {
280
+ const result = runCli([
281
+ '--status',
282
+ testRecord.uuid,
283
+ '--output-format',
284
+ 'text',
285
+ ]);
286
+
287
+ expect(result.exitCode).toBe(0);
288
+ expect(result.stdout).not.toContain('Current Time:');
289
+ });
194
290
  });
195
291
  });
196
292