@live-codes/browser-compilers 0.22.5 → 0.22.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@live-codes/browser-compilers",
3
- "version": "0.22.5",
3
+ "version": "0.22.7",
4
4
  "description": "Compilers that run in the browser, for use in livecodes.io",
5
5
  "author": "Hatem Hosny",
6
6
  "license": "MIT",
@@ -63,6 +63,8 @@
63
63
  "babel-preset-solid": "1.9.10",
64
64
  "codemirror-theme-catppuccin": "0.3.0",
65
65
  "cssnano-preset-default": "7.0.10",
66
+ "dotenv": "17.3.1",
67
+ "dotenv-expand": "12.0.3",
66
68
  "dts-bundle": "0.1.1",
67
69
  "elkjs-svg": "0.2.1",
68
70
  "eslint": "8.11.0",
@@ -81,8 +83,8 @@
81
83
  "posthtml-css-modules": "0.1.3",
82
84
  "prettier-plugin-java": "2.7.7",
83
85
  "pug": "3.0.3",
84
- "react": "19.2.0",
85
- "react-dom": "19.2.0",
86
+ "react": "19.2.3",
87
+ "react-dom": "19.2.3",
86
88
  "react-native-web": "0.21.2",
87
89
  "remark-gfm": "4.0.1",
88
90
  "sass": "1.93.3",
@@ -172,6 +172,13 @@ esbuild.build({
172
172
  globalName: 'pluginPug',
173
173
  });
174
174
 
175
+ // @prettier/plugin-minizinc
176
+ esbuild.build({
177
+ ...baseOptions,
178
+ entryPoints: ['vendor_modules/src/prettier-plugin-minizinc/prettier-plugin-minizinc.js'],
179
+ outfile: 'dist/prettier/parser-minizinc.js',
180
+ });
181
+
175
182
  // clientside-haml-js
176
183
  mkdirp(targetDir + '/clientside-haml-js');
177
184
  fs.copyFileSync(
@@ -652,3 +659,11 @@ esbuild.build({
652
659
  outfile: 'dist/path-browserify/path-browserify.js',
653
660
  format: 'esm',
654
661
  });
662
+
663
+ // dotenv
664
+ esbuild.build({
665
+ ...baseOptions,
666
+ entryPoints: ['vendor_modules/imports/dotenv.js'],
667
+ outfile: 'dist/dotenv/dotenv.js',
668
+ globalName: 'dotenv',
669
+ });
@@ -0,0 +1,6 @@
1
+ import { parse } from 'dotenv';
2
+ import { expand } from 'dotenv-expand';
3
+
4
+ export { parse, expand };
5
+ export const parseAndExpand = (/** @type {string} */ env) =>
6
+ expand({ parsed: parse(env || '') }).parsed;
@@ -0,0 +1,474 @@
1
+ const PLUGIN_NAME = 'minizinc';
2
+
3
+ // Parser
4
+ const parser = {
5
+ parse(text, parsers, options) {
6
+ return {
7
+ type: 'minizinc-root',
8
+ body: text,
9
+ loc: {
10
+ start: { line: 1, column: 0 },
11
+ end: { line: text.split('\n').length, column: 0 },
12
+ },
13
+ };
14
+ },
15
+ astFormat: PLUGIN_NAME,
16
+ locStart: (node) => node.loc?.start ?? { line: 1, column: 0 },
17
+ locEnd: (node) => node.loc?.end ?? { line: 1, column: 0 },
18
+ };
19
+
20
+ // Printer
21
+ const printer = {
22
+ print(path, options, print) {
23
+ const node = path.getValue();
24
+
25
+ if (node.type === 'minizinc-root') {
26
+ return formatMiniZinc(node.body, options);
27
+ }
28
+
29
+ return node.body;
30
+ },
31
+ };
32
+
33
+ function formatMiniZinc(text, options) {
34
+ const printWidth = options.printWidth || 80;
35
+ const tabSize = options.tabWidth || 2;
36
+
37
+ // First, split multiple statements on same line
38
+ const normalizedText = splitMultipleStatements(text);
39
+
40
+ // Then, join continuation lines into complete statements
41
+ const statements = joinContinuationLines(normalizedText);
42
+
43
+ const formatted = [];
44
+
45
+ for (let i = 0; i < statements.length; i++) {
46
+ const statement = statements[i];
47
+
48
+ // Skip empty lines but preserve single line breaks
49
+ if (statement.trim() === '') {
50
+ formatted.push('');
51
+ continue;
52
+ }
53
+
54
+ // Format the statement
55
+ const formattedStatement = formatStatement(statement, printWidth, tabSize);
56
+ formatted.push(formattedStatement);
57
+ }
58
+
59
+ return formatted.join('\n');
60
+ }
61
+
62
+ function normalizeComment(comment) {
63
+ // Ensure there's a space after %
64
+ if (comment.startsWith('%') && comment.length > 1 && comment[1] !== ' ') {
65
+ return '% ' + comment.slice(1);
66
+ }
67
+ return comment;
68
+ }
69
+
70
+ function splitMultipleStatements(text) {
71
+ const lines = text.split('\n');
72
+ const result = [];
73
+
74
+ for (const line of lines) {
75
+ const splitLines = splitLineByStatements(line);
76
+ result.push(...splitLines);
77
+ }
78
+
79
+ return result.join('\n');
80
+ }
81
+
82
+ function splitLineByStatements(line) {
83
+ // Check if line starts with a comment
84
+ const trimmed = line.trim();
85
+ if (trimmed.startsWith('%')) {
86
+ return [normalizeComment(trimmed)];
87
+ }
88
+
89
+ // Find where comment starts (if any)
90
+ const commentIndex = findCommentStart(line);
91
+
92
+ if (commentIndex !== -1) {
93
+ const codePart = line.slice(0, commentIndex);
94
+ const commentPart = line.slice(commentIndex).trim();
95
+
96
+ if (!codePart.trim()) {
97
+ return [normalizeComment(commentPart)];
98
+ }
99
+
100
+ // Split the code part by statements
101
+ const codeStatements = splitCodeByStatements(codePart);
102
+
103
+ if (codeStatements.length === 0) {
104
+ return [line];
105
+ }
106
+
107
+ // Attach comment to the last statement
108
+ const lastIndex = codeStatements.length - 1;
109
+ codeStatements[lastIndex] = codeStatements[lastIndex] + ' ' + normalizeComment(commentPart);
110
+
111
+ return codeStatements;
112
+ }
113
+
114
+ return splitCodeByStatements(line);
115
+ }
116
+
117
+ function findCommentStart(line) {
118
+ let inString = false;
119
+ let stringChar = null;
120
+
121
+ for (let i = 0; i < line.length; i++) {
122
+ const char = line[i];
123
+ const nextChar = i + 1 < line.length ? line[i + 1] : null;
124
+
125
+ // Handle escape sequences
126
+ if (char === '\\') {
127
+ if (nextChar !== null) {
128
+ i++; // Skip next character
129
+ }
130
+ continue;
131
+ }
132
+
133
+ // Track string boundaries
134
+ if (char === '"' || char === "'") {
135
+ if (!inString) {
136
+ inString = true;
137
+ stringChar = char;
138
+ } else if (char === stringChar) {
139
+ inString = false;
140
+ stringChar = null;
141
+ }
142
+ continue;
143
+ }
144
+
145
+ // Check for comment start (not in string)
146
+ if (!inString && char === '%') {
147
+ return i;
148
+ }
149
+ }
150
+
151
+ return -1;
152
+ }
153
+
154
+ function splitCodeByStatements(code) {
155
+ const statements = [];
156
+ let current = '';
157
+ let inString = false;
158
+ let stringChar = null;
159
+ let depth = 0;
160
+
161
+ for (let i = 0; i < code.length; i++) {
162
+ const char = code[i];
163
+ const nextChar = i + 1 < code.length ? code[i + 1] : null;
164
+
165
+ // Handle escape sequences
166
+ if (char === '\\') {
167
+ current += char;
168
+ if (nextChar !== null) {
169
+ i++;
170
+ current += code[i];
171
+ }
172
+ continue;
173
+ }
174
+
175
+ // Track string boundaries
176
+ if (char === '"' || char === "'") {
177
+ if (!inString) {
178
+ inString = true;
179
+ stringChar = char;
180
+ } else if (char === stringChar) {
181
+ inString = false;
182
+ stringChar = null;
183
+ }
184
+ current += char;
185
+ continue;
186
+ }
187
+
188
+ // Track nested brackets/parens/braces
189
+ if (!inString) {
190
+ if (char === '[' || char === '(' || char === '{') depth++;
191
+ if (char === ']' || char === ')' || char === '}') depth--;
192
+ }
193
+
194
+ current += char;
195
+
196
+ // Check if this is the end of a statement (semicolon at depth 0, not in string)
197
+ if (char === ';' && !inString && depth === 0) {
198
+ const trimmed = current.trim();
199
+ if (trimmed) {
200
+ statements.push(trimmed);
201
+ }
202
+ current = '';
203
+ }
204
+ }
205
+
206
+ // Add any remaining content
207
+ const trimmed = current.trim();
208
+ if (trimmed) {
209
+ statements.push(trimmed);
210
+ }
211
+
212
+ // If no statements were found, return the original code
213
+ return statements.length > 0 ? statements : [code];
214
+ }
215
+
216
+ function endsWithStatement(line) {
217
+ // Remove trailing comment if present
218
+ const commentIndex = findCommentStart(line);
219
+ const codeOnly = commentIndex !== -1 ? line.slice(0, commentIndex) : line;
220
+ const trimmed = codeOnly.trim();
221
+
222
+ return (
223
+ trimmed.endsWith(';') ||
224
+ trimmed.endsWith(']') ||
225
+ (trimmed.endsWith(')') && !line.includes('++'))
226
+ );
227
+ }
228
+
229
+ function joinContinuationLines(text) {
230
+ const lines = text.split('\n');
231
+ const statements = [];
232
+ let currentStatement = '';
233
+
234
+ for (let i = 0; i < lines.length; i++) {
235
+ const line = lines[i];
236
+ const trimmed = line.trim();
237
+
238
+ if (trimmed === '') {
239
+ if (currentStatement) {
240
+ statements.push(currentStatement);
241
+ currentStatement = '';
242
+ }
243
+ statements.push('');
244
+ continue;
245
+ }
246
+
247
+ // Don't join comment lines
248
+ if (trimmed.startsWith('%')) {
249
+ if (currentStatement) {
250
+ statements.push(currentStatement);
251
+ currentStatement = '';
252
+ }
253
+ statements.push(trimmed);
254
+ continue;
255
+ }
256
+
257
+ // Check if this line is a continuation (doesn't end with semicolon or closing bracket)
258
+ const endsStatement = endsWithStatement(trimmed);
259
+
260
+ if (currentStatement) {
261
+ currentStatement += ' ' + trimmed;
262
+ } else {
263
+ currentStatement = trimmed;
264
+ }
265
+
266
+ if (endsStatement) {
267
+ statements.push(currentStatement);
268
+ currentStatement = '';
269
+ }
270
+ }
271
+
272
+ if (currentStatement) {
273
+ statements.push(currentStatement);
274
+ }
275
+
276
+ return statements;
277
+ }
278
+
279
+ function formatStatement(statement, printWidth, tabSize) {
280
+ const trimmed = statement.trim();
281
+
282
+ // Don't format comment lines (they're already normalized)
283
+ if (trimmed.startsWith('%')) {
284
+ return trimmed;
285
+ }
286
+
287
+ // If statement is short enough, return as-is
288
+ if (trimmed.length <= printWidth) {
289
+ return trimmed;
290
+ }
291
+
292
+ // Check if statement contains brackets that need formatting
293
+ const bracketMatch = trimmed.match(/^(\w+)\s*\[(.+)\];?\s*$/);
294
+ if (bracketMatch) {
295
+ return formatBracketExpression(trimmed, printWidth, tabSize);
296
+ }
297
+
298
+ // Check if statement contains parentheses (function calls)
299
+ const parenMatch = trimmed.match(/^(.+?)\((.+)\);?\s*$/);
300
+ if (parenMatch) {
301
+ return formatParenExpression(trimmed, printWidth, tabSize);
302
+ }
303
+
304
+ return trimmed;
305
+ }
306
+
307
+ function formatBracketExpression(line, printWidth, tabSize) {
308
+ // Match the pattern: keyword [content]
309
+ const match = line.match(/^(\w+)\s*\[(.+)\];?\s*$/);
310
+
311
+ if (!match) {
312
+ return line;
313
+ }
314
+
315
+ const keyword = match[1];
316
+ const content = match[2];
317
+ const hasSemicolon = line.trimEnd().endsWith(';');
318
+
319
+ // Split content by commas
320
+ const elements = splitByComma(content);
321
+
322
+ // Calculate the length if we keep it on one line
323
+ const oneLine = `${keyword} [${content}]${hasSemicolon ? ';' : ''}`;
324
+
325
+ if (oneLine.length <= printWidth) {
326
+ return oneLine;
327
+ }
328
+
329
+ // Need to wrap - format with indentation
330
+ const indent = ' '.repeat(keyword.length + 2); // +2 for space and opening bracket
331
+ const formattedElements = [];
332
+
333
+ for (let i = 0; i < elements.length; i++) {
334
+ if (i === 0) {
335
+ formattedElements.push(elements[i]);
336
+ } else {
337
+ formattedElements.push(indent + elements[i]);
338
+ }
339
+ }
340
+
341
+ return `${keyword} [${formattedElements.join(',\n')}]${hasSemicolon ? ';' : ''}`;
342
+ }
343
+
344
+ function formatParenExpression(line, printWidth, tabSize) {
345
+ const hasSemicolon = line.trimEnd().endsWith(';');
346
+ const lineWithoutSemi = hasSemicolon ? line.slice(0, -1).trimEnd() : line;
347
+
348
+ // Find the outermost function call
349
+ let parenDepth = 0;
350
+ let firstOpenParen = -1;
351
+
352
+ for (let i = 0; i < lineWithoutSemi.length; i++) {
353
+ if (lineWithoutSemi[i] === '(') {
354
+ if (parenDepth === 0) {
355
+ firstOpenParen = i;
356
+ }
357
+ parenDepth++;
358
+ } else if (lineWithoutSemi[i] === ')') {
359
+ parenDepth--;
360
+ }
361
+ }
362
+
363
+ if (firstOpenParen === -1) {
364
+ return line;
365
+ }
366
+
367
+ const beforeParen = lineWithoutSemi.slice(0, firstOpenParen);
368
+ const afterParen = lineWithoutSemi.slice(firstOpenParen + 1);
369
+
370
+ // Remove trailing closing paren
371
+ const content = afterParen.endsWith(')') ? afterParen.slice(0, -1) : afterParen;
372
+
373
+ // Split content by commas at the top level
374
+ const elements = splitByComma(content);
375
+
376
+ // Calculate the length if we keep it on one line
377
+ const oneLine = `${beforeParen}(${content})${hasSemicolon ? ';' : ''}`;
378
+
379
+ if (oneLine.length <= printWidth) {
380
+ return oneLine;
381
+ }
382
+
383
+ // Need to wrap - format with indentation using tabSize
384
+ const indent = ' '.repeat(tabSize);
385
+ const formattedElements = [];
386
+
387
+ for (let i = 0; i < elements.length; i++) {
388
+ if (i === 0) {
389
+ formattedElements.push(elements[i]);
390
+ } else {
391
+ formattedElements.push(indent + elements[i]);
392
+ }
393
+ }
394
+
395
+ return `${beforeParen}(${formattedElements.join(',\n')})${hasSemicolon ? ';' : ''}`;
396
+ }
397
+
398
+ function splitByComma(str) {
399
+ const elements = [];
400
+ let current = '';
401
+ let inString = false;
402
+ let stringChar = null;
403
+ let depth = 0;
404
+
405
+ for (let i = 0; i < str.length; i++) {
406
+ const char = str[i];
407
+ const nextChar = i + 1 < str.length ? str[i + 1] : null;
408
+
409
+ // If we're at a backslash, include it and the next character without interpretation
410
+ if (char === '\\') {
411
+ current += char;
412
+ if (nextChar !== null) {
413
+ i++;
414
+ current += str[i];
415
+ }
416
+ continue;
417
+ }
418
+
419
+ // Track string boundaries (only if not escaped)
420
+ if (char === '"' || char === "'") {
421
+ if (!inString) {
422
+ inString = true;
423
+ stringChar = char;
424
+ } else if (char === stringChar) {
425
+ inString = false;
426
+ stringChar = null;
427
+ }
428
+ current += char;
429
+ continue;
430
+ }
431
+
432
+ // Track nested brackets/parens/braces
433
+ if (!inString) {
434
+ if (char === '[' || char === '(' || char === '{') depth++;
435
+ if (char === ']' || char === ')' || char === '}') depth--;
436
+ }
437
+
438
+ // Split on comma only if we're not in a string or nested structure
439
+ if (char === ',' && !inString && depth === 0) {
440
+ elements.push(current.trim());
441
+ current = '';
442
+ } else {
443
+ current += char;
444
+ }
445
+ }
446
+
447
+ // Add the last element
448
+ if (current.trim()) {
449
+ elements.push(current.trim());
450
+ }
451
+
452
+ return elements;
453
+ }
454
+
455
+ // Language definition
456
+ export const languages = [
457
+ {
458
+ name: 'MiniZinc',
459
+ parsers: [PLUGIN_NAME],
460
+ extensions: ['.mzn', '.dzn'],
461
+ vscodeLanguageIds: ['minizinc'],
462
+ },
463
+ ];
464
+
465
+ // Export plugin
466
+ export const parsers = {
467
+ [PLUGIN_NAME]: parser,
468
+ };
469
+
470
+ export const printers = {
471
+ [PLUGIN_NAME]: printer,
472
+ };
473
+
474
+ (globalThis.prettierPlugins ??= {})[PLUGIN_NAME] = { languages, parsers, printers };
@@ -0,0 +1,2 @@
1
+ generated by Claude Sonnet 4.5
2
+ https://poe.com/invite/3148b6067080461eaf6925fa86d19cb1