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 +6 -0
- package/package.json +1 -1
- package/src/lib/status-formatter.js +50 -3
- package/test/session-name-status.test.js +69 -0
- package/test/status-query.test.js +96 -0
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
|
@@ -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(
|
|
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
|
|