terminalhire 0.42.33 → 0.42.35

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.
@@ -6,8 +6,8 @@
6
6
  * repository's. The repository's code has already run: its build wrote files into the
7
7
  * clone, or its serve script is listening in a container this one shares a network
8
8
  * namespace with. This process serves or reaches that app over loopback, drives a
9
- * headless Chromium through each route × viewport × colour scheme, and writes PNGs
10
- * plus `manifest.json` to /out.
9
+ * headless Chromium through each planned shot (with no spec, one per route: desktop,
10
+ * light), and writes PNGs plus `manifest.json` to /out.
11
11
  *
12
12
  * No network in either mode: static capture runs under `--network=none`, and server
13
13
  * capture joins an app container that itself has `--network=none`. Whatever the page
@@ -27,7 +27,16 @@ export const VIEWPORTS = {
27
27
  desktop: { width: 1280, height: 800, isMobile: false },
28
28
  mobile: { width: 390, height: 844, isMobile: true },
29
29
  };
30
- const SCHEMES = ['light', 'dark'];
30
+ export const SCHEMES = ['light', 'dark'];
31
+ /**
32
+ * What gets shot. With no spec, one picture per route at desktop size in light mode
33
+ * (TERM-1272). The desktop/mobile × light/dark matrix this replaces suited one claim
34
+ * and cost every other one three more renders of the same screen, most often a
35
+ * loading state; mobile and dark are for a spec to ask for, shot by shot.
36
+ */
37
+ export function plannedShots(req) {
38
+ return req.routes.map((route) => ({ route, viewport: 'desktop', scheme: 'light' }));
39
+ }
31
40
  // ---- which directory is the site -------------------------------------------
32
41
  /**
33
42
  * When the build started, read off the marker the build step wrote. Taken from the
@@ -287,53 +296,49 @@ async function shoot(origin, basePath, req, out) {
287
296
  const items = [];
288
297
  const failures = [];
289
298
  try {
290
- for (const route of req.routes) {
299
+ for (const { route, viewport, scheme } of plannedShots(req)) {
291
300
  const url = `${origin}${basePath.replace(/\/$/, '')}${route.startsWith('/') ? route : `/${route}`}`;
292
- for (const viewport of Object.keys(VIEWPORTS)) {
293
- for (const scheme of SCHEMES) {
294
- const v = VIEWPORTS[viewport];
295
- const context = await browser.newContext({
296
- viewport: { width: v.width, height: v.height },
297
- isMobile: v.isMobile,
298
- hasTouch: v.isMobile,
299
- colorScheme: scheme,
300
- deviceScaleFactor: 1,
301
- });
302
- try {
303
- const page = await context.newPage();
304
- await page.goto(url, { waitUntil: 'load', timeout: 20_000 });
305
- // A page with no backend may keep retrying forever, so idle is
306
- // waited for briefly and never required.
307
- await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => undefined);
308
- await page.waitForTimeout(300);
309
- // Frozen, so two shots of the same page are the same bytes: a spinner
310
- // caught on different frames would otherwise hide that light and dark
311
- // render identically, which is the one comparison the notes rely on.
312
- const png = await page.screenshot({ animations: 'disabled', caret: 'hide' });
313
- const file = shotName(route, viewport, scheme);
314
- writeFileSync(join(out, file), png);
315
- items.push({
316
- route,
317
- viewport,
318
- scheme,
319
- file,
320
- sha256: createHash('sha256').update(png).digest('hex'),
321
- bytes: png.length,
322
- ...PNG_SIZE(png),
323
- });
324
- }
325
- catch (err) {
326
- failures.push({
327
- route,
328
- viewport,
329
- scheme,
330
- error: String(err?.message ?? err).slice(0, 300),
331
- });
332
- }
333
- finally {
334
- await context.close();
335
- }
336
- }
301
+ const v = VIEWPORTS[viewport];
302
+ const context = await browser.newContext({
303
+ viewport: { width: v.width, height: v.height },
304
+ isMobile: v.isMobile,
305
+ hasTouch: v.isMobile,
306
+ colorScheme: scheme,
307
+ deviceScaleFactor: 1,
308
+ });
309
+ try {
310
+ const page = await context.newPage();
311
+ await page.goto(url, { waitUntil: 'load', timeout: 20_000 });
312
+ // A page with no backend may keep retrying forever, so idle is
313
+ // waited for briefly and never required.
314
+ await page.waitForLoadState('networkidle', { timeout: 5_000 }).catch(() => undefined);
315
+ await page.waitForTimeout(300);
316
+ // Frozen, so two shots of the same page are the same bytes: a spinner
317
+ // caught on different frames would otherwise hide that light and dark
318
+ // render identically, which is the one comparison the notes rely on.
319
+ const png = await page.screenshot({ animations: 'disabled', caret: 'hide' });
320
+ const file = shotName(route, viewport, scheme);
321
+ writeFileSync(join(out, file), png);
322
+ items.push({
323
+ route,
324
+ viewport,
325
+ scheme,
326
+ file,
327
+ sha256: createHash('sha256').update(png).digest('hex'),
328
+ bytes: png.length,
329
+ ...PNG_SIZE(png),
330
+ });
331
+ }
332
+ catch (err) {
333
+ failures.push({
334
+ route,
335
+ viewport,
336
+ scheme,
337
+ error: String(err?.message ?? err).slice(0, 300),
338
+ });
339
+ }
340
+ finally {
341
+ await context.close();
337
342
  }
338
343
  }
339
344
  }
@@ -12592,6 +12592,11 @@ function wrapIndented(text, indent, width) {
12592
12592
  lines.push(pad + line);
12593
12593
  return lines;
12594
12594
  }
12595
+ function projectMarkerLine(bounty) {
12596
+ const tasks = bounty?.project?.tasks;
12597
+ if (typeof tasks !== "number" || !Number.isInteger(tasks) || tasks <= 0) return null;
12598
+ return tasks > 1 ? `project \xB7 ${tasks} tasks` : "project";
12599
+ }
12595
12600
  function printBounty(i, job, score, reason, matchedTags, claimedIds = /* @__PURE__ */ new Set(), continuityNote = null) {
12596
12601
  const b = job.bounty ?? {};
12597
12602
  const stars = b.repoStars != null ? ` \xB7 ${b.repoStars}\u2605` : "";
@@ -12607,6 +12612,8 @@ ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
12607
12612
  console.log(
12608
12613
  ` ${formatAmount(b)}${effort} \xB7 ${sanitizeText(b.repoFullName ?? job.company)}${stars}${scoreStr}${contend}${badge}`
12609
12614
  );
12615
+ const marker = projectMarkerLine(b);
12616
+ if (marker) console.log(` ${marker}`);
12610
12617
  if (reason) console.log(` ${reason}`);
12611
12618
  if (continuityNote) console.log(` ${continuityNote}`);
12612
12619
  if (b.publicSummary) {
@@ -12862,6 +12869,7 @@ export {
12862
12869
  getBounties,
12863
12870
  isPinnedFounderBounty,
12864
12871
  printBounty,
12872
+ projectMarkerLine,
12865
12873
  rankBounties,
12866
12874
  run,
12867
12875
  wrapIndented
@@ -5978,10 +5978,17 @@ var init_profile = __esm({
5978
5978
  // src/open-url.js
5979
5979
  var open_url_exports = {};
5980
5980
  __export(open_url_exports, {
5981
- openInBrowser: () => openInBrowser
5981
+ openInBrowser: () => openInBrowser,
5982
+ shouldOpenBrowser: () => shouldOpenBrowser
5982
5983
  });
5983
- import { spawn } from "child_process";
5984
- function openInBrowser(url) {
5984
+ import { spawn as nodeSpawn } from "child_process";
5985
+ function shouldOpenBrowser(env = process.env) {
5986
+ if (env.NODE_TEST_CONTEXT) return false;
5987
+ const off = String(env.TERMINALHIRE_NO_BROWSER ?? "").trim().toLowerCase();
5988
+ return !(off === "1" || off === "true");
5989
+ }
5990
+ function openInBrowser(url, { env = process.env, spawn = nodeSpawn } = {}) {
5991
+ if (!shouldOpenBrowser(env)) return;
5985
5992
  let cmd;
5986
5993
  let args;
5987
5994
  if (process.platform === "darwin") {