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.
Files changed (52) hide show
  1. package/README.md +842 -0
  2. package/dist/index.cjs +7613 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.js +7530 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/mrmd-js.iife.js +7618 -0
  7. package/dist/mrmd-js.iife.js.map +1 -0
  8. package/package.json +47 -0
  9. package/src/analysis/format.js +371 -0
  10. package/src/analysis/index.js +18 -0
  11. package/src/analysis/is-complete.js +394 -0
  12. package/src/constants.js +44 -0
  13. package/src/execute/css.js +205 -0
  14. package/src/execute/html.js +162 -0
  15. package/src/execute/index.js +41 -0
  16. package/src/execute/interface.js +144 -0
  17. package/src/execute/javascript.js +197 -0
  18. package/src/execute/registry.js +245 -0
  19. package/src/index.js +136 -0
  20. package/src/lsp/complete.js +353 -0
  21. package/src/lsp/format.js +310 -0
  22. package/src/lsp/hover.js +126 -0
  23. package/src/lsp/index.js +55 -0
  24. package/src/lsp/inspect.js +466 -0
  25. package/src/lsp/parse.js +455 -0
  26. package/src/lsp/variables.js +283 -0
  27. package/src/runtime.js +518 -0
  28. package/src/session/console-capture.js +181 -0
  29. package/src/session/context/iframe.js +407 -0
  30. package/src/session/context/index.js +12 -0
  31. package/src/session/context/interface.js +38 -0
  32. package/src/session/context/main.js +357 -0
  33. package/src/session/index.js +16 -0
  34. package/src/session/manager.js +327 -0
  35. package/src/session/session.js +678 -0
  36. package/src/transform/async.js +133 -0
  37. package/src/transform/extract.js +251 -0
  38. package/src/transform/index.js +10 -0
  39. package/src/transform/persistence.js +176 -0
  40. package/src/types/analysis.js +24 -0
  41. package/src/types/capabilities.js +44 -0
  42. package/src/types/completion.js +47 -0
  43. package/src/types/execution.js +62 -0
  44. package/src/types/index.js +16 -0
  45. package/src/types/inspection.js +39 -0
  46. package/src/types/session.js +32 -0
  47. package/src/types/streaming.js +74 -0
  48. package/src/types/variables.js +54 -0
  49. package/src/utils/ansi-renderer.js +301 -0
  50. package/src/utils/css-applicator.js +149 -0
  51. package/src/utils/html-renderer.js +355 -0
  52. package/src/utils/index.js +25 -0
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "mrmd-js",
3
+ "version": "2.0.0",
4
+ "description": "MRP-compliant browser JavaScript runtime for mrmd notebooks",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "rollup -c",
20
+ "dev": "rollup -c -w",
21
+ "test": "vitest",
22
+ "serve": "node test-app/server.js",
23
+ "demo": "npm run build && npm run serve"
24
+ },
25
+ "devDependencies": {
26
+ "@rollup/plugin-node-resolve": "^15.2.3",
27
+ "jsdom": "^27.4.0",
28
+ "rollup": "^4.9.0",
29
+ "vitest": "^1.0.0"
30
+ },
31
+ "keywords": [
32
+ "mrmd",
33
+ "mrp",
34
+ "notebook",
35
+ "javascript",
36
+ "runtime",
37
+ "repl",
38
+ "lsp",
39
+ "completion",
40
+ "browser"
41
+ ],
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/anthropics/mrmd-packages"
46
+ }
47
+ }
@@ -0,0 +1,371 @@
1
+ /**
2
+ * Code Formatting
3
+ *
4
+ * Formats JavaScript code. Can integrate with prettier if available,
5
+ * otherwise provides basic formatting.
6
+ *
7
+ * @module analysis/format
8
+ */
9
+
10
+ /**
11
+ * @typedef {import('../types/analysis.js').FormatResult} FormatResult
12
+ */
13
+
14
+ /**
15
+ * @typedef {Object} FormatOptions
16
+ * @property {number} [tabWidth=2] - Number of spaces per tab
17
+ * @property {boolean} [useTabs=false] - Use tabs instead of spaces
18
+ * @property {boolean} [semi=true] - Add semicolons
19
+ * @property {boolean} [singleQuote=false] - Use single quotes
20
+ * @property {number} [printWidth=80] - Line width
21
+ */
22
+
23
+ /** @type {any} */
24
+ let prettierInstance = null;
25
+
26
+ /**
27
+ * Set prettier instance for formatting
28
+ * This allows external prettier to be provided
29
+ *
30
+ * @param {any} prettier - Prettier instance
31
+ */
32
+ export function setPrettier(prettier) {
33
+ prettierInstance = prettier;
34
+ }
35
+
36
+ /**
37
+ * Check if prettier is available
38
+ * @returns {boolean}
39
+ */
40
+ export function hasPrettier() {
41
+ return prettierInstance !== null;
42
+ }
43
+
44
+ /**
45
+ * Format JavaScript code
46
+ *
47
+ * @param {string} code - Code to format
48
+ * @param {FormatOptions} [options]
49
+ * @returns {Promise<FormatResult>}
50
+ */
51
+ export async function formatCode(code, options = {}) {
52
+ // Try prettier first
53
+ if (prettierInstance) {
54
+ try {
55
+ const formatted = await formatWithPrettier(code, options);
56
+ return {
57
+ formatted,
58
+ changed: formatted !== code,
59
+ };
60
+ } catch (e) {
61
+ // Prettier failed, fall back to basic formatting
62
+ console.warn('Prettier formatting failed:', e);
63
+ }
64
+ }
65
+
66
+ // Fall back to basic formatting
67
+ const formatted = basicFormat(code, options);
68
+ return {
69
+ formatted,
70
+ changed: formatted !== code,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Format with prettier
76
+ * @param {string} code
77
+ * @param {FormatOptions} options
78
+ * @returns {Promise<string>}
79
+ */
80
+ async function formatWithPrettier(code, options) {
81
+ const prettierOptions = {
82
+ parser: 'babel',
83
+ tabWidth: options.tabWidth ?? 2,
84
+ useTabs: options.useTabs ?? false,
85
+ semi: options.semi ?? true,
86
+ singleQuote: options.singleQuote ?? false,
87
+ printWidth: options.printWidth ?? 80,
88
+ };
89
+
90
+ // prettier might be async or sync depending on version
91
+ const result = prettierInstance.format(code, prettierOptions);
92
+ return result instanceof Promise ? await result : result;
93
+ }
94
+
95
+ /**
96
+ * Basic code formatting (no external dependencies)
97
+ *
98
+ * @param {string} code
99
+ * @param {FormatOptions} options
100
+ * @returns {string}
101
+ */
102
+ export function basicFormat(code, options = {}) {
103
+ const tabWidth = options.tabWidth ?? 2;
104
+ const useTabs = options.useTabs ?? false;
105
+ const semi = options.semi ?? true;
106
+ const indent = useTabs ? '\t' : ' '.repeat(tabWidth);
107
+
108
+ let result = code;
109
+
110
+ // Normalize line endings
111
+ result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
112
+
113
+ // Normalize whitespace around operators
114
+ result = normalizeOperatorSpacing(result);
115
+
116
+ // Normalize comma spacing
117
+ result = result.replace(/,\s*/g, ', ');
118
+
119
+ // Normalize colon spacing in objects
120
+ result = normalizeColonSpacing(result);
121
+
122
+ // Fix indentation
123
+ result = fixIndentation(result, indent);
124
+
125
+ // Add/remove trailing semicolons
126
+ if (semi) {
127
+ result = addSemicolons(result);
128
+ }
129
+
130
+ // Remove trailing whitespace
131
+ result = result.split('\n').map(line => line.trimEnd()).join('\n');
132
+
133
+ // Ensure single trailing newline
134
+ result = result.trimEnd() + '\n';
135
+
136
+ return result;
137
+ }
138
+
139
+ /**
140
+ * Normalize spacing around operators
141
+ * @param {string} code
142
+ * @returns {string}
143
+ */
144
+ function normalizeOperatorSpacing(code) {
145
+ // This is tricky because we need to handle strings and regex
146
+ // For now, do a simple replacement that might not be perfect
147
+
148
+ // Binary operators (add spaces around)
149
+ const binaryOps = [
150
+ '===', '!==', '==', '!=',
151
+ '<=', '>=', '<', '>',
152
+ '&&', '||', '??',
153
+ '+=', '-=', '*=', '/=', '%=',
154
+ '**=', '&=', '|=', '^=',
155
+ '<<=', '>>=', '>>>=',
156
+ '=>',
157
+ ];
158
+
159
+ let result = code;
160
+
161
+ // Process each operator (order matters - longer first)
162
+ for (const op of binaryOps) {
163
+ const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
164
+ // Only if not already properly spaced
165
+ result = result.replace(
166
+ new RegExp(`(\\S)${escaped}(\\S)`, 'g'),
167
+ `$1 ${op} $2`
168
+ );
169
+ }
170
+
171
+ // Single = assignment (but not ==, ===, =>, etc)
172
+ result = result.replace(/(\w)=(?![=>])(\S)/g, '$1 = $2');
173
+
174
+ return result;
175
+ }
176
+
177
+ /**
178
+ * Normalize colon spacing in objects
179
+ * @param {string} code
180
+ * @returns {string}
181
+ */
182
+ function normalizeColonSpacing(code) {
183
+ // Object property colons: add space after but not before
184
+ // This is imperfect but handles common cases
185
+ return code.replace(/(\w+)\s*:\s*/g, '$1: ');
186
+ }
187
+
188
+ /**
189
+ * Fix indentation based on bracket depth
190
+ * @param {string} code
191
+ * @param {string} indent
192
+ * @returns {string}
193
+ */
194
+ function fixIndentation(code, indent) {
195
+ const lines = code.split('\n');
196
+ const result = [];
197
+ let depth = 0;
198
+
199
+ for (const line of lines) {
200
+ const trimmed = line.trim();
201
+
202
+ if (!trimmed) {
203
+ result.push('');
204
+ continue;
205
+ }
206
+
207
+ // Check if line starts with closing bracket
208
+ const startsWithClose = /^[}\])]/.test(trimmed);
209
+ if (startsWithClose && depth > 0) {
210
+ depth--;
211
+ }
212
+
213
+ // Add indentation
214
+ result.push(indent.repeat(depth) + trimmed);
215
+
216
+ // Count bracket changes for next line
217
+ const opens = (trimmed.match(/[{[(]/g) || []).length;
218
+ const closes = (trimmed.match(/[}\])]/g) || []).length;
219
+ depth += opens - closes;
220
+
221
+ // Ensure depth doesn't go negative
222
+ if (depth < 0) depth = 0;
223
+ }
224
+
225
+ return result.join('\n');
226
+ }
227
+
228
+ /**
229
+ * Add semicolons to statements that need them
230
+ * @param {string} code
231
+ * @returns {string}
232
+ */
233
+ function addSemicolons(code) {
234
+ const lines = code.split('\n');
235
+ const result = [];
236
+
237
+ for (let i = 0; i < lines.length; i++) {
238
+ let line = lines[i];
239
+ const trimmed = line.trim();
240
+
241
+ // Skip empty lines
242
+ if (!trimmed) {
243
+ result.push(line);
244
+ continue;
245
+ }
246
+
247
+ // Skip lines that don't need semicolons
248
+ const skipPatterns = [
249
+ /^\/\//, // Comment
250
+ /^\/\*/, // Block comment start
251
+ /\*\/$/, // Block comment end
252
+ /^\*/, // Block comment middle
253
+ /^import\s/, // Import (might need semi, but complex)
254
+ /^export\s/, // Export
255
+ /^if\s*\(/, // If
256
+ /^else/, // Else
257
+ /^for\s*\(/, // For
258
+ /^while\s*\(/, // While
259
+ /^do\s*{?$/, // Do
260
+ /^switch\s*\(/, // Switch
261
+ /^try\s*{?$/, // Try
262
+ /^catch\s*\(/, // Catch
263
+ /^finally\s*{?$/, // Finally
264
+ /^function\s/, // Function declaration
265
+ /^class\s/, // Class
266
+ /^async\s+function/, // Async function
267
+ /[{,]\s*$/, // Ends with { or ,
268
+ /^\s*[}\])]/, // Starts with closing bracket
269
+ ];
270
+
271
+ let needsSemi = true;
272
+ for (const pattern of skipPatterns) {
273
+ if (pattern.test(trimmed)) {
274
+ needsSemi = false;
275
+ break;
276
+ }
277
+ }
278
+
279
+ // Already has semicolon
280
+ if (trimmed.endsWith(';')) {
281
+ needsSemi = false;
282
+ }
283
+
284
+ // Check if next non-empty line suggests continuation
285
+ if (needsSemi) {
286
+ for (let j = i + 1; j < lines.length; j++) {
287
+ const nextTrimmed = lines[j].trim();
288
+ if (!nextTrimmed) continue;
289
+ if (/^[.?[]/.test(nextTrimmed)) {
290
+ // Next line is continuation
291
+ needsSemi = false;
292
+ }
293
+ break;
294
+ }
295
+ }
296
+
297
+ if (needsSemi) {
298
+ // Find where to insert semicolon (before trailing comment)
299
+ const commentMatch = line.match(/^(.*?)(\s*\/\/.*)$/);
300
+ if (commentMatch) {
301
+ line = commentMatch[1] + ';' + commentMatch[2];
302
+ } else {
303
+ line = line.trimEnd() + ';';
304
+ }
305
+ }
306
+
307
+ result.push(line);
308
+ }
309
+
310
+ return result.join('\n');
311
+ }
312
+
313
+ /**
314
+ * Format HTML code (basic)
315
+ * @param {string} code
316
+ * @returns {string}
317
+ */
318
+ export function formatHtml(code) {
319
+ // Very basic HTML formatting
320
+ let result = code;
321
+
322
+ // Normalize line endings
323
+ result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
324
+
325
+ // Add newlines after block elements
326
+ result = result.replace(/(<\/(?:div|p|ul|ol|li|h[1-6]|header|footer|section|article|nav|aside|main|table|tr|thead|tbody|form)>)/gi, '$1\n');
327
+
328
+ // Add newlines before block elements
329
+ result = result.replace(/(<(?:div|p|ul|ol|li|h[1-6]|header|footer|section|article|nav|aside|main|table|tr|thead|tbody|form)(?:\s[^>]*)?>)/gi, '\n$1');
330
+
331
+ // Remove multiple blank lines
332
+ result = result.replace(/\n{3,}/g, '\n\n');
333
+
334
+ return result.trim() + '\n';
335
+ }
336
+
337
+ /**
338
+ * Format CSS code (basic)
339
+ * @param {string} code
340
+ * @returns {string}
341
+ */
342
+ export function formatCss(code) {
343
+ let result = code;
344
+
345
+ // Normalize line endings
346
+ result = result.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
347
+
348
+ // Add newlines after { and ;
349
+ result = result.replace(/\{/g, ' {\n');
350
+ result = result.replace(/;/g, ';\n');
351
+ result = result.replace(/\}/g, '\n}\n');
352
+
353
+ // Fix property spacing
354
+ result = result.replace(/:\s*/g, ': ');
355
+
356
+ // Fix indentation
357
+ const lines = result.split('\n');
358
+ const formatted = [];
359
+ let depth = 0;
360
+
361
+ for (const line of lines) {
362
+ const trimmed = line.trim();
363
+ if (!trimmed) continue;
364
+
365
+ if (trimmed === '}') depth--;
366
+ formatted.push(' '.repeat(Math.max(0, depth)) + trimmed);
367
+ if (trimmed.endsWith('{')) depth++;
368
+ }
369
+
370
+ return formatted.join('\n') + '\n';
371
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Code Analysis
3
+ *
4
+ * Utilities for analyzing JavaScript code including completeness
5
+ * checking and formatting.
6
+ *
7
+ * @module analysis
8
+ */
9
+
10
+ export { isComplete, getSuggestedIndent } from './is-complete.js';
11
+ export {
12
+ formatCode,
13
+ basicFormat,
14
+ formatHtml,
15
+ formatCss,
16
+ setPrettier,
17
+ hasPrettier,
18
+ } from './format.js';