leglas 0.7.3 → 0.7.4

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.
package/dist/bin.js CHANGED
@@ -2745,6 +2745,84 @@ async function startAppProcess(options) {
2745
2745
  throw new Error(`${options.label} did not start within ${Math.round(readyTimeoutMs / 1e3)}s. Check that its dev command serves the port it is given.`);
2746
2746
  }
2747
2747
 
2748
+ // ../server/dist/branches.js
2749
+ function publicBranchState(state) {
2750
+ if (state.status === "ready")
2751
+ return { status: "ready" };
2752
+ return state;
2753
+ }
2754
+ function createBranchRegistry(options) {
2755
+ const states = new Map(options.previews.map((preview) => [preview.title, { status: "idle" }]));
2756
+ const previews = new Map(options.previews.map((preview) => [preview.title, preview]));
2757
+ const inflight = /* @__PURE__ */ new Map();
2758
+ const boot = options.startWorktree ?? startWorktree;
2759
+ let closed = false;
2760
+ let stopPromise = null;
2761
+ const transition = (title, state) => {
2762
+ const previous = states.get(title);
2763
+ if (previous?.status === "starting" && state.status === "starting" && previous.phase === state.phase) {
2764
+ return previous;
2765
+ }
2766
+ states.set(title, state);
2767
+ options.onChange?.(title, state);
2768
+ return state;
2769
+ };
2770
+ const begin = (title) => {
2771
+ const preview = previews.get(title);
2772
+ const current = states.get(title);
2773
+ if (preview === void 0 || current === void 0 || closed)
2774
+ return void 0;
2775
+ if (current.status === "starting")
2776
+ return inflight.get(title);
2777
+ if (current.status === "ready")
2778
+ return Promise.resolve(current);
2779
+ transition(title, { status: "starting", phase: "checking out" });
2780
+ let checkout;
2781
+ try {
2782
+ checkout = Promise.resolve(boot({
2783
+ cwd: options.cwd,
2784
+ branch: preview.branch,
2785
+ installCommand: options.installCommand,
2786
+ devCommand: options.devCommand ?? "",
2787
+ onLog: (line) => {
2788
+ transition(title, {
2789
+ status: "starting",
2790
+ phase: line.startsWith("installing ") ? "installing" : "starting"
2791
+ });
2792
+ }
2793
+ }));
2794
+ } catch (error) {
2795
+ checkout = Promise.reject(error);
2796
+ }
2797
+ const starting = checkout.then((worktree) => {
2798
+ transition(title, { status: "starting", phase: "starting" });
2799
+ return transition(title, { status: "ready", worktree });
2800
+ }).catch((error) => transition(title, {
2801
+ status: "failed",
2802
+ reason: error instanceof Error ? error.message : String(error)
2803
+ })).finally(() => {
2804
+ inflight.delete(title);
2805
+ });
2806
+ inflight.set(title, starting);
2807
+ return starting;
2808
+ };
2809
+ return {
2810
+ state: (title) => states.get(title),
2811
+ start: begin,
2812
+ stop: () => {
2813
+ if (stopPromise !== null)
2814
+ return stopPromise;
2815
+ closed = true;
2816
+ stopPromise = Promise.allSettled([...inflight.values()]).then(async () => {
2817
+ const worktrees = [...states.values()].filter((state) => state.status === "ready").map((state) => state.worktree);
2818
+ await Promise.all(worktrees.map((worktree) => worktree.stop().catch(() => {
2819
+ })));
2820
+ });
2821
+ return stopPromise;
2822
+ }
2823
+ };
2824
+ }
2825
+
2748
2826
  // ../server/dist/failure.js
2749
2827
  var NEEDS_TRUST = /not inside a trusted directory|--skip-git-repo-check/i;
2750
2828
  var MISSING_BINARY = /\b(ENOENT|EACCES|ENOTDIR)\b/;
@@ -5292,6 +5370,32 @@ async function startServer(options) {
5292
5370
  const { config, configErrors = [], configWarnings = [], shellDir = null, project = "", cwd = process.cwd(), leglasCommand = "npx -y leglas", fileMounts = /* @__PURE__ */ new Map(), detect = () => detectAgents() } = options;
5293
5371
  const browserPool = options.pool ?? createBrowserPool();
5294
5372
  const live = options.live ?? createLiveHub();
5373
+ const branches = createBranchRegistry({
5374
+ cwd,
5375
+ previews: (config?.previews ?? []).flatMap((preview) => preview.branch === void 0 ? [] : [{ title: preview.title, branch: preview.branch }]),
5376
+ installCommand: config?.installCommand ?? DEFAULT_INSTALL_COMMAND,
5377
+ devCommand: config?.devCommand,
5378
+ onChange: () => live.nudge("config"),
5379
+ ...options.startWorktree === void 0 ? {} : { startWorktree: options.startWorktree }
5380
+ });
5381
+ const previewForConfig = (preview) => {
5382
+ if (preview.branch === void 0)
5383
+ return preview;
5384
+ const state = branches.state(preview.title) ?? { status: "idle" };
5385
+ const { url: route, ...withoutUrl } = preview;
5386
+ return state.status === "ready" ? {
5387
+ ...withoutUrl,
5388
+ url: `${state.worktree.url}${route}`,
5389
+ state: publicBranchState(state)
5390
+ } : { ...withoutUrl, state: publicBranchState(state) };
5391
+ };
5392
+ const previewsForConfig = (previews) => previews.map(previewForConfig);
5393
+ const readyPreview = (preview) => {
5394
+ if (preview.branch === void 0)
5395
+ return preview;
5396
+ const state = branches.state(preview.title);
5397
+ return state?.status === "ready" ? { ...preview, url: `${state.worktree.url}${preview.url}` } : null;
5398
+ };
5295
5399
  if (options.pool === void 0) {
5296
5400
  void reapOrphanedBrowsers().catch(() => {
5297
5401
  });
@@ -5325,7 +5429,7 @@ async function startServer(options) {
5325
5429
  }
5326
5430
  return Promise.resolve(agentsCache.agents);
5327
5431
  };
5328
- const livePreviews = async () => {
5432
+ const livePreviewDefinitions = async () => {
5329
5433
  const localRead = await readLocalPreviews(cwd).catch(() => null);
5330
5434
  const local = localRead?.errors.length === 0 ? localRead.previews : [];
5331
5435
  const localTitles = new Set(local.map((entry) => entry.title));
@@ -5335,6 +5439,7 @@ async function startServer(options) {
5335
5439
  const fresh = local.filter((entry) => !known.has(entry.title) && entry.branch === void 0 && entry.file === void 0);
5336
5440
  return [...boot, ...fresh];
5337
5441
  };
5442
+ const livePreviews = async () => (await livePreviewDefinitions()).map(readyPreview).filter((preview) => preview !== null);
5338
5443
  void probeAgents().catch(() => {
5339
5444
  });
5340
5445
  const server = http2.createServer((req, res) => {
@@ -5356,7 +5461,7 @@ async function startServer(options) {
5356
5461
  project,
5357
5462
  devServer: target,
5358
5463
  scanPreviews: config?.scanPreviews ?? true,
5359
- previews: boot,
5464
+ previews: previewsForConfig(boot),
5360
5465
  errors,
5361
5466
  warnings: configWarnings
5362
5467
  });
@@ -5369,7 +5474,7 @@ async function startServer(options) {
5369
5474
  project,
5370
5475
  devServer: target,
5371
5476
  scanPreviews: config?.scanPreviews ?? true,
5372
- previews: [...currentBoot, ...fresh],
5477
+ previews: previewsForConfig([...currentBoot, ...fresh]),
5373
5478
  errors,
5374
5479
  warnings: configWarnings
5375
5480
  });
@@ -5377,11 +5482,46 @@ async function startServer(options) {
5377
5482
  project,
5378
5483
  devServer: target,
5379
5484
  scanPreviews: config?.scanPreviews ?? true,
5380
- previews: boot,
5485
+ previews: previewsForConfig(boot),
5381
5486
  errors,
5382
5487
  warnings: configWarnings
5383
5488
  }));
5384
5489
  }
5490
+ if (path === `${LEGLAS_PREFIX}/api/previews/start` && req.method === "POST") {
5491
+ let body = "";
5492
+ req.on("data", (chunk) => body += chunk);
5493
+ return void req.on("end", async () => {
5494
+ const parsed2 = jsonBody(body);
5495
+ if (parsed2 === null) {
5496
+ return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
5497
+ }
5498
+ if (typeof parsed2.title !== "string" || parsed2.title.trim() === "") {
5499
+ return sendJson(res, 400, { ok: false, error: "Body needs a direction title." });
5500
+ }
5501
+ const preview = (await livePreviewDefinitions()).find((entry) => entry.title === parsed2.title);
5502
+ if (preview === void 0) {
5503
+ return sendJson(res, 404, { ok: false, error: "No such direction." });
5504
+ }
5505
+ if (preview.branch === void 0) {
5506
+ return sendJson(res, 400, {
5507
+ ok: false,
5508
+ error: `"${preview.title}" is not a branch preview.`
5509
+ });
5510
+ }
5511
+ if (config?.devCommand === void 0) {
5512
+ return sendJson(res, 400, {
5513
+ ok: false,
5514
+ error: `"${preview.title}" cannot start because the config sets no devCommand.`
5515
+ });
5516
+ }
5517
+ void branches.start(preview.title);
5518
+ const state = branches.state(preview.title);
5519
+ if (state === void 0) {
5520
+ return sendJson(res, 404, { ok: false, error: "No such branch preview." });
5521
+ }
5522
+ return sendJson(res, 200, { ok: true, state: publicBranchState(state) });
5523
+ });
5524
+ }
5385
5525
  if (path === `${LEGLAS_PREFIX}/api/previews/delete` && req.method === "POST") {
5386
5526
  let body = "";
5387
5527
  req.on("data", (chunk) => body += chunk);
@@ -6076,7 +6216,12 @@ async function startServer(options) {
6076
6216
  return closePromise;
6077
6217
  liveFiles.close();
6078
6218
  liveHealth.close();
6079
- closePromise = Promise.all([runner.stop(), browserPool.close(), live.close()]).then(() => new Promise((done) => {
6219
+ closePromise = Promise.all([
6220
+ branches.stop(),
6221
+ runner.stop(),
6222
+ browserPool.close(),
6223
+ live.close()
6224
+ ]).then(() => new Promise((done) => {
6080
6225
  for (const socket of sockets)
6081
6226
  socket.destroy();
6082
6227
  sockets.clear();
@@ -7377,8 +7522,7 @@ async function run3(options, deps) {
7377
7522
  const local = await readLocalPreviews(options.cwd);
7378
7523
  let devServer = options.userPort === void 0 ? loaded.config?.devServer ?? "http://localhost:3000" : `http://localhost:${options.userPort}`;
7379
7524
  const merged = loaded.config === null ? null : { ...loaded.config, devServer, previews: [...loaded.config.previews, ...local.previews] };
7380
- const worktrees = [];
7381
- const worktreeErrors = [];
7525
+ const previewErrors = [];
7382
7526
  const previews = [];
7383
7527
  let app = null;
7384
7528
  const needsApp = (merged?.previews ?? []).some(
@@ -7395,7 +7539,7 @@ async function run3(options, deps) {
7395
7539
  devServer = app.url;
7396
7540
  merged.devServer = app.url;
7397
7541
  } catch (error) {
7398
- worktreeErrors.push(error instanceof Error ? error.message : String(error));
7542
+ previewErrors.push(error instanceof Error ? error.message : String(error));
7399
7543
  }
7400
7544
  }
7401
7545
  const fileMounts = /* @__PURE__ */ new Map();
@@ -7403,7 +7547,7 @@ async function run3(options, deps) {
7403
7547
  if (preview.file !== void 0) {
7404
7548
  const absolute = join19(options.cwd, preview.file);
7405
7549
  if (!existsSync6(absolute)) {
7406
- worktreeErrors.push(
7550
+ previewErrors.push(
7407
7551
  `"${preview.title}" names file ${preview.file}, which does not exist. The preview is skipped.`
7408
7552
  );
7409
7553
  continue;
@@ -7419,29 +7563,7 @@ async function run3(options, deps) {
7419
7563
  });
7420
7564
  continue;
7421
7565
  }
7422
- if (preview.branch === void 0) {
7423
- previews.push(preview);
7424
- continue;
7425
- }
7426
- if (merged?.devCommand === void 0) {
7427
- worktreeErrors.push(
7428
- `"${preview.title}" names branch ${preview.branch}, but the config sets no devCommand, so Leglas cannot start that checkout. Add devCommand (with {port}) to the config.`
7429
- );
7430
- continue;
7431
- }
7432
- if (!options.json) deps.log(` starting ${preview.branch}\u2026`);
7433
- try {
7434
- const worktree = await startWorktree({
7435
- cwd: options.cwd,
7436
- branch: preview.branch,
7437
- installCommand: merged.installCommand,
7438
- devCommand: merged.devCommand
7439
- });
7440
- worktrees.push(worktree);
7441
- previews.push({ ...preview, url: `${worktree.url}${preview.url}` });
7442
- } catch (error) {
7443
- worktreeErrors.push(error instanceof Error ? error.message : String(error));
7444
- }
7566
+ previews.push(preview);
7445
7567
  }
7446
7568
  const config = merged === null ? null : { ...merged, previews };
7447
7569
  const configWarnings = [];
@@ -7451,7 +7573,7 @@ async function run3(options, deps) {
7451
7573
  const ownerWarning = needsApp && app === null ? inspectLocalDevServer(devServer).then((owners) => devServerOwnerWarning(devServer, projectRoot, owners)).catch(() => null) : Promise.resolve(null);
7452
7574
  const serverPromise = startServer({
7453
7575
  config,
7454
- configErrors: [...loaded.errors, ...local.errors, ...worktreeErrors],
7576
+ configErrors: [...loaded.errors, ...local.errors, ...previewErrors],
7455
7577
  configWarnings,
7456
7578
  fileMounts,
7457
7579
  shellDir: findShellDir(),
@@ -7490,9 +7612,9 @@ async function run3(options, deps) {
7490
7612
  );
7491
7613
  deps.log(`config ${configLabel}`);
7492
7614
  deps.log(` ${previewCount} preview${previewCount === 1 ? "" : "s"}`);
7493
- if (loaded.errors.length + worktreeErrors.length > 0) {
7615
+ if (loaded.errors.length + previewErrors.length > 0) {
7494
7616
  deps.log("");
7495
- for (const error of [...loaded.errors, ...worktreeErrors]) deps.log(` ! ${error}`);
7617
+ for (const error of [...loaded.errors, ...previewErrors]) deps.log(` ! ${error}`);
7496
7618
  deps.log(" Fix the config and reload; Leglas will pick it up on restart.");
7497
7619
  }
7498
7620
  if (configWarnings.length > 0) {
@@ -7515,8 +7637,6 @@ async function run3(options, deps) {
7515
7637
  devServer,
7516
7638
  previewCount,
7517
7639
  stop: async () => {
7518
- await Promise.all(worktrees.map((worktree) => worktree.stop().catch(() => {
7519
- })));
7520
7640
  await app?.stop().catch(() => {
7521
7641
  });
7522
7642
  await server.close();
package/dist/index.js CHANGED
@@ -3121,6 +3121,84 @@ async function startAppProcess(options) {
3121
3121
  throw new Error(`${options.label} did not start within ${Math.round(readyTimeoutMs / 1e3)}s. Check that its dev command serves the port it is given.`);
3122
3122
  }
3123
3123
 
3124
+ // ../server/dist/branches.js
3125
+ function publicBranchState(state) {
3126
+ if (state.status === "ready")
3127
+ return { status: "ready" };
3128
+ return state;
3129
+ }
3130
+ function createBranchRegistry(options) {
3131
+ const states = new Map(options.previews.map((preview) => [preview.title, { status: "idle" }]));
3132
+ const previews = new Map(options.previews.map((preview) => [preview.title, preview]));
3133
+ const inflight = /* @__PURE__ */ new Map();
3134
+ const boot = options.startWorktree ?? startWorktree;
3135
+ let closed = false;
3136
+ let stopPromise = null;
3137
+ const transition = (title, state) => {
3138
+ const previous = states.get(title);
3139
+ if (previous?.status === "starting" && state.status === "starting" && previous.phase === state.phase) {
3140
+ return previous;
3141
+ }
3142
+ states.set(title, state);
3143
+ options.onChange?.(title, state);
3144
+ return state;
3145
+ };
3146
+ const begin = (title) => {
3147
+ const preview = previews.get(title);
3148
+ const current = states.get(title);
3149
+ if (preview === void 0 || current === void 0 || closed)
3150
+ return void 0;
3151
+ if (current.status === "starting")
3152
+ return inflight.get(title);
3153
+ if (current.status === "ready")
3154
+ return Promise.resolve(current);
3155
+ transition(title, { status: "starting", phase: "checking out" });
3156
+ let checkout;
3157
+ try {
3158
+ checkout = Promise.resolve(boot({
3159
+ cwd: options.cwd,
3160
+ branch: preview.branch,
3161
+ installCommand: options.installCommand,
3162
+ devCommand: options.devCommand ?? "",
3163
+ onLog: (line) => {
3164
+ transition(title, {
3165
+ status: "starting",
3166
+ phase: line.startsWith("installing ") ? "installing" : "starting"
3167
+ });
3168
+ }
3169
+ }));
3170
+ } catch (error) {
3171
+ checkout = Promise.reject(error);
3172
+ }
3173
+ const starting = checkout.then((worktree) => {
3174
+ transition(title, { status: "starting", phase: "starting" });
3175
+ return transition(title, { status: "ready", worktree });
3176
+ }).catch((error) => transition(title, {
3177
+ status: "failed",
3178
+ reason: error instanceof Error ? error.message : String(error)
3179
+ })).finally(() => {
3180
+ inflight.delete(title);
3181
+ });
3182
+ inflight.set(title, starting);
3183
+ return starting;
3184
+ };
3185
+ return {
3186
+ state: (title) => states.get(title),
3187
+ start: begin,
3188
+ stop: () => {
3189
+ if (stopPromise !== null)
3190
+ return stopPromise;
3191
+ closed = true;
3192
+ stopPromise = Promise.allSettled([...inflight.values()]).then(async () => {
3193
+ const worktrees = [...states.values()].filter((state) => state.status === "ready").map((state) => state.worktree);
3194
+ await Promise.all(worktrees.map((worktree) => worktree.stop().catch(() => {
3195
+ })));
3196
+ });
3197
+ return stopPromise;
3198
+ }
3199
+ };
3200
+ }
3201
+
3124
3202
  // ../server/dist/failure.js
3125
3203
  var NEEDS_TRUST = /not inside a trusted directory|--skip-git-repo-check/i;
3126
3204
  var MISSING_BINARY = /\b(ENOENT|EACCES|ENOTDIR)\b/;
@@ -5668,6 +5746,32 @@ async function startServer(options) {
5668
5746
  const { config, configErrors = [], configWarnings = [], shellDir = null, project = "", cwd = process.cwd(), leglasCommand = "npx -y leglas", fileMounts = /* @__PURE__ */ new Map(), detect = () => detectAgents() } = options;
5669
5747
  const browserPool = options.pool ?? createBrowserPool();
5670
5748
  const live = options.live ?? createLiveHub();
5749
+ const branches = createBranchRegistry({
5750
+ cwd,
5751
+ previews: (config?.previews ?? []).flatMap((preview) => preview.branch === void 0 ? [] : [{ title: preview.title, branch: preview.branch }]),
5752
+ installCommand: config?.installCommand ?? DEFAULT_INSTALL_COMMAND,
5753
+ devCommand: config?.devCommand,
5754
+ onChange: () => live.nudge("config"),
5755
+ ...options.startWorktree === void 0 ? {} : { startWorktree: options.startWorktree }
5756
+ });
5757
+ const previewForConfig = (preview) => {
5758
+ if (preview.branch === void 0)
5759
+ return preview;
5760
+ const state = branches.state(preview.title) ?? { status: "idle" };
5761
+ const { url: route, ...withoutUrl } = preview;
5762
+ return state.status === "ready" ? {
5763
+ ...withoutUrl,
5764
+ url: `${state.worktree.url}${route}`,
5765
+ state: publicBranchState(state)
5766
+ } : { ...withoutUrl, state: publicBranchState(state) };
5767
+ };
5768
+ const previewsForConfig = (previews) => previews.map(previewForConfig);
5769
+ const readyPreview = (preview) => {
5770
+ if (preview.branch === void 0)
5771
+ return preview;
5772
+ const state = branches.state(preview.title);
5773
+ return state?.status === "ready" ? { ...preview, url: `${state.worktree.url}${preview.url}` } : null;
5774
+ };
5671
5775
  if (options.pool === void 0) {
5672
5776
  void reapOrphanedBrowsers().catch(() => {
5673
5777
  });
@@ -5701,7 +5805,7 @@ async function startServer(options) {
5701
5805
  }
5702
5806
  return Promise.resolve(agentsCache.agents);
5703
5807
  };
5704
- const livePreviews = async () => {
5808
+ const livePreviewDefinitions = async () => {
5705
5809
  const localRead = await readLocalPreviews(cwd).catch(() => null);
5706
5810
  const local = localRead?.errors.length === 0 ? localRead.previews : [];
5707
5811
  const localTitles = new Set(local.map((entry) => entry.title));
@@ -5711,6 +5815,7 @@ async function startServer(options) {
5711
5815
  const fresh = local.filter((entry) => !known.has(entry.title) && entry.branch === void 0 && entry.file === void 0);
5712
5816
  return [...boot, ...fresh];
5713
5817
  };
5818
+ const livePreviews = async () => (await livePreviewDefinitions()).map(readyPreview).filter((preview) => preview !== null);
5714
5819
  void probeAgents().catch(() => {
5715
5820
  });
5716
5821
  const server = http2.createServer((req, res) => {
@@ -5732,7 +5837,7 @@ async function startServer(options) {
5732
5837
  project,
5733
5838
  devServer: target,
5734
5839
  scanPreviews: config?.scanPreviews ?? true,
5735
- previews: boot,
5840
+ previews: previewsForConfig(boot),
5736
5841
  errors,
5737
5842
  warnings: configWarnings
5738
5843
  });
@@ -5745,7 +5850,7 @@ async function startServer(options) {
5745
5850
  project,
5746
5851
  devServer: target,
5747
5852
  scanPreviews: config?.scanPreviews ?? true,
5748
- previews: [...currentBoot, ...fresh],
5853
+ previews: previewsForConfig([...currentBoot, ...fresh]),
5749
5854
  errors,
5750
5855
  warnings: configWarnings
5751
5856
  });
@@ -5753,11 +5858,46 @@ async function startServer(options) {
5753
5858
  project,
5754
5859
  devServer: target,
5755
5860
  scanPreviews: config?.scanPreviews ?? true,
5756
- previews: boot,
5861
+ previews: previewsForConfig(boot),
5757
5862
  errors,
5758
5863
  warnings: configWarnings
5759
5864
  }));
5760
5865
  }
5866
+ if (path === `${LEGLAS_PREFIX}/api/previews/start` && req.method === "POST") {
5867
+ let body = "";
5868
+ req.on("data", (chunk) => body += chunk);
5869
+ return void req.on("end", async () => {
5870
+ const parsed = jsonBody(body);
5871
+ if (parsed === null) {
5872
+ return sendJson(res, 400, { ok: false, error: "Body must be JSON." });
5873
+ }
5874
+ if (typeof parsed.title !== "string" || parsed.title.trim() === "") {
5875
+ return sendJson(res, 400, { ok: false, error: "Body needs a direction title." });
5876
+ }
5877
+ const preview = (await livePreviewDefinitions()).find((entry) => entry.title === parsed.title);
5878
+ if (preview === void 0) {
5879
+ return sendJson(res, 404, { ok: false, error: "No such direction." });
5880
+ }
5881
+ if (preview.branch === void 0) {
5882
+ return sendJson(res, 400, {
5883
+ ok: false,
5884
+ error: `"${preview.title}" is not a branch preview.`
5885
+ });
5886
+ }
5887
+ if (config?.devCommand === void 0) {
5888
+ return sendJson(res, 400, {
5889
+ ok: false,
5890
+ error: `"${preview.title}" cannot start because the config sets no devCommand.`
5891
+ });
5892
+ }
5893
+ void branches.start(preview.title);
5894
+ const state = branches.state(preview.title);
5895
+ if (state === void 0) {
5896
+ return sendJson(res, 404, { ok: false, error: "No such branch preview." });
5897
+ }
5898
+ return sendJson(res, 200, { ok: true, state: publicBranchState(state) });
5899
+ });
5900
+ }
5761
5901
  if (path === `${LEGLAS_PREFIX}/api/previews/delete` && req.method === "POST") {
5762
5902
  let body = "";
5763
5903
  req.on("data", (chunk) => body += chunk);
@@ -6452,7 +6592,12 @@ async function startServer(options) {
6452
6592
  return closePromise;
6453
6593
  liveFiles.close();
6454
6594
  liveHealth.close();
6455
- closePromise = Promise.all([runner.stop(), browserPool.close(), live.close()]).then(() => new Promise((done) => {
6595
+ closePromise = Promise.all([
6596
+ branches.stop(),
6597
+ runner.stop(),
6598
+ browserPool.close(),
6599
+ live.close()
6600
+ ]).then(() => new Promise((done) => {
6456
6601
  for (const socket of sockets)
6457
6602
  socket.destroy();
6458
6603
  sockets.clear();
@@ -7367,8 +7512,7 @@ async function run3(options, deps) {
7367
7512
  const local = await readLocalPreviews(options.cwd);
7368
7513
  let devServer = options.userPort === void 0 ? loaded.config?.devServer ?? "http://localhost:3000" : `http://localhost:${options.userPort}`;
7369
7514
  const merged = loaded.config === null ? null : { ...loaded.config, devServer, previews: [...loaded.config.previews, ...local.previews] };
7370
- const worktrees = [];
7371
- const worktreeErrors = [];
7515
+ const previewErrors = [];
7372
7516
  const previews = [];
7373
7517
  let app = null;
7374
7518
  const needsApp = (merged?.previews ?? []).some(
@@ -7385,7 +7529,7 @@ async function run3(options, deps) {
7385
7529
  devServer = app.url;
7386
7530
  merged.devServer = app.url;
7387
7531
  } catch (error) {
7388
- worktreeErrors.push(error instanceof Error ? error.message : String(error));
7532
+ previewErrors.push(error instanceof Error ? error.message : String(error));
7389
7533
  }
7390
7534
  }
7391
7535
  const fileMounts = /* @__PURE__ */ new Map();
@@ -7393,7 +7537,7 @@ async function run3(options, deps) {
7393
7537
  if (preview.file !== void 0) {
7394
7538
  const absolute = join19(options.cwd, preview.file);
7395
7539
  if (!existsSync6(absolute)) {
7396
- worktreeErrors.push(
7540
+ previewErrors.push(
7397
7541
  `"${preview.title}" names file ${preview.file}, which does not exist. The preview is skipped.`
7398
7542
  );
7399
7543
  continue;
@@ -7409,29 +7553,7 @@ async function run3(options, deps) {
7409
7553
  });
7410
7554
  continue;
7411
7555
  }
7412
- if (preview.branch === void 0) {
7413
- previews.push(preview);
7414
- continue;
7415
- }
7416
- if (merged?.devCommand === void 0) {
7417
- worktreeErrors.push(
7418
- `"${preview.title}" names branch ${preview.branch}, but the config sets no devCommand, so Leglas cannot start that checkout. Add devCommand (with {port}) to the config.`
7419
- );
7420
- continue;
7421
- }
7422
- if (!options.json) deps.log(` starting ${preview.branch}\u2026`);
7423
- try {
7424
- const worktree = await startWorktree({
7425
- cwd: options.cwd,
7426
- branch: preview.branch,
7427
- installCommand: merged.installCommand,
7428
- devCommand: merged.devCommand
7429
- });
7430
- worktrees.push(worktree);
7431
- previews.push({ ...preview, url: `${worktree.url}${preview.url}` });
7432
- } catch (error) {
7433
- worktreeErrors.push(error instanceof Error ? error.message : String(error));
7434
- }
7556
+ previews.push(preview);
7435
7557
  }
7436
7558
  const config = merged === null ? null : { ...merged, previews };
7437
7559
  const configWarnings = [];
@@ -7441,7 +7563,7 @@ async function run3(options, deps) {
7441
7563
  const ownerWarning = needsApp && app === null ? inspectLocalDevServer(devServer).then((owners) => devServerOwnerWarning(devServer, projectRoot, owners)).catch(() => null) : Promise.resolve(null);
7442
7564
  const serverPromise = startServer({
7443
7565
  config,
7444
- configErrors: [...loaded.errors, ...local.errors, ...worktreeErrors],
7566
+ configErrors: [...loaded.errors, ...local.errors, ...previewErrors],
7445
7567
  configWarnings,
7446
7568
  fileMounts,
7447
7569
  shellDir: findShellDir(),
@@ -7480,9 +7602,9 @@ async function run3(options, deps) {
7480
7602
  );
7481
7603
  deps.log(`config ${configLabel}`);
7482
7604
  deps.log(` ${previewCount} preview${previewCount === 1 ? "" : "s"}`);
7483
- if (loaded.errors.length + worktreeErrors.length > 0) {
7605
+ if (loaded.errors.length + previewErrors.length > 0) {
7484
7606
  deps.log("");
7485
- for (const error of [...loaded.errors, ...worktreeErrors]) deps.log(` ! ${error}`);
7607
+ for (const error of [...loaded.errors, ...previewErrors]) deps.log(` ! ${error}`);
7486
7608
  deps.log(" Fix the config and reload; Leglas will pick it up on restart.");
7487
7609
  }
7488
7610
  if (configWarnings.length > 0) {
@@ -7505,8 +7627,6 @@ async function run3(options, deps) {
7505
7627
  devServer,
7506
7628
  previewCount,
7507
7629
  stop: async () => {
7508
- await Promise.all(worktrees.map((worktree) => worktree.stop().catch(() => {
7509
- })));
7510
7630
  await app?.stop().catch(() => {
7511
7631
  });
7512
7632
  await server.close();