Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions js/.changeset/issue-105-current-time.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'start-command': patch
---

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.
53 changes: 50 additions & 3 deletions js/src/lib/status-formatter.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,45 @@ function enrichDetachedStatus(record) {
return enriched;
}

/**
* Wrap a record so its serialized form includes a `currentTime` field when
* the status is "executing". This reflects the moment `--status` was invoked,
* making it easy to compute how long a command has been running.
* The original record is not mutated.
* @param {Object} record - Execution record
* @returns {Object} Record-like object with `toObject()` augmented when executing
*/
function attachCurrentTime(record) {
if (!record || record.status !== 'executing') {
return record;
}

const currentTime = new Date().toISOString();
const wrapped = Object.create(Object.getPrototypeOf(record));
Object.assign(wrapped, record);
wrapped.currentTime = currentTime;
wrapped.toObject = function () {
const base =
typeof record.toObject === 'function'
? record.toObject.call(this)
: { ...this };
// Insert currentTime right after startTime for readability
const ordered = {};
for (const [key, value] of Object.entries(base)) {
ordered[key] = value;
if (key === 'startTime') {
ordered.currentTime = currentTime;
}
}
if (!('currentTime' in ordered)) {
ordered.currentTime = currentTime;
}
return ordered;
};

return wrapped;
}

/**
* Format execution record as Links Notation (indented style)
* Uses nested Links notation for object values (like options) instead of JSON
Expand Down Expand Up @@ -192,9 +231,14 @@ function formatRecordAsText(record) {
`Shell: ${obj.shell}`,
`Platform: ${obj.platform}`,
`Start Time: ${obj.startTime}`,
`End Time: ${obj.endTime || 'N/A'}`,
`Log Path: ${obj.logPath}`,
];
if (obj.currentTime) {
lines.push(`Current Time: ${obj.currentTime}`);
}
lines.push(
`End Time: ${obj.endTime || 'N/A'}`,
`Log Path: ${obj.logPath}`
);

// Format options as nested list instead of JSON
const optionEntries = Object.entries(obj.options || {}).filter(
Expand Down Expand Up @@ -250,9 +294,11 @@ function queryStatus(store, identifier, outputFormat) {
try {
// Enrich detached execution status with live session check
const enrichedRecord = enrichDetachedStatus(record);
// Attach currentTime so callers can see how long an executing command has been running
const withCurrentTime = attachCurrentTime(enrichedRecord);
return {
success: true,
output: formatRecord(enrichedRecord, outputFormat || 'links-notation'),
output: formatRecord(withCurrentTime, outputFormat || 'links-notation'),
};
} catch (err) {
return { success: false, error: err.message };
Expand All @@ -266,4 +312,5 @@ module.exports = {
queryStatus,
isDetachedSessionAlive,
enrichDetachedStatus,
attachCurrentTime,
};
69 changes: 69 additions & 0 deletions js/test/session-name-status.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const {
queryStatus,
isDetachedSessionAlive,
enrichDetachedStatus,
attachCurrentTime,
} = require('../src/lib/status-formatter');

// Use temp directory for tests
Expand Down Expand Up @@ -308,4 +309,72 @@ describe('Issue #101: Detached status enrichment', () => {
});
});

describe('Issue #105: attachCurrentTime for executing status', () => {
it('should add currentTime to serialization when status is executing', () => {
const record = new ExecutionRecord({
command: 'sleep 60',
pid: 12345,
logPath: '/tmp/test.log',
});

const before = Date.now();
const wrapped = attachCurrentTime(record);
const obj = wrapped.toObject();
const after = Date.now();

expect(obj.currentTime).toBeDefined();
const currentTimeMs = new Date(obj.currentTime).getTime();
expect(Number.isNaN(currentTimeMs)).toBe(false);
expect(currentTimeMs).toBeGreaterThanOrEqual(before - 1);
expect(currentTimeMs).toBeLessThanOrEqual(after + 1);
});

it('should not add currentTime when status is executed', () => {
const record = new ExecutionRecord({
command: 'echo hello',
pid: 12345,
logPath: '/tmp/test.log',
});
record.complete(0);

const wrapped = attachCurrentTime(record);
// attachCurrentTime should return the original record unchanged
expect(wrapped).toBe(record);
const obj = wrapped.toObject();
expect(obj.currentTime).toBeUndefined();
});

it('should not mutate the original record', () => {
const record = new ExecutionRecord({
command: 'sleep 60',
pid: 12345,
logPath: '/tmp/test.log',
});

const wrapped = attachCurrentTime(record);
expect(wrapped).not.toBe(record);
// The original record's toObject output should not include currentTime
const originalObj = record.toObject();
expect(originalObj.currentTime).toBeUndefined();
});

it('should place currentTime right after startTime in serialization order', () => {
const record = new ExecutionRecord({
command: 'sleep 60',
pid: 12345,
logPath: '/tmp/test.log',
});

const wrapped = attachCurrentTime(record);
const keys = Object.keys(wrapped.toObject());
const startIndex = keys.indexOf('startTime');
expect(startIndex).toBeGreaterThanOrEqual(0);
expect(keys[startIndex + 1]).toBe('currentTime');
});

it('should handle null record gracefully', () => {
expect(attachCurrentTime(null)).toBeNull();
});
});

console.log('=== Session Name Status Tests (Issue #101) ===');
96 changes: 96 additions & 0 deletions js/test/status-query.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,102 @@ describe('--status query functionality', () => {
expect(parsed.exitCode).toBeNull();
expect(parsed.endTime).toBeNull();
});

it('should include currentTime for executing commands (JSON)', () => {
const beforeQuery = Date.now();
const executingRecord = new ExecutionRecord({
command: 'sleep 100',
pid: 99999,
logPath: '/tmp/executing.log',
});
store.save(executingRecord);

const result = runCli([
'--status',
executingRecord.uuid,
'--output-format',
'json',
]);

expect(result.exitCode).toBe(0);
const parsed = JSON.parse(result.stdout);
expect(parsed.currentTime).toBeDefined();
// currentTime should be a valid ISO timestamp at or after the query started
const currentTimeMs = new Date(parsed.currentTime).getTime();
expect(Number.isNaN(currentTimeMs)).toBe(false);
expect(currentTimeMs).toBeGreaterThanOrEqual(beforeQuery - 1);
expect(currentTimeMs).toBeLessThanOrEqual(Date.now() + 1);
// Should be >= startTime
expect(currentTimeMs).toBeGreaterThanOrEqual(
new Date(parsed.startTime).getTime()
);
});

it('should not include currentTime for completed commands (JSON)', () => {
// testRecord from beforeEach is already completed
const result = runCli([
'--status',
testRecord.uuid,
'--output-format',
'json',
]);

expect(result.exitCode).toBe(0);
const parsed = JSON.parse(result.stdout);
expect(parsed.status).toBe('executed');
expect(parsed.currentTime).toBeUndefined();
});

it('should include currentTime in links-notation for executing commands', () => {
const executingRecord = new ExecutionRecord({
command: 'sleep 100',
pid: 99999,
logPath: '/tmp/executing.log',
});
store.save(executingRecord);

const result = runCli(['--status', executingRecord.uuid]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('status executing');
// currentTime should appear as an indented property with an ISO timestamp value
expect(result.stdout).toMatch(
/\n {2}currentTime "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/
);
});

it('should include Current Time in text format for executing commands', () => {
const executingRecord = new ExecutionRecord({
command: 'sleep 100',
pid: 99999,
logPath: '/tmp/executing.log',
});
store.save(executingRecord);

const result = runCli([
'--status',
executingRecord.uuid,
'--output-format',
'text',
]);

expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('Status:');
expect(result.stdout).toContain('executing');
expect(result.stdout).toContain('Current Time:');
});

it('should not include Current Time in text format for completed commands', () => {
const result = runCli([
'--status',
testRecord.uuid,
'--output-format',
'text',
]);

expect(result.exitCode).toBe(0);
expect(result.stdout).not.toContain('Current Time:');
});
});
});

Expand Down
Loading