Skip to content
Open
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 .changeset/local-reporter-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@thatopen/services': patch
---

`thatopen run` and `thatopen local-server`: `executionReporter.error()` exists locally, as documented and as in the cloud. Error lines print as `[error]` and carry `level: "error"` in local-server messages.
2 changes: 1 addition & 1 deletion docs/cli/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: "Build and run a cloud component locally as a child process, mirror

Builds and executes a cloud component locally, replicating what the platform's cloud engine does. Useful for testing component logic and parameters before publishing.

Execution output (`MESSAGE`, `PROGRESS`, `SUCCESS`, `FAIL`) is streamed to the terminal with elapsed time.
Execution output (`MESSAGE`, `ERROR` from `executionReporter.error`, `PROGRESS`, `SUCCESS`, `FAIL`) is streamed to the terminal with elapsed time.

**Usage:**
```bash
Expand Down
3 changes: 3 additions & 0 deletions src/cli/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ export const runCommand = new Command('run')
case 'MESSAGE':
console.log(`[${elapsed}s] [message] ${msg.message}`);
break;
case 'ERROR':
console.error(`[${elapsed}s] [error] ${msg.message}`);
break;
case 'PROGRESS':
console.log(`[${elapsed}s] [progress] ${msg.message}%`);
break;
Expand Down
46 changes: 46 additions & 0 deletions src/cli/lib/engine-script.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect } from 'vitest';
import { fork } from 'node:child_process';
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { buildEngineScript } from './engine-script';

/**
* The local engine must offer the same executionReporter as the platform's,
* including error(), which the cloud-component README documents.
*/
describe('buildEngineScript', () => {
it('provides executionReporter.error', async () => {
const root = mkdtempSync(join(tmpdir(), 'thatopen-engine-'));
const services = join(root, 'node_modules', '@thatopen', 'services');
mkdirSync(services, { recursive: true });
writeFileSync(join(services, 'index.js'), 'exports.EngineServicesClient = class {};');
const bundle = `var main = async () => {
executionReporter.error('bad row');
return { type: 'SUCCESS', message: 'done' };
};`;
const script = join(root, 'engine.js');
writeFileSync(
script,
buildEngineScript(bundle, 'token', 'http://localhost', {}, {
executionId: 'e',
toolId: 'local',
toolVersion: 'local',
}),
);

const messages = await new Promise<unknown[]>((resolve, reject) => {
const received: unknown[] = [];
const child = fork(script, [], { stdio: ['ignore', 'ignore', 'ignore', 'ipc'] });
child.on('message', (m) => received.push(m));
child.on('error', reject);
child.on('exit', () => resolve(received));
setTimeout(() => child.kill(), 5000);
});

expect(messages).toEqual([
{ type: 'ERROR', message: 'bad row' },
{ type: 'SUCCESS', message: 'done' },
]);
});
});
3 changes: 3 additions & 0 deletions src/cli/lib/engine-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const executionReporter = {
progress: (message) => {
process.send({ type: 'PROGRESS', message });
},
error: (message) => {
process.send({ type: 'ERROR', message });
},
};

let thatOpenServices, executionParams;
Expand Down
9 changes: 7 additions & 2 deletions src/cli/lib/execution-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ export interface ExecutionMessage {
_id: string;
executionId: string;
content: string;
/** Set to "error" for executionReporter.error lines, as the platform does. */
level?: 'error';
createdAt: string;
}

Expand Down Expand Up @@ -155,15 +157,18 @@ export class ExecutionManager {
state.updatedAt = now;

switch (msg.type) {
case 'MESSAGE': {
case 'MESSAGE':
case 'ERROR': {
const isError = msg.type === 'ERROR';
const msgEntity: ExecutionMessage = {
_id: randomUUID(),
executionId,
content: msg.message,
...(isError && { level: 'error' as const }),
createdAt: now,
};
state.messages.push(msgEntity);
console.log(`[${executionId.slice(0, 8)}] [message] ${msg.message}`);
console.log(`[${executionId.slice(0, 8)}] [${isError ? 'error' : 'message'}] ${msg.message}`);
this.emit(executionId, {
messageUpdate: {
...msgEntity,
Expand Down