@frontmcp/uipack 1.8.5 → 1.8.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.
@@ -469,8 +469,24 @@ function isFrontmcpUiResolvable(...candidatePaths) {
469
469
  }
470
470
 
471
471
  // libs/uipack/src/component/transpiler.ts
472
+ function loadEsbuild(filename) {
473
+ try {
474
+ return __require("esbuild");
475
+ } catch (err) {
476
+ if (!isMissingEsbuild(err)) throw err;
477
+ const subject = filename ? `FileSource widget "${filename}"` : "A FileSource widget";
478
+ throw new Error(
479
+ `${subject} needs esbuild, which is not installed. @frontmcp/uipack loads esbuild on demand to bundle FileSource widgets (.tsx/.jsx) when the tool is called, so it must be installed where the server runs: \`npm install esbuild\` (in "dependencies", not "devDependencies"). Projects created with \`frontmcp create\` already get it through the \`frontmcp\` package.`,
480
+ { cause: err }
481
+ );
482
+ }
483
+ }
484
+ function isMissingEsbuild(err) {
485
+ if (!(err instanceof Error)) return false;
486
+ return err.code === "MODULE_NOT_FOUND" && /Cannot find module ['"]esbuild['"]/.test(err.message);
487
+ }
472
488
  function transpileReactSource(source, filename) {
473
- const esbuild = __require("esbuild");
489
+ const esbuild = loadEsbuild(filename);
474
490
  const loader = filename?.endsWith(".tsx") ? "tsx" : "jsx";
475
491
  const result = esbuild.transformSync(source, {
476
492
  loader,
@@ -489,7 +505,7 @@ function bundleFileSource(source, filename, resolveDir, componentName, options =
489
505
  `FileSource widget "${filename}" requires the @frontmcp/ui package, which provides the React bridge mount that's injected at bundle time. Install it (e.g. \`npm install @frontmcp/ui\` or \`yarn add @frontmcp/ui\`) and try again.`
490
506
  );
491
507
  }
492
- const esbuild = __require("esbuild");
508
+ const esbuild = loadEsbuild(filename);
493
509
  const mountCode = `
494
510
  // --- Auto-generated mount ---
495
511
  import { createElement as __h } from 'react';
@@ -586,7 +602,8 @@ if (__root) {
586
602
  } catch (err) {
587
603
  const message = err instanceof Error ? err.message : String(err);
588
604
  throw new Error(
589
- `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`
605
+ `Failed to bundle FileSource "${filename}": ${message}. If the error mentions @frontmcp/ui or @frontmcp/uipack, ensure both packages are installed in the consuming project.`,
606
+ { cause: err }
590
607
  );
591
608
  }
592
609
  }
@@ -648,6 +665,37 @@ function resolveImportSource(source) {
648
665
  peerDependencies: []
649
666
  };
650
667
  }
668
+ var BUILD_DIR_SEGMENTS = /* @__PURE__ */ new Set(["dist", "build", "out"]);
669
+ function missingWidgetError(filePath, cause) {
670
+ const sourceWidget = findSourceWidget(filePath);
671
+ const error = new Error(
672
+ `FileSource widget not found (ENOENT): no file at "${filePath}". The widget source is read from this path when the tool is called, so it has to exist where the compiled tool runs \u2014 a path built from \`__dirname\` or \`import.meta.url\` points into the build output once the tool is compiled. tsc does not copy \`*.widget.tsx\` / \`*.widget.jsx\` files into its output; \`frontmcp build\` does, and any other build needs a step that copies them next to the compiled tool.` + (sourceWidget ? ` The source widget exists at "${sourceWidget}" \u2014 copy it to "${filePath}", or build with \`frontmcp build\`.` : ""),
673
+ { cause }
674
+ );
675
+ error.code = "ENOENT";
676
+ error.path = filePath;
677
+ return error;
678
+ }
679
+ function findSourceWidget(filePath) {
680
+ const path = __require("path");
681
+ const fs = __require("fs");
682
+ const parts = filePath.split(path.sep);
683
+ for (let i = parts.length - 2; i >= 0; i--) {
684
+ if (!BUILD_DIR_SEGMENTS.has(parts[i])) continue;
685
+ const candidates = [[...parts.slice(0, i), "src", ...parts.slice(i + 1)]];
686
+ if (parts.length - i > 2) candidates.push([...parts.slice(0, i), "src", ...parts.slice(i + 2)]);
687
+ candidates.push([...parts.slice(0, i), ...parts.slice(i + 1)]);
688
+ for (const candidate of candidates) {
689
+ const candidatePath = candidate.join(path.sep);
690
+ try {
691
+ if (fs.existsSync(candidatePath)) return candidatePath;
692
+ } catch {
693
+ }
694
+ }
695
+ return void 0;
696
+ }
697
+ return void 0;
698
+ }
651
699
  function resolveFileSource(source, options = {}) {
652
700
  const path = __require("path");
653
701
  const ext = path.extname(source.file).toLowerCase();
@@ -662,9 +710,11 @@ function resolveFileSource(source, options = {}) {
662
710
  const isNotFound = err?.code === "ENOENT";
663
711
  if (isNotFound && wasRelative) {
664
712
  throw new Error(
665
- `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`
713
+ `FileSource widget "${source.file}" not found at "${filePath}". Relative paths are resolved against process.cwd() ("${process.cwd()}"), not the tool file's directory. To anchor the path to the tool file, pass an absolute path \u2014 e.g. \`{ file: fileURLToPath(new URL('./widget.tsx', import.meta.url)) }\` from \`node:url\` (see issue #444).`,
714
+ { cause: err }
666
715
  );
667
716
  }
717
+ if (isNotFound) throw missingWidgetError(filePath, err);
668
718
  throw err;
669
719
  }
670
720
  const componentName = source.exportName || extractDefaultExportName(rawSource) || "Component";
@@ -882,6 +932,23 @@ function __applySizingCss(sizing) {
882
932
  }
883
933
  }
884
934
 
935
+ function __measureHeight() {
936
+ // <html> at fit-content includes body margins and collapsed child margins;
937
+ // content overflowing a fixed-height body is added; a px max-height clamps.
938
+ var de = document.documentElement;
939
+ var body = document.body;
940
+ if (!de || !body) return 0;
941
+ var prev = de.style.height;
942
+ de.style.height = 'fit-content';
943
+ var dr = de.getBoundingClientRect();
944
+ var br = body.getBoundingClientRect();
945
+ var h = Math.max(dr.height, br.top + body.scrollHeight + Math.max(0, dr.bottom - br.bottom) - dr.top);
946
+ de.style.height = prev;
947
+ var maxHeight = getComputedStyle(de).maxHeight;
948
+ var mx = maxHeight && maxHeight.slice(-2) === 'px' ? parseFloat(maxHeight) : NaN;
949
+ return Math.ceil(isNaN(mx) ? h : Math.min(h, mx));
950
+ }
951
+
885
952
  function __initAutoResize() {
886
953
  if (typeof window === 'undefined') return;
887
954
  // Idempotent: a re-injected IIFE must not stack observers.
@@ -904,23 +971,30 @@ function __initAutoResize() {
904
971
  if (typeof ResizeObserver === 'undefined') return;
905
972
 
906
973
  function startObserving() {
907
- var target = document.getElementById('root') || document.body;
908
- if (!target) return;
974
+ if (!document.body) return;
975
+ var targets = [document.documentElement, document.body];
976
+ var root = document.getElementById('root');
977
+ if (root) targets.push(root);
909
978
 
910
979
  var rafId = null;
911
980
  var lastReported = -1;
912
981
  function report() {
913
982
  rafId = null;
914
983
  try {
915
- var rect = target.getBoundingClientRect();
916
- var height = Math.ceil(rect.height);
917
- var width = Math.ceil(rect.width);
984
+ // Before the handshake an ext-apps host can't receive the report; the
985
+ // bridge:ready listener below sends it once the bridge is up.
986
+ if (window.FrontMcpBridge && window.FrontMcpBridge.initialized === false) return;
987
+ var height = __measureHeight();
988
+ var width = Math.ceil(document.documentElement.getBoundingClientRect().width);
918
989
  if (height === lastReported || height <= 0) return;
919
990
  lastReported = height;
920
991
  var payload = { height: height, width: width };
921
992
  if (sizing.aspectRatio != null) payload.aspectRatio = sizing.aspectRatio;
922
993
  if (window.FrontMcpBridge && typeof window.FrontMcpBridge.setSize === 'function') {
923
- window.FrontMcpBridge.setSize(payload).catch(function() {});
994
+ window.FrontMcpBridge.setSize(payload).catch(function() {
995
+ // Forget a rejected report so the same height is sent again.
996
+ if (lastReported === height) lastReported = -1;
997
+ });
924
998
  }
925
999
  window.dispatchEvent(new CustomEvent('widget:resize', { detail: payload }));
926
1000
  } catch (e) {}
@@ -941,9 +1015,17 @@ function __initAutoResize() {
941
1015
  window.__mcpResizeObserver.disconnect();
942
1016
  }
943
1017
  var ro = new ResizeObserver(function() { schedule(); });
944
- ro.observe(target);
1018
+ for (var i = 0; i < targets.length; i++) ro.observe(targets[i]);
945
1019
  window.__mcpResizeObserver = ro;
946
1020
  } catch (e) {}
1021
+ // Report once the bridge is ready (the report scheduled below is skipped
1022
+ // while the handshake is still pending).
1023
+ var readySent = false;
1024
+ window.addEventListener('bridge:ready', function() {
1025
+ if (readySent) return;
1026
+ readySent = true;
1027
+ schedule();
1028
+ });
947
1029
  // Report once on init so the host gets an initial measurement.
948
1030
  schedule();
949
1031
  }
@@ -991,12 +1073,57 @@ function detectViewport() {
991
1073
  return undefined;
992
1074
  }
993
1075
 
1076
+ function __applyHostTheme(theme) {
1077
+ // Follow a host-supplied theme: <meta name="color-scheme"> sets the frame's
1078
+ // canvas and form controls (a widget's own :root color-scheme still wins),
1079
+ // <html data-theme> is a styling hook. The OS fallback is never written.
1080
+ if (theme !== 'light' && theme !== 'dark') return;
1081
+ if (typeof document === 'undefined' || !document.documentElement) return;
1082
+ try {
1083
+ var meta = document.querySelector('meta[name="color-scheme"]');
1084
+ var head = document.head || document.getElementsByTagName('head')[0];
1085
+ if (!meta && head) {
1086
+ meta = document.createElement('meta');
1087
+ meta.setAttribute('name', 'color-scheme');
1088
+ head.appendChild(meta);
1089
+ }
1090
+ if (meta) meta.setAttribute('content', theme);
1091
+ document.documentElement.setAttribute('data-theme', theme);
1092
+ } catch (e) {}
1093
+ }
1094
+
1095
+ function extractToolOutput(params) {
1096
+ // A host sends a CallToolResult: prefer structuredContent, then parsed text, then the raw content.
1097
+ if (!params) return undefined;
1098
+ if (params.structuredContent !== undefined && params.structuredContent !== null) return params.structuredContent;
1099
+ var content = params.content;
1100
+ if (Array.isArray(content)) {
1101
+ for (var i = 0; i < content.length; i++) {
1102
+ var item = content[i];
1103
+ if (item && item.type === 'text' && typeof item.text === 'string') {
1104
+ try { return JSON.parse(item.text); } catch (e) { return item.text; }
1105
+ }
1106
+ }
1107
+ }
1108
+ return content;
1109
+ }
1110
+
994
1111
  function readInjectedData() {
995
1112
  var data = { toolInput: {}, toolOutput: undefined, structuredContent: undefined };
996
1113
  if (typeof window !== 'undefined') {
997
1114
  if (window.__mcpToolInput) data.toolInput = window.__mcpToolInput;
998
1115
  if (window.__mcpToolOutput) data.toolOutput = window.__mcpToolOutput;
999
1116
  if (window.__mcpStructuredContent) data.structuredContent = window.__mcpStructuredContent;
1117
+ // The OpenAI Apps SDK hands the widget its data as window.openai.toolInput / toolOutput
1118
+ // (toolOutput is the tool's structuredContent).
1119
+ var oa = window.openai;
1120
+ if (oa && typeof oa === 'object') {
1121
+ if (oa.toolInput && !window.__mcpToolInput) data.toolInput = oa.toolInput;
1122
+ if (oa.toolOutput !== undefined && oa.toolOutput !== null) {
1123
+ if (data.toolOutput === undefined) data.toolOutput = oa.toolOutput;
1124
+ if (data.structuredContent === undefined) data.structuredContent = oa.toolOutput;
1125
+ }
1126
+ }
1000
1127
  }
1001
1128
  return data;
1002
1129
  }
@@ -1045,7 +1172,24 @@ var OpenAIAdapter = {
1045
1172
  if (sdk.displayMode) {
1046
1173
  context.hostContext.displayMode = sdk.displayMode;
1047
1174
  }
1048
- // Note: OpenAI SDK does not have an onContextChange equivalent
1175
+ // The Apps SDK re-dispatches the changed globals in an 'openai:set_globals' event.
1176
+ window.addEventListener('openai:set_globals', function(event) {
1177
+ var globals = (event && event.detail && event.detail.globals) || window.openai || {};
1178
+ if (globals.toolInput !== undefined && globals.toolInput !== null) {
1179
+ context.toolInput = globals.toolInput;
1180
+ window.dispatchEvent(new CustomEvent('tool:input', { detail: { arguments: context.toolInput } }));
1181
+ }
1182
+ if (globals.toolOutput !== undefined && globals.toolOutput !== null) {
1183
+ context.structuredContent = globals.toolOutput;
1184
+ context.notifyToolResult(globals.toolOutput);
1185
+ }
1186
+ if (globals.theme && globals.theme !== context.hostContext.theme) {
1187
+ context.notifyContextChange({ theme: globals.theme });
1188
+ }
1189
+ if (globals.displayMode && globals.displayMode !== context.hostContext.displayMode) {
1190
+ context.notifyContextChange({ displayMode: globals.displayMode });
1191
+ }
1192
+ });
1049
1193
  return Promise.resolve();
1050
1194
  },
1051
1195
  callTool: function(context, name, args) {
@@ -1097,6 +1241,8 @@ var ExtAppsAdapter = {
1097
1241
  pendingRequests: {},
1098
1242
  requestId: 0,
1099
1243
  hostCapabilities: {},
1244
+ handshakeSettled: false,
1245
+ pendingSize: null,
1100
1246
  canHandle: function() {
1101
1247
  if (typeof window === 'undefined') return false;
1102
1248
  if (window.parent === window) return false;
@@ -1190,9 +1336,10 @@ var ExtAppsAdapter = {
1190
1336
  window.dispatchEvent(new CustomEvent('tool:input-partial', { detail: { arguments: context.toolInput } }));
1191
1337
  break;
1192
1338
  case 'ui/notifications/tool-result':
1193
- context.toolOutput = params.content;
1339
+ var resultOutput = extractToolOutput(params);
1340
+ context.toolOutput = resultOutput;
1194
1341
  context.structuredContent = params.structuredContent;
1195
- context.notifyToolResult(params.content);
1342
+ context.notifyToolResult(resultOutput);
1196
1343
  window.dispatchEvent(new CustomEvent('tool:result', { detail: params }));
1197
1344
  break;
1198
1345
  case 'ui/notifications/host-context-changed':
@@ -1246,6 +1393,15 @@ var ExtAppsAdapter = {
1246
1393
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
1247
1394
  });
1248
1395
  },
1396
+ sendNotification: function(method, params) {
1397
+ // JSON-RPC notification: no id, no response. Same origin rule as sendRequest.
1398
+ if (!this.trustedOrigin && this.trustedOrigins.length === 0) {
1399
+ return Promise.reject(new Error('Cannot send notification: no trusted origin established'));
1400
+ }
1401
+ var targetOrigin = this.trustedOrigin || this.trustedOrigins[0];
1402
+ window.parent.postMessage({ jsonrpc: '2.0', method: method, params: params }, targetOrigin);
1403
+ return Promise.resolve();
1404
+ },
1249
1405
  sendHandshake: function(context) {
1250
1406
  // Send ui/initialize using '*' as target origin since TOFU hasn't
1251
1407
  // been established yet. The response from the host will establish
@@ -1262,6 +1418,7 @@ var ExtAppsAdapter = {
1262
1418
  var timeout = setTimeout(function() {
1263
1419
  delete self.pendingRequests[id];
1264
1420
  // Handshake timeout is non-fatal \u2014 notifications may still arrive
1421
+ self.settleHandshake();
1265
1422
  resolve();
1266
1423
  }, 10000);
1267
1424
 
@@ -1275,7 +1432,9 @@ var ExtAppsAdapter = {
1275
1432
  supportsDisplayModes: true
1276
1433
  });
1277
1434
  if (result.hostContext) {
1278
- Object.assign(context.hostContext, result.hostContext);
1435
+ // Notify (not a silent merge) so onContextChange listeners and the
1436
+ // host theme see the handshake's context.
1437
+ context.notifyContextChange(result.hostContext);
1279
1438
  }
1280
1439
  // Send ui/notifications/initialized to tell the host the view is ready.
1281
1440
  // Per MCP Apps spec, the host waits for this before sending tool-result.
@@ -1285,9 +1444,10 @@ var ExtAppsAdapter = {
1285
1444
  method: 'ui/notifications/initialized',
1286
1445
  params: {}
1287
1446
  }, targetOrigin);
1447
+ self.settleHandshake();
1288
1448
  resolve();
1289
1449
  },
1290
- reject: function(err) { resolve(); }, // Non-fatal
1450
+ reject: function(err) { self.settleHandshake(); resolve(); }, // Non-fatal
1291
1451
  timeout: timeout
1292
1452
  };
1293
1453
 
@@ -1301,6 +1461,18 @@ var ExtAppsAdapter = {
1301
1461
  performHandshake: function(context) {
1302
1462
  return this.sendHandshake(context);
1303
1463
  },
1464
+ settleHandshake: function() {
1465
+ // Deliver the latest size requested while no host origin was trusted.
1466
+ this.handshakeSettled = true;
1467
+ var pending = this.pendingSize;
1468
+ if (!pending) return;
1469
+ this.pendingSize = null;
1470
+ this.sendNotification('ui/notifications/size-changed', pending.params).then(function(result) {
1471
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].resolve(result);
1472
+ }, function(err) {
1473
+ for (var i = 0; i < pending.waiters.length; i++) pending.waiters[i].reject(err);
1474
+ });
1475
+ },
1304
1476
  callTool: function(context, name, args) {
1305
1477
  if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
1306
1478
  return Promise.reject(new Error('Server tool proxy not supported'));
@@ -1322,13 +1494,22 @@ var ExtAppsAdapter = {
1322
1494
  return this.sendRequest('ui/setDisplayMode', { mode: mode });
1323
1495
  },
1324
1496
  setSize: function(context, size) {
1325
- // FrontMCP sizing channel \u2014 parallels ui/setDisplayMode. Reports the
1326
- // measured/desired widget dimensions to the host.
1327
- return this.sendRequest('ui/setSize', {
1328
- height: size && size.height,
1329
- width: size && size.width,
1330
- aspectRatio: size && size.aspectRatio
1331
- });
1497
+ // Standard ext-apps sizing: the view tells the host its dimensions with the
1498
+ // ui/notifications/size-changed notification ({ width?, height? } in px).
1499
+ var params = {};
1500
+ if (size && typeof size.width === 'number') params.width = size.width;
1501
+ if (size && typeof size.height === 'number') params.height = size.height;
1502
+ if (!this.handshakeSettled && !this.trustedOrigin && this.trustedOrigins.length === 0) {
1503
+ // No host origin yet: keep only the latest size and send it when the
1504
+ // handshake settles; every caller's promise follows that one request.
1505
+ var self = this;
1506
+ if (!this.pendingSize) this.pendingSize = { params: params, waiters: [] };
1507
+ this.pendingSize.params = params;
1508
+ return new Promise(function(resolve, reject) {
1509
+ self.pendingSize.waiters.push({ resolve: resolve, reject: reject });
1510
+ });
1511
+ }
1512
+ return this.sendNotification('ui/notifications/size-changed', params);
1332
1513
  },
1333
1514
  requestClose: function(context) {
1334
1515
  return this.sendRequest('ui/close', {});
@@ -1574,6 +1755,7 @@ function FrontMcpBridge() {
1574
1755
  toolResultListeners: [],
1575
1756
  notifyContextChange: function(changes) {
1576
1757
  Object.assign(this.hostContext, changes);
1758
+ if (changes && changes.theme) __applyHostTheme(changes.theme);
1577
1759
  for (var i = 0; i < this.contextListeners.length; i++) {
1578
1760
  try { this.contextListeners[i](changes); } catch(e) {}
1579
1761
  }
@@ -1750,7 +1932,7 @@ FrontMcpBridge.prototype.requestDisplayMode = function(mode) {
1750
1932
 
1751
1933
  // Report a desired widget size to the host. \`size\` is { height?, width?, aspectRatio? }.
1752
1934
  // Per-adapter behaviour: Claude/generic no-op (host measures the DOM),
1753
- // ext-apps sends ui/setSize, OpenAI forwards to its SDK when available.
1935
+ // ext-apps sends ui/notifications/size-changed, OpenAI forwards to its SDK when available.
1754
1936
  FrontMcpBridge.prototype.setSize = function(size) {
1755
1937
  if (!this._adapter) return Promise.reject(new Error('Not initialized'));
1756
1938
  if (!this._adapter.setSize) return Promise.resolve();
@@ -1852,7 +2034,7 @@ FrontMcpBridge.prototype._setupDataToolCallHandler = function() {
1852
2034
  target.classList.add('opacity-50', 'cursor-not-allowed');
1853
2035
 
1854
2036
  // Add spinner for buttons
1855
- var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
2037
+ var spinner = '<svg class="animate-spin -ml-1 mr-2 h-4 w-4 inline" style="width:1em;height:1em;vertical-align:-0.125em" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>';
1856
2038
  if (target.tagName === 'BUTTON') {
1857
2039
  target.innerHTML = spinner + 'Loading...';
1858
2040
  }
@@ -1933,7 +2115,22 @@ function minifyJS(code) {
1933
2115
  if (code.length > MAX_MINIFY_CODE_LENGTH) {
1934
2116
  return code;
1935
2117
  }
1936
- return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1").replace(/\s+/g, " ").replace(/\s*([{};,:()[\]])\s*/g, "$1").replace(/;\}/g, "}").trim();
2118
+ const out = [];
2119
+ let inBlockComment = false;
2120
+ for (const raw of code.split("\n")) {
2121
+ const line = raw.trim();
2122
+ if (inBlockComment) {
2123
+ if (line.includes("*/")) inBlockComment = false;
2124
+ continue;
2125
+ }
2126
+ if (line === "" || line.startsWith("//")) continue;
2127
+ if (line.startsWith("/*")) {
2128
+ if (!line.includes("*/")) inBlockComment = true;
2129
+ continue;
2130
+ }
2131
+ out.push(line);
2132
+ }
2133
+ return out.join("\n");
1937
2134
  }
1938
2135
  function generatePlatformBundle(platform, options = {}) {
1939
2136
  const platformAdapters = {