next-leak 0.11.1 → 0.11.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.
@@ -92815,14 +92815,36 @@ function explainRuntimeFailure(stderr, maxOldSpaceMb) {
92815
92815
  }
92816
92816
  return `the measured process exited mid-run. ${explainStartupFailure(stderr)}`;
92817
92817
  }
92818
- function appNeverListened(hostname, port, budgetMs, stderr) {
92819
- const head = `app on ${hostname}:${port} \u2014 the process started and answered on its control channel, but never listened within ${Math.round(budgetMs / 1e3)}s`;
92818
+ function appNeverListened(hostname, port, budgetMs, stderr, lastFailure = void 0) {
92819
+ const refused = lastFailure === void 0 ? "" : ` (${lastFailure})`;
92820
+ const head = `app on ${hostname}:${port} \u2014 the process started and answered on its control channel, but never accepted a connection${refused} within ${Math.round(budgetMs / 1e3)}s`;
92820
92821
  if (stderr.trim() !== "") {
92821
92822
  return `${head}. It wrote this while starting:
92822
92823
  ${stderr.trim()}`;
92823
92824
  }
92824
92825
  return `${head}, and wrote nothing to stderr. An app that boots slower than that needs a larger budget: --ready-timeout <seconds>. Otherwise it is hanging during startup \u2014 starting the same server.js by hand, with PORT set, hangs the same way and can be interrupted to see where`;
92825
92826
  }
92827
+ var NOT_LISTENING_CODES = /* @__PURE__ */ new Set([
92828
+ "ECONNREFUSED",
92829
+ "EHOSTUNREACH",
92830
+ "ENETUNREACH",
92831
+ "ENOTFOUND"
92832
+ ]);
92833
+ function probeFailure(cause) {
92834
+ const inner = cause?.cause;
92835
+ const code = inner?.code;
92836
+ if (typeof code === "string") {
92837
+ return code;
92838
+ }
92839
+ const message = inner?.message;
92840
+ if (typeof message === "string" && message !== "") {
92841
+ return message;
92842
+ }
92843
+ return cause instanceof Error ? cause.message : String(cause);
92844
+ }
92845
+ function meansNotListening(failure) {
92846
+ return NOT_LISTENING_CODES.has(failure);
92847
+ }
92826
92848
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
92827
92849
  async function pollUntil(deadline, describe, probe, failed) {
92828
92850
  for (; ; ) {
@@ -92916,15 +92938,20 @@ ${stderrTailBuffer}`;
92916
92938
  },
92917
92939
  failed
92918
92940
  );
92941
+ let lastFailure;
92919
92942
  await pollUntil(
92920
92943
  Date.now() + readyTimeoutMs,
92921
- () => appNeverListened(hostname, options.appPort, readyTimeoutMs, stderrWindow()),
92944
+ () => appNeverListened(hostname, options.appPort, readyTimeoutMs, stderrWindow(), lastFailure),
92922
92945
  async () => {
92946
+ const url = `http://${hostname}:${options.appPort}${options.readyPath ?? "/"}`;
92923
92947
  try {
92924
- await fetch(`http://${hostname}:${options.appPort}/`, { method: "HEAD" });
92948
+ const response = await fetch(url, { method: "GET", redirect: "manual" });
92949
+ await response.body?.cancel();
92925
92950
  return true;
92926
- } catch {
92927
- return void 0;
92951
+ } catch (cause) {
92952
+ const failure = probeFailure(cause);
92953
+ lastFailure = failure;
92954
+ return meansNotListening(failure) ? void 0 : true;
92928
92955
  }
92929
92956
  },
92930
92957
  failed
@@ -93909,6 +93936,10 @@ async function runRitual(options, deps = defaultDeps) {
93909
93936
  serverPath: options.serverPath,
93910
93937
  workDir: options.workDir,
93911
93938
  bootstrapPath: options.bootstrapPath,
93939
+ // Wait on the route this ritual is about to measure. `/` is a different
93940
+ // page with different failure modes, and readiness judged on it withdrew
93941
+ // routes that were serving fine (#74).
93942
+ readyPath: options.route,
93912
93943
  ...options.maxOldSpaceMb !== void 0 && { maxOldSpaceMb: options.maxOldSpaceMb },
93913
93944
  ...options.readyTimeoutMs !== void 0 && { readyTimeoutMs: options.readyTimeoutMs }
93914
93945
  };
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ import {
22
22
  runSelfCheck,
23
23
  unregisterChild,
24
24
  validateTarget
25
- } from "./chunk-672J2BQO.js";
25
+ } from "./chunk-QNYUT2XF.js";
26
26
  import {
27
27
  classifyTrend
28
28
  } from "./chunk-4FYSLLSX.js";
package/dist/index.js CHANGED
@@ -41,7 +41,7 @@ import {
41
41
  sourceIndexAt,
42
42
  summarizeBaseline,
43
43
  validateTarget
44
- } from "./chunk-672J2BQO.js";
44
+ } from "./chunk-QNYUT2XF.js";
45
45
  import {
46
46
  renderHtmlReport
47
47
  } from "./chunk-YLHE4N5G.js";
@@ -30,6 +30,13 @@ export type LaunchOptions = {
30
30
  /** Path to the built bootstrap module loaded with `--import`. */
31
31
  bootstrapPath: string;
32
32
  hostname?: string;
33
+ /**
34
+ * Path the readiness probe asks for. Defaults to `/`, but a run should pass
35
+ * the route it is about to measure: an app can serve that route perfectly
36
+ * and still fail on `/` — an i18n redirect, an auth wall, a rewrite — and
37
+ * the wait would then be judging a page nobody asked about (#74).
38
+ */
39
+ readyPath?: string;
33
40
  maxOldSpaceMb?: number;
34
41
  /**
35
42
  * Budget for each of the two waits below, not for the pair. Default:
@@ -103,7 +110,15 @@ export declare function explainRuntimeFailure(stderr: string, maxOldSpaceMb: num
103
110
  * there, which reads like the tool failed to connect to something that was
104
111
  * running. The process is up: what did not happen is the listen.
105
112
  */
106
- export declare function appNeverListened(hostname: string, port: number, budgetMs: number, stderr: string): string;
113
+ export declare function appNeverListened(hostname: string, port: number, budgetMs: number, stderr: string, lastFailure?: string | undefined): string;
114
+ /**
115
+ * `fetch` rejects with a bare `TypeError: fetch failed`; what happened is one
116
+ * level down, and not always as a `code` — a redirect loop arrives as a
117
+ * message with none.
118
+ */
119
+ export declare function probeFailure(cause: unknown): string;
120
+ /** Whether a probe failure means the port is not open yet. */
121
+ export declare function meansNotListening(failure: string): boolean;
107
122
  /**
108
123
  * Spawns the measured server in a fresh child process with GC exposed and the
109
124
  * control-channel bootstrap preloaded, and waits until both the app port and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-leak",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "description": "Find out whether your Next.js app actually leaks memory — how much, on which route, and whose fault it is.",
5
5
  "keywords": [
6
6
  "nextjs",