open-ready 0.1.1 β†’ 0.1.2

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 (2) hide show
  1. package/open-when-ready.mjs +102 -33
  2. package/package.json +2 -1
@@ -3,46 +3,76 @@ import fsPromises from "fs/promises";
3
3
  import fs from "fs";
4
4
  import path from "path";
5
5
  import { spawn } from "child_process";
6
- import open from "open";
6
+ import opener from "opener";
7
7
  import minimist from "minimist";
8
+ import waitOn from "wait-on";
8
9
 
9
10
  const argv = minimist(process.argv.slice(2));
10
11
  const cmdArgs = argv._;
11
12
  const aiBase = argv["ai-base"] || "https://perplexity.ai?q=";
12
13
  const noAi = argv.noAi || argv.noai || argv.ai === false;
14
+ const noOpen = argv.noOpen || argv.noopen || false;
13
15
  const maxErrorContextChars = 1000;
16
+ const pollDelay = argv.pollDelay || 1200;
14
17
 
15
18
  const nextDir = path.join(".", ".next");
16
19
  const logPath = fs.existsSync(nextDir)
17
20
  ? path.join(".next", "port.log")
18
21
  : "open-when-ready.log";
19
22
 
20
- console.log(`πŸ“ Log: ${logPath}`);
23
+ console.log(`πŸ“ Log: ${logPath} | Poll: ${pollDelay}ms`);
21
24
 
25
+ /**
26
+ * Opens browser when dev server is ready.
27
+ *
28
+ * ### Features
29
+ * - **Automatic**: Detects server start
30
+ * - **Versatile**: Supports multiple browsers
31
+ *
32
+ * ```js
33
+ * console.log("Ready!");
34
+ * ```
35
+ */
22
36
  function getErrorContext(log) {
23
- console.log("πŸ” Log preview:", log.slice(0, 200));
24
37
  const lines = log.split(/\n/);
25
- const errorRegex = /β¨―|[Ss]yntax[Ee]rror|error/i;
26
- console.log("Regex:", errorRegex);
38
+ const errorRegex = /β¨―|[Ss]yntax[Ee]rror|error|failed|exception/i;
27
39
  for (let i = 0; i < lines.length; i++) {
28
40
  if (errorRegex.test(lines[i])) {
29
- console.log("βœ… MATCHED on line", i, ":", lines[i].slice(0, 100));
30
41
  const context = lines
31
42
  .slice(Math.max(0, i - 5), i + 16)
32
43
  .slice(0, 25)
33
44
  .join(" ")
34
- .replace(/[^\w\s:./\-]/g, "")
45
+ .replace(/[^\w\s:./\\-]/g, "")
35
46
  .trim()
36
47
  .replace(/\s{2,}/g, " ")
37
48
  .slice(0, maxErrorContextChars)
38
49
  .replace(/ /g, "%20");
39
- console.log("Context:", context.slice(0, 100));
40
50
  return context;
41
51
  }
42
52
  }
43
53
  return "";
44
54
  }
45
55
 
56
+ function extractUrl(log) {
57
+ // Better regex for Local: line
58
+ const localMatch = log.match(/Local:\s+(http:\/\/localhost:\d+)/i);
59
+ if (localMatch) {
60
+ console.log("βœ… EXTRACTED URL:", localMatch[1]);
61
+ return localMatch[1];
62
+ }
63
+
64
+ // Fallback port
65
+ const portMatch = log.match(/localhost:(\d+)/);
66
+ if (portMatch) {
67
+ const url = `http://localhost:${portMatch[1]}`;
68
+ console.log("βœ… FALLBACK URL:", url);
69
+ return url;
70
+ }
71
+
72
+ console.log("❌ NO URL FOUND");
73
+ return null;
74
+ }
75
+
46
76
  async function run() {
47
77
  try {
48
78
  await fsPromises.rm(logPath, { force: true });
@@ -52,6 +82,7 @@ async function run() {
52
82
  const proc = spawn(cmdArgs.join(" "), [], {
53
83
  stdio: ["ignore", "pipe", "pipe", "ipc"],
54
84
  shell: true,
85
+ detached: true,
55
86
  });
56
87
 
57
88
  const logStream = fs.createWriteStream(logPath);
@@ -61,44 +92,82 @@ async function run() {
61
92
  proc.stderr?.pipe(logStream);
62
93
 
63
94
  let opened = false;
64
- let pollCount = 0;
95
+ let errorOpened = false;
96
+ let lastReadySeen = false;
97
+ let lastLogSize = 0;
98
+ let stableCount = 0;
65
99
 
66
- // Aggressive 500ms poll + debug
67
100
  const poll = setInterval(async () => {
68
- pollCount++;
69
- console.log(`⏱️ Poll #${pollCount}`);
70
- if (opened) return;
71
101
  try {
72
- await sleep(100); // Brief settle
73
102
  const stats = fs.statSync(logPath);
74
- console.log("πŸ“Š Log size:", stats.size);
75
- if (stats.size > 100) {
76
- const log = await fsPromises.readFile(logPath, "utf8");
77
- const errorRegex = /β¨―|[Ss]yntax[Ee]rror|error/i;
78
- if (errorRegex.test(log) && !noAi) {
79
- console.log("🎯 ERROR TRIGGERED!");
80
- const err = getErrorContext(log);
81
- console.log("πŸ”— Opening:", `${aiBase}next.js+${err.slice(0, 50)}...`);
82
- await open(`${aiBase}next.js+${err}`);
83
- opened = true;
103
+ const currentLog = await fsPromises.readFile(logPath, "utf8");
104
+
105
+ // Stability check LAST - check ready first!
106
+ const logChanged = stats.size !== lastLogSize;
107
+ if (logChanged && stats.size > 100) {
108
+ stableCount = 0;
109
+ lastLogSize = stats.size;
110
+ console.log(
111
+ `πŸ“Š ${stats.size}B CHANGED | LOG:\n${currentLog.slice(0, 500)}...`,
112
+ );
113
+ } else if (stats.size > 100) {
114
+ stableCount++;
115
+ console.log(`πŸ“Š ${stats.size}B STABLE (${stableCount}/5)`);
116
+ if (stableCount >= 5) {
117
+ console.log("πŸ›‘ Log stable 5 polls - stopping");
84
118
  clearInterval(poll);
85
- proc.kill();
86
119
  return;
87
120
  }
88
121
  }
122
+
123
+ // Error first
124
+ const errorRegex = /β¨―|[Ss]yntax[Ee]rror|error|failed|exception/i;
125
+ if (errorRegex.test(currentLog) && !noAi && !errorOpened) {
126
+ console.log("🎯 ERROR!");
127
+ const err = getErrorContext(currentLog);
128
+ const customPrompt = "Explain what the error is, how to fix it, then give a shell script with the best recommended solution.";
129
+ const prompt = encodeURIComponent(customPrompt + " ");
130
+ opener(`${aiBase}${prompt}next.js+${err}`);
131
+ errorOpened = true;
132
+ return;
133
+ }
134
+
135
+ // Ready check BEFORE stability stops us - with transition guard
136
+ const readyRegex = /(?:ready[\s-]*started server|Ready in \d+ms)/i;
137
+ const isReadyNow = readyRegex.test(currentLog);
138
+ if (isReadyNow && !lastReadySeen && !opened) {
139
+ opened = true;
140
+ lastReadySeen = true;
141
+ console.log("πŸŽ‰ READY DETECTED!");
142
+ const url = extractUrl(currentLog);
143
+ if (url && !noOpen) {
144
+ console.log("🌐 Testing", url);
145
+ try {
146
+ await waitOn(url, { timeout: 10000, http: true });
147
+ console.log("βœ… HTTP READY!");
148
+ } catch (e) {
149
+ console.log(
150
+ "⚠ HTTP not ready, but opening:",
151
+ e.message.slice(0, 50),
152
+ );
153
+ }
154
+ console.log("πŸš€ OPENING BROWSER:", url);
155
+ opener(url);
156
+ }
157
+ clearInterval(poll);
158
+ return;
159
+ }
160
+ lastReadySeen = isReadyNow;
89
161
  } catch (e) {
90
162
  console.log("Poll error:", e.message);
91
163
  }
92
- }, 500);
164
+ }, pollDelay);
93
165
 
94
- setTimeout(() => {
166
+ process.on("SIGINT", () => {
167
+ console.log("\nπŸ‘‹ Exiting (dev server alive)");
95
168
  clearInterval(poll);
96
- proc.kill();
97
- }, 30000);
98
- }
99
-
100
- function sleep(ms) {
101
- return new Promise((r) => setTimeout(r, ms));
169
+ process.exit(0);
170
+ });
102
171
  }
103
172
 
104
173
  run().catch(console.error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-ready",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Smart dev server launcher: errorβ†’AI search, successβ†’auto-open browser. Any CLI tool.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "dependencies": {
22
22
  "minimist": "^1.2.8",
23
23
  "open": "^10.1.0",
24
+ "opener": "^1.5.2",
24
25
  "wait-on": "^8.0.1"
25
26
  },
26
27
  "engines": {