mrmd-js 2.0.0
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/README.md +842 -0
- package/dist/index.cjs +7613 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +7530 -0
- package/dist/index.js.map +1 -0
- package/dist/mrmd-js.iife.js +7618 -0
- package/dist/mrmd-js.iife.js.map +1 -0
- package/package.json +47 -0
- package/src/analysis/format.js +371 -0
- package/src/analysis/index.js +18 -0
- package/src/analysis/is-complete.js +394 -0
- package/src/constants.js +44 -0
- package/src/execute/css.js +205 -0
- package/src/execute/html.js +162 -0
- package/src/execute/index.js +41 -0
- package/src/execute/interface.js +144 -0
- package/src/execute/javascript.js +197 -0
- package/src/execute/registry.js +245 -0
- package/src/index.js +136 -0
- package/src/lsp/complete.js +353 -0
- package/src/lsp/format.js +310 -0
- package/src/lsp/hover.js +126 -0
- package/src/lsp/index.js +55 -0
- package/src/lsp/inspect.js +466 -0
- package/src/lsp/parse.js +455 -0
- package/src/lsp/variables.js +283 -0
- package/src/runtime.js +518 -0
- package/src/session/console-capture.js +181 -0
- package/src/session/context/iframe.js +407 -0
- package/src/session/context/index.js +12 -0
- package/src/session/context/interface.js +38 -0
- package/src/session/context/main.js +357 -0
- package/src/session/index.js +16 -0
- package/src/session/manager.js +327 -0
- package/src/session/session.js +678 -0
- package/src/transform/async.js +133 -0
- package/src/transform/extract.js +251 -0
- package/src/transform/index.js +10 -0
- package/src/transform/persistence.js +176 -0
- package/src/types/analysis.js +24 -0
- package/src/types/capabilities.js +44 -0
- package/src/types/completion.js +47 -0
- package/src/types/execution.js +62 -0
- package/src/types/index.js +16 -0
- package/src/types/inspection.js +39 -0
- package/src/types/session.js +32 -0
- package/src/types/streaming.js +74 -0
- package/src/types/variables.js +54 -0
- package/src/utils/ansi-renderer.js +301 -0
- package/src/utils/css-applicator.js +149 -0
- package/src/utils/html-renderer.js +355 -0
- package/src/utils/index.js +25 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML Executor
|
|
3
|
+
*
|
|
4
|
+
* Executes HTML cells by producing displayData with text/html MIME type.
|
|
5
|
+
* Optionally extracts and executes inline scripts.
|
|
6
|
+
*
|
|
7
|
+
* @module execute/html
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BaseExecutor } from './interface.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
|
|
14
|
+
* @typedef {import('../types/execution.js').ExecuteOptions} ExecuteOptions
|
|
15
|
+
* @typedef {import('../types/execution.js').ExecutionResult} ExecutionResult
|
|
16
|
+
* @typedef {import('../types/execution.js').DisplayData} DisplayData
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Regex to match script tags and capture their content
|
|
21
|
+
*/
|
|
22
|
+
const SCRIPT_REGEX = /<script[^>]*>([\s\S]*?)<\/script>/gi;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Regex to match style tags and capture their content
|
|
26
|
+
*/
|
|
27
|
+
const STYLE_REGEX = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Extract script tags from HTML
|
|
31
|
+
* @param {string} html
|
|
32
|
+
* @returns {{ html: string, scripts: string[] }}
|
|
33
|
+
*/
|
|
34
|
+
function extractScripts(html) {
|
|
35
|
+
const scripts = [];
|
|
36
|
+
|
|
37
|
+
const cleaned = html.replace(SCRIPT_REGEX, (_, content) => {
|
|
38
|
+
if (content.trim()) {
|
|
39
|
+
scripts.push(content);
|
|
40
|
+
}
|
|
41
|
+
return '';
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return { html: cleaned, scripts };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Extract style tags from HTML
|
|
49
|
+
* @param {string} html
|
|
50
|
+
* @returns {{ html: string, styles: string[] }}
|
|
51
|
+
*/
|
|
52
|
+
function extractStyles(html) {
|
|
53
|
+
const styles = [];
|
|
54
|
+
|
|
55
|
+
const cleaned = html.replace(STYLE_REGEX, (_, content) => {
|
|
56
|
+
if (content.trim()) {
|
|
57
|
+
styles.push(content);
|
|
58
|
+
}
|
|
59
|
+
return '';
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return { html: cleaned, styles };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* HTML executor - produces displayData for HTML content
|
|
67
|
+
*/
|
|
68
|
+
export class HtmlExecutor extends BaseExecutor {
|
|
69
|
+
/** @type {readonly string[]} */
|
|
70
|
+
languages = ['html', 'htm', 'xhtml'];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Execute HTML cell
|
|
74
|
+
* @param {string} code - HTML content
|
|
75
|
+
* @param {ExecutionContext} context - Execution context
|
|
76
|
+
* @param {ExecuteOptions} [options] - Execution options
|
|
77
|
+
* @returns {Promise<ExecutionResult>}
|
|
78
|
+
*/
|
|
79
|
+
async execute(code, context, options = {}) {
|
|
80
|
+
const startTime = performance.now();
|
|
81
|
+
|
|
82
|
+
// Extract scripts and styles
|
|
83
|
+
const { html: htmlWithoutScripts, scripts } = extractScripts(code);
|
|
84
|
+
const { html: cleanHtml, styles } = extractStyles(htmlWithoutScripts);
|
|
85
|
+
|
|
86
|
+
// Build display data
|
|
87
|
+
/** @type {DisplayData[]} */
|
|
88
|
+
const displayData = [];
|
|
89
|
+
|
|
90
|
+
// Main HTML content
|
|
91
|
+
displayData.push({
|
|
92
|
+
data: {
|
|
93
|
+
'text/html': code, // Send original HTML including scripts/styles
|
|
94
|
+
},
|
|
95
|
+
metadata: {
|
|
96
|
+
// Metadata for client to decide how to render
|
|
97
|
+
hasScripts: scripts.length > 0,
|
|
98
|
+
hasStyles: styles.length > 0,
|
|
99
|
+
scriptCount: scripts.length,
|
|
100
|
+
styleCount: styles.length,
|
|
101
|
+
trusted: options.cellMeta?.trusted ?? false,
|
|
102
|
+
// Client can use this to decide whether to execute scripts
|
|
103
|
+
executeScripts: options.cellMeta?.executeScripts ?? true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
// Optionally include cleaned HTML (without scripts/styles) as alternate
|
|
108
|
+
if (scripts.length > 0 || styles.length > 0) {
|
|
109
|
+
displayData.push({
|
|
110
|
+
data: {
|
|
111
|
+
'text/html+safe': cleanHtml.trim(),
|
|
112
|
+
},
|
|
113
|
+
metadata: {
|
|
114
|
+
description: 'HTML content with scripts and styles removed',
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Include extracted styles as separate CSS display data
|
|
120
|
+
if (styles.length > 0) {
|
|
121
|
+
displayData.push({
|
|
122
|
+
data: {
|
|
123
|
+
'text/css': styles.join('\n\n'),
|
|
124
|
+
},
|
|
125
|
+
metadata: {
|
|
126
|
+
source: 'extracted',
|
|
127
|
+
description: 'Styles extracted from HTML',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const duration = performance.now() - startTime;
|
|
133
|
+
|
|
134
|
+
// Build info message
|
|
135
|
+
const parts = [];
|
|
136
|
+
if (cleanHtml.trim()) parts.push('HTML');
|
|
137
|
+
if (styles.length > 0) parts.push(`${styles.length} style${styles.length > 1 ? 's' : ''}`);
|
|
138
|
+
if (scripts.length > 0) parts.push(`${scripts.length} script${scripts.length > 1 ? 's' : ''}`);
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
stdout: `Rendered: ${parts.join(', ') || 'empty'}`,
|
|
143
|
+
stderr: '',
|
|
144
|
+
result: undefined,
|
|
145
|
+
displayData,
|
|
146
|
+
assets: [],
|
|
147
|
+
executionCount: 0,
|
|
148
|
+
duration,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create an HTML executor
|
|
155
|
+
* @returns {HtmlExecutor}
|
|
156
|
+
*/
|
|
157
|
+
export function createHtmlExecutor() {
|
|
158
|
+
return new HtmlExecutor();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Export utilities for use by clients
|
|
162
|
+
export { extractScripts, extractStyles };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Execute Module
|
|
3
|
+
*
|
|
4
|
+
* Provides executors for different languages and the registry to manage them.
|
|
5
|
+
*
|
|
6
|
+
* @module execute
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Interface
|
|
10
|
+
export { BaseExecutor } from './interface.js';
|
|
11
|
+
|
|
12
|
+
// Registry
|
|
13
|
+
export { ExecutorRegistry, createExecutorRegistry } from './registry.js';
|
|
14
|
+
|
|
15
|
+
// Executors
|
|
16
|
+
export { JavaScriptExecutor, createJavaScriptExecutor } from './javascript.js';
|
|
17
|
+
export { HtmlExecutor, createHtmlExecutor, extractScripts, extractStyles } from './html.js';
|
|
18
|
+
export {
|
|
19
|
+
CssExecutor,
|
|
20
|
+
createCssExecutor,
|
|
21
|
+
scopeStyles,
|
|
22
|
+
generateScopeClass,
|
|
23
|
+
} from './css.js';
|
|
24
|
+
|
|
25
|
+
// Import for factory function
|
|
26
|
+
import { ExecutorRegistry } from './registry.js';
|
|
27
|
+
import { JavaScriptExecutor } from './javascript.js';
|
|
28
|
+
import { HtmlExecutor } from './html.js';
|
|
29
|
+
import { CssExecutor } from './css.js';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create a registry with default executors registered
|
|
33
|
+
* @returns {ExecutorRegistry}
|
|
34
|
+
*/
|
|
35
|
+
export function createDefaultExecutorRegistry() {
|
|
36
|
+
const registry = new ExecutorRegistry();
|
|
37
|
+
registry.register(new JavaScriptExecutor());
|
|
38
|
+
registry.register(new HtmlExecutor());
|
|
39
|
+
registry.register(new CssExecutor());
|
|
40
|
+
return registry;
|
|
41
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executor Interface
|
|
3
|
+
*
|
|
4
|
+
* Defines the contract for language executors.
|
|
5
|
+
* Each executor handles one or more languages and produces MRP-compliant results.
|
|
6
|
+
*
|
|
7
|
+
* @module execute/interface
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
|
|
12
|
+
* @typedef {import('../types/execution.js').ExecuteOptions} ExecuteOptions
|
|
13
|
+
* @typedef {import('../types/execution.js').ExecutionResult} ExecutionResult
|
|
14
|
+
* @typedef {import('../types/streaming.js').StreamEvent} StreamEvent
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {Object} Executor
|
|
19
|
+
* @property {readonly string[]} languages - Language identifiers this executor handles
|
|
20
|
+
* @property {function(string, ExecutionContext, ExecuteOptions=): Promise<ExecutionResult>} execute - Execute code
|
|
21
|
+
* @property {function(string, ExecutionContext, ExecuteOptions=): AsyncGenerator<StreamEvent>} [executeStream] - Execute with streaming
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @typedef {Object} ExecutorConfig
|
|
26
|
+
* @property {string[]} languages - Language identifiers to register
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Base class for executors (optional, executors can also be plain objects)
|
|
31
|
+
* @abstract
|
|
32
|
+
*/
|
|
33
|
+
export class BaseExecutor {
|
|
34
|
+
/** @type {readonly string[]} */
|
|
35
|
+
languages = [];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Execute code
|
|
39
|
+
* @param {string} code - Code to execute
|
|
40
|
+
* @param {ExecutionContext} context - Execution context
|
|
41
|
+
* @param {ExecuteOptions} [options] - Execution options
|
|
42
|
+
* @returns {Promise<ExecutionResult>}
|
|
43
|
+
* @abstract
|
|
44
|
+
*/
|
|
45
|
+
async execute(code, context, options = {}) {
|
|
46
|
+
throw new Error('execute() must be implemented by subclass');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Execute code with streaming output
|
|
51
|
+
* Default implementation wraps execute() result
|
|
52
|
+
*
|
|
53
|
+
* @param {string} code - Code to execute
|
|
54
|
+
* @param {ExecutionContext} context - Execution context
|
|
55
|
+
* @param {ExecuteOptions} [options] - Execution options
|
|
56
|
+
* @returns {AsyncGenerator<StreamEvent>}
|
|
57
|
+
*/
|
|
58
|
+
async *executeStream(code, context, options = {}) {
|
|
59
|
+
const execId = options.execId || `exec-${Date.now()}`;
|
|
60
|
+
const timestamp = new Date().toISOString();
|
|
61
|
+
|
|
62
|
+
// Start event
|
|
63
|
+
yield /** @type {import('../types/streaming.js').StartEvent} */ ({
|
|
64
|
+
type: 'start',
|
|
65
|
+
execId,
|
|
66
|
+
timestamp,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
// Execute
|
|
71
|
+
const result = await this.execute(code, context, options);
|
|
72
|
+
|
|
73
|
+
// Stream stdout
|
|
74
|
+
if (result.stdout) {
|
|
75
|
+
yield /** @type {import('../types/streaming.js').StdoutEvent} */ ({
|
|
76
|
+
type: 'stdout',
|
|
77
|
+
content: result.stdout,
|
|
78
|
+
accumulated: result.stdout,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Stream stderr
|
|
83
|
+
if (result.stderr) {
|
|
84
|
+
yield /** @type {import('../types/streaming.js').StderrEvent} */ ({
|
|
85
|
+
type: 'stderr',
|
|
86
|
+
content: result.stderr,
|
|
87
|
+
accumulated: result.stderr,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Stream display data
|
|
92
|
+
for (const display of result.displayData) {
|
|
93
|
+
yield /** @type {import('../types/streaming.js').DisplayEvent} */ ({
|
|
94
|
+
type: 'display',
|
|
95
|
+
data: display.data,
|
|
96
|
+
metadata: display.metadata,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Stream assets
|
|
101
|
+
for (const asset of result.assets) {
|
|
102
|
+
yield /** @type {import('../types/streaming.js').AssetEvent} */ ({
|
|
103
|
+
type: 'asset',
|
|
104
|
+
path: asset.path,
|
|
105
|
+
url: asset.url,
|
|
106
|
+
mimeType: asset.mimeType,
|
|
107
|
+
assetType: asset.assetType,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Result event
|
|
112
|
+
yield /** @type {import('../types/streaming.js').ResultEvent} */ ({
|
|
113
|
+
type: 'result',
|
|
114
|
+
result,
|
|
115
|
+
});
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// Error event
|
|
118
|
+
yield /** @type {import('../types/streaming.js').ErrorEvent} */ ({
|
|
119
|
+
type: 'error',
|
|
120
|
+
error: {
|
|
121
|
+
type: error instanceof Error ? error.name : 'Error',
|
|
122
|
+
message: error instanceof Error ? error.message : String(error),
|
|
123
|
+
traceback: error instanceof Error && error.stack ? error.stack.split('\n') : undefined,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Done event
|
|
129
|
+
yield /** @type {import('../types/streaming.js').DoneEvent} */ ({
|
|
130
|
+
type: 'done',
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Check if this executor supports a language
|
|
136
|
+
* @param {string} language
|
|
137
|
+
* @returns {boolean}
|
|
138
|
+
*/
|
|
139
|
+
supports(language) {
|
|
140
|
+
return this.languages.includes(language.toLowerCase());
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export {};
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JavaScript Executor
|
|
3
|
+
*
|
|
4
|
+
* Executes JavaScript code in an execution context.
|
|
5
|
+
* Handles variable persistence, async/await, and console output.
|
|
6
|
+
*
|
|
7
|
+
* @module execute/javascript
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { BaseExecutor } from './interface.js';
|
|
11
|
+
import { transformForPersistence } from '../transform/persistence.js';
|
|
12
|
+
import { wrapWithLastExpression } from '../transform/async.js';
|
|
13
|
+
import { extractDeclaredVariables } from '../transform/extract.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {import('../session/context/interface.js').ExecutionContext} ExecutionContext
|
|
17
|
+
* @typedef {import('../types/execution.js').ExecuteOptions} ExecuteOptions
|
|
18
|
+
* @typedef {import('../types/execution.js').ExecutionResult} ExecutionResult
|
|
19
|
+
* @typedef {import('../types/execution.js').ExecutionError} ExecutionError
|
|
20
|
+
* @typedef {import('../types/execution.js').DisplayData} DisplayData
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Format a value for display as a string
|
|
25
|
+
* @param {*} value
|
|
26
|
+
* @param {number} [maxLength=1000]
|
|
27
|
+
* @returns {string | undefined}
|
|
28
|
+
*/
|
|
29
|
+
function formatValue(value, maxLength = 1000) {
|
|
30
|
+
if (value === undefined) return undefined;
|
|
31
|
+
if (value === null) return 'null';
|
|
32
|
+
|
|
33
|
+
if (typeof value === 'function') {
|
|
34
|
+
return `[Function: ${value.name || 'anonymous'}]`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (typeof value === 'symbol') {
|
|
38
|
+
return value.toString();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeof value === 'object') {
|
|
42
|
+
try {
|
|
43
|
+
const json = JSON.stringify(value, null, 2);
|
|
44
|
+
if (json.length > maxLength) {
|
|
45
|
+
return json.slice(0, maxLength) + '...';
|
|
46
|
+
}
|
|
47
|
+
return json;
|
|
48
|
+
} catch {
|
|
49
|
+
return String(value);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const str = String(value);
|
|
54
|
+
if (str.length > maxLength) {
|
|
55
|
+
return str.slice(0, maxLength) + '...';
|
|
56
|
+
}
|
|
57
|
+
return str;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* JavaScript executor
|
|
62
|
+
*/
|
|
63
|
+
export class JavaScriptExecutor extends BaseExecutor {
|
|
64
|
+
/** @type {readonly string[]} */
|
|
65
|
+
languages = ['javascript', 'js', 'ecmascript', 'es'];
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Execute JavaScript code
|
|
69
|
+
* @param {string} code - Code to execute
|
|
70
|
+
* @param {ExecutionContext} context - Execution context
|
|
71
|
+
* @param {ExecuteOptions} [options] - Execution options
|
|
72
|
+
* @returns {Promise<ExecutionResult>}
|
|
73
|
+
*/
|
|
74
|
+
async execute(code, context, options = {}) {
|
|
75
|
+
const startTime = performance.now();
|
|
76
|
+
|
|
77
|
+
// Extract and track declared variables
|
|
78
|
+
const declaredVars = extractDeclaredVariables(code);
|
|
79
|
+
for (const varName of declaredVars) {
|
|
80
|
+
context.trackVariable(varName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Transform code for persistence (const/let → var)
|
|
84
|
+
const transformed = transformForPersistence(code);
|
|
85
|
+
|
|
86
|
+
// Wrap to capture last expression value and support async
|
|
87
|
+
const wrapped = wrapWithLastExpression(transformed);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Execute in context (pass execId for input() support)
|
|
91
|
+
const rawResult = await context.execute(wrapped, { execId: options.execId });
|
|
92
|
+
const duration = performance.now() - startTime;
|
|
93
|
+
|
|
94
|
+
// Format result
|
|
95
|
+
return this.#formatResult(rawResult, context, duration, options);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
const duration = performance.now() - startTime;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: false,
|
|
101
|
+
stdout: '',
|
|
102
|
+
stderr: error instanceof Error ? `${error.name}: ${error.message}` : String(error),
|
|
103
|
+
error: this.#formatError(error),
|
|
104
|
+
displayData: [],
|
|
105
|
+
assets: [],
|
|
106
|
+
executionCount: 0,
|
|
107
|
+
duration,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Format raw execution result to MRP ExecutionResult
|
|
114
|
+
* @param {import('../session/context/interface.js').RawExecutionResult} raw
|
|
115
|
+
* @param {ExecutionContext} context
|
|
116
|
+
* @param {number} duration
|
|
117
|
+
* @param {ExecuteOptions} options
|
|
118
|
+
* @returns {ExecutionResult}
|
|
119
|
+
*/
|
|
120
|
+
#formatResult(raw, context, duration, options) {
|
|
121
|
+
// Separate logs into stdout/stderr
|
|
122
|
+
const stdout = raw.logs
|
|
123
|
+
.filter((log) => log.type === 'log' || log.type === 'info')
|
|
124
|
+
.map((log) => log.args.map((arg) => formatValue(arg) ?? '').join(' '))
|
|
125
|
+
.join('\n');
|
|
126
|
+
|
|
127
|
+
const stderr = raw.logs
|
|
128
|
+
.filter((log) => log.type === 'error' || log.type === 'warn')
|
|
129
|
+
.map((log) => {
|
|
130
|
+
const prefix = log.type === 'error' ? 'Error: ' : 'Warning: ';
|
|
131
|
+
return prefix + log.args.map((arg) => formatValue(arg) ?? '').join(' ');
|
|
132
|
+
})
|
|
133
|
+
.join('\n');
|
|
134
|
+
|
|
135
|
+
// Format error if present
|
|
136
|
+
/** @type {ExecutionError | undefined} */
|
|
137
|
+
let error;
|
|
138
|
+
if (raw.error) {
|
|
139
|
+
error = this.#formatError(raw.error);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Get display data from context
|
|
143
|
+
/** @type {DisplayData[]} */
|
|
144
|
+
const displayData = 'getDisplayQueue' in context ? context.getDisplayQueue() : [];
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
success: !raw.error,
|
|
148
|
+
stdout,
|
|
149
|
+
stderr,
|
|
150
|
+
result: raw.result,
|
|
151
|
+
resultString: formatValue(raw.result),
|
|
152
|
+
error,
|
|
153
|
+
displayData,
|
|
154
|
+
assets: [],
|
|
155
|
+
executionCount: 0, // Will be set by session
|
|
156
|
+
duration,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Format an error
|
|
162
|
+
* @param {*} error
|
|
163
|
+
* @returns {ExecutionError}
|
|
164
|
+
*/
|
|
165
|
+
#formatError(error) {
|
|
166
|
+
if (error instanceof Error) {
|
|
167
|
+
/** @type {ExecutionError} */
|
|
168
|
+
const formatted = {
|
|
169
|
+
type: error.name,
|
|
170
|
+
message: error.message,
|
|
171
|
+
traceback: error.stack?.split('\n'),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// Try to extract line/column from stack
|
|
175
|
+
const lineMatch = error.stack?.match(/:(\d+):(\d+)/);
|
|
176
|
+
if (lineMatch) {
|
|
177
|
+
formatted.line = parseInt(lineMatch[1], 10);
|
|
178
|
+
formatted.column = parseInt(lineMatch[2], 10);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return formatted;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
type: 'Error',
|
|
186
|
+
message: String(error),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Create a JavaScript executor
|
|
193
|
+
* @returns {JavaScriptExecutor}
|
|
194
|
+
*/
|
|
195
|
+
export function createJavaScriptExecutor() {
|
|
196
|
+
return new JavaScriptExecutor();
|
|
197
|
+
}
|