@seybel/breakhound 1.0.5 → 1.0.6

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 (3) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +189 -84
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -159,7 +159,7 @@ The folder is for you, not your repo, so it keeps itself out of git: breakhound
159
159
 
160
160
  ## Chrome extension
161
161
 
162
- The breakhound extension runs the same checks on the tab you have open, with nothing to install in your project. Get it from the [Chrome Web Store](https://chromewebstore.google.com/detail/ddnmhnjmcokbfabeileblceehjhefkpc); it works on any page Chrome can open, from localhost to production. The quickest way in is to right-click the page and choose **Scan with breakhound**: the breakhound panel opens in Chrome's side panel and starts right away. Clicking the breakhound icon in the toolbar opens it ready to scan, and the same panel is a **breakhound** tab in DevTools. The page grows steadily from 320px to 1920px (or the widths you set) while the timeline fills in live: each run of widths that share a layout, every width where the layout changes, and where something breaks. Then you get:
162
+ The breakhound extension runs the same checks on the tab you have open, with nothing to install in your project. Get it from the [Chrome Web Store](https://chromewebstore.google.com/detail/ddnmhnjmcokbfabeileblceehjhefkpc); it works on any page Chrome can open, from localhost to production. For a page opened from a file (`file://`), Chrome asks you to turn on **Allow access to file URLs** for breakhound once; the panel has a button that takes you there. The quickest way in is to right-click the page and choose **Scan with breakhound**: the breakhound panel opens in Chrome's side panel and starts right away. Clicking the breakhound icon in the toolbar opens it ready to scan, and the same panel is a **breakhound** tab in DevTools. The page grows steadily from 320px to 1920px (or the widths you set) while the timeline fills in live: each run of widths that share a layout, every width where the layout changes, and where something breaks. Then you get:
163
163
 
164
164
  - the issues, each with its exact width ranges, the element, the numbers and the likely fix
165
165
  - the timeline: the layout bands, the media query widths, the changes no media or container query explains, and a bar for each issue across the widths it covers
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { existsSync as existsSync16 } from "fs";
5
- import { join as join18, resolve as resolve7 } from "path";
5
+ import { join as join19, resolve as resolve7 } from "path";
6
6
  import { parseArgs, styleText as styleText9 } from "util";
7
7
 
8
8
  // ../engine/src/index.ts
@@ -2107,11 +2107,12 @@ async function printPdf(html, file) {
2107
2107
 
2108
2108
  // src/auth.ts
2109
2109
  import { existsSync as existsSync7, writeFileSync as writeFileSync2 } from "fs";
2110
- import { dirname as dirname4, join as join8 } from "path";
2110
+ import { dirname as dirname4, join as join9 } from "path";
2111
2111
  import { createInterface } from "readline";
2112
2112
  import { styleText as styleText3 } from "util";
2113
2113
 
2114
2114
  // src/app.ts
2115
+ import { join as join7 } from "path";
2115
2116
  import { styleText } from "util";
2116
2117
 
2117
2118
  // ../routes/src/ast.ts
@@ -2917,8 +2918,8 @@ function staticRoutes(project) {
2917
2918
  }
2918
2919
 
2919
2920
  // ../routes/src/project.ts
2920
- import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
2921
- import { dirname as dirname2, join as join5 } from "path";
2921
+ import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
2922
+ import { dirname as dirname2, join as join5, relative as relative3 } from "path";
2922
2923
  var FRAMEWORKS = {
2923
2924
  next: { name: "Next.js", routing: "files" },
2924
2925
  nuxt: { name: "Nuxt", routing: "code" },
@@ -3052,11 +3053,69 @@ function normalizeBasePath(path) {
3052
3053
  if (!trimmed) return "";
3053
3054
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
3054
3055
  }
3056
+ var RUNS_PACKAGES = /\b(turbo|nx|lerna|moon)\b|\bpnpm\b.*\s(-r|--recursive|--filter|-F)\b|\b(npm|yarn|bun)\b.*\s(--workspaces?|-w|--filter|workspaces?)\b/;
3057
+ function workspaceDirs(root, pkg) {
3058
+ const globs = Array.isArray(pkg.workspaces) ? [...pkg.workspaces] : [...pkg.workspaces?.packages ?? []];
3059
+ const pnpm = join5(root, "pnpm-workspace.yaml");
3060
+ if (existsSync4(pnpm)) {
3061
+ let inPackages = false;
3062
+ for (const line of readFileSync5(pnpm, "utf8").split(/\r?\n/)) {
3063
+ if (/^packages\s*:/.test(line)) inPackages = true;
3064
+ else if (/^\S/.test(line)) inPackages = false;
3065
+ else if (inPackages) {
3066
+ const item = line.match(/^\s*-\s*['"]?([^'"#]+?)['"]?\s*(?:#.*)?$/)?.[1];
3067
+ if (item) globs.push(item);
3068
+ }
3069
+ }
3070
+ }
3071
+ const expand = (dir, parts, depth = 0) => {
3072
+ const [part, ...rest2] = parts;
3073
+ if (part === void 0) return [dir];
3074
+ if (part === "." || part === "") return expand(dir, rest2, depth);
3075
+ if (!part.includes("*"))
3076
+ return existsSync4(join5(dir, part)) ? expand(join5(dir, part), rest2, depth) : [];
3077
+ let subdirs;
3078
+ try {
3079
+ subdirs = readdirSync2(dir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== "node_modules" && !d.name.startsWith(".")).map((d) => d.name);
3080
+ } catch {
3081
+ return [];
3082
+ }
3083
+ if (part === "**") {
3084
+ return [
3085
+ ...expand(dir, rest2, depth),
3086
+ ...depth < 3 ? subdirs.flatMap((d) => expand(join5(dir, d), parts, depth + 1)) : []
3087
+ ];
3088
+ }
3089
+ const match = new RegExp(
3090
+ `^${part.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*")}$`
3091
+ );
3092
+ return subdirs.filter((d) => match.test(d)).flatMap((d) => expand(join5(dir, d), rest2, depth));
3093
+ };
3094
+ const of = (globs2) => globs2.flatMap((g) => expand(root, g.replace(/\/+$/, "").split("/")));
3095
+ const left = new Set(of(globs.filter((g) => g.startsWith("!")).map((g) => g.slice(1))));
3096
+ return [...new Set(of(globs.filter((g) => !g.startsWith("!"))))].filter((d) => !left.has(d));
3097
+ }
3098
+ function workspaceApps(root, pkg) {
3099
+ return workspaceDirs(root, pkg).filter((dir) => {
3100
+ const app = readJson(join5(dir, "package.json"));
3101
+ if (!app) return false;
3102
+ const framework = detectFramework({ ...app.dependencies, ...app.devDependencies });
3103
+ const script = pickDevScript(app.scripts ?? {});
3104
+ return framework.id !== "unknown" && !!script && DEV_SERVER.test(script.command);
3105
+ });
3106
+ }
3055
3107
  function detectProject(cwd) {
3056
3108
  const pkg = readJson(join5(cwd, "package.json"));
3057
3109
  if (!pkg) return null;
3058
3110
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
3059
3111
  const framework = detectFramework(deps);
3112
+ const own = pickDevScript(pkg.scripts ?? {});
3113
+ const isApp = framework.id !== "unknown" && !!own && !RUNS_PACKAGES.test(own.command);
3114
+ const apps = isApp ? [] : workspaceApps(cwd, pkg);
3115
+ if (apps.length === 1) {
3116
+ const app = detectProject(apps[0]);
3117
+ if (app) return { ...app, workspace: relative3(cwd, apps[0]) };
3118
+ }
3060
3119
  const devScript = pickDevScript(pkg.scripts ?? {});
3061
3120
  const defaultPort = DEFAULT_PORT[framework.id];
3062
3121
  const configured = [...new Set(configuredPorts(cwd, devScript?.command))];
@@ -3068,7 +3127,8 @@ function detectProject(cwd) {
3068
3127
  devScript,
3069
3128
  ports: [...new Set(ports)],
3070
3129
  configuredPorts: configured,
3071
- basePath: detectBasePath(cwd)
3130
+ basePath: detectBasePath(cwd),
3131
+ ...apps.length > 1 ? { apps: apps.map((a) => relative3(cwd, a)) } : {}
3072
3132
  };
3073
3133
  }
3074
3134
 
@@ -3266,14 +3326,14 @@ var RouteQueue = class {
3266
3326
  function readRuntimeRoutes() {
3267
3327
  const w = window;
3268
3328
  const paths = /* @__PURE__ */ new Set();
3269
- const join19 = (base, path) => path.startsWith("/") ? path : `${base.replace(/\/?\*$/, "").replace(/\/+$/, "")}/${path}`;
3329
+ const join20 = (base, path) => path.startsWith("/") ? path : `${base.replace(/\/?\*$/, "").replace(/\/+$/, "")}/${path}`;
3270
3330
  const manifest = w.__reactRouterManifest?.routes ?? w.__remixManifest?.routes;
3271
3331
  if (manifest && typeof manifest === "object") {
3272
3332
  const fullPath = (id, depth = 0) => {
3273
3333
  const r = manifest[id];
3274
3334
  if (!r || depth > 50) return "";
3275
3335
  const parent = r.parentId ? fullPath(r.parentId, depth + 1) : "";
3276
- return r.path ? join19(parent, r.path) : parent;
3336
+ return r.path ? join20(parent, r.path) : parent;
3277
3337
  };
3278
3338
  for (const id of Object.keys(manifest)) {
3279
3339
  const r = manifest[id];
@@ -3320,7 +3380,7 @@ function readRuntimeRoutes() {
3320
3380
  if (e?.props?.children && !e.props.path) walkElements(e.props.children, base);
3321
3381
  continue;
3322
3382
  }
3323
- const full = typeof e.props.path === "string" ? join19(base, e.props.path) : base;
3383
+ const full = typeof e.props.path === "string" ? join20(base, e.props.path) : base;
3324
3384
  if (typeof e.props.path === "string") paths.add(full);
3325
3385
  else if (e.props.index) paths.add(indexPath2(base));
3326
3386
  walkElements(e.props.children, full);
@@ -3330,7 +3390,7 @@ function readRuntimeRoutes() {
3330
3390
  if (!Array.isArray(routes) || depth > 30) return;
3331
3391
  for (const r of routes) {
3332
3392
  if (!r || typeof r !== "object") continue;
3333
- const full = typeof r.path === "string" ? join19(base, r.path) : base;
3393
+ const full = typeof r.path === "string" ? join20(base, r.path) : base;
3334
3394
  if (typeof r.path === "string") paths.add(full);
3335
3395
  else if (r.index) paths.add(indexPath2(base));
3336
3396
  walkObjects(r.children, full, depth + 1);
@@ -3342,7 +3402,7 @@ function readRuntimeRoutes() {
3342
3402
  if (Array.isArray(matches) && matches.length) {
3343
3403
  let base = "";
3344
3404
  for (const m of matches)
3345
- if (typeof m.route?.path === "string") base = join19(base, m.route.path);
3405
+ if (typeof m.route?.path === "string") base = join20(base, m.route.path);
3346
3406
  return base;
3347
3407
  }
3348
3408
  }
@@ -3371,7 +3431,7 @@ function readRuntimeRoutes() {
3371
3431
  if (Array.isArray(matches)) {
3372
3432
  let base = "";
3373
3433
  for (const m of matches) {
3374
- if (typeof m?.route?.path === "string") base = join19(base, m.route.path);
3434
+ if (typeof m?.route?.path === "string") base = join20(base, m.route.path);
3375
3435
  if (Array.isArray(m?.route?.children)) {
3376
3436
  kind ??= "react-router";
3377
3437
  walkObjects(m.route.children, base);
@@ -3555,7 +3615,7 @@ async function startServer(project, options = {}) {
3555
3615
  }
3556
3616
  const timeoutMs = options.timeoutMs ?? 12e4;
3557
3617
  const child = spawn(command, {
3558
- cwd: project.root,
3618
+ cwd: options.cwd ?? project.root,
3559
3619
  shell: true,
3560
3620
  detached: process.platform !== "win32",
3561
3621
  stdio: ["ignore", "pipe", "pipe"],
@@ -3592,6 +3652,13 @@ async function startServer(project, options = {}) {
3592
3652
  let failingSince = null;
3593
3653
  while (Date.now() < deadline) {
3594
3654
  if (child.exitCode !== null) {
3655
+ const printed2 = printedUrl;
3656
+ const page = printed2 ? await probe(`${printed2}/`, 5e3) : null;
3657
+ if (printed2 && page && page.status < 500 && looksLikeProject(project, page, true)) {
3658
+ process.removeListener("exit", cleanup);
3659
+ return { url: printed2, alreadyRunning: true, stop: async () => {
3660
+ } };
3661
+ }
3595
3662
  throw new Error(
3596
3663
  `The dev server (${command}) stopped before it was ready:
3597
3664
  ${output.slice(-15).join("\n")}`
@@ -3946,9 +4013,34 @@ async function resolveServer(cwd, config, quiet) {
3946
4013
  "No package.json here, so there is no app to find. Run breakhound in your project folder, or pass a URL: npx @seybel/breakhound http://localhost:3000"
3947
4014
  );
3948
4015
  }
3949
- if (config.server?.port) {
3950
- project.ports = [config.server.port, ...project.ports.filter((p) => p !== config.server?.port)];
3951
- project.configuredPorts = [config.server.port, ...project.configuredPorts];
4016
+ const withPort = (p) => {
4017
+ const port = config.server?.port;
4018
+ if (!port) return p;
4019
+ p.ports = [port, ...p.ports.filter((q) => q !== port)];
4020
+ p.configuredPorts = [port, ...p.configuredPorts];
4021
+ return p;
4022
+ };
4023
+ withPort(project);
4024
+ if (project.workspace) log(quiet, styleText("dim", `Using the app in ${project.workspace}`));
4025
+ if (project.apps && !config.server?.command) {
4026
+ const found = await Promise.all(
4027
+ project.apps.map(async (dir2) => {
4028
+ const found2 = detectProject(join7(cwd, dir2));
4029
+ const app2 = found2 && withPort(found2);
4030
+ const running3 = app2 ? await findRunningServer(app2) : null;
4031
+ return app2 && running3 ? { dir: dir2, app: app2, running: running3 } : null;
4032
+ })
4033
+ );
4034
+ const live = found.filter((f) => f !== null);
4035
+ if (live.length !== 1 || !live[0]) {
4036
+ throw new Error(
4037
+ `This is a monorepo with ${project.apps.length} apps (${project.apps.join(", ")}). Run breakhound in the one to scan, e.g. cd ${project.apps[0]} && npx @seybel/breakhound, or start its dev server first.`
4038
+ );
4039
+ }
4040
+ const { dir, app, running: running2 } = live[0];
4041
+ const baseUrl2 = withBasePath(running2.url, app.basePath);
4042
+ log(quiet, `${styleText("green", "\u2714")} ${app.framework.name} in ${dir} running at ${baseUrl2}`);
4043
+ return { project: { ...app, workspace: dir }, baseUrl: baseUrl2, server: null };
3952
4044
  }
3953
4045
  const running = config.server?.command ? null : await findRunningServer(project);
3954
4046
  if (running) {
@@ -3961,7 +4053,8 @@ async function resolveServer(cwd, config, quiet) {
3961
4053
  log(quiet, styleText("dim", `Starting ${name ?? "the app"} with ${label}...`));
3962
4054
  let pid;
3963
4055
  const started = await startServer(project, {
3964
- ...command ? { command } : {},
4056
+ // A command from the config runs where breakhound was run, like the user would run it.
4057
+ ...command ? { command, cwd } : {},
3965
4058
  ...config.server?.port ? { port: config.server.port } : {},
3966
4059
  ...config.server?.timeout ? { timeoutMs: config.server.timeout * 1e3 } : {},
3967
4060
  onSpawn: (id) => {
@@ -3972,6 +4065,12 @@ async function resolveServer(cwd, config, quiet) {
3972
4065
  guard.untrackServer(pid);
3973
4066
  throw error;
3974
4067
  });
4068
+ if (started.alreadyRunning) {
4069
+ guard.untrackServer(pid);
4070
+ const baseUrl2 = withBasePath(started.url, project.basePath);
4071
+ log(quiet, `${styleText("green", "\u2714")} ${name ?? "App"} already running at ${baseUrl2}`);
4072
+ return { project, baseUrl: baseUrl2, server: null };
4073
+ }
3975
4074
  const server2 = {
3976
4075
  ...started,
3977
4076
  stop: async () => {
@@ -4058,7 +4157,7 @@ async function planRoutes(app, config, scanner, explicit) {
4058
4157
  import { spawnSync as spawnSync3 } from "child_process";
4059
4158
  import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
4060
4159
  import { createRequire as createRequire2 } from "module";
4061
- import { dirname as dirname3, join as join7 } from "path";
4160
+ import { dirname as dirname3, join as join8 } from "path";
4062
4161
  import { styleText as styleText2 } from "util";
4063
4162
  var DOWNLOAD_MB = { chromium: 150, webkit: 80, firefox: 90 };
4064
4163
  function installed(name) {
@@ -4086,7 +4185,7 @@ function playwrightCli() {
4086
4185
  const manifest = require2.resolve("playwright/package.json");
4087
4186
  const { bin } = JSON.parse(readFileSync8(manifest, "utf8"));
4088
4187
  const file = typeof bin === "string" ? bin : bin?.playwright ?? "cli.js";
4089
- return join7(dirname3(manifest), file);
4188
+ return join8(dirname3(manifest), file);
4090
4189
  }
4091
4190
  function installBrowsers(names, withDeps) {
4092
4191
  const cli = playwrightCli();
@@ -4117,7 +4216,7 @@ async function runLogin(cwd, config, options = {}) {
4117
4216
  };
4118
4217
  ensureBrowsers();
4119
4218
  const app = await resolveServer(cwd, config, options.quiet ?? false);
4120
- const file = join8(cwd, AUTH_FILE);
4219
+ const file = join9(cwd, AUTH_FILE);
4121
4220
  const browser = await launch(Boolean(automate));
4122
4221
  try {
4123
4222
  const context = await browser.newContext({
@@ -4243,7 +4342,7 @@ function copyToClipboard(text2) {
4243
4342
 
4244
4343
  // src/config.ts
4245
4344
  import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
4246
- import { join as join9, resolve as resolve2 } from "path";
4345
+ import { join as join10, resolve as resolve2 } from "path";
4247
4346
  import { fileURLToPath as fileURLToPath2 } from "url";
4248
4347
  var CONFIG_FILES = [
4249
4348
  "breakhound.config.ts",
@@ -4261,7 +4360,7 @@ function selfModule() {
4261
4360
  return "@seybel/breakhound";
4262
4361
  }
4263
4362
  async function loadConfig(cwd, explicit) {
4264
- const file = explicit ? resolve2(cwd, explicit) : CONFIG_FILES.map((f) => join9(cwd, f)).find((f) => existsSync8(f));
4363
+ const file = explicit ? resolve2(cwd, explicit) : CONFIG_FILES.map((f) => join10(cwd, f)).find((f) => existsSync8(f));
4265
4364
  if (!file) return { config: {}, file: null, warnings: [] };
4266
4365
  if (!existsSync8(file)) throw new Error(`Config file not found: ${file}`);
4267
4366
  let config;
@@ -4444,14 +4543,14 @@ function normalizeIgnore(ignore) {
4444
4543
  // src/fix.ts
4445
4544
  import { execFileSync } from "child_process";
4446
4545
  import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync3, writeFileSync as writeFileSync6 } from "fs";
4447
- import { join as join13, normalize as normalize2 } from "path";
4546
+ import { join as join14, normalize as normalize2 } from "path";
4448
4547
  import { styleText as styleText6 } from "util";
4449
4548
 
4450
4549
  // src/session.ts
4451
4550
  import { spawnSync as spawnSync5 } from "child_process";
4452
4551
  import { randomBytes } from "crypto";
4453
4552
  import { existsSync as existsSync9, readFileSync as readFileSync11, realpathSync, writeFileSync as writeFileSync3 } from "fs";
4454
- import { join as join10, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
4553
+ import { join as join11, relative as relative4, resolve as resolve3, sep as sep2 } from "path";
4455
4554
  var SESSION_FILE = "session.json";
4456
4555
  function git(cwd, args, input) {
4457
4556
  const run2 = spawnSync5("git", args, {
@@ -4484,7 +4583,7 @@ function fromRoot(root, cwd, path) {
4484
4583
  base = realpathSync(cwd);
4485
4584
  } catch {
4486
4585
  }
4487
- return relative3(root, resolve3(base, path)).split(sep2).join("/");
4586
+ return relative4(root, resolve3(base, path)).split(sep2).join("/");
4488
4587
  }
4489
4588
  function changedFiles(cwd, outDir = ".breakhound") {
4490
4589
  const root = gitRoot(cwd);
@@ -4503,7 +4602,7 @@ function changedFiles(cwd, outDir = ".breakhound") {
4503
4602
  return files.filter((f) => f !== skip && !f.startsWith(`${skip}/`));
4504
4603
  }
4505
4604
  function sessionPath(cwd, outDir) {
4506
- return join10(resolve3(cwd, outDir), SESSION_FILE);
4605
+ return join11(resolve3(cwd, outDir), SESSION_FILE);
4507
4606
  }
4508
4607
  function readSession(cwd, outDir = ".breakhound") {
4509
4608
  const file = sessionPath(cwd, outDir);
@@ -4710,11 +4809,11 @@ function undo(cwd, req) {
4710
4809
 
4711
4810
  // src/verify.ts
4712
4811
  import { existsSync as existsSync11, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
4713
- import { join as join12, resolve as resolve5 } from "path";
4812
+ import { join as join13, resolve as resolve5 } from "path";
4714
4813
 
4715
4814
  // src/scan.ts
4716
- import { existsSync as existsSync10, readdirSync as readdirSync2, readFileSync as readFileSync12, rmSync, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
4717
- import { isAbsolute, join as join11, relative as relative5, resolve as resolve4, sep as sep3 } from "path";
4815
+ import { existsSync as existsSync10, readdirSync as readdirSync3, readFileSync as readFileSync12, rmSync, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
4816
+ import { isAbsolute, join as join12, relative as relative6, resolve as resolve4, sep as sep3 } from "path";
4718
4817
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "url";
4719
4818
  import { styleText as styleText5 } from "util";
4720
4819
 
@@ -4872,7 +4971,7 @@ ${SETUP}`
4872
4971
  }
4873
4972
 
4874
4973
  // src/print.ts
4875
- import { relative as relative4 } from "path";
4974
+ import { relative as relative5 } from "path";
4876
4975
  import { styleText as styleText4 } from "util";
4877
4976
  var BADGE = {
4878
4977
  error: styleText4("red", "\u2716 error "),
@@ -4949,7 +5048,7 @@ function printIssues(result) {
4949
5048
  }
4950
5049
  function printFooter(paths, result, cwd) {
4951
5050
  const rel = (p) => {
4952
- const r = relative4(cwd, p);
5051
+ const r = relative5(cwd, p);
4953
5052
  return r.startsWith("..") ? p : r;
4954
5053
  };
4955
5054
  console.log("");
@@ -5134,7 +5233,7 @@ function routeOf(url, cwd) {
5134
5233
  const u = new URL(url);
5135
5234
  if (u.protocol === "file:") {
5136
5235
  const path = fileURLToPath3(u);
5137
- const rel = relative5(cwd, path);
5236
+ const rel = relative6(cwd, path);
5138
5237
  return rel.startsWith("..") ? path : rel;
5139
5238
  }
5140
5239
  return `${u.pathname}${u.search}` || "/";
@@ -5142,9 +5241,9 @@ function routeOf(url, cwd) {
5142
5241
  var SCREENSHOT_FILE = /^[0-9a-f]{10}\.png$/;
5143
5242
  function clearScreenshots(dir) {
5144
5243
  if (!existsSync10(dir)) return;
5145
- for (const name of readdirSync2(dir)) {
5146
- if (SCREENSHOT_FILE.test(name)) rmSync(join11(dir, name), { force: true });
5147
- else if (BROWSER_NAMES.includes(name)) clearScreenshots(join11(dir, name));
5244
+ for (const name of readdirSync3(dir)) {
5245
+ if (SCREENSHOT_FILE.test(name)) rmSync(join12(dir, name), { force: true });
5246
+ else if (BROWSER_NAMES.includes(name)) clearScreenshots(join12(dir, name));
5148
5247
  }
5149
5248
  }
5150
5249
  function resolveTargets(targets, config, cwd) {
@@ -5170,25 +5269,26 @@ function projectPath(file, root, basePath = "") {
5170
5269
  if (isAbsolute(path)) candidates.push(path);
5171
5270
  if (path.startsWith("/")) {
5172
5271
  const inApp = basePath && path.startsWith(`${basePath}/`) ? path.slice(basePath.length) : path;
5173
- candidates.push(join11(root, inApp));
5272
+ candidates.push(join12(root, inApp));
5174
5273
  } else {
5175
- candidates.push(join11(root, path));
5274
+ candidates.push(join12(root, path));
5176
5275
  }
5177
5276
  for (const candidate of candidates) {
5178
- const rel = relative5(root, candidate);
5277
+ const rel = relative6(root, candidate);
5179
5278
  if (!rel.startsWith("..") && !isAbsolute(rel) && isFile(candidate))
5180
5279
  return rel.split(sep3).join("/");
5181
5280
  }
5182
5281
  return null;
5183
5282
  }
5184
- function relativeSources(routes, root, basePath) {
5283
+ function relativeSources(routes, root, basePath, workspace = "") {
5284
+ const prefix = workspace ? `${workspace.split(sep3).join("/")}/` : "";
5185
5285
  for (const route of routes) {
5186
5286
  for (const issue of route.issues) {
5187
5287
  for (const element of [issue.element, issue.related]) {
5188
5288
  const source = element?.source;
5189
5289
  if (!element || !source) continue;
5190
5290
  const file = projectPath(source.file, root, basePath);
5191
- element.source = file ? { ...source, file } : null;
5291
+ element.source = file ? { ...source, file: `${prefix}${file}` } : null;
5192
5292
  }
5193
5293
  }
5194
5294
  }
@@ -5245,11 +5345,11 @@ async function scan(req) {
5245
5345
  const pageMode = jobs ? jobs.every((j) => !j.url.startsWith("http")) : req.targets.length > 0 && req.targets.every((t) => isPageTarget(t, cwd));
5246
5346
  const pageTargets = jobs && pageMode ? jobs : pageMode ? resolveTargets(req.targets, config, cwd) : [];
5247
5347
  const outDir = resolve4(cwd, config.outDir ?? ".breakhound");
5248
- const screenshotDir = join11(outDir, "screenshots");
5348
+ const screenshotDir = join12(outDir, "screenshots");
5249
5349
  const browsers = config.browsers?.length ? config.browsers : ["chromium"];
5250
5350
  const [firstBrowser = "chromium", ...otherBrowsers] = browsers;
5251
5351
  ensureBrowsers(browsers);
5252
- const authFile = join11(cwd, AUTH_FILE);
5352
+ const authFile = join12(cwd, AUTH_FILE);
5253
5353
  const auth = existsSync10(authFile) ? { storageState: authFile } : {};
5254
5354
  const scanner = new Scanner({ ...auth, browser: firstBrowser });
5255
5355
  const startedAt = /* @__PURE__ */ new Date();
@@ -5369,7 +5469,7 @@ async function scan(req) {
5369
5469
  const result2 = await other.scanPage(route.url, {
5370
5470
  ...baseOptions,
5371
5471
  route: route.route,
5372
- ...baseOptions.screenshotDir ? { screenshotDir: join11(baseOptions.screenshotDir, browser) } : {}
5472
+ ...baseOptions.screenshotDir ? { screenshotDir: join12(baseOptions.screenshotDir, browser) } : {}
5373
5473
  });
5374
5474
  for (const i of result2.issues)
5375
5475
  if (i.screenshot) i.screenshot = `${browser}/${i.screenshot}`;
@@ -5440,7 +5540,12 @@ async function scan(req) {
5440
5540
  if (!req.app) await app?.server?.stop();
5441
5541
  }
5442
5542
  applySeverity(routes, config);
5443
- relativeSources(routes, app?.project?.root ?? cwd, app?.project?.basePath ?? "");
5543
+ relativeSources(
5544
+ routes,
5545
+ app?.project?.root ?? cwd,
5546
+ app?.project?.basePath ?? "",
5547
+ app?.project?.workspace
5548
+ );
5444
5549
  const firstUrl = pageTargets[0]?.url;
5445
5550
  const baseUrl = app?.baseUrl ?? config.baseUrl ?? (firstUrl?.startsWith("http") ? new URL(firstUrl).origin : null);
5446
5551
  const framework = app?.project?.framework;
@@ -5458,7 +5563,7 @@ async function scan(req) {
5458
5563
  const kept = req.app ? app : null;
5459
5564
  if (req.write === false) return { result, reports: null, app: kept };
5460
5565
  const reports = await saveReports(result, config, cwd);
5461
- const baselineFile = join11(outDir, BASELINE_FILE);
5566
+ const baselineFile = join12(outDir, BASELINE_FILE);
5462
5567
  const earlier = !pageMode && req.targets.length > 0 && existsSync10(baselineFile) ? JSON.parse(readFileSync12(baselineFile, "utf8")) : null;
5463
5568
  const baseline = earlier && !earlier.from ? mergeScans(earlier, result) : result;
5464
5569
  writeFileSync4(baselineFile, `${JSON.stringify(baseline, null, 2)}
@@ -5479,7 +5584,7 @@ async function saveExtensionScan(sent, config, cwd) {
5479
5584
  issues: r.issues.filter((i) => off[i.detector] !== false)
5480
5585
  }));
5481
5586
  applySeverity(routes, config);
5482
- relativeSources(routes, project?.root ?? cwd, project?.basePath ?? "");
5587
+ relativeSources(routes, project?.root ?? cwd, project?.basePath ?? "", project?.workspace);
5483
5588
  const framework = project?.framework;
5484
5589
  const result = createScanResult(routes, {
5485
5590
  version: sent.tool.version,
@@ -5493,9 +5598,9 @@ async function saveExtensionScan(sent, config, cwd) {
5493
5598
  from: "extension"
5494
5599
  });
5495
5600
  const outDir = resolve4(cwd, config.outDir ?? ".breakhound");
5496
- clearScreenshots(join11(outDir, "screenshots"));
5601
+ clearScreenshots(join12(outDir, "screenshots"));
5497
5602
  await saveReports(result, config, cwd);
5498
- writeFileSync4(join11(outDir, BASELINE_FILE), `${JSON.stringify(result, null, 2)}
5603
+ writeFileSync4(join12(outDir, BASELINE_FILE), `${JSON.stringify(result, null, 2)}
5499
5604
  `);
5500
5605
  touchSession(cwd, config.outDir ?? ".breakhound");
5501
5606
  return result;
@@ -5503,14 +5608,14 @@ async function saveExtensionScan(sent, config, cwd) {
5503
5608
  function saveReports(result, config, cwd) {
5504
5609
  const outDir = resolve4(cwd, config.outDir ?? ".breakhound");
5505
5610
  return writeReports(result, outDir, {
5506
- screenshotDir: join11(outDir, "screenshots"),
5611
+ screenshotDir: join12(outDir, "screenshots"),
5507
5612
  pdf: config.pdf ?? false,
5508
5613
  verifyCommand: "npx @seybel/breakhound verify"
5509
5614
  });
5510
5615
  }
5511
5616
  function readLastResult(cwd, outDir = ".breakhound") {
5512
5617
  const dir = resolve4(cwd, outDir);
5513
- const file = join11(dir, "result.json");
5618
+ const file = join12(dir, "result.json");
5514
5619
  if (!existsSync10(file)) throw new Error("No scan yet. Run breakhound first.");
5515
5620
  return { result: JSON.parse(readFileSync12(file, "utf8")), dir };
5516
5621
  }
@@ -5537,7 +5642,7 @@ function mergeScans(earlier, now) {
5537
5642
  function readBaseline(cwd, outDir = ".breakhound") {
5538
5643
  const dir = resolve5(cwd, outDir);
5539
5644
  for (const name of [BASELINE_FILE, "result.json"]) {
5540
- const file = join12(dir, name);
5645
+ const file = join13(dir, name);
5541
5646
  if (existsSync11(file)) return JSON.parse(readFileSync13(file, "utf8"));
5542
5647
  }
5543
5648
  throw new Error("Nothing to verify against yet. Run breakhound first to scan the app.");
@@ -5676,7 +5781,7 @@ async function finishVerify(req, baseline, result) {
5676
5781
  const outcome = { baselineAt: baseline.startedAt, ...diff, current, reports };
5677
5782
  const dir = resolve5(req.cwd, req.config.outDir ?? ".breakhound");
5678
5783
  const { current: _, reports: __, ...saved } = outcome;
5679
- writeFileSync5(join12(dir, "verify.json"), `${JSON.stringify(saved, null, 2)}
5784
+ writeFileSync5(join13(dir, "verify.json"), `${JSON.stringify(saved, null, 2)}
5680
5785
  `);
5681
5786
  return outcome;
5682
5787
  }
@@ -5794,12 +5899,12 @@ function pickFiles(cwd, issue, tracked = trackedFiles(cwd)) {
5794
5899
  const terms = searchTerms(issue);
5795
5900
  const scores = /* @__PURE__ */ new Map();
5796
5901
  for (const source of [issue.element.source, issue.related?.source]) {
5797
- if (source?.file && existsSync12(join13(cwd, source.file))) {
5902
+ if (source?.file && existsSync12(join14(cwd, source.file))) {
5798
5903
  scores.set(source.file, (scores.get(source.file) ?? 0) + 100);
5799
5904
  }
5800
5905
  }
5801
5906
  for (const file of tracked) {
5802
- const full = join13(cwd, file);
5907
+ const full = join14(cwd, file);
5803
5908
  try {
5804
5909
  if (statSync3(full).size > MAX_SCAN_BYTES) continue;
5805
5910
  const content = readFileSync14(full, "utf8");
@@ -5808,7 +5913,7 @@ function pickFiles(cwd, issue, tracked = trackedFiles(cwd)) {
5808
5913
  } catch {
5809
5914
  }
5810
5915
  }
5811
- return [...scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_FILES3).map(([path]) => ({ path, content: excerpt(readFileSync14(join13(cwd, path), "utf8"), terms) }));
5916
+ return [...scores.entries()].sort((a, b) => b[1] - a[1]).slice(0, MAX_FILES3).map(([path]) => ({ path, content: excerpt(readFileSync14(join14(cwd, path), "utf8"), terms) }));
5812
5917
  }
5813
5918
  function parseEdits(reply) {
5814
5919
  const edits = [];
@@ -5855,7 +5960,7 @@ function applyEdits(cwd, edits, allowed) {
5855
5960
  const originals = /* @__PURE__ */ new Map();
5856
5961
  for (const edit of edits) {
5857
5962
  if (!allowed.has(edit.file)) return { error: `${edit.file} wasn't one of the files given.` };
5858
- const current = next.get(edit.file) ?? readFileSync14(join13(cwd, edit.file), "utf8");
5963
+ const current = next.get(edit.file) ?? readFileSync14(join14(cwd, edit.file), "utf8");
5859
5964
  if (!originals.has(edit.file)) originals.set(edit.file, current);
5860
5965
  const at = locate(current, edit.search);
5861
5966
  if (at === "many") {
@@ -5867,11 +5972,11 @@ function applyEdits(cwd, edits, allowed) {
5867
5972
  return { error: `The search text for ${edit.file} isn't in the file. Copy it exactly.` };
5868
5973
  next.set(edit.file, current.slice(0, at.start) + edit.replace + current.slice(at.end));
5869
5974
  }
5870
- for (const [file, content] of next) writeFileSync6(join13(cwd, file), content);
5975
+ for (const [file, content] of next) writeFileSync6(join14(cwd, file), content);
5871
5976
  return { originals };
5872
5977
  }
5873
5978
  function restore2(cwd, originals) {
5874
- for (const [file, content] of originals) writeFileSync6(join13(cwd, file), content);
5979
+ for (const [file, content] of originals) writeFileSync6(join14(cwd, file), content);
5875
5980
  }
5876
5981
  function promptFor(group, text2, files, feedback) {
5877
5982
  const parts = [text2, "", "Files:", ""];
@@ -6012,7 +6117,7 @@ async function fix(req) {
6012
6117
 
6013
6118
  // src/init.ts
6014
6119
  import { existsSync as existsSync13, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
6015
- import { join as join14 } from "path";
6120
+ import { join as join15 } from "path";
6016
6121
  import { styleText as styleText7 } from "util";
6017
6122
  var TEMPLATE = (baseUrl, typed) => `${typed ? "import { defineConfig } from '@seybel/breakhound';\n\nexport default defineConfig({" : "/** @type {import('@seybel/breakhound').BreakhoundConfig} */\nexport default {"}
6018
6123
  // breakhound finds your dev server, or starts it, by itself. Set this to
@@ -6049,7 +6154,7 @@ var JSON_TEMPLATE = `${JSON.stringify(
6049
6154
  `;
6050
6155
  function isInstalled(cwd) {
6051
6156
  try {
6052
- const pkg = JSON.parse(readFileSync15(join14(cwd, "package.json"), "utf8"));
6157
+ const pkg = JSON.parse(readFileSync15(join15(cwd, "package.json"), "utf8"));
6053
6158
  return Boolean(pkg.dependencies?.breakhound || pkg.devDependencies?.breakhound);
6054
6159
  } catch {
6055
6160
  return false;
@@ -6057,7 +6162,7 @@ function isInstalled(cwd) {
6057
6162
  }
6058
6163
  function guessBaseUrl(cwd) {
6059
6164
  try {
6060
- const pkg = JSON.parse(readFileSync15(join14(cwd, "package.json"), "utf8"));
6165
+ const pkg = JSON.parse(readFileSync15(join15(cwd, "package.json"), "utf8"));
6061
6166
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
6062
6167
  const dev = pkg.scripts?.dev ?? "";
6063
6168
  const port = dev.match(/(?:--port|-p)[ =](\d{2,5})/)?.[1];
@@ -6072,15 +6177,15 @@ function guessBaseUrl(cwd) {
6072
6177
  return "http://localhost:3000";
6073
6178
  }
6074
6179
  function runInit(cwd) {
6075
- const existing = ["ts", "mts", "js", "mjs", "cjs", "json"].map((ext) => `breakhound.config.${ext}`).find((f) => existsSync13(join14(cwd, f)));
6180
+ const existing = ["ts", "mts", "js", "mjs", "cjs", "json"].map((ext) => `breakhound.config.${ext}`).find((f) => existsSync13(join15(cwd, f)));
6076
6181
  if (existing) {
6077
6182
  console.log(`${existing} already exists. Nothing to do.`);
6078
6183
  return;
6079
6184
  }
6080
6185
  const installed2 = isInstalled(cwd);
6081
- const typed = existsSync13(join14(cwd, "tsconfig.json"));
6186
+ const typed = existsSync13(join15(cwd, "tsconfig.json"));
6082
6187
  const file = !installed2 ? "breakhound.config.json" : typed ? "breakhound.config.ts" : "breakhound.config.mjs";
6083
- writeFileSync7(join14(cwd, file), installed2 ? TEMPLATE(guessBaseUrl(cwd), typed) : JSON_TEMPLATE);
6188
+ writeFileSync7(join15(cwd, file), installed2 ? TEMPLATE(guessBaseUrl(cwd), typed) : JSON_TEMPLATE);
6084
6189
  console.log(`${styleText7("green", "\u2714")} Created ${file}`);
6085
6190
  if (!installed2) {
6086
6191
  console.log(
@@ -6097,7 +6202,7 @@ Now run ${styleText7("bold", "npx @seybel/breakhound")}.`);
6097
6202
 
6098
6203
  // src/mcp.ts
6099
6204
  import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
6100
- import { join as join16, resolve as resolve6 } from "path";
6205
+ import { join as join17, resolve as resolve6 } from "path";
6101
6206
  import { createInterface as createInterface2 } from "readline";
6102
6207
  import { fileURLToPath as fileURLToPath4 } from "url";
6103
6208
 
@@ -6105,7 +6210,7 @@ import { fileURLToPath as fileURLToPath4 } from "url";
6105
6210
  import { randomUUID } from "crypto";
6106
6211
  import { readFileSync as readFileSync16 } from "fs";
6107
6212
  import { createServer } from "http";
6108
- import { basename, join as join15 } from "path";
6213
+ import { basename, join as join16 } from "path";
6109
6214
  var EXTENSION_IDS = ["ddnmhnjmcokbfabeileblceehjhefkpc"];
6110
6215
  var POLL_MS = 25e3;
6111
6216
  var PICKUP_MS = 2e4;
@@ -6130,7 +6235,7 @@ function allowedOrigins() {
6130
6235
  }
6131
6236
  function projectName(root) {
6132
6237
  try {
6133
- const name = JSON.parse(readFileSync16(join15(root, "package.json"), "utf8")).name;
6238
+ const name = JSON.parse(readFileSync16(join16(root, "package.json"), "utf8")).name;
6134
6239
  if (typeof name === "string" && name) return name;
6135
6240
  } catch {
6136
6241
  }
@@ -6550,7 +6655,7 @@ function createTools() {
6550
6655
  true
6551
6656
  );
6552
6657
  const out = { content: [{ type: "text", text: found.text }] };
6553
- const shot = found.issue.screenshot && join16(dir, "screenshots", found.issue.screenshot);
6658
+ const shot = found.issue.screenshot && join17(dir, "screenshots", found.issue.screenshot);
6554
6659
  if (shot && existsSync14(shot)) {
6555
6660
  out.content.push({
6556
6661
  type: "image",
@@ -6683,7 +6788,7 @@ Run verify to see the issues back.`
6683
6788
  ];
6684
6789
  }
6685
6790
  function fixedDetails(ctx, routes) {
6686
- const file = join16(resolve6(ctx.root, ctx.config.outDir ?? ".breakhound"), "verify.json");
6791
+ const file = join17(resolve6(ctx.root, ctx.config.outDir ?? ".breakhound"), "verify.json");
6687
6792
  if (!existsSync14(file)) return [];
6688
6793
  try {
6689
6794
  const v = JSON.parse(readFileSync17(file, "utf8"));
@@ -6754,7 +6859,7 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6754
6859
  "package.json",
6755
6860
  "breakhound.config.json",
6756
6861
  "breakhound.config.ts"
6757
- ].some((f) => existsSync14(join16(cwd, f)));
6862
+ ].some((f) => existsSync14(join17(cwd, f)));
6758
6863
  if (!looksLikeProject2 && clientCanListRoots) {
6759
6864
  try {
6760
6865
  const res = await request("roots/list");
@@ -6901,7 +7006,7 @@ async function runMcpServer(version, io = { input: process.stdin, output: proces
6901
7006
  import { spawnSync as spawnSync6 } from "child_process";
6902
7007
  import { existsSync as existsSync15, mkdirSync as mkdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "fs";
6903
7008
  import { homedir } from "os";
6904
- import { dirname as dirname5, join as join17, relative as relative6 } from "path";
7009
+ import { dirname as dirname5, join as join18, relative as relative7 } from "path";
6905
7010
  import { createInterface as createInterface3 } from "readline/promises";
6906
7011
  import { styleText as styleText8 } from "util";
6907
7012
  var SERVER_NAME = "breakhound";
@@ -6967,12 +7072,12 @@ function onPath(command) {
6967
7072
  function allClients(cwd, home = homedir()) {
6968
7073
  const { command, args } = serverCommand();
6969
7074
  return [
6970
- jsonClient("claude", "Claude Code", join17(cwd, ".mcp.json"), "mcpServers", { command, args }),
6971
- jsonClient("cursor", "Cursor", join17(cwd, ".cursor", "mcp.json"), "mcpServers", {
7075
+ jsonClient("claude", "Claude Code", join18(cwd, ".mcp.json"), "mcpServers", { command, args }),
7076
+ jsonClient("cursor", "Cursor", join18(cwd, ".cursor", "mcp.json"), "mcpServers", {
6972
7077
  command,
6973
7078
  args
6974
7079
  }),
6975
- jsonClient("vscode", "VS Code", join17(cwd, ".vscode", "mcp.json"), "servers", {
7080
+ jsonClient("vscode", "VS Code", join18(cwd, ".vscode", "mcp.json"), "servers", {
6976
7081
  type: "stdio",
6977
7082
  command,
6978
7083
  args,
@@ -6984,7 +7089,7 @@ function allClients(cwd, home = homedir()) {
6984
7089
  jsonClient(
6985
7090
  "antigravity",
6986
7091
  "Antigravity",
6987
- join17(cwd, ".agents", "mcp_config.json"),
7092
+ join18(cwd, ".agents", "mcp_config.json"),
6988
7093
  "mcpServers",
6989
7094
  {
6990
7095
  command,
@@ -6994,26 +7099,26 @@ function allClients(cwd, home = homedir()) {
6994
7099
  jsonClient(
6995
7100
  "windsurf",
6996
7101
  "Windsurf",
6997
- join17(home, ".codeium", "windsurf", "mcp_config.json"),
7102
+ join18(home, ".codeium", "windsurf", "mcp_config.json"),
6998
7103
  "mcpServers",
6999
7104
  { command, args }
7000
7105
  ),
7001
- codexClient(join17(home, ".codex", "config.toml"))
7106
+ codexClient(join18(home, ".codex", "config.toml"))
7002
7107
  ];
7003
7108
  }
7004
7109
  function detectClients(cwd, home = homedir()) {
7005
7110
  const found = {
7006
- claude: existsSync15(join17(cwd, ".mcp.json")) || existsSync15(join17(cwd, ".claude")) || existsSync15(join17(cwd, "CLAUDE.md")) || existsSync15(join17(home, ".claude")) || onPath("claude"),
7007
- cursor: existsSync15(join17(cwd, ".cursor")) || existsSync15(join17(home, ".cursor")),
7008
- vscode: existsSync15(join17(cwd, ".vscode")) || onPath("code"),
7009
- antigravity: existsSync15(join17(cwd, ".agents", "mcp_config.json")) || existsSync15(join17(home, ".gemini", "config", "mcp_config.json")) || existsSync15(join17(home, ".gemini", "antigravity")) || onPath("antigravity"),
7010
- windsurf: existsSync15(join17(home, ".codeium", "windsurf")),
7011
- codex: existsSync15(join17(home, ".codex"))
7111
+ claude: existsSync15(join18(cwd, ".mcp.json")) || existsSync15(join18(cwd, ".claude")) || existsSync15(join18(cwd, "CLAUDE.md")) || existsSync15(join18(home, ".claude")) || onPath("claude"),
7112
+ cursor: existsSync15(join18(cwd, ".cursor")) || existsSync15(join18(home, ".cursor")),
7113
+ vscode: existsSync15(join18(cwd, ".vscode")) || onPath("code"),
7114
+ antigravity: existsSync15(join18(cwd, ".agents", "mcp_config.json")) || existsSync15(join18(home, ".gemini", "config", "mcp_config.json")) || existsSync15(join18(home, ".gemini", "antigravity")) || onPath("antigravity"),
7115
+ windsurf: existsSync15(join18(home, ".codeium", "windsurf")),
7116
+ codex: existsSync15(join18(home, ".codex"))
7012
7117
  };
7013
7118
  return allClients(cwd, home).filter((c) => found[c.id]);
7014
7119
  }
7015
7120
  function show(path, cwd) {
7016
- const rel = relative6(cwd, path);
7121
+ const rel = relative7(cwd, path);
7017
7122
  return rel.startsWith("..") ? path.replace(homedir(), "~") : rel;
7018
7123
  }
7019
7124
  async function installMcp(cwd, options = {}) {
@@ -7279,7 +7384,7 @@ async function main(argv) {
7279
7384
  }
7280
7385
  const outDir = values.out ?? fileConfig.outDir ?? ".breakhound";
7281
7386
  if (command === "report") {
7282
- const file = join18(resolve7(cwd, outDir), "report.html");
7387
+ const file = join19(resolve7(cwd, outDir), "report.html");
7283
7388
  if (!existsSync16(file)) throw new Error("No report yet. Run breakhound first.");
7284
7389
  openInDefaultApp(file);
7285
7390
  console.log(file);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seybel/breakhound",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Find where your layout breaks across every viewport width, then hand the fix to your coding agent.",
5
5
  "keywords": [
6
6
  "responsive",