@monoes/monodesign 1.2.16 → 1.2.18

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.
@@ -43,12 +43,6 @@ const CDP_PORT_RELEASE_TIMEOUT_MS = process.env.CI ? 15_000 : 5_000;
43
43
  const CDP_PORT_RELEASE_POLL_MS = 25;
44
44
  const PROFILE_CLEANUP_RETRY_MS = 2_000;
45
45
 
46
- // Ports we launch headless Chrome on for detection runs. Deliberately away
47
- // from 9222 (the default `monomind browse` port): we must never attach to a
48
- // browser we did not launch, because browser.close() issues Browser.close.
49
- const MONOBROWSE_PORT_MIN = 9520;
50
- const MONOBROWSE_PORT_RANGE = 380;
51
-
52
46
  function serializeEvalArg(arg) {
53
47
  const json = JSON.stringify(arg);
54
48
  return json === undefined ? 'undefined' : json;
@@ -60,11 +54,21 @@ function mapWaitUntil(waitUntil) {
60
54
  return 'load';
61
55
  }
62
56
 
57
+ // This timer is deliberately NOT unref'd. Both call sites race it against CDP
58
+ // I/O that can die silently: goto() awaits a promise settled only by a
59
+ // Page.loadEventFired/domContentEventFired event, and close() races
60
+ // Browser.close — the very command that tears the websocket down. An unref'd
61
+ // timer never fires once nothing else pins the event loop, and a launched
62
+ // Chrome is detached and unref'd, so that websocket is the only ref'd handle
63
+ // here — exactly the one that goes away. Node would then consider the loop
64
+ // drained and exit 0 with the race still pending, instead of surfacing the
65
+ // timeout. The .finally() below clears it either way, so keeping it ref'd
66
+ // only ever costs a caller `ms`, never a hang. (Same rule as monobrowse's
67
+ // waitForProcessExit, #314.)
63
68
  function withTimeout(promise, ms, label) {
64
69
  let handle;
65
70
  const timeout = new Promise((_, reject) => {
66
71
  handle = setTimeout(() => reject(new Error(`Timeout waiting for ${label} after ${ms}ms`)), ms);
67
- handle.unref?.();
68
72
  });
69
73
  return Promise.race([promise, timeout]).finally(() => clearTimeout(handle));
70
74
  }
@@ -176,20 +180,16 @@ async function launchMonobrowseBrowser(options = {}) {
176
180
  const headless = options.headless ?? true;
177
181
  const launchArgs = options.launchArgs || (process.env.CI ? ['--no-sandbox', '--disable-setuid-sandbox'] : []);
178
182
 
183
+ // We must never attach to a browser we did not launch: browser.close()
184
+ // issues Browser.close. Picking a "free" port ourselves cannot guarantee
185
+ // that — the port is only probed, and a concurrent launch (node --test runs
186
+ // files in parallel) can pick the same one before either Chrome binds it.
187
+ // The losing Chrome then listens on [::1] instead and its launcher attaches
188
+ // to the winner's browser, which dies under it ("CDP connection closed")
189
+ // when the winner closes. Port 0 has Chrome take a kernel-assigned port and
190
+ // report it in its own profile dir, so the endpoint is always ours.
179
191
  const forcedPort = Number(process.env.MONODESIGN_MONOBROWSE_PORT);
180
- let port = null;
181
- if (Number.isInteger(forcedPort) && forcedPort > 0) {
182
- port = forcedPort;
183
- } else {
184
- // Pick a free-looking port; skip ports where a CDP endpoint already
185
- // listens so we never take over (and later Browser.close) someone
186
- // else's browser session.
187
- for (let attempt = 0; attempt < 5 && port === null; attempt++) {
188
- const candidate = MONOBROWSE_PORT_MIN + Math.floor(Math.random() * MONOBROWSE_PORT_RANGE);
189
- if (!(await mb.isPortOpen(candidate))) port = candidate;
190
- }
191
- if (port === null) throw new Error('Could not find a free CDP port for monobrowse');
192
- }
192
+ const port = Number.isInteger(forcedPort) && forcedPort > 0 ? forcedPort : 0;
193
193
 
194
194
  // CI runners (observed on the Windows GH Actions runner) are slower to get
195
195
  // Chrome's CDP port open than the 10s default — a batch of sequential
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monodesign",
3
- "version": "1.2.16",
3
+ "version": "1.2.18",
4
4
  "description": "Frontend design intelligence for AI agents — design tokens, antipattern detection, and the monodesign skill",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "optionalDependencies": {
47
47
  "puppeteer": "^25.1.0",
48
- "@monoes/monobrowse": "1.0.16"
48
+ "@monoes/monobrowse": "1.0.19"
49
49
  },
50
50
  "scripts": {
51
51
  "prebuild": "node ../../../scripts/build-fs.mjs clean dist tsconfig.tsbuildinfo",