@reddoorla/maintenance 0.55.0 → 0.56.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 (148) hide show
  1. package/README.md +3 -1
  2. package/dist/airtable-2BJX4ATJ.js +9 -0
  3. package/dist/airtable-2BJX4ATJ.js.map +1 -0
  4. package/dist/announce-JEVCDDKL.js +171 -0
  5. package/dist/announce-JEVCDDKL.js.map +1 -0
  6. package/dist/bump-deps-O3SOLQYY.js +57 -0
  7. package/dist/bump-deps-O3SOLQYY.js.map +1 -0
  8. package/dist/chunk-2X322ZES.js +99 -0
  9. package/dist/chunk-2X322ZES.js.map +1 -0
  10. package/dist/chunk-4NAOR44V.js +153 -0
  11. package/dist/chunk-4NAOR44V.js.map +1 -0
  12. package/dist/chunk-4PY4I5OR.js +1167 -0
  13. package/dist/chunk-4PY4I5OR.js.map +1 -0
  14. package/dist/chunk-5WL3ET24.js +203 -0
  15. package/dist/chunk-5WL3ET24.js.map +1 -0
  16. package/dist/chunk-6LIWDIXU.js +38 -0
  17. package/dist/chunk-6LIWDIXU.js.map +1 -0
  18. package/dist/chunk-6VAL7XJT.js +52 -0
  19. package/dist/chunk-6VAL7XJT.js.map +1 -0
  20. package/dist/chunk-BWLUNY7L.js +585 -0
  21. package/dist/chunk-BWLUNY7L.js.map +1 -0
  22. package/dist/chunk-CASCWQQX.js +161 -0
  23. package/dist/chunk-CASCWQQX.js.map +1 -0
  24. package/dist/chunk-CLVTVDOU.js +274 -0
  25. package/dist/chunk-CLVTVDOU.js.map +1 -0
  26. package/dist/chunk-D3DQFC32.js +79 -0
  27. package/dist/chunk-D3DQFC32.js.map +1 -0
  28. package/dist/chunk-DIVB6ZF7.js +217 -0
  29. package/dist/chunk-DIVB6ZF7.js.map +1 -0
  30. package/dist/chunk-EYODPUHV.js +37 -0
  31. package/dist/chunk-EYODPUHV.js.map +1 -0
  32. package/dist/chunk-FDIFTIXC.js +70 -0
  33. package/dist/chunk-FDIFTIXC.js.map +1 -0
  34. package/dist/chunk-FUD4I2ZW.js +35 -0
  35. package/dist/chunk-FUD4I2ZW.js.map +1 -0
  36. package/dist/chunk-HG5DTFNF.js +62 -0
  37. package/dist/chunk-HG5DTFNF.js.map +1 -0
  38. package/dist/chunk-HN6I2XWI.js +65 -0
  39. package/dist/chunk-HN6I2XWI.js.map +1 -0
  40. package/dist/chunk-HVOOCK6L.js +84 -0
  41. package/dist/chunk-HVOOCK6L.js.map +1 -0
  42. package/dist/chunk-IMIR7SC2.js +51 -0
  43. package/dist/chunk-IMIR7SC2.js.map +1 -0
  44. package/dist/chunk-J3OPWFZE.js +64 -0
  45. package/dist/chunk-J3OPWFZE.js.map +1 -0
  46. package/dist/chunk-JNXCDUYB.js +211 -0
  47. package/dist/chunk-JNXCDUYB.js.map +1 -0
  48. package/dist/chunk-JQG222CI.js +49 -0
  49. package/dist/chunk-JQG222CI.js.map +1 -0
  50. package/dist/chunk-LKGVSM2O.js +43 -0
  51. package/dist/chunk-LKGVSM2O.js.map +1 -0
  52. package/dist/chunk-LYCKN7ZR.js +41 -0
  53. package/dist/chunk-LYCKN7ZR.js.map +1 -0
  54. package/dist/chunk-O65I7G7G.js +246 -0
  55. package/dist/chunk-O65I7G7G.js.map +1 -0
  56. package/dist/chunk-OCS7SZUZ.js +243 -0
  57. package/dist/chunk-OCS7SZUZ.js.map +1 -0
  58. package/dist/chunk-OGGRFBL4.js +14 -0
  59. package/dist/chunk-OGGRFBL4.js.map +1 -0
  60. package/dist/chunk-OQPMZ6BM.js +368 -0
  61. package/dist/chunk-OQPMZ6BM.js.map +1 -0
  62. package/dist/chunk-OTGB6YQO.js +52 -0
  63. package/dist/chunk-OTGB6YQO.js.map +1 -0
  64. package/dist/chunk-PF6BNYGE.js +162 -0
  65. package/dist/chunk-PF6BNYGE.js.map +1 -0
  66. package/dist/chunk-QTHKOIFJ.js +13 -0
  67. package/dist/chunk-QTHKOIFJ.js.map +1 -0
  68. package/dist/chunk-U5SZ4FOP.js +26 -0
  69. package/dist/chunk-U5SZ4FOP.js.map +1 -0
  70. package/dist/chunk-VXSX2FHO.js +269 -0
  71. package/dist/chunk-VXSX2FHO.js.map +1 -0
  72. package/dist/chunk-W3G7XFBZ.js +444 -0
  73. package/dist/chunk-W3G7XFBZ.js.map +1 -0
  74. package/dist/chunk-WEKL2HYJ.js +31 -0
  75. package/dist/chunk-WEKL2HYJ.js.map +1 -0
  76. package/dist/chunk-WVRPOXKH.js +74 -0
  77. package/dist/chunk-WVRPOXKH.js.map +1 -0
  78. package/dist/chunk-XKOZUVK2.js +121 -0
  79. package/dist/chunk-XKOZUVK2.js.map +1 -0
  80. package/dist/chunk-XO2RUYVM.js +138 -0
  81. package/dist/chunk-XO2RUYVM.js.map +1 -0
  82. package/dist/chunk-XTK5VIQB.js +97 -0
  83. package/dist/chunk-XTK5VIQB.js.map +1 -0
  84. package/dist/chunk-XXTZBPUY.js +9 -0
  85. package/dist/chunk-XXTZBPUY.js.map +1 -0
  86. package/dist/chunk-ZYBOT5PM.js +153 -0
  87. package/dist/chunk-ZYBOT5PM.js.map +1 -0
  88. package/dist/cli/bin.js +75 -7785
  89. package/dist/cli/bin.js.map +1 -1
  90. package/dist/cli/commands/audit.js +37 -2336
  91. package/dist/cli/commands/audit.js.map +1 -1
  92. package/dist/client-EPVO3FEH.js +10 -0
  93. package/dist/client-EPVO3FEH.js.map +1 -0
  94. package/dist/client-JA6QLEDW.js +48 -0
  95. package/dist/client-JA6QLEDW.js.map +1 -0
  96. package/dist/configs/lighthouse.js +4 -31
  97. package/dist/configs/lighthouse.js.map +1 -1
  98. package/dist/configs/playwright-a11y.js +6 -33
  99. package/dist/configs/playwright-a11y.js.map +1 -1
  100. package/dist/convert-to-pnpm-UR64X3OX.js +51 -0
  101. package/dist/convert-to-pnpm-UR64X3OX.js.map +1 -0
  102. package/dist/db-AXKMRTVA.js +23 -0
  103. package/dist/db-AXKMRTVA.js.map +1 -0
  104. package/dist/digest-YTO73BEO.js +408 -0
  105. package/dist/digest-YTO73BEO.js.map +1 -0
  106. package/dist/forms/index.js +3 -8
  107. package/dist/forms/index.js.map +1 -1
  108. package/dist/github-signals-YD64R2AF.js +122 -0
  109. package/dist/github-signals-YD64R2AF.js.map +1 -0
  110. package/dist/index.js +102 -4757
  111. package/dist/index.js.map +1 -1
  112. package/dist/init-JITQXFVL.js +85 -0
  113. package/dist/init-JITQXFVL.js.map +1 -0
  114. package/dist/launch-HXHEL6ID.js +207 -0
  115. package/dist/launch-HXHEL6ID.js.map +1 -0
  116. package/dist/lighthouse-airtable-4FKYKFH6.js +12 -0
  117. package/dist/lighthouse-airtable-4FKYKFH6.js.map +1 -0
  118. package/dist/migrate-P2LS4WXI.js +7 -0
  119. package/dist/migrate-P2LS4WXI.js.map +1 -0
  120. package/dist/onboard-TCQOP2W4.js +69 -0
  121. package/dist/onboard-TCQOP2W4.js.map +1 -0
  122. package/dist/orchestrate-RMSBHLQK.js +19 -0
  123. package/dist/orchestrate-RMSBHLQK.js.map +1 -0
  124. package/dist/recipes/sync-configs.js +9 -505
  125. package/dist/recipes/sync-configs.js.map +1 -1
  126. package/dist/renovate-dispatch-ZGLXNPEK.js +97 -0
  127. package/dist/renovate-dispatch-ZGLXNPEK.js.map +1 -0
  128. package/dist/report-SD3CNBOQ.js +240 -0
  129. package/dist/report-SD3CNBOQ.js.map +1 -0
  130. package/dist/resend-G2BJMAQV.js +7 -0
  131. package/dist/resend-G2BJMAQV.js.map +1 -0
  132. package/dist/self-updating-CIUMOMYI.js +61 -0
  133. package/dist/self-updating-CIUMOMYI.js.map +1 -0
  134. package/dist/svelte-codemods-BYRJAU7I.js +51 -0
  135. package/dist/svelte-codemods-BYRJAU7I.js.map +1 -0
  136. package/dist/sync-configs-JPRLAW5J.js +109 -0
  137. package/dist/sync-configs-JPRLAW5J.js.map +1 -0
  138. package/dist/upgrade-ERA45EVR.js +64 -0
  139. package/dist/upgrade-ERA45EVR.js.map +1 -0
  140. package/dist/util/git.js +19 -76
  141. package/dist/util/git.js.map +1 -1
  142. package/dist/util/pkg.js +5 -45
  143. package/dist/util/pkg.js.map +1 -1
  144. package/dist/websites-X5DLC2PC.js +43 -0
  145. package/dist/websites-X5DLC2PC.js.map +1 -0
  146. package/dist/write-audits-to-airtable-CE4POBAF.js +13 -0
  147. package/dist/write-audits-to-airtable-CE4POBAF.js.map +1 -0
  148. package/package.json +13 -12
@@ -0,0 +1,10 @@
1
+ import {
2
+ openBase,
3
+ readAirtableConfig
4
+ } from "./chunk-HN6I2XWI.js";
5
+ import "./chunk-6VAL7XJT.js";
6
+ export {
7
+ openBase,
8
+ readAirtableConfig
9
+ };
10
+ //# sourceMappingURL=client-EPVO3FEH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,48 @@
1
+ import {
2
+ runMigrations
3
+ } from "./chunk-HG5DTFNF.js";
4
+ import {
5
+ defaultCredentialsPath
6
+ } from "./chunk-6VAL7XJT.js";
7
+
8
+ // src/db/client.ts
9
+ import { createClient } from "@libsql/client";
10
+ import { Kysely } from "kysely";
11
+ import { LibsqlDialect } from "@libsql/kysely-libsql";
12
+ function missing(name) {
13
+ return Object.assign(
14
+ new Error(
15
+ `${name} not set. Export it in your shell or put it in ${defaultCredentialsPath()} as ${name}=...`
16
+ ),
17
+ { exitCode: 2 }
18
+ );
19
+ }
20
+ function readDbConfig() {
21
+ const url = process.env.TURSO_DATABASE_URL;
22
+ if (!url) throw missing("TURSO_DATABASE_URL");
23
+ const authToken = process.env.TURSO_AUTH_TOKEN;
24
+ return authToken ? { url, authToken } : { url };
25
+ }
26
+ var migrationsByUrl = /* @__PURE__ */ new Map();
27
+ function ensureMigrated(url, client) {
28
+ if (url === ":memory:") return runMigrations(client).then(() => void 0);
29
+ const cached = migrationsByUrl.get(url);
30
+ if (cached) return cached;
31
+ const p = runMigrations(client).then(() => void 0).catch((err) => {
32
+ migrationsByUrl.delete(url);
33
+ throw err;
34
+ });
35
+ migrationsByUrl.set(url, p);
36
+ return p;
37
+ }
38
+ async function openDb(cfg) {
39
+ const clientConfig = cfg.authToken ? { url: cfg.url, authToken: cfg.authToken } : { url: cfg.url };
40
+ const client = createClient(clientConfig);
41
+ await ensureMigrated(cfg.url, client);
42
+ return new Kysely({ dialect: new LibsqlDialect({ client }) });
43
+ }
44
+ export {
45
+ openDb,
46
+ readDbConfig
47
+ };
48
+ //# sourceMappingURL=client-JA6QLEDW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/client.ts"],"sourcesContent":["import { createClient, type Client, type Config as LibsqlConfig } from \"@libsql/client\";\nimport { Kysely } from \"kysely\";\nimport { LibsqlDialect } from \"@libsql/kysely-libsql\";\nimport { defaultCredentialsPath } from \"../util/credentials.js\";\nimport type { Database } from \"./schema.js\";\nimport { runMigrations } from \"./migrate.js\";\n\nexport type DbConfig = { url: string; authToken?: string };\nexport type Db = Kysely<Database>;\n\nfunction missing(name: string): Error {\n return Object.assign(\n new Error(\n `${name} not set. Export it in your shell or put it in ${defaultCredentialsPath()} as ${name}=...`,\n ),\n { exitCode: 2 },\n );\n}\n\n/** Read TURSO_DATABASE_URL (+ optional TURSO_AUTH_TOKEN) from the environment,\n * mirroring readAirtableConfig. The token is optional so a local `file:`/`:memory:`\n * url works with no token. */\nexport function readDbConfig(): DbConfig {\n const url = process.env.TURSO_DATABASE_URL;\n if (!url) throw missing(\"TURSO_DATABASE_URL\");\n const authToken = process.env.TURSO_AUTH_TOKEN;\n return authToken ? { url, authToken } : { url };\n}\n\n// Migrations are idempotent but cost two Turso round-trips (CREATE TABLE IF NOT EXISTS +\n// SELECT applied ids). Running them on EVERY openDb — i.e. every warm Netlify invocation —\n// is pure latency on the form-ingest hot path and every dashboard GET. Cache the run per\n// database URL so a warm process migrates once. \":memory:\" is deliberately EXCLUDED: each\n// in-memory client is a brand-new, separate database, so a cached \"already migrated\" would\n// hand the next openDb an empty db (file:/libsql:/https: all persist → safe to cache).\nconst migrationsByUrl = new Map<string, Promise<void>>();\n\nfunction ensureMigrated(url: string, client: Client): Promise<void> {\n if (url === \":memory:\") return runMigrations(client).then(() => undefined);\n const cached = migrationsByUrl.get(url);\n if (cached) return cached;\n const p = runMigrations(client)\n .then(() => undefined)\n .catch((err: unknown) => {\n // Don't poison the process: a transient first-call failure (a Turso blip) must not\n // leave a rejected promise cached for every future openDb. Evict so the next retries.\n migrationsByUrl.delete(url);\n throw err;\n });\n migrationsByUrl.set(url, p);\n return p;\n}\n\n/** Open a libSQL-backed Kysely. Builds ONE client, ensures migrations are applied on it\n * (once per process per persistent url — see ensureMigrated), then wraps that exact client\n * in the Kysely dialect — the shared client is required so an in-memory db (per-client) is\n * the same database the queries see. The same code serves Turso (libsql:// url + token), a\n * self-hosted sqld, a local file: url, and :memory: for tests — host portability is a\n * connection-string swap. */\nexport async function openDb(cfg: DbConfig): Promise<Db> {\n const clientConfig: LibsqlConfig = cfg.authToken\n ? { url: cfg.url, authToken: cfg.authToken }\n : { url: cfg.url };\n const client: Client = createClient(clientConfig);\n await ensureMigrated(cfg.url, client);\n return new Kysely<Database>({ dialect: new LibsqlDialect({ client }) });\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,oBAA8D;AACvE,SAAS,cAAc;AACvB,SAAS,qBAAqB;AAQ9B,SAAS,QAAQ,MAAqB;AACpC,SAAO,OAAO;AAAA,IACZ,IAAI;AAAA,MACF,GAAG,IAAI,kDAAkD,uBAAuB,CAAC,OAAO,IAAI;AAAA,IAC9F;AAAA,IACA,EAAE,UAAU,EAAE;AAAA,EAChB;AACF;AAKO,SAAS,eAAyB;AACvC,QAAM,MAAM,QAAQ,IAAI;AACxB,MAAI,CAAC,IAAK,OAAM,QAAQ,oBAAoB;AAC5C,QAAM,YAAY,QAAQ,IAAI;AAC9B,SAAO,YAAY,EAAE,KAAK,UAAU,IAAI,EAAE,IAAI;AAChD;AAQA,IAAM,kBAAkB,oBAAI,IAA2B;AAEvD,SAAS,eAAe,KAAa,QAA+B;AAClE,MAAI,QAAQ,WAAY,QAAO,cAAc,MAAM,EAAE,KAAK,MAAM,MAAS;AACzE,QAAM,SAAS,gBAAgB,IAAI,GAAG;AACtC,MAAI,OAAQ,QAAO;AACnB,QAAM,IAAI,cAAc,MAAM,EAC3B,KAAK,MAAM,MAAS,EACpB,MAAM,CAAC,QAAiB;AAGvB,oBAAgB,OAAO,GAAG;AAC1B,UAAM;AAAA,EACR,CAAC;AACH,kBAAgB,IAAI,KAAK,CAAC;AAC1B,SAAO;AACT;AAQA,eAAsB,OAAO,KAA4B;AACvD,QAAM,eAA6B,IAAI,YACnC,EAAE,KAAK,IAAI,KAAK,WAAW,IAAI,UAAU,IACzC,EAAE,KAAK,IAAI,IAAI;AACnB,QAAM,SAAiB,aAAa,YAAY;AAChD,QAAM,eAAe,IAAI,KAAK,MAAM;AACpC,SAAO,IAAI,OAAiB,EAAE,SAAS,IAAI,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC;AACxE;","names":[]}
@@ -1,34 +1,7 @@
1
- // src/configs/lighthouse.ts
2
- var lighthouseConfig = {
3
- ci: {
4
- collect: {
5
- url: ["http://localhost:5173/dev/a11y-fixtures"],
6
- // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects
7
- // the `run` form too. Keeps this config portable across the fleet
8
- // while sites transition to pnpm.
9
- startServerCommand: "npm run vite:dev",
10
- startServerReadyPattern: "ready in",
11
- startServerReadyTimeout: 12e4,
12
- numberOfRuns: 1,
13
- settings: {
14
- preset: "desktop",
15
- skipAudits: ["uses-http2"]
16
- }
17
- },
18
- assert: {
19
- assertions: {
20
- "categories:accessibility": ["error", { minScore: 0.95 }],
21
- "categories:best-practices": ["error", { minScore: 0.9 }],
22
- "categories:seo": ["error", { minScore: 0.9 }],
23
- "categories:performance": ["warn", { minScore: 0.7 }]
24
- }
25
- },
26
- upload: {
27
- target: "temporary-public-storage"
28
- }
29
- }
30
- };
31
- var lighthouse_default = lighthouseConfig;
1
+ import {
2
+ lighthouseConfig,
3
+ lighthouse_default
4
+ } from "../chunk-EYODPUHV.js";
32
5
  export {
33
6
  lighthouse_default as default,
34
7
  lighthouseConfig
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/configs/lighthouse.ts"],"sourcesContent":["export const lighthouseConfig = {\n ci: {\n collect: {\n url: [\"http://localhost:5173/dev/a11y-fixtures\"],\n // `npm run vite:dev` works on both pnpm and npm sites — pnpm respects\n // the `run` form too. Keeps this config portable across the fleet\n // while sites transition to pnpm.\n startServerCommand: \"npm run vite:dev\",\n startServerReadyPattern: \"ready in\",\n startServerReadyTimeout: 120_000,\n numberOfRuns: 1,\n settings: {\n preset: \"desktop\",\n skipAudits: [\"uses-http2\"],\n },\n },\n assert: {\n assertions: {\n \"categories:accessibility\": [\"error\", { minScore: 0.95 }],\n \"categories:best-practices\": [\"error\", { minScore: 0.9 }],\n \"categories:seo\": [\"error\", { minScore: 0.9 }],\n \"categories:performance\": [\"warn\", { minScore: 0.7 }],\n },\n },\n upload: {\n target: \"temporary-public-storage\",\n },\n },\n} as const;\n\nexport default lighthouseConfig;\n"],"mappings":";AAAO,IAAM,mBAAmB;AAAA,EAC9B,IAAI;AAAA,IACF,SAAS;AAAA,MACP,KAAK,CAAC,yCAAyC;AAAA;AAAA;AAAA;AAAA,MAI/C,oBAAoB;AAAA,MACpB,yBAAyB;AAAA,MACzB,yBAAyB;AAAA,MACzB,cAAc;AAAA,MACd,UAAU;AAAA,QACR,QAAQ;AAAA,QACR,YAAY,CAAC,YAAY;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,YAAY;AAAA,QACV,4BAA4B,CAAC,SAAS,EAAE,UAAU,KAAK,CAAC;AAAA,QACxD,6BAA6B,CAAC,SAAS,EAAE,UAAU,IAAI,CAAC;AAAA,QACxD,kBAAkB,CAAC,SAAS,EAAE,UAAU,IAAI,CAAC;AAAA,QAC7C,0BAA0B,CAAC,QAAQ,EAAE,UAAU,IAAI,CAAC;AAAA,MACtD;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,IACV;AAAA,EACF;AACF;AAEA,IAAO,qBAAQ;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,36 +1,9 @@
1
- // src/configs/playwright-a11y.ts
2
- import { defineConfig, devices } from "@playwright/test";
3
- var a11yRoutes = [
4
- { path: "/dev/a11y-fixtures", name: "a11y fixtures" },
5
- { path: "/dev/animate-in", name: "animate-in demo" }
6
- ];
7
- var smokeRoutes = [{ path: "/", name: "home" }];
8
- var playwrightA11yConfig = defineConfig({
9
- testDir: "tests",
10
- testMatch: /.*\.spec\.ts$/,
11
- fullyParallel: true,
12
- forbidOnly: !!process.env.CI,
13
- retries: process.env.CI ? 2 : 0,
14
- reporter: process.env.CI ? "github" : "list",
15
- use: {
16
- baseURL: "http://localhost:5173",
17
- trace: "on-first-retry"
18
- },
19
- projects: [
20
- {
21
- name: "chromium",
22
- use: { ...devices["Desktop Chrome"] }
23
- }
24
- ],
25
- webServer: {
26
- // Portable across pnpm and npm sites — pnpm respects `npm run` too.
27
- command: "npm run vite:dev",
28
- url: "http://localhost:5173/dev/a11y-fixtures",
29
- reuseExistingServer: !process.env.CI,
30
- timeout: 12e4
31
- }
32
- });
33
- var playwright_a11y_default = playwrightA11yConfig;
1
+ import {
2
+ a11yRoutes,
3
+ playwrightA11yConfig,
4
+ playwright_a11y_default,
5
+ smokeRoutes
6
+ } from "../chunk-LYCKN7ZR.js";
34
7
  export {
35
8
  a11yRoutes,
36
9
  playwright_a11y_default as default,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/configs/playwright-a11y.ts"],"sourcesContent":["import { defineConfig, devices, type PlaywrightTestConfig } from \"@playwright/test\";\n\nexport type A11yRoute = { path: string; name: string };\n\nexport const a11yRoutes: A11yRoute[] = [\n { path: \"/dev/a11y-fixtures\", name: \"a11y fixtures\" },\n { path: \"/dev/animate-in\", name: \"animate-in demo\" },\n];\n\n// Routes smoke-loaded for client-side (hydration) errors only — NOT axe-scanned.\n// Catches the class of bug where build + SSR succeed but client hydration throws\n// and blanks the page (data-dynamiq 2026-06-09: a Svelte 4->5 `run()` referenced\n// a `$state` declared after it → TDZ ReferenceError on hydrate). `/` is the one\n// route every site has; real routes carry a11y debt we don't gate on here, so we\n// assert only that they don't crash on hydrate.\nexport const smokeRoutes: A11yRoute[] = [{ path: \"/\", name: \"home\" }];\n\nexport const playwrightA11yConfig: PlaywrightTestConfig = defineConfig({\n testDir: \"tests\",\n testMatch: /.*\\.spec\\.ts$/,\n fullyParallel: true,\n forbidOnly: !!process.env.CI,\n retries: process.env.CI ? 2 : 0,\n reporter: process.env.CI ? \"github\" : \"list\",\n use: {\n baseURL: \"http://localhost:5173\",\n trace: \"on-first-retry\",\n },\n projects: [\n {\n name: \"chromium\",\n use: { ...devices[\"Desktop Chrome\"] },\n },\n ],\n webServer: {\n // Portable across pnpm and npm sites — pnpm respects `npm run` too.\n command: \"npm run vite:dev\",\n url: \"http://localhost:5173/dev/a11y-fixtures\",\n reuseExistingServer: !process.env.CI,\n timeout: 120_000,\n },\n});\n\nexport default playwrightA11yConfig;\n"],"mappings":";AAAA,SAAS,cAAc,eAA0C;AAI1D,IAAM,aAA0B;AAAA,EACrC,EAAE,MAAM,sBAAsB,MAAM,gBAAgB;AAAA,EACpD,EAAE,MAAM,mBAAmB,MAAM,kBAAkB;AACrD;AAQO,IAAM,cAA2B,CAAC,EAAE,MAAM,KAAK,MAAM,OAAO,CAAC;AAE7D,IAAM,uBAA6C,aAAa;AAAA,EACrE,SAAS;AAAA,EACT,WAAW;AAAA,EACX,eAAe;AAAA,EACf,YAAY,CAAC,CAAC,QAAQ,IAAI;AAAA,EAC1B,SAAS,QAAQ,IAAI,KAAK,IAAI;AAAA,EAC9B,UAAU,QAAQ,IAAI,KAAK,WAAW;AAAA,EACtC,KAAK;AAAA,IACH,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA,EACA,UAAU;AAAA,IACR;AAAA,MACE,MAAM;AAAA,MACN,KAAK,EAAE,GAAG,QAAQ,gBAAgB,EAAE;AAAA,IACtC;AAAA,EACF;AAAA,EACA,WAAW;AAAA;AAAA,IAET,SAAS;AAAA,IACT,KAAK;AAAA,IACL,qBAAqB,CAAC,QAAQ,IAAI;AAAA,IAClC,SAAS;AAAA,EACX;AACF,CAAC;AAED,IAAO,0BAAQ;","names":[]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,51 @@
1
+ import {
2
+ appendSkipNotice,
3
+ fleetWorkdir,
4
+ prepareFleetSites
5
+ } from "./chunk-4NAOR44V.js";
6
+ import {
7
+ resolveSites
8
+ } from "./chunk-J3OPWFZE.js";
9
+ import {
10
+ convertToPnpm
11
+ } from "./chunk-2X322ZES.js";
12
+ import "./chunk-XKOZUVK2.js";
13
+ import "./chunk-OTGB6YQO.js";
14
+ import "./chunk-FDIFTIXC.js";
15
+ import "./chunk-HVOOCK6L.js";
16
+ import "./chunk-XXTZBPUY.js";
17
+ import "./chunk-XTK5VIQB.js";
18
+ import "./chunk-U5SZ4FOP.js";
19
+
20
+ // src/cli/commands/convert-to-pnpm.ts
21
+ import { resolve } from "path";
22
+ function formatResult(r) {
23
+ if (r.status === "noop") return `[${r.site}] noop: ${r.notes ?? ""}`;
24
+ if (r.status === "failed") return `[${r.site}] failed: ${r.notes ?? ""}`;
25
+ return `[${r.site}] applied: ${r.commits.length} commit(s)
26
+ ${r.notes ?? ""}`;
27
+ }
28
+ async function runConvertToPnpmCommand(site, opts) {
29
+ const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();
30
+ let sites = await resolveSites({
31
+ ...site !== void 0 ? { site } : {},
32
+ ...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
33
+ cwd
34
+ });
35
+ let skipped = [];
36
+ if (opts.fleet) {
37
+ const workdir = opts.workdir ?? fleetWorkdir();
38
+ const prep = await prepareFleetSites(sites, { workdir });
39
+ sites = prep.prepared;
40
+ skipped = prep.skipped;
41
+ }
42
+ const results = [];
43
+ for (const s of sites) results.push(await convertToPnpm(s));
44
+ const output = results.map(formatResult).join("\n");
45
+ const code = results.some((r) => r.status === "failed") ? 1 : 0;
46
+ return { output: appendSkipNotice(output, skipped), code };
47
+ }
48
+ export {
49
+ runConvertToPnpmCommand
50
+ };
51
+ //# sourceMappingURL=convert-to-pnpm-UR64X3OX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/convert-to-pnpm.ts"],"sourcesContent":["import { resolve } from \"node:path\";\nimport { convertToPnpm } from \"../../recipes/convert-to-pnpm.js\";\nimport type { RecipeResult } from \"../../types.js\";\nimport { resolveSites } from \"../fleet/resolve-sites.js\";\nimport { prepareFleetSites, appendSkipNotice, type SkippedSite } from \"../fleet/prepare-sites.js\";\nimport { fleetWorkdir } from \"../../util/fleet-workdir.js\";\n\nexport type ConvertToPnpmCommandOptions = {\n fleet?: string;\n workdir?: string;\n cwd?: string;\n};\n\nfunction formatResult(r: RecipeResult): string {\n if (r.status === \"noop\") return `[${r.site}] noop: ${r.notes ?? \"\"}`;\n if (r.status === \"failed\") return `[${r.site}] failed: ${r.notes ?? \"\"}`;\n return `[${r.site}] applied: ${r.commits.length} commit(s)\\n${r.notes ?? \"\"}`;\n}\n\nexport async function runConvertToPnpmCommand(\n site: string | undefined,\n opts: ConvertToPnpmCommandOptions,\n): Promise<{ output: string; code: number }> {\n const cwd = opts.cwd ? resolve(opts.cwd) : process.cwd();\n\n let sites = await resolveSites({\n ...(site !== undefined ? { site } : {}),\n ...(opts.fleet !== undefined ? { fleet: opts.fleet } : {}),\n cwd,\n });\n\n let skipped: SkippedSite[] = [];\n if (opts.fleet) {\n const workdir = opts.workdir ?? fleetWorkdir();\n const prep = await prepareFleetSites(sites, { workdir });\n sites = prep.prepared;\n skipped = prep.skipped;\n }\n\n const results: RecipeResult[] = [];\n for (const s of sites) results.push(await convertToPnpm(s));\n\n const output = results.map(formatResult).join(\"\\n\");\n const code = results.some((r) => r.status === \"failed\") ? 1 : 0;\n return { output: appendSkipNotice(output, skipped), code };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,SAAS,eAAe;AAaxB,SAAS,aAAa,GAAyB;AAC7C,MAAI,EAAE,WAAW,OAAQ,QAAO,IAAI,EAAE,IAAI,WAAW,EAAE,SAAS,EAAE;AAClE,MAAI,EAAE,WAAW,SAAU,QAAO,IAAI,EAAE,IAAI,aAAa,EAAE,SAAS,EAAE;AACtE,SAAO,IAAI,EAAE,IAAI,cAAc,EAAE,QAAQ,MAAM;AAAA,EAAe,EAAE,SAAS,EAAE;AAC7E;AAEA,eAAsB,wBACpB,MACA,MAC2C;AAC3C,QAAM,MAAM,KAAK,MAAM,QAAQ,KAAK,GAAG,IAAI,QAAQ,IAAI;AAEvD,MAAI,QAAQ,MAAM,aAAa;AAAA,IAC7B,GAAI,SAAS,SAAY,EAAE,KAAK,IAAI,CAAC;AAAA,IACrC,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,IACxD;AAAA,EACF,CAAC;AAED,MAAI,UAAyB,CAAC;AAC9B,MAAI,KAAK,OAAO;AACd,UAAM,UAAU,KAAK,WAAW,aAAa;AAC7C,UAAM,OAAO,MAAM,kBAAkB,OAAO,EAAE,QAAQ,CAAC;AACvD,YAAQ,KAAK;AACb,cAAU,KAAK;AAAA,EACjB;AAEA,QAAM,UAA0B,CAAC;AACjC,aAAW,KAAK,MAAO,SAAQ,KAAK,MAAM,cAAc,CAAC,CAAC;AAE1D,QAAM,SAAS,QAAQ,IAAI,YAAY,EAAE,KAAK,IAAI;AAClD,QAAM,OAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ,IAAI,IAAI;AAC9D,SAAO,EAAE,QAAQ,iBAAiB,QAAQ,OAAO,GAAG,KAAK;AAC3D;","names":[]}
@@ -0,0 +1,23 @@
1
+ // src/cli/commands/db.ts
2
+ async function runDbCommand(action, opts) {
3
+ if (action === "migrate") {
4
+ const { readDbConfig } = await import("./client-JA6QLEDW.js");
5
+ const cfg = opts.url ? { url: opts.url } : readDbConfig();
6
+ const { runMigrations } = await import("./migrate-P2LS4WXI.js");
7
+ const { createClient } = await import("@libsql/client");
8
+ const client = createClient(cfg.url === ":memory:" ? { url: ":memory:" } : cfg);
9
+ const ran = await runMigrations(client);
10
+ return {
11
+ output: ran.length ? `Applied migrations: ${ran.join(", ")}` : "Already up to date.",
12
+ code: 0
13
+ };
14
+ }
15
+ return {
16
+ output: `unknown db action '${action}'. Use: migrate.`,
17
+ code: 1
18
+ };
19
+ }
20
+ export {
21
+ runDbCommand
22
+ };
23
+ //# sourceMappingURL=db-AXKMRTVA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli/commands/db.ts"],"sourcesContent":["export type DbCommandOptions = {\n /** Override the libSQL url (tests use \":memory:\"); otherwise read from env. */\n url?: string;\n cwd?: string;\n verbose?: boolean;\n};\n\n/** `db <action>` — migrate. The db layer is imported dynamically so a non-db\n * CLI invocation (and `--help`) never loads @libsql/client. Config is resolved\n * inside each branch so an unknown action returns without needing any Turso env. */\nexport async function runDbCommand(\n action: string,\n opts: DbCommandOptions,\n): Promise<{ output: string; code: number }> {\n if (action === \"migrate\") {\n const { readDbConfig } = await import(\"../../db/client.js\");\n const cfg = opts.url ? { url: opts.url } : readDbConfig();\n const { runMigrations } = await import(\"../../db/migrate.js\");\n const { createClient } = await import(\"@libsql/client\");\n const client = createClient(cfg.url === \":memory:\" ? { url: \":memory:\" } : cfg);\n const ran = await runMigrations(client);\n return {\n output: ran.length ? `Applied migrations: ${ran.join(\", \")}` : \"Already up to date.\",\n code: 0,\n };\n }\n\n return {\n output: `unknown db action '${action}'. Use: migrate.`,\n code: 1,\n };\n}\n"],"mappings":";AAUA,eAAsB,aACpB,QACA,MAC2C;AAC3C,MAAI,WAAW,WAAW;AACxB,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,sBAAoB;AAC1D,UAAM,MAAM,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,aAAa;AACxD,UAAM,EAAE,cAAc,IAAI,MAAM,OAAO,uBAAqB;AAC5D,UAAM,EAAE,aAAa,IAAI,MAAM,OAAO,gBAAgB;AACtD,UAAM,SAAS,aAAa,IAAI,QAAQ,aAAa,EAAE,KAAK,WAAW,IAAI,GAAG;AAC9E,UAAM,MAAM,MAAM,cAAc,MAAM;AACtC,WAAO;AAAA,MACL,QAAQ,IAAI,SAAS,uBAAuB,IAAI,KAAK,IAAI,CAAC,KAAK;AAAA,MAC/D,MAAM;AAAA,IACR;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ,sBAAsB,MAAM;AAAA,IACpC,MAAM;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,408 @@
1
+ import {
2
+ isIdempotencyConflict
3
+ } from "./chunk-OGGRFBL4.js";
4
+ import {
5
+ escapeHtml,
6
+ isPendingApproval,
7
+ listAllReports
8
+ } from "./chunk-OCS7SZUZ.js";
9
+ import {
10
+ listWebsites,
11
+ siteSlug
12
+ } from "./chunk-CLVTVDOU.js";
13
+ import {
14
+ openBase,
15
+ readAirtableConfig
16
+ } from "./chunk-HN6I2XWI.js";
17
+ import {
18
+ defaultResendClient
19
+ } from "./chunk-WEKL2HYJ.js";
20
+ import "./chunk-6VAL7XJT.js";
21
+
22
+ // src/alerts/digest-collectors.ts
23
+ function dashboardUrl(baseUrl, siteName) {
24
+ return `${baseUrl.replace(/\/$/, "")}/s/${siteSlug(siteName)}`;
25
+ }
26
+ var GITHUB_SIGNALS_STALE_DAYS = 3;
27
+ var MS_PER_DAY = 24 * 60 * 60 * 1e3;
28
+ function gitHubSignalsStale(swept, now) {
29
+ if (swept === null) return true;
30
+ const ageMs = now.getTime() - Date.parse(swept);
31
+ if (!Number.isFinite(ageMs)) return true;
32
+ return ageMs > GITHUB_SIGNALS_STALE_DAYS * MS_PER_DAY;
33
+ }
34
+ function collectVulnAlerts(sites, baseUrl) {
35
+ const items = [];
36
+ for (const s of sites) {
37
+ const critical = s.securityVulnsCritical ?? 0;
38
+ const high = s.securityVulnsHigh ?? 0;
39
+ const metric = critical + high;
40
+ if (metric <= 0) continue;
41
+ items.push({
42
+ key: `vuln:${s.id}`,
43
+ kind: "vuln",
44
+ siteName: s.name,
45
+ title: `${metric} critical/high ${metric === 1 ? "vuln" : "vulns"}`,
46
+ url: dashboardUrl(baseUrl, s.name),
47
+ severity: critical > 0 ? "critical" : "warning",
48
+ metric
49
+ });
50
+ }
51
+ return items;
52
+ }
53
+ var LIGHTHOUSE_FLOOR = 75;
54
+ var LIGHTHOUSE_CATEGORIES = [
55
+ { field: "pScore", slug: "performance", label: "Performance" },
56
+ { field: "rScore", slug: "accessibility", label: "Accessibility" },
57
+ { field: "bpScore", slug: "best-practices", label: "Best Practices" },
58
+ { field: "seoScore", slug: "seo", label: "SEO" }
59
+ ];
60
+ function collectLighthouseAlerts(sites, baseUrl) {
61
+ const items = [];
62
+ for (const s of sites) {
63
+ for (const cat of LIGHTHOUSE_CATEGORIES) {
64
+ const score = s[cat.field];
65
+ if (score === null || score >= LIGHTHOUSE_FLOOR) continue;
66
+ items.push({
67
+ key: `lighthouse:${s.id}:${cat.slug}`,
68
+ kind: "lighthouse",
69
+ siteName: s.name,
70
+ title: `Lighthouse ${cat.label} ${score} (below ${LIGHTHOUSE_FLOOR})`,
71
+ url: dashboardUrl(baseUrl, s.name),
72
+ severity: "warning",
73
+ metric: 100 - score
74
+ });
75
+ }
76
+ }
77
+ return items;
78
+ }
79
+ function collectDeliveryFailures(reports, sitesById, baseUrl) {
80
+ const items = [];
81
+ for (const r of reports) {
82
+ if (r.deliveryStatus !== "bounced" && r.deliveryStatus !== "complained") continue;
83
+ const site = sitesById.get(r.siteId);
84
+ if (!site) continue;
85
+ const complained = r.deliveryStatus === "complained";
86
+ items.push({
87
+ key: `delivery:${r.id}`,
88
+ kind: "delivery",
89
+ siteName: site.name,
90
+ title: complained ? "Spam complaint on a sent report" : "A sent report bounced",
91
+ url: dashboardUrl(baseUrl, site.name),
92
+ severity: complained ? "critical" : "warning",
93
+ metric: 1
94
+ });
95
+ }
96
+ return items;
97
+ }
98
+ function collectRenovateAlerts(sites, baseUrl, now = /* @__PURE__ */ new Date()) {
99
+ const items = [];
100
+ for (const s of sites) {
101
+ if (gitHubSignalsStale(s.githubSignalsAt, now)) continue;
102
+ const n = s.renovateFailingCis ?? 0;
103
+ if (n <= 0) continue;
104
+ items.push({
105
+ key: `renovate:${s.id}`,
106
+ kind: "renovate",
107
+ siteName: s.name,
108
+ title: `${n} Renovate ${n === 1 ? "PR" : "PRs"} failing CI`,
109
+ url: dashboardUrl(baseUrl, s.name),
110
+ severity: "warning",
111
+ metric: n
112
+ });
113
+ }
114
+ return items;
115
+ }
116
+ function collectCiAlerts(sites, baseUrl, now = /* @__PURE__ */ new Date()) {
117
+ const items = [];
118
+ for (const s of sites) {
119
+ if (gitHubSignalsStale(s.githubSignalsAt, now)) continue;
120
+ if (s.defaultBranchCi !== "failing") continue;
121
+ items.push({
122
+ key: `ci:${s.id}`,
123
+ kind: "ci",
124
+ siteName: s.name,
125
+ title: "Default-branch CI failing",
126
+ url: dashboardUrl(baseUrl, s.name),
127
+ severity: "warning",
128
+ metric: 1
129
+ });
130
+ }
131
+ return items;
132
+ }
133
+ var ANALYTICS_SOFT_FAIL_STALE_DAYS = 45;
134
+ function collectAnalyticsFailures(sites, baseUrl, now = /* @__PURE__ */ new Date()) {
135
+ const items = [];
136
+ for (const s of sites) {
137
+ const at = s.analyticsSoftFailAt;
138
+ if (at === null) continue;
139
+ const ageMs = now.getTime() - Date.parse(at);
140
+ if (Number.isFinite(ageMs) && ageMs > ANALYTICS_SOFT_FAIL_STALE_DAYS * MS_PER_DAY) continue;
141
+ items.push({
142
+ key: `analytics:${s.id}`,
143
+ kind: "analytics",
144
+ siteName: s.name,
145
+ title: "GA/Search enrichment failing (analytics blank)",
146
+ url: dashboardUrl(baseUrl, s.name),
147
+ severity: "warning",
148
+ metric: 1
149
+ });
150
+ }
151
+ return items;
152
+ }
153
+
154
+ // src/alerts/digest-state.ts
155
+ function diffAttention(items, prior, today) {
156
+ const tagged = [];
157
+ const next = {};
158
+ for (const it of items) {
159
+ const was = prior[it.key];
160
+ let status;
161
+ let firstFlaggedAt;
162
+ if (!was) {
163
+ status = "new";
164
+ firstFlaggedAt = today;
165
+ } else if (it.metric > was.metric) {
166
+ status = "worse";
167
+ firstFlaggedAt = was.firstFlaggedAt;
168
+ } else {
169
+ status = "standing";
170
+ firstFlaggedAt = was.firstFlaggedAt;
171
+ }
172
+ tagged.push({ ...it, status });
173
+ next[it.key] = { metric: it.metric, firstFlaggedAt };
174
+ }
175
+ return { tagged, next };
176
+ }
177
+ var DIGEST_STATE_TABLE = "Digest State";
178
+ async function readDigestState(base) {
179
+ const rows = [];
180
+ await base(DIGEST_STATE_TABLE).select({ maxRecords: 1, pageSize: 1 }).eachPage((records, fetchNextPage) => {
181
+ for (const rec of records) rows.push({ id: rec.id, fields: rec.fields });
182
+ fetchNextPage();
183
+ });
184
+ const first = rows[0];
185
+ if (!first) return {};
186
+ const raw = first.fields["Snapshot"];
187
+ if (typeof raw !== "string") return {};
188
+ try {
189
+ return JSON.parse(raw);
190
+ } catch {
191
+ return {};
192
+ }
193
+ }
194
+ async function writeDigestState(base, snap, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
195
+ const rows = [];
196
+ await base(DIGEST_STATE_TABLE).select({ maxRecords: 1, pageSize: 1 }).eachPage((records, fetchNextPage) => {
197
+ for (const rec of records) rows.push({ id: rec.id });
198
+ fetchNextPage();
199
+ });
200
+ const fields = {
201
+ Snapshot: JSON.stringify(snap),
202
+ "Updated At": updatedAt
203
+ };
204
+ const existing = rows[0];
205
+ if (existing) {
206
+ await base(DIGEST_STATE_TABLE).update([{ id: existing.id, fields }]);
207
+ } else {
208
+ await base(DIGEST_STATE_TABLE).create([{ fields }]);
209
+ }
210
+ }
211
+
212
+ // src/reports/digest.ts
213
+ var GREY = "#757575";
214
+ var RED = "#C00";
215
+ var ANCHOR_STYLE = `color:${RED};font-family:helvetica,sans-serif`;
216
+ function readySection(items) {
217
+ const heading = `<h2 style="color:${RED};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Ready for your yes</h2>`;
218
+ if (items.length === 0) {
219
+ return `${heading}<p style="color:${GREY};font-family:helvetica,sans-serif;font-size:16px;margin:0">Nothing waiting on you.</p>`;
220
+ }
221
+ const rows = items.map((it) => {
222
+ const safeUrl = it.dashboardUrl.startsWith("https://") ? it.dashboardUrl : void 0;
223
+ const link = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">review &amp; approve</a>` : `review &amp; approve`;
224
+ return `
225
+ <tr>
226
+ <td style="color:${GREY};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">
227
+ <strong style="color:#222">${escapeHtml(it.siteName)}</strong> \u2014 ${escapeHtml(it.reportType)} (${escapeHtml(it.period)})
228
+ \u2014 ${link}
229
+ </td>
230
+ </tr>`;
231
+ }).join("");
232
+ return `${heading}<table role="presentation" style="border-collapse:collapse;margin:0">${rows}</table>`;
233
+ }
234
+ var SEVERITY_ORDER = { critical: 0, warning: 1 };
235
+ function attentionBadge(status) {
236
+ if (status === "new")
237
+ return `<strong style="color:${RED};font-family:helvetica,sans-serif">NEW</strong> `;
238
+ if (status === "worse")
239
+ return `<strong style="color:${RED};font-family:helvetica,sans-serif">WORSE</strong> `;
240
+ return "";
241
+ }
242
+ function attentionSection(items) {
243
+ const heading = `<h2 style="color:${RED};font-family:helvetica,sans-serif;font-size:20px;font-weight:700;margin:32px 0 8px">Needs attention</h2>`;
244
+ if (items.length === 0) {
245
+ return `${heading}<p style="color:${GREY};font-family:helvetica,sans-serif;font-size:16px;margin:0">All clear \u2014 nothing needs attention.</p>`;
246
+ }
247
+ const bySite = /* @__PURE__ */ new Map();
248
+ for (const it of items) {
249
+ const bucket = bySite.get(it.siteName);
250
+ if (bucket) bucket.push(it);
251
+ else bySite.set(it.siteName, [it]);
252
+ }
253
+ const groups = [...bySite.entries()].map(([siteName, siteItems]) => {
254
+ const sorted = [...siteItems].sort(
255
+ (a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]
256
+ );
257
+ const rows = sorted.map((it) => {
258
+ const safeUrl = it.url?.startsWith("https://") ? it.url : void 0;
259
+ const titleHtml = safeUrl ? `<a href="${escapeHtml(safeUrl)}" style="${ANCHOR_STYLE}">${escapeHtml(it.title)}</a>` : escapeHtml(it.title);
260
+ return `
261
+ <tr>
262
+ <td style="color:${GREY};font-family:helvetica,sans-serif;font-size:16px;line-height:24px;padding-bottom:8px">${attentionBadge(it.status)}${titleHtml}</td>
263
+ </tr>`;
264
+ }).join("");
265
+ return `
266
+ <tr>
267
+ <td style="color:#222;font-family:helvetica,sans-serif;font-size:16px;font-weight:700;padding:8px 0 4px">${escapeHtml(siteName)}</td>
268
+ </tr>
269
+ ${rows}`;
270
+ }).join("");
271
+ return `${heading}<table role="presentation" style="border-collapse:collapse;margin:0">${groups}</table>`;
272
+ }
273
+ var FROM_ADDRESS = "Reddoor Reports <reports@reddoorla.com>";
274
+ var DIGEST_OPERATOR_FALLBACK = "info@reddoorla.com";
275
+ function digestDateKey(d) {
276
+ return d.toISOString().slice(0, 10);
277
+ }
278
+ async function listPendingApproval(base) {
279
+ return (await listAllReports(base)).filter(isPendingApproval);
280
+ }
281
+ function runCollector(label, fn) {
282
+ try {
283
+ return fn();
284
+ } catch (e) {
285
+ console.warn(`\u26A0 attention collector "${label}" failed: ${e.message}`);
286
+ return [];
287
+ }
288
+ }
289
+ async function collectAttention(deps) {
290
+ const reports = deps.reports ?? await listAllReports(deps.base);
291
+ const websites = deps.websites ?? await listWebsites(deps.base);
292
+ const now = deps.now ?? /* @__PURE__ */ new Date();
293
+ const sitesById = new Map(websites.map((w) => [w.id, w]));
294
+ return [
295
+ ...runCollector("vuln", () => collectVulnAlerts(websites, deps.baseUrl)),
296
+ ...runCollector("delivery", () => collectDeliveryFailures(reports, sitesById, deps.baseUrl)),
297
+ ...runCollector("lighthouse", () => collectLighthouseAlerts(websites, deps.baseUrl)),
298
+ ...runCollector("renovate", () => collectRenovateAlerts(websites, deps.baseUrl, now)),
299
+ ...runCollector("ci", () => collectCiAlerts(websites, deps.baseUrl, now)),
300
+ ...runCollector("analytics", () => collectAnalyticsFailures(websites, deps.baseUrl, now))
301
+ ];
302
+ }
303
+ async function runDigest(options) {
304
+ const today = /* @__PURE__ */ new Date();
305
+ try {
306
+ const base = options.base ?? openBase(readAirtableConfig());
307
+ const reports = await listAllReports(base);
308
+ const websites = await listWebsites(base);
309
+ const sites = new Map(websites.map((w) => [w.id, w]));
310
+ const pending = reports.filter(isPendingApproval);
311
+ const readyForYourYes = [];
312
+ const baseUrl = options.baseUrl.replace(/\/$/, "");
313
+ for (const r of pending) {
314
+ const site = sites.get(r.siteId);
315
+ if (!site) continue;
316
+ const slug = siteSlug(site.name);
317
+ readyForYourYes.push({
318
+ siteName: site.name,
319
+ reportType: r.reportType,
320
+ period: r.period ?? "\u2014",
321
+ dashboardUrl: slug ? `${baseUrl}/s/${slug}` : baseUrl
322
+ });
323
+ }
324
+ const collected = await collectAttention({
325
+ base,
326
+ baseUrl: options.baseUrl,
327
+ websites,
328
+ reports,
329
+ now: today
330
+ });
331
+ const prior = await readDigestState(base);
332
+ const { tagged, next } = diffAttention(collected, prior, digestDateKey(today));
333
+ const needsAttention = tagged;
334
+ if (readyForYourYes.length === 0 && needsAttention.length === 0) {
335
+ try {
336
+ await writeDigestState(base, next);
337
+ } catch (e) {
338
+ console.warn(`\u26A0 digest state write failed: ${e.message}`);
339
+ }
340
+ return { output: "Digest skipped (nothing ready, nothing needs attention).", code: 0 };
341
+ }
342
+ const html = renderDigestHtml({ readyForYourYes, needsAttention });
343
+ const client = options.resend ?? defaultResendClient();
344
+ const to = [process.env.OPERATOR_EMAIL?.trim() || DIGEST_OPERATOR_FALLBACK];
345
+ const n = readyForYourYes.length;
346
+ const reportWord = n === 1 ? "report" : "reports";
347
+ let result;
348
+ try {
349
+ result = await client.send({
350
+ from: FROM_ADDRESS,
351
+ to,
352
+ subject: `Your fleet \u2014 ${digestDateKey(today)}: ${n} ${reportWord} ready for your yes`,
353
+ html,
354
+ idempotencyKey: `digest-${digestDateKey(today)}`
355
+ });
356
+ } catch (err) {
357
+ if (isIdempotencyConflict(err)) {
358
+ return {
359
+ output: "Digest already sent today (content changed since the first send) \u2014 skipped to avoid a duplicate.",
360
+ code: 0
361
+ };
362
+ }
363
+ throw err;
364
+ }
365
+ try {
366
+ await writeDigestState(base, next);
367
+ } catch (e) {
368
+ console.warn(`\u26A0 digest state write failed: ${e.message}`);
369
+ }
370
+ return { output: `Digest sent to ${to.join(", ")} (${result.messageId})`, code: 0 };
371
+ } catch (err) {
372
+ if (typeof err.exitCode === "number") {
373
+ throw err;
374
+ }
375
+ const message = err instanceof Error ? err.message : String(err);
376
+ return { output: `digest failed: ${message}`, code: 1 };
377
+ }
378
+ }
379
+ function renderDigestHtml(sections) {
380
+ return `<!doctype html>
381
+ <html>
382
+ <head><meta charset="utf-8"></head>
383
+ <body style="margin:0;padding:0;background:#ffffff">
384
+ <table width="100%" style="border-collapse:collapse">
385
+ <tr>
386
+ <td align="center" style="padding:24px">
387
+ <table width="600" style="border-collapse:collapse">
388
+ <tr>
389
+ <td>
390
+ <h1 style="color:${RED};font-family:helvetica,sans-serif;font-size:24px;font-weight:700;margin:0 0 8px">Your fleet today</h1>
391
+ ${readySection(sections.readyForYourYes)}
392
+ ${attentionSection(sections.needsAttention)}
393
+ </td>
394
+ </tr>
395
+ </table>
396
+ </td>
397
+ </tr>
398
+ </table>
399
+ </body>
400
+ </html>`;
401
+ }
402
+ export {
403
+ collectAttention,
404
+ listPendingApproval,
405
+ renderDigestHtml,
406
+ runDigest
407
+ };
408
+ //# sourceMappingURL=digest-YTO73BEO.js.map