@reddoorla/maintenance 0.61.0 → 0.62.0

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 (103) hide show
  1. package/dist/airtable-HHAWHBNM.js +9 -0
  2. package/dist/{announce-Q2DXP5OO.js → announce-54EBITHX.js} +16 -6
  3. package/dist/announce-54EBITHX.js.map +1 -0
  4. package/dist/{bump-deps-RYHQIKW4.js → bump-deps-2U6GIHM5.js} +2 -2
  5. package/dist/{chunk-BWLUNY7L.js → chunk-2T3HZ3DB.js} +24 -14
  6. package/dist/chunk-2T3HZ3DB.js.map +1 -0
  7. package/dist/{chunk-D3LFQNJK.js → chunk-3HM4S422.js} +2 -2
  8. package/dist/{chunk-LMXIZITU.js → chunk-4REUHZAW.js} +40 -4
  9. package/dist/chunk-4REUHZAW.js.map +1 -0
  10. package/dist/chunk-5L3FPQIJ.js +92 -0
  11. package/dist/chunk-5L3FPQIJ.js.map +1 -0
  12. package/dist/{client-JA6QLEDW.js → chunk-6ZKM7OIJ.js} +5 -4
  13. package/dist/{chunk-KC4HU7BC.js → chunk-76VIUWR3.js} +31 -6
  14. package/dist/chunk-76VIUWR3.js.map +1 -0
  15. package/dist/{chunk-L4ULXHIN.js → chunk-BLD6AYJO.js} +15 -1
  16. package/dist/chunk-BLD6AYJO.js.map +1 -0
  17. package/dist/{chunk-VDBENQUA.js → chunk-BMDBOPN3.js} +2 -2
  18. package/dist/{chunk-HB7QPECR.js → chunk-KTPPKO2C.js} +6 -6
  19. package/dist/chunk-L5F664ZA.js +45 -0
  20. package/dist/chunk-L5F664ZA.js.map +1 -0
  21. package/dist/{chunk-XB6T4ETK.js → chunk-OSRFDGQM.js} +21 -1
  22. package/dist/chunk-OSRFDGQM.js.map +1 -0
  23. package/dist/{chunk-6CSCLBAA.js → chunk-SAOCVNX7.js} +2 -2
  24. package/dist/{chunk-5PHCZ6L5.js → chunk-SS7BKOIX.js} +89 -2
  25. package/dist/chunk-SS7BKOIX.js.map +1 -0
  26. package/dist/{chunk-TZN2G6FD.js → chunk-TYRCYHRA.js} +3 -3
  27. package/dist/{chunk-HG5DTFNF.js → chunk-XKKTK6WA.js} +17 -1
  28. package/dist/chunk-XKKTK6WA.js.map +1 -0
  29. package/dist/{chunk-JNXCDUYB.js → chunk-XVR7UM6Z.js} +4 -4
  30. package/dist/{chunk-HPSGCDWY.js → chunk-YYUY2OVP.js} +2 -2
  31. package/dist/{chunk-TLZ4V2BE.js → chunk-ZEX2T6JH.js} +3 -2
  32. package/dist/{chunk-TLZ4V2BE.js.map → chunk-ZEX2T6JH.js.map} +1 -1
  33. package/dist/cli/bin.js +16 -15
  34. package/dist/cli/bin.js.map +1 -1
  35. package/dist/cli/commands/audit.d.ts +3 -2
  36. package/dist/cli/commands/audit.js +29 -8
  37. package/dist/cli/commands/audit.js.map +1 -1
  38. package/dist/client-GTMJTXBK.js +11 -0
  39. package/dist/{convert-to-pnpm-KNCXU6FM.js → convert-to-pnpm-JG3767ZQ.js} +2 -2
  40. package/dist/{db-AXKMRTVA.js → db-Y6BVTNH7.js} +3 -3
  41. package/dist/{digest-WAS3EJNC.js → digest-ABJROO4H.js} +3 -3
  42. package/dist/{github-signals-UL2BBS3Q.js → github-signals-XTJLKOXJ.js} +26 -5
  43. package/dist/github-signals-XTJLKOXJ.js.map +1 -0
  44. package/dist/index.d.ts +60 -3
  45. package/dist/index.js +86 -21
  46. package/dist/index.js.map +1 -1
  47. package/dist/{init-KD56UENX.js → init-DMAIKUQE.js} +8 -8
  48. package/dist/{launch-UWCI3JRM.js → launch-UZPPB4IE.js} +10 -9
  49. package/dist/{launch-UWCI3JRM.js.map → launch-UZPPB4IE.js.map} +1 -1
  50. package/dist/{lighthouse-airtable-N3KCBPVA.js → lighthouse-airtable-HTEW6BBT.js} +3 -3
  51. package/dist/migrate-BFOG2OCP.js +7 -0
  52. package/dist/{onboard-SCZ3UKZ2.js → onboard-TTIC3YZI.js} +2 -2
  53. package/dist/{orchestrate-D72PB6NG.js → orchestrate-5GGOL5EX.js} +7 -4
  54. package/dist/recipes/sync-configs.d.ts +1 -1
  55. package/dist/recipes/sync-configs.js +2 -2
  56. package/dist/{renovate-dispatch-NG5KUQKY.js → renovate-dispatch-QDDMTATD.js} +3 -3
  57. package/dist/{report-EPHXV2T2.js → report-SZGZIDDG.js} +6 -6
  58. package/dist/{self-updating-CFHXJSAX.js → self-updating-2RLUP4AA.js} +4 -4
  59. package/dist/{svelte-codemods-HNITLWQM.js → svelte-codemods-BPLKSVIA.js} +2 -2
  60. package/dist/{sync-configs-BZXBYKTI.js → sync-configs-VFRV3TJ4.js} +4 -4
  61. package/dist/{types-QG-QhCYh.d.ts → types-Dg4mHvyf.d.ts} +6 -1
  62. package/dist/{upgrade-6YDUELFA.js → upgrade-WCFD2TRN.js} +2 -2
  63. package/dist/{websites-IYP7IGLU.js → websites-HM2NMZMR.js} +2 -2
  64. package/dist/{write-audits-to-airtable-VDGHCZGQ.js → write-audits-to-airtable-QF26PCAR.js} +5 -4
  65. package/dist/write-audits-to-airtable-QF26PCAR.js.map +1 -0
  66. package/package.json +1 -1
  67. package/dist/airtable-YZ6VMSQZ.js +0 -9
  68. package/dist/announce-Q2DXP5OO.js.map +0 -1
  69. package/dist/chunk-5PHCZ6L5.js.map +0 -1
  70. package/dist/chunk-BWLUNY7L.js.map +0 -1
  71. package/dist/chunk-HG5DTFNF.js.map +0 -1
  72. package/dist/chunk-KC4HU7BC.js.map +0 -1
  73. package/dist/chunk-L4ULXHIN.js.map +0 -1
  74. package/dist/chunk-LMXIZITU.js.map +0 -1
  75. package/dist/chunk-XB6T4ETK.js.map +0 -1
  76. package/dist/github-signals-UL2BBS3Q.js.map +0 -1
  77. package/dist/migrate-P2LS4WXI.js +0 -7
  78. /package/dist/{airtable-YZ6VMSQZ.js.map → airtable-HHAWHBNM.js.map} +0 -0
  79. /package/dist/{bump-deps-RYHQIKW4.js.map → bump-deps-2U6GIHM5.js.map} +0 -0
  80. /package/dist/{chunk-D3LFQNJK.js.map → chunk-3HM4S422.js.map} +0 -0
  81. /package/dist/{client-JA6QLEDW.js.map → chunk-6ZKM7OIJ.js.map} +0 -0
  82. /package/dist/{chunk-VDBENQUA.js.map → chunk-BMDBOPN3.js.map} +0 -0
  83. /package/dist/{chunk-HB7QPECR.js.map → chunk-KTPPKO2C.js.map} +0 -0
  84. /package/dist/{chunk-6CSCLBAA.js.map → chunk-SAOCVNX7.js.map} +0 -0
  85. /package/dist/{chunk-TZN2G6FD.js.map → chunk-TYRCYHRA.js.map} +0 -0
  86. /package/dist/{chunk-JNXCDUYB.js.map → chunk-XVR7UM6Z.js.map} +0 -0
  87. /package/dist/{chunk-HPSGCDWY.js.map → chunk-YYUY2OVP.js.map} +0 -0
  88. /package/dist/{lighthouse-airtable-N3KCBPVA.js.map → client-GTMJTXBK.js.map} +0 -0
  89. /package/dist/{convert-to-pnpm-KNCXU6FM.js.map → convert-to-pnpm-JG3767ZQ.js.map} +0 -0
  90. /package/dist/{db-AXKMRTVA.js.map → db-Y6BVTNH7.js.map} +0 -0
  91. /package/dist/{digest-WAS3EJNC.js.map → digest-ABJROO4H.js.map} +0 -0
  92. /package/dist/{init-KD56UENX.js.map → init-DMAIKUQE.js.map} +0 -0
  93. /package/dist/{migrate-P2LS4WXI.js.map → lighthouse-airtable-HTEW6BBT.js.map} +0 -0
  94. /package/dist/{orchestrate-D72PB6NG.js.map → migrate-BFOG2OCP.js.map} +0 -0
  95. /package/dist/{onboard-SCZ3UKZ2.js.map → onboard-TTIC3YZI.js.map} +0 -0
  96. /package/dist/{websites-IYP7IGLU.js.map → orchestrate-5GGOL5EX.js.map} +0 -0
  97. /package/dist/{renovate-dispatch-NG5KUQKY.js.map → renovate-dispatch-QDDMTATD.js.map} +0 -0
  98. /package/dist/{report-EPHXV2T2.js.map → report-SZGZIDDG.js.map} +0 -0
  99. /package/dist/{self-updating-CFHXJSAX.js.map → self-updating-2RLUP4AA.js.map} +0 -0
  100. /package/dist/{svelte-codemods-HNITLWQM.js.map → svelte-codemods-BPLKSVIA.js.map} +0 -0
  101. /package/dist/{sync-configs-BZXBYKTI.js.map → sync-configs-VFRV3TJ4.js.map} +0 -0
  102. /package/dist/{upgrade-6YDUELFA.js.map → upgrade-WCFD2TRN.js.map} +0 -0
  103. /package/dist/{write-audits-to-airtable-VDGHCZGQ.js.map → websites-HM2NMZMR.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/audits/github-signals.ts","../src/cli/commands/github-signals.ts"],"sourcesContent":["import type { Site } from \"../types.js\";\nimport type { CiState, PullRequestSummary } from \"../github/gh.js\";\nimport { isFailingRenovatePR } from \"../alerts/renovate.js\";\n\n/** One swept row, ready for the Airtable writer (slug-keyed by `site`). */\nexport type GitHubSignalsRow = {\n site: string; // the site name/slug the writer matches on\n repo: string; // owner/repo\n renovateFailingCis: number;\n ciState: CiState;\n lastCommitAt: string | null;\n};\n\n/** Injected GitHub reads (so the sweep is pure + testable). */\nexport type GitHubSignalsDeps = {\n openPullRequests: (repo: string) => Promise<PullRequestSummary[]>;\n defaultBranchStatus: (repo: string) => Promise<{ ciState: CiState; lastCommitAt: string | null }>;\n};\n\n/** Per repo-backed site: count its failing Renovate PRs + read its default-branch\n * status. Sites without `gitRepo` are skipped (not errors). A repo whose probe\n * throws is reported via `onSkip` and produces no row — one GitHub hiccup never\n * sinks the sweep. PURE over `deps`. */\nexport async function collectGitHubSignals(\n sites: Site[],\n deps: GitHubSignalsDeps,\n onSkip: (s: { repo: string }) => void = () => {},\n): Promise<GitHubSignalsRow[]> {\n const rows: GitHubSignalsRow[] = [];\n for (const s of sites) {\n const repo = s.gitRepo;\n if (!repo) continue;\n const name = s.name;\n if (!name) continue;\n try {\n const prs = await deps.openPullRequests(repo);\n const status = await deps.defaultBranchStatus(repo);\n rows.push({\n site: name,\n repo,\n renovateFailingCis: prs.filter(isFailingRenovatePR).length,\n ciState: status.ciState,\n lastCommitAt: status.lastCommitAt,\n });\n } catch {\n onSkip({ repo });\n }\n }\n return rows;\n}\n","import { openBase, readAirtableConfig } from \"../../reports/airtable/client.js\";\nimport { listWebsites, siteSlug, updateGitHubSignals } from \"../../reports/airtable/websites.js\";\nimport type { Site } from \"../../types.js\";\nimport { collectGitHubSignals } from \"../../audits/github-signals.js\";\nimport { makeGitHub } from \"../../github/gh.js\";\nimport {\n formatFleetWriteSummary,\n type FleetWriteResult,\n} from \"../../audits/write-audits-to-airtable.js\";\nimport { detectSignalEvents, fleetSweptEvent } from \"../../audits/fleet-event-detectors.js\";\nimport { recordFleetEventsBestEffort } from \"../../audits/fleet-events-writer.js\";\nimport type { FleetEvent } from \"../../db/fleet-events.js\";\n\n/** Exit code for a fleet github-signals run. Exit 1 when failures are the\n * MAJORITY of the fleet (`failed > written`), not only on a total wipeout —\n * a run where 11/12 repos failed but 1 wrote should still signal an outage.\n * All-success or a minority of flakes (e.g. 1/12 failed) stays exit 0. The\n * no-token clean-skip returns 0 separately (before any probe runs). */\nexport function githubSignalsExitCode(written: number, failed: number): number {\n return failed > written ? 1 : 0;\n}\n\n/** `github-signals --fleet --write-airtable`: sweep every repo-backed site for its\n * Renovate-failing count + default-branch CI state + last-commit date, write each\n * row serially (Airtable ~5 req/sec), and emit FLEET_WRITE_SUMMARY for CI. A\n * missing fleet token is a clean skip (local runs), not a failure. */\nexport async function runGitHubSignalsCommand(opts: {\n fleet?: boolean | undefined;\n writeAirtable?: boolean | undefined;\n}): Promise<{ output: string; code: number }> {\n if (!opts.fleet || !opts.writeAirtable) {\n return { output: \"github-signals currently supports only --fleet --write-airtable\", code: 2 };\n }\n const token = process.env.RENOVATE_TOKEN?.trim() || process.env.GH_TOKEN?.trim();\n if (!token) {\n return {\n output: \"github-signals skipped: no RENOVATE_TOKEN/GH_TOKEN (fleet read) configured.\",\n code: 0,\n };\n }\n const base = openBase(readAirtableConfig());\n const websites = await listWebsites(base);\n const gh = makeGitHub({ token });\n const sites: Site[] = websites.map((w) => ({\n path: \"\",\n name: w.name,\n meta: {},\n ...(w.gitRepo ? { gitRepo: w.gitRepo } : {}),\n }));\n\n const skipped: string[] = [];\n const rows = await collectGitHubSignals(\n sites,\n {\n openPullRequests: (r) => gh.openPullRequests(r),\n defaultBranchStatus: (r) => gh.defaultBranchStatus(r),\n },\n ({ repo }) => skipped.push(repo),\n );\n\n const sweptAt = new Date().toISOString();\n const result: FleetWriteResult = { written: [], failed: [] };\n const byRepo = new Map(websites.filter((w) => w.gitRepo).map((w) => [w.gitRepo, w]));\n const events: FleetEvent[] = [];\n const sweptMs = Date.parse(sweptAt);\n const since24h = new Date(sweptMs - 24 * 60 * 60 * 1000).toISOString();\n // Serial: Airtable's ~5 req/sec limit (matches writeFleetAuditsToAirtable).\n for (const row of rows) {\n const target = byRepo.get(row.repo);\n if (!target) {\n result.failed.push({ slug: siteSlug(row.site), error: \"no Websites row matched\" });\n continue;\n }\n try {\n await updateGitHubSignals(base, target.id, {\n renovateFailingCis: row.renovateFailingCis,\n ciState: row.ciState,\n lastCommitAt: row.lastCommitAt,\n sweptAt,\n });\n result.written.push({\n siteName: target.name,\n writes: [{ audit: \"github-signals\", counts: row }],\n });\n // Fleet-activity events for this repo: merged Renovate PRs since the last sweep\n // (watermark = the row's prior GitHub Signals At, else a 24h fallback) + a\n // CI-recovered transition. A PR-fetch hiccup drops only this repo's PR events.\n const since = target.githubSignalsAt ?? since24h;\n let merged: Awaited<ReturnType<typeof gh.mergedRenovatePullRequests>> = [];\n try {\n merged = await gh.mergedRenovatePullRequests(row.repo, since);\n } catch {\n // PR list unavailable this run — skip pr_automerged for this repo, keep ci_recovered\n }\n events.push(...detectSignalEvents(target, row, merged, sweptAt));\n } catch (e) {\n result.failed.push({ slug: siteSlug(row.site), error: (e as Error).message });\n }\n }\n for (const repo of skipped) result.failed.push({ slug: repo, error: \"probe failed (skipped)\" });\n\n events.push(fleetSweptEvent(\"github-signals\", result.written.length, sweptAt));\n await recordFleetEventsBestEffort(events, new Date());\n\n // Exit non-zero when failures are the MAJORITY of the fleet, not only on a\n // total wipeout. A run where 11/12 repos failed but 1 wrote used to return 0,\n // masking a large outage. The nightly cron step is `continue-on-error`, so a\n // non-zero here is an operator-visibility signal, not a red build.\n return {\n output: formatFleetWriteSummary(result),\n code: githubSignalsExitCode(result.written.length, result.failed.length),\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as Site, a as AuditResult, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-QG-QhCYh.js';
2
- export { C as ConfigName } from './types-QG-QhCYh.js';
1
+ import { S as Site, a as AuditResult, A as AuditName, b as RecipeResult, R as RecipeName, I as InventoryProvider } from './types-Dg4mHvyf.js';
2
+ export { C as ConfigName } from './types-Dg4mHvyf.js';
3
3
  import * as airtable_lib_airtable_base_js from 'airtable/lib/airtable_base.js';
4
4
  import { F as FormType } from './types-RXY-vY-5.js';
5
5
  export { SyncConfigsOptions, syncConfigs } from './recipes/sync-configs.js';
@@ -68,10 +68,33 @@ type BrowserRunner = {
68
68
  close?: () => Promise<void>;
69
69
  };
70
70
 
71
+ /** Result of probing a site's latest PRODUCTION Netlify deploy. Every field is
72
+ * nullable: a network/parse failure (or a site with no deploys yet) degrades to
73
+ * all-nulls rather than throwing, so the audit always returns a verdict.
74
+ * - `state` is Netlify's raw deploy state (`ready`/`error`/`building`/…), lower-cased,
75
+ * or null when it couldn't be read.
76
+ * - `deployedAt` is the deploy's publish/create time (ISO), or null.
77
+ * - `logUrl` links to the deploy (its live URL or the Netlify admin/build-log page).
78
+ * - `errorMessage` is Netlify's `error_message` for a failed build, or null. */
79
+ type NetlifyDeployCheck = {
80
+ state: string | null;
81
+ deployedAt: string | null;
82
+ logUrl: string | null;
83
+ errorMessage: string | null;
84
+ };
85
+ /** Injected IO so the check is unit-testable without hitting the Netlify API.
86
+ * `fetchLatestProductionDeploy` returns the parsed first (latest) production
87
+ * deploy object, or null when the site has none / the call failed. `now` is the
88
+ * clock for the checked-at stamp. */
89
+ type NetlifyDeployDeps = {
90
+ fetchLatestProductionDeploy: (siteId: string) => Promise<NetlifyDeployCheck | null>;
91
+ now: Date;
92
+ };
93
+
71
94
  type AuditContext = {
72
95
  site: Site;
73
96
  spawn?: SpawnFn;
74
- /** Clock injection (domain + browser audits). Defaults to `new Date()`. */
97
+ /** Clock injection (domain + browser + netlify-deploy audits). Defaults to `new Date()`. */
75
98
  now?: Date;
76
99
  /** DNS/TLS injection for the domain audit (tests). Defaults to real DNS+TLS. */
77
100
  domainDeps?: DomainDeps;
@@ -79,6 +102,8 @@ type AuditContext = {
79
102
  discoverDeps?: DiscoverDeps;
80
103
  /** Playwright runner injection for the browser audit (tests). Defaults to real Playwright. */
81
104
  browserRunner?: BrowserRunner;
105
+ /** Netlify API injection for the netlify-deploy audit (tests). Defaults to a real API call. */
106
+ netlifyDeployDeps?: NetlifyDeployDeps;
82
107
  };
83
108
 
84
109
  declare function depsAudit(ctx: AuditContext): Promise<AuditResult>;
@@ -326,6 +351,16 @@ type WebsiteRow = {
326
351
  * expires (null = unresolved or no usable cert); `domainCheckedAt` is when it last ran. */
327
352
  certDaysRemaining: number | null;
328
353
  domainCheckedAt: string | null;
354
+ /** Netlify site id — the IDENTITY the `netlify-deploy` audit needs to query the
355
+ * Netlify API. Read-only input (operator-set in Airtable); null = not on Netlify
356
+ * (or not wired) → that audit skips. Never derived from the URL. */
357
+ netlifyId: string | null;
358
+ /** Latest PRODUCTION deploy health (the `netlify-deploy` audit). `deployStatus` is
359
+ * Netlify's deploy state lower-cased (`ready`/`error`/`building`/…), null = none / not
360
+ * checked; `lastDeployAt` is when it deployed; `deployLogUrl` links to the deploy/log. */
361
+ deployStatus: string | null;
362
+ lastDeployAt: string | null;
363
+ deployLogUrl: string | null;
329
364
  /** Deployed-URL browser probe (the `browser` audit): cross-engine render OK, mobile render OK,
330
365
  * internal-links OK + broken count, and when it last ran (one timestamp gates all three). */
331
366
  crossbrowserOk: boolean | null;
@@ -380,7 +415,11 @@ type ResolvedCopy = {
380
415
  announceBody: string;
381
416
  announceImprovementResend: string;
382
417
  announceImprovementSvelte5: string;
418
+ /** The cadence lead-in, ending at the em dash. Rendered followed by `announceCadenceNote`
419
+ * in italics. */
383
420
  announceCadence: string;
421
+ /** The reassurance tail, rendered italic after `announceCadence`. */
422
+ announceCadenceNote: string;
384
423
  announceOpenDoor: string;
385
424
  };
386
425
 
@@ -418,6 +457,10 @@ type ReportData = {
418
457
  * `| undefined` is explicit so callers can pass `undefined` under exactOptionalPropertyTypes. */
419
458
  gaUsersCurrent?: number | undefined;
420
459
  gaUsersPrevious?: number | undefined;
460
+ /** Length in days of the GA "current" window (compared against the equal-length prior window).
461
+ * Drives the analytics trend label ("vs the previous N days"); omitted → the generic
462
+ * "last period". `| undefined` for exactOptionalPropertyTypes. */
463
+ gaPeriodDays?: number | undefined;
421
464
  /** Site's rounded average Google position for its query, when on page 1 (from Search Console).
422
465
  * `undefined` = not on page 1, not checked, or unconfigured — rendered as today's plain check. */
423
466
  searchPosition?: number | undefined;
@@ -721,6 +764,8 @@ type AttentionItem = {
721
764
  autoFixExhausted?: boolean;
722
765
  };
723
766
 
767
+ type FleetEventType = "pr_automerged" | "vuln_cleared" | "ci_recovered" | "site_launched" | "fleet_swept" | "cert_renewed";
768
+
724
769
  type Tier = "attention" | "watch" | "healthy";
725
770
  type SiteCard = {
726
771
  site: WebsiteRow;
@@ -765,6 +810,16 @@ type CockpitSummary = {
765
810
  /** Count of NEW submissions across the fleet (optional for back-compat). */
766
811
  newSubmissions?: number;
767
812
  };
813
+ /** A render-ready row for the cockpit "Recently" lane. `url` is an external link
814
+ * (PR) when present; `slug` links to `/s/<slug>` when the event names a site. */
815
+ type RecentEntry = {
816
+ type: FleetEventType;
817
+ summary: string;
818
+ siteName: string | null;
819
+ slug: string | null;
820
+ url: string | null;
821
+ ts: string;
822
+ };
768
823
  type CockpitModel = {
769
824
  summary: CockpitSummary;
770
825
  /** All visible sites, ordered: attention (worst-first) → watch (A-Z) → healthy (A-Z). */
@@ -777,6 +832,8 @@ type CockpitModel = {
777
832
  caught: number;
778
833
  through: number;
779
834
  } | null;
835
+ /** Recent fleet-activity events for the "Recently" lane (optional for back-compat). */
836
+ recent?: RecentEntry[];
780
837
  };
781
838
 
782
839
  /**
package/dist/index.js CHANGED
@@ -1,34 +1,41 @@
1
+ import {
2
+ findDueReports
3
+ } from "./chunk-WVRPOXKH.js";
4
+ import {
5
+ upgradeSvelte4to5
6
+ } from "./chunk-O65I7G7G.js";
1
7
  import {
2
8
  DEFAULT_INIT_STEPS,
3
9
  a11yFixturesPage,
4
10
  init
5
- } from "./chunk-HB7QPECR.js";
11
+ } from "./chunk-KTPPKO2C.js";
12
+ import {
13
+ convertToPnpm
14
+ } from "./chunk-2X322ZES.js";
6
15
  import {
7
16
  svelteCodemods
8
17
  } from "./chunk-FUD4I2ZW.js";
18
+ import "./chunk-OQPMZ6BM.js";
9
19
  import {
10
20
  onboard,
11
21
  selfCaretRange,
12
22
  selfPackageVersion
13
23
  } from "./chunk-ZYBOT5PM.js";
14
- import "./chunk-VDBENQUA.js";
15
- import {
16
- findDueReports
17
- } from "./chunk-WVRPOXKH.js";
18
24
  import {
19
25
  draftReportForSite
20
- } from "./chunk-TZN2G6FD.js";
26
+ } from "./chunk-TYRCYHRA.js";
21
27
  import "./chunk-6LIWDIXU.js";
28
+ import "./chunk-BMDBOPN3.js";
22
29
  import {
23
30
  sendApprovedReports
24
- } from "./chunk-KC4HU7BC.js";
25
- import "./chunk-OGGRFBL4.js";
31
+ } from "./chunk-76VIUWR3.js";
26
32
  import {
27
33
  BLURRED_CID,
28
34
  CHECK_CID,
29
35
  loadBundledImages,
30
36
  renderReportHtml
31
- } from "./chunk-BWLUNY7L.js";
37
+ } from "./chunk-2T3HZ3DB.js";
38
+ import "./chunk-OGGRFBL4.js";
32
39
  import {
33
40
  checklistFor,
34
41
  escapeHtml,
@@ -39,18 +46,11 @@ import {
39
46
  import {
40
47
  bumpDeps
41
48
  } from "./chunk-D3DQFC32.js";
42
- import {
43
- upgradeSvelte4to5
44
- } from "./chunk-O65I7G7G.js";
45
- import "./chunk-OQPMZ6BM.js";
46
- import {
47
- convertToPnpm
48
- } from "./chunk-2X322ZES.js";
49
49
  import {
50
50
  syncConfigs
51
- } from "./chunk-JNXCDUYB.js";
52
- import "./chunk-CASCWQQX.js";
51
+ } from "./chunk-XVR7UM6Z.js";
53
52
  import "./chunk-XKOZUVK2.js";
53
+ import "./chunk-CASCWQQX.js";
54
54
  import "./chunk-OTGB6YQO.js";
55
55
  import {
56
56
  ALL_AUDIT_NAMES,
@@ -61,8 +61,11 @@ import {
61
61
  runAudits,
62
62
  runAuditsAcross,
63
63
  securityAudit
64
- } from "./chunk-5PHCZ6L5.js";
64
+ } from "./chunk-SS7BKOIX.js";
65
65
  import "./chunk-LKGVSM2O.js";
66
+ import "./chunk-L5F664ZA.js";
67
+ import "./chunk-6ZKM7OIJ.js";
68
+ import "./chunk-XKKTK6WA.js";
66
69
  import {
67
70
  fromJsonFile,
68
71
  localPath
@@ -72,12 +75,12 @@ import "./chunk-XXTZBPUY.js";
72
75
  import "./chunk-XTK5VIQB.js";
73
76
  import {
74
77
  fromAirtableBase
75
- } from "./chunk-TLZ4V2BE.js";
78
+ } from "./chunk-ZEX2T6JH.js";
76
79
  import "./chunk-U5SZ4FOP.js";
77
80
  import {
78
81
  SEVERITY_RANK,
79
82
  siteSlug
80
- } from "./chunk-L4ULXHIN.js";
83
+ } from "./chunk-BLD6AYJO.js";
81
84
  import "./chunk-HN6I2XWI.js";
82
85
  import "./chunk-WEKL2HYJ.js";
83
86
  import "./chunk-6VAL7XJT.js";
@@ -639,6 +642,14 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
639
642
 
640
643
  // src/dashboard/fleet-cockpit.ts
641
644
  var MS_PER_DAY = 24 * 60 * 60 * 1e3;
645
+ var READY_DEPLOY_STATES = /* @__PURE__ */ new Set(["ready"]);
646
+ var FAILED_DEPLOY_STATES = /* @__PURE__ */ new Set(["error", "failed", "rejected"]);
647
+ function isReadyDeployStatus(status) {
648
+ return status !== null && READY_DEPLOY_STATES.has(status.toLowerCase());
649
+ }
650
+ function isFailedDeployStatus(status) {
651
+ return status !== null && FAILED_DEPLOY_STATES.has(status.toLowerCase());
652
+ }
642
653
  var NEEDS_YOU_GROUP_RANK = { broken: 0, approval: 1, slipping: 2 };
643
654
  function buildNeedsYouFeed(model) {
644
655
  const bySlug = /* @__PURE__ */ new Map();
@@ -776,10 +787,25 @@ function card(site) {
776
787
  site.securityVulnsModerate,
777
788
  site.securityVulnsLow
778
789
  )}
790
+ <span class="metric-label">deploy</span> ${deployBadge(site)}
779
791
  </span>
780
792
  </div>
781
793
  </article>`;
782
794
  }
795
+ function deployBadge(site) {
796
+ const state = site.deployStatus;
797
+ if (state === null) {
798
+ return `<span class="metric deploy unknown" title="No deploy data">${DASH2}</span>`;
799
+ }
800
+ const cls = isReadyDeployStatus(state) ? "deploy ready" : isFailedDeployStatus(state) ? "deploy failed" : "deploy building";
801
+ const when = relativeTimeFromNow(site.lastDeployAt);
802
+ const label = escapeHtml(`${state}${when !== DASH2 ? ` \xB7 ${when}` : ""}`);
803
+ const url = site.deployLogUrl ? safeUrl(site.deployLogUrl) : "#";
804
+ if (url !== "#") {
805
+ return `<a class="metric ${cls}" href="${escapeHtml(url)}" target="_blank" rel="noopener">${label}</a>`;
806
+ }
807
+ return `<span class="metric ${cls}">${label}</span>`;
808
+ }
783
809
  function submBadge(c) {
784
810
  const n = c.newSubmissions ?? 0;
785
811
  return n > 0 ? `<span class="chip">\u{1F4E5} ${n} new</span>` : "";
@@ -890,6 +916,16 @@ h1 { margin: 0 0 0.25rem; font-size: 1.75rem; }
890
916
  .cluster.lighthouse .score { display: inline-block; min-width: 2.25rem; text-align: right; }
891
917
  .metric-label { color: #999; font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.04em; }
892
918
  .metric { font-feature-settings: "tnum"; }
919
+ .metric.deploy { font-size:0.78rem; padding:0.05rem 0.4rem; border-radius:6px; text-decoration:none; }
920
+ .metric.deploy.ready { background:#e6f4ea; color:#1a7f37; }
921
+ .metric.deploy.failed { background:#fdecea; color:#b00; font-weight:600; }
922
+ .metric.deploy.building { background:#fff4e5; color:#9a6700; }
923
+ .metric.deploy.unknown { color:#999; }
924
+ @media (prefers-color-scheme: dark) {
925
+ .metric.deploy.ready { background:#0f2417; color:#4ac26b; }
926
+ .metric.deploy.failed { background:#3a1412; color:#ff6b6b; }
927
+ .metric.deploy.building { background:#2e2410; color:#e3b341; }
928
+ }
893
929
  .spam-rollup { font-size:0.9rem; margin-bottom:1rem; }
894
930
  .muted { color:#999; }
895
931
  .filters { display:flex; flex-wrap:wrap; gap:0.4rem; margin-bottom:1.25rem; }
@@ -988,6 +1024,34 @@ function renderNeedsYouFeed(feed) {
988
1024
  return `<section class="needs-you"><h2>Needs you (${feed.length})</h2>${blocks}</section>`;
989
1025
  }
990
1026
  var SUBMISSIONS_STRIP_CAP = 10;
1027
+ var RECENT_ICON = {
1028
+ pr_automerged: "\u{1F527}",
1029
+ vuln_cleared: "\u{1F6E1}",
1030
+ ci_recovered: "\u2705",
1031
+ site_launched: "\u{1F680}",
1032
+ cert_renewed: "\u{1F512}",
1033
+ fleet_swept: "\u{1F504}"
1034
+ };
1035
+ function renderRecentlyLane(model) {
1036
+ const events = model.recent ?? [];
1037
+ if (events.length === 0) return "";
1038
+ const rows = events.map((e) => {
1039
+ const icon = RECENT_ICON[e.type] ?? "\u2022";
1040
+ const when = escapeHtml(relativeTimeFromNow(e.ts));
1041
+ const site = e.siteName ? `<strong>${escapeHtml(e.siteName)}</strong> \u2014 ` : "";
1042
+ const link = e.url ? `<a href="${escapeHtml(e.url)}">view \u25B8</a>` : e.slug ? `<a href="/s/${escapeHtml(e.slug)}">open \u25B8</a>` : "";
1043
+ return `<div class="recent-row" data-type="${e.type}">
1044
+ <span class="recent-icon">${icon}</span>
1045
+ <span class="recent-what">${site}${escapeHtml(e.summary)}</span>
1046
+ <span class="muted">${when}</span>
1047
+ ${link}
1048
+ </div>`;
1049
+ }).join("");
1050
+ return `<details class="recently">
1051
+ <summary>\u{1F527} Recently (${events.length})</summary>
1052
+ ${rows}
1053
+ </details>`;
1054
+ }
991
1055
  function renderInboxLane(model) {
992
1056
  const subs = model.submissions ?? [];
993
1057
  const spam = model.spam;
@@ -1151,6 +1215,7 @@ function renderCockpitHtml(model) {
1151
1215
  ${verdictBar(model, feed.length)}
1152
1216
  ${renderNeedsYouFeed(feed)}
1153
1217
  ${renderFleetBrowsePanel(model)}
1218
+ ${renderRecentlyLane(model)}
1154
1219
  ${renderInboxLane(model)}
1155
1220
  ${AUDIT_SCRIPT}
1156
1221
  ${FLEET_BROWSE_SCRIPT}