@reddoorla/maintenance 0.90.0 → 0.91.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.
- package/dist/{announce-NDTLRQCN.js → announce-6OS4C57D.js} +3 -3
- package/dist/{blux-YGBGS24U.js → blux-NXQLMS4W.js} +4 -4
- package/dist/blux-NXQLMS4W.js.map +1 -0
- package/dist/{chunk-WS6NU475.js → chunk-22GKJO5F.js} +2 -2
- package/dist/{chunk-NYUYNYQL.js → chunk-7MKT4I5T.js} +15 -1
- package/dist/chunk-7MKT4I5T.js.map +1 -0
- package/dist/{chunk-DXWLWR4Q.js → chunk-A6T2R63Z.js} +11 -2
- package/dist/chunk-A6T2R63Z.js.map +1 -0
- package/dist/{chunk-W45FZEGE.js → chunk-DMPHP7UP.js} +2 -2
- package/dist/{chunk-KGUKL3CV.js → chunk-E22WAIF2.js} +2 -2
- package/dist/{chunk-VBEKL445.js → chunk-FCS36FDT.js} +2 -2
- package/dist/{chunk-5PWB3JHJ.js → chunk-H6BLTEFN.js} +27 -8
- package/dist/chunk-H6BLTEFN.js.map +1 -0
- package/dist/{chunk-ZOCJBJQV.js → chunk-MVDMKKBN.js} +2 -2
- package/dist/{chunk-MFHS7IQ2.js → chunk-OCNSIBMP.js} +27 -1
- package/dist/{chunk-MFHS7IQ2.js.map → chunk-OCNSIBMP.js.map} +1 -1
- package/dist/{chunk-DFLN2KO3.js → chunk-QD427NIO.js} +2 -2
- package/dist/chunk-RQLXEMEE.js +496 -0
- package/dist/chunk-RQLXEMEE.js.map +1 -0
- package/dist/{chunk-DCDF5A7N.js → chunk-UXJZU23T.js} +3 -3
- package/dist/chunk-WVVT6SJK.js +3382 -0
- package/dist/chunk-WVVT6SJK.js.map +1 -0
- package/dist/{chunk-O3GT46R2.js → chunk-YLYXW5PH.js} +14 -2
- package/dist/{chunk-O3GT46R2.js.map → chunk-YLYXW5PH.js.map} +1 -1
- package/dist/cli/bin.js +20 -17
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +7 -7
- package/dist/client-M2V6FHH5.js +11 -0
- package/dist/configs/eslint.js +5 -1
- package/dist/configs/eslint.js.map +1 -1
- package/dist/configs/playwright-a11y.js +1 -1
- package/dist/{db-LOSJ74ZJ.js → db-IMMQSRAB.js} +9 -9
- package/dist/{digest-CS6KLCZB.js → digest-EOA4JLTV.js} +13 -13
- package/dist/{digest-collectors-NI2M3UTC.js → digest-collectors-ZA3BTS3R.js} +7 -7
- package/dist/{email-LJQT5J7F.js → email-PLPDJUDH.js} +4 -3
- package/dist/{email-LJQT5J7F.js.map → email-PLPDJUDH.js.map} +1 -1
- package/dist/{ensure-site-FFDUMY6E.js → ensure-site-SF6KICTN.js} +2 -2
- package/dist/{forms-notify-target-IHJX7NWA.js → forms-notify-target-YCZUAHTY.js} +2 -2
- package/dist/{github-signals-FCLNHKMF.js → github-signals-3WLIJ5RR.js} +5 -5
- package/dist/{header-image-7A7XLVEU.js → header-image-DOCDFFRO.js} +2 -2
- package/dist/{health-mirror-GMSF3Z5R.js → health-mirror-55RRU6BI.js} +3 -3
- package/dist/index.js +11 -11
- package/dist/{init-S4MJ3Y6W.js → init-NXZ4NXTL.js} +5 -5
- package/dist/{launch-7FT4EYEO.js → launch-WPEQ3TY2.js} +5 -5
- package/dist/migrate-K4JETR36.js +7 -0
- package/dist/{orchestrate-6HROYTM6.js → orchestrate-KMQIUP3H.js} +5 -5
- package/dist/pipeline-HQJYZ443.js +29 -0
- package/dist/{preflight-LIN5NCYU.js → preflight-VPHQYWTY.js} +6 -6
- package/dist/{prismic-models-VNGIKS2B.js → prismic-models-FPDSUXDO.js} +2 -2
- package/dist/prospect/types.d.ts +792 -2
- package/dist/prospect/types.js +8 -0
- package/dist/{prospect-audit-JCWTKJTS.js → prospect-audit-TFIXSTE5.js} +27 -6
- package/dist/prospect-audit-TFIXSTE5.js.map +1 -0
- package/dist/{prospect-audits-3PMIO73D.js → prospect-audits-RVYEA4KH.js} +6 -4
- package/dist/recipes/sync-configs.js +1 -1
- package/dist/render-3GYW2J34.js +11 -0
- package/dist/{report-AAY2HZZR.js → report-Y4LXDHRV.js} +11 -11
- package/dist/{report-mirror-GMNSH5HF.js → report-mirror-6ZDCAQZB.js} +3 -3
- package/dist/{schema-75PJECAE.js → schema-2V7BGYVT.js} +1 -1
- package/dist/{schema-75PJECAE.js.map → schema-2V7BGYVT.js.map} +1 -1
- package/dist/{selftest-CUX2FIQD.js → selftest-IFGSJP4X.js} +5 -5
- package/dist/{site-mirror-GCFLBBWK.js → site-mirror-3E7JYTJG.js} +3 -3
- package/dist/{submissions-7LGJJSDL.js → submissions-UMJS3YST.js} +2 -2
- package/dist/{sync-configs-ZYLTMUNX.js → sync-configs-PVRLZRKL.js} +2 -2
- package/package.json +2 -1
- package/dist/blux-YGBGS24U.js.map +0 -1
- package/dist/chunk-5PWB3JHJ.js.map +0 -1
- package/dist/chunk-DXKF552B.js +0 -1630
- package/dist/chunk-DXKF552B.js.map +0 -1
- package/dist/chunk-DXWLWR4Q.js.map +0 -1
- package/dist/chunk-NYUYNYQL.js.map +0 -1
- package/dist/client-JYKPVCJX.js +0 -11
- package/dist/migrate-YITCXBLS.js +0 -7
- package/dist/pipeline-MQJD4CKN.js +0 -16
- package/dist/prospect-audit-JCWTKJTS.js.map +0 -1
- package/dist/render-P3SBUFVB.js +0 -10
- /package/dist/{announce-NDTLRQCN.js.map → announce-6OS4C57D.js.map} +0 -0
- /package/dist/{chunk-WS6NU475.js.map → chunk-22GKJO5F.js.map} +0 -0
- /package/dist/{chunk-W45FZEGE.js.map → chunk-DMPHP7UP.js.map} +0 -0
- /package/dist/{chunk-KGUKL3CV.js.map → chunk-E22WAIF2.js.map} +0 -0
- /package/dist/{chunk-VBEKL445.js.map → chunk-FCS36FDT.js.map} +0 -0
- /package/dist/{chunk-ZOCJBJQV.js.map → chunk-MVDMKKBN.js.map} +0 -0
- /package/dist/{chunk-DFLN2KO3.js.map → chunk-QD427NIO.js.map} +0 -0
- /package/dist/{chunk-DCDF5A7N.js.map → chunk-UXJZU23T.js.map} +0 -0
- /package/dist/{client-JYKPVCJX.js.map → client-M2V6FHH5.js.map} +0 -0
- /package/dist/{db-LOSJ74ZJ.js.map → db-IMMQSRAB.js.map} +0 -0
- /package/dist/{digest-CS6KLCZB.js.map → digest-EOA4JLTV.js.map} +0 -0
- /package/dist/{digest-collectors-NI2M3UTC.js.map → digest-collectors-ZA3BTS3R.js.map} +0 -0
- /package/dist/{ensure-site-FFDUMY6E.js.map → ensure-site-SF6KICTN.js.map} +0 -0
- /package/dist/{forms-notify-target-IHJX7NWA.js.map → forms-notify-target-YCZUAHTY.js.map} +0 -0
- /package/dist/{github-signals-FCLNHKMF.js.map → github-signals-3WLIJ5RR.js.map} +0 -0
- /package/dist/{header-image-7A7XLVEU.js.map → header-image-DOCDFFRO.js.map} +0 -0
- /package/dist/{health-mirror-GMSF3Z5R.js.map → health-mirror-55RRU6BI.js.map} +0 -0
- /package/dist/{init-S4MJ3Y6W.js.map → init-NXZ4NXTL.js.map} +0 -0
- /package/dist/{launch-7FT4EYEO.js.map → launch-WPEQ3TY2.js.map} +0 -0
- /package/dist/{migrate-YITCXBLS.js.map → migrate-K4JETR36.js.map} +0 -0
- /package/dist/{orchestrate-6HROYTM6.js.map → orchestrate-KMQIUP3H.js.map} +0 -0
- /package/dist/{pipeline-MQJD4CKN.js.map → pipeline-HQJYZ443.js.map} +0 -0
- /package/dist/{preflight-LIN5NCYU.js.map → preflight-VPHQYWTY.js.map} +0 -0
- /package/dist/{prismic-models-VNGIKS2B.js.map → prismic-models-FPDSUXDO.js.map} +0 -0
- /package/dist/{prospect-audits-3PMIO73D.js.map → prospect-audits-RVYEA4KH.js.map} +0 -0
- /package/dist/{render-P3SBUFVB.js.map → render-3GYW2J34.js.map} +0 -0
- /package/dist/{report-AAY2HZZR.js.map → report-Y4LXDHRV.js.map} +0 -0
- /package/dist/{report-mirror-GMNSH5HF.js.map → report-mirror-6ZDCAQZB.js.map} +0 -0
- /package/dist/{selftest-CUX2FIQD.js.map → selftest-IFGSJP4X.js.map} +0 -0
- /package/dist/{site-mirror-GCFLBBWK.js.map → site-mirror-3E7JYTJG.js.map} +0 -0
- /package/dist/{submissions-7LGJJSDL.js.map → submissions-UMJS3YST.js.map} +0 -0
- /package/dist/{sync-configs-ZYLTMUNX.js.map → sync-configs-PVRLZRKL.js.map} +0 -0
|
@@ -10,9 +10,9 @@ function freezeGuardsDbWrite(action, force, authoritative) {
|
|
|
10
10
|
}
|
|
11
11
|
async function runDbCommand(action, opts, deps = {}) {
|
|
12
12
|
if (action === "migrate") {
|
|
13
|
-
const { readDbConfig } = await import("./client-
|
|
13
|
+
const { readDbConfig } = await import("./client-M2V6FHH5.js");
|
|
14
14
|
const cfg = opts.url ? { url: opts.url } : readDbConfig();
|
|
15
|
-
const { runMigrations } = await import("./migrate-
|
|
15
|
+
const { runMigrations } = await import("./migrate-K4JETR36.js");
|
|
16
16
|
const { createClient } = await import("@libsql/client");
|
|
17
17
|
const client = createClient(cfg.url === ":memory:" ? { url: ":memory:" } : cfg);
|
|
18
18
|
const ran = await runMigrations(client);
|
|
@@ -22,7 +22,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
24
|
if (action === "replay-deadletters") {
|
|
25
|
-
const { readDbConfig, openDb } = await import("./client-
|
|
25
|
+
const { readDbConfig, openDb } = await import("./client-M2V6FHH5.js");
|
|
26
26
|
const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
|
|
27
27
|
const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
|
|
28
28
|
const { getWebsiteBySlug } = await import("./websites-D6WYFAES.js");
|
|
@@ -87,7 +87,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
87
87
|
const { TURSO_IS_AUTHORITATIVE } = await import("./freeze-A6MUCZU4.js");
|
|
88
88
|
const refused = freezeGuardsDbWrite(action, opts.force === true, TURSO_IS_AUTHORITATIVE);
|
|
89
89
|
if (refused) return refused;
|
|
90
|
-
const { readDbConfig, openDb } = await import("./client-
|
|
90
|
+
const { readDbConfig, openDb } = await import("./client-M2V6FHH5.js");
|
|
91
91
|
const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
|
|
92
92
|
const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
|
|
93
93
|
const base = openBase(readAirtableConfig());
|
|
@@ -150,7 +150,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
150
150
|
return { output: formatParityResult(result), code: result.mismatches.length > 0 ? 1 : 0 };
|
|
151
151
|
}
|
|
152
152
|
if (action === "backfill-header-images") {
|
|
153
|
-
const { readDbConfig, openDb } = await import("./client-
|
|
153
|
+
const { readDbConfig, openDb } = await import("./client-M2V6FHH5.js");
|
|
154
154
|
const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
|
|
155
155
|
const { openBase, readAirtableConfig } = await import("./client-EPVO3FEH.js");
|
|
156
156
|
const base = openBase(readAirtableConfig());
|
|
@@ -172,7 +172,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
172
172
|
return { output: formatBackfillResult(result), code: result.failed.length > 0 ? 1 : 0 };
|
|
173
173
|
}
|
|
174
174
|
if (action === "backfill-digest-state") {
|
|
175
|
-
const { readDbConfig, openDb } = await import("./client-
|
|
175
|
+
const { readDbConfig, openDb } = await import("./client-M2V6FHH5.js");
|
|
176
176
|
const db = await openDb(opts.url ? { url: opts.url } : readDbConfig());
|
|
177
177
|
const { readDigestState: readTurso, writeDigestState: writeTurso } = await import("./digest-state-3KHZF7GD.js");
|
|
178
178
|
const existing = await readTurso(db);
|
|
@@ -197,7 +197,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
197
197
|
};
|
|
198
198
|
}
|
|
199
199
|
if (action === "dump") {
|
|
200
|
-
const { readDbConfig } = await import("./client-
|
|
200
|
+
const { readDbConfig } = await import("./client-M2V6FHH5.js");
|
|
201
201
|
const cfg = opts.url ? { url: opts.url } : readDbConfig();
|
|
202
202
|
const { createClient } = await import("@libsql/client");
|
|
203
203
|
const client = createClient(cfg.url === ":memory:" ? { url: ":memory:" } : cfg);
|
|
@@ -325,7 +325,7 @@ async function runDbCommand(action, opts, deps = {}) {
|
|
|
325
325
|
for (const table of Object.keys(restored)) {
|
|
326
326
|
if (!(table in manifest.tables)) mismatches.push(`${table}: not in origin manifest`);
|
|
327
327
|
}
|
|
328
|
-
const { DATABASE_TABLES } = await import("./schema-
|
|
328
|
+
const { DATABASE_TABLES } = await import("./schema-2V7BGYVT.js");
|
|
329
329
|
for (const table of DATABASE_TABLES) {
|
|
330
330
|
if (!(table in restored)) mismatches.push(`${table}: MISSING from the backup entirely`);
|
|
331
331
|
}
|
|
@@ -350,4 +350,4 @@ export {
|
|
|
350
350
|
freezeGuardsDbWrite,
|
|
351
351
|
runDbCommand
|
|
352
352
|
};
|
|
353
|
-
//# sourceMappingURL=db-
|
|
353
|
+
//# sourceMappingURL=db-IMMQSRAB.js.map
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
collectRenovateAlerts,
|
|
14
14
|
collectTurnstileGuardrailAlerts,
|
|
15
15
|
collectVulnAlerts
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-MVDMKKBN.js";
|
|
17
|
+
import "./chunk-E22WAIF2.js";
|
|
18
18
|
import "./chunk-PZR4EBPY.js";
|
|
19
19
|
import {
|
|
20
20
|
isIdempotencyConflict
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-FCS36FDT.js";
|
|
22
22
|
import {
|
|
23
23
|
isPendingApproval,
|
|
24
24
|
listAllReports
|
|
@@ -47,10 +47,10 @@ import {
|
|
|
47
47
|
import {
|
|
48
48
|
defaultResendClient
|
|
49
49
|
} from "./chunk-WEKL2HYJ.js";
|
|
50
|
-
import "./chunk-
|
|
51
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-DMPHP7UP.js";
|
|
51
|
+
import "./chunk-22GKJO5F.js";
|
|
52
52
|
import "./chunk-RARREDQE.js";
|
|
53
|
-
import "./chunk-
|
|
53
|
+
import "./chunk-OCNSIBMP.js";
|
|
54
54
|
import "./chunk-6VAL7XJT.js";
|
|
55
55
|
|
|
56
56
|
// src/reports/digest.ts
|
|
@@ -149,7 +149,7 @@ async function listPendingApproval(base) {
|
|
|
149
149
|
async function fetchNotifyBounceCounts(now) {
|
|
150
150
|
try {
|
|
151
151
|
const [{ openDb, readDbConfig }, { countNotifyBouncedBySite }, { screenOutsSince }] = await Promise.all([
|
|
152
|
-
import("./client-
|
|
152
|
+
import("./client-M2V6FHH5.js"),
|
|
153
153
|
import("./submissions-3I3GAQAX.js"),
|
|
154
154
|
import("./screenouts-7P5GOZMV.js")
|
|
155
155
|
]);
|
|
@@ -162,7 +162,7 @@ async function fetchNotifyBounceCounts(now) {
|
|
|
162
162
|
}
|
|
163
163
|
async function readDigestStateFromDb() {
|
|
164
164
|
const [{ openDb, readDbConfig }, { readDigestState }] = await Promise.all([
|
|
165
|
-
import("./client-
|
|
165
|
+
import("./client-M2V6FHH5.js"),
|
|
166
166
|
import("./digest-state-3KHZF7GD.js")
|
|
167
167
|
]);
|
|
168
168
|
return readDigestState(await openDb(readDbConfig()));
|
|
@@ -176,7 +176,7 @@ async function writeRollupOnce(options, now) {
|
|
|
176
176
|
}
|
|
177
177
|
async function writeDigestStateToDb(next) {
|
|
178
178
|
const [{ openDb, readDbConfig }, { writeDigestState: writeDigestState2 }] = await Promise.all([
|
|
179
|
-
import("./client-
|
|
179
|
+
import("./client-M2V6FHH5.js"),
|
|
180
180
|
import("./digest-state-3KHZF7GD.js")
|
|
181
181
|
]);
|
|
182
182
|
await writeDigestState2(await openDb(readDbConfig()), next);
|
|
@@ -184,11 +184,11 @@ async function writeDigestStateToDb(next) {
|
|
|
184
184
|
async function writeCockpitRollupToDb(now) {
|
|
185
185
|
if (!process.env.TURSO_DATABASE_URL) return "absent";
|
|
186
186
|
const [{ openDb, readDbConfig }, { writeCockpitRollup }, screenouts, submissions, collectors] = await Promise.all([
|
|
187
|
-
import("./client-
|
|
187
|
+
import("./client-M2V6FHH5.js"),
|
|
188
188
|
import("./digest-state-3KHZF7GD.js"),
|
|
189
189
|
import("./screenouts-7P5GOZMV.js"),
|
|
190
190
|
import("./submissions-3I3GAQAX.js"),
|
|
191
|
-
import("./digest-collectors-
|
|
191
|
+
import("./digest-collectors-ZA3BTS3R.js")
|
|
192
192
|
]);
|
|
193
193
|
const db = await openDb(readDbConfig());
|
|
194
194
|
const SCREEN_OUT_WINDOW_DAYS = 30;
|
|
@@ -242,7 +242,7 @@ var SUBMISSIONS_DIGEST_WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
|
242
242
|
async function fetchSubmissionsDigestCounts(now) {
|
|
243
243
|
try {
|
|
244
244
|
const [{ openDb, readDbConfig }, { countSubmissionsSinceBySite }] = await Promise.all([
|
|
245
|
-
import("./client-
|
|
245
|
+
import("./client-M2V6FHH5.js"),
|
|
246
246
|
import("./submissions-3I3GAQAX.js")
|
|
247
247
|
]);
|
|
248
248
|
const db = await openDb(readDbConfig());
|
|
@@ -424,4 +424,4 @@ export {
|
|
|
424
424
|
renderDigestHtml,
|
|
425
425
|
runDigest
|
|
426
426
|
};
|
|
427
|
-
//# sourceMappingURL=digest-
|
|
427
|
+
//# sourceMappingURL=digest-EOA4JLTV.js.map
|
|
@@ -12,10 +12,10 @@ import {
|
|
|
12
12
|
collectTurnstileGuardrailAlerts,
|
|
13
13
|
collectVulnAlerts,
|
|
14
14
|
prismicAckIsLive
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-MVDMKKBN.js";
|
|
16
|
+
import "./chunk-E22WAIF2.js";
|
|
17
17
|
import "./chunk-PZR4EBPY.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-FCS36FDT.js";
|
|
19
19
|
import "./chunk-F3JWOPTG.js";
|
|
20
20
|
import "./chunk-VAD43MOR.js";
|
|
21
21
|
import "./chunk-IY6256GK.js";
|
|
@@ -27,10 +27,10 @@ import "./chunk-5UINJNAJ.js";
|
|
|
27
27
|
import "./chunk-BGTYPVLT.js";
|
|
28
28
|
import "./chunk-7NYN2P6I.js";
|
|
29
29
|
import "./chunk-WEKL2HYJ.js";
|
|
30
|
-
import "./chunk-
|
|
31
|
-
import "./chunk-
|
|
30
|
+
import "./chunk-DMPHP7UP.js";
|
|
31
|
+
import "./chunk-22GKJO5F.js";
|
|
32
32
|
import "./chunk-RARREDQE.js";
|
|
33
|
-
import "./chunk-
|
|
33
|
+
import "./chunk-OCNSIBMP.js";
|
|
34
34
|
import "./chunk-6VAL7XJT.js";
|
|
35
35
|
export {
|
|
36
36
|
NOTIFY_BOUNCE_THRESHOLD,
|
|
@@ -47,4 +47,4 @@ export {
|
|
|
47
47
|
collectVulnAlerts,
|
|
48
48
|
prismicAckIsLive
|
|
49
49
|
};
|
|
50
|
-
//# sourceMappingURL=digest-collectors-
|
|
50
|
+
//# sourceMappingURL=digest-collectors-ZA3BTS3R.js.map
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
renderProspectReport
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-H6BLTEFN.js";
|
|
4
|
+
import "./chunk-WVVT6SJK.js";
|
|
5
|
+
import "./chunk-RQLXEMEE.js";
|
|
5
6
|
import {
|
|
6
7
|
escapeHtml,
|
|
7
8
|
safeUrl
|
|
@@ -201,4 +202,4 @@ export {
|
|
|
201
202
|
parseProspectAuditRecipients,
|
|
202
203
|
sendAuditEmail
|
|
203
204
|
};
|
|
204
|
-
//# sourceMappingURL=email-
|
|
205
|
+
//# sourceMappingURL=email-PLPDJUDH.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/prospect/email.ts"],"sourcesContent":["import { escapeHtml, safeUrl } from \"../util/html.js\";\nimport { hostnameOf } from \"../util/url.js\";\nimport { renderProspectReport } from \"./render.js\";\nimport { defaultResendClient, type ResendClient } from \"../reports/send/resend.js\";\nimport type { AnalyzeResult, Fix, ProspectAuditResult, StageResult } from \"./types.js\";\n\n/**\n * Mirrors `reports/send/orchestrate.ts`'s `FROM_ADDRESS` (its line 18) — copied,\n * not imported, exactly like `digest.ts` and `recipes/selftest-email.ts` already\n * do. orchestrate.ts's own `FROM_ADDRESS` isn't exported, and importing it would\n * drag its whole Airtable/MJML dependency chain into this module's graph, which\n * stays lazy-loaded on purpose (see bin.ts's central-dep-blocker comment — this\n * module reaches `resend`, a devDependency, and must only ever be reached via a\n * dynamic `import()` from the CLI).\n */\nconst FROM_ADDRESS = \"Reddoor Reports <reports@reddoorla.com>\";\n\nconst IMPACT_ORDER: Record<Fix[\"impact\"], number> = { high: 0, medium: 1, low: 2 };\n\n/** \"top few fixes\" per the design — enough to be useful on an internal sheet\n * without turning it into the full client report. */\nconst TOP_FIX_COUNT = 3;\n\nexport type AuditEmailContent = {\n subject: string;\n html: string;\n /** The FULL client-facing report (render.ts's `renderProspectReport`, pure\n * and deterministic like this function), so `sendAuditEmail` can attach it\n * without a second render call on the default path. */\n attachmentHtml: string;\n};\n\n/** The searchable business name when the pipeline resolved one, else the\n * hostname — mirrors render.ts's identical fallback (types.ts's businessName\n * doc: \"Null when no name was ever resolved... itself a finding, not an\n * error\"). Used for both the subject line and the sheet's own heading. */\nfunction resolveName(result: ProspectAuditResult): string {\n return result.businessName && result.businessName.trim()\n ? result.businessName\n : hostnameOf(result.url);\n}\n\n/**\n * Mail subject headers are plain text, never HTML-rendered by a client — no\n * `escapeHtml` needed there (that would wrongly show literal \"<\" etc. in\n * the subject line). A raw newline/control character IS worth collapsing:\n * a business name that embeds one (scraped page title, model output) would\n * otherwise visibly break the subject, and a stray CR/LF in a header value\n * is the classic email-header-injection vector — defense-in-depth even\n * though the Resend API's JSON transport already neutralizes it here.\n */\nfunction subjectSafe(s: string): string {\n return s.replace(/\\s+/g, \" \").trim();\n}\n\n/** Local copy of render.ts's date formatter — small enough that importing it\n * (render.ts doesn't export it) isn't worth the coupling. */\nfunction formatIsoDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n timeZone: \"UTC\",\n });\n}\n\ntype NotMeasuredLine = { label: string; reason: string };\n\n/**\n * Everything on the sheet that reads \"not measured\", and why — in plain\n * words. Unlike render.ts's client-facing report (which collapses every\n * stage failure to STAGE_FAILED_MESSAGE so a stranger never sees operator\n * vocabulary), this email goes to Tucker/Tim/Erik: the real diagnostic\n * (a fetch error, an LLM timeout) is more useful to them than a sanitized\n * phrase, so stage `error` strings are surfaced verbatim (still escaped —\n * they can embed arbitrary text from a fetch response).\n *\n * The four-Scores branches mirror checks.ts's `computeScores` exactly: which\n * stage feeds which score, and the cases where that stage's OWN data still\n * nulls the score even though the stage itself succeeded (crawlerAccessMeasured\n * false, avgMissing null, empty buyerQuestions, a null visibilityScore). If\n * computeScores' conditions change, this needs the matching update.\n */\nfunction notMeasuredLines(result: ProspectAuditResult): NotMeasuredLine[] {\n const lines: NotMeasuredLine[] = [];\n\n if (!result.checks.ok) {\n lines.push({\n label: \"Findability\",\n reason: `the checks stage failed — ${result.checks.error}`,\n });\n lines.push({\n label: \"Readability\",\n reason: `the checks stage failed — ${result.checks.error}`,\n });\n } else {\n const c = result.checks.data;\n if (result.scores.findability === null) {\n lines.push({\n label: \"Findability\",\n reason: !c.crawlerAccessMeasured\n ? \"robots.txt could not be fetched, so crawler access wasn't measured\"\n : \"every crawled page failed to produce readable content\",\n });\n }\n if (result.scores.readability === null) {\n lines.push({\n label: \"Readability\",\n reason: \"no page produced a comparable raw/rendered pair to measure JavaScript dependence\",\n });\n }\n }\n\n if (!result.lighthouse.ok) {\n lines.push({ label: \"Lighthouse\", reason: result.lighthouse.error });\n }\n\n if (!result.analyze.ok) {\n lines.push({ label: \"Answers\", reason: result.analyze.error });\n } else if (result.scores.answers === null) {\n lines.push({ label: \"Answers\", reason: \"the model found no buyer questions to check\" });\n }\n\n // Named for what the stage measures (the receipts section), not for the\n // removed score label — the email no longer presents an AI Visibility score.\n if (!result.probes.ok) {\n lines.push({ label: \"AI engine answers\", reason: result.probes.error });\n } else if (result.scores.aiVisibility === null) {\n lines.push({ label: \"AI engine answers\", reason: \"no category (buyer-question) query ran\" });\n }\n\n return lines;\n}\n\n/** The three SITE scores. aiVisibility is deliberately absent — the report\n * reorganised around what the client controls, and a visibility number in a\n * score row reads as ours to move. It stays computed and stored; the report\n * page presents the receipts. */\nconst SCORE_FIELDS: { key: keyof ProspectAuditResult[\"scores\"]; label: string }[] = [\n { key: \"findability\", label: \"Findability\" },\n { key: \"readability\", label: \"Readability\" },\n { key: \"answers\", label: \"Answers\" },\n];\n\nfunction scoresTableHtml(scores: ProspectAuditResult[\"scores\"]): string {\n const cells = SCORE_FIELDS.map(({ key, label }) => {\n const value = scores[key];\n const display = value === null ? \"Not measured\" : String(value);\n const valueStyle =\n value === null ? \"color:#8a857e;font-size:14px;\" : \"color:#d71920;font-size:28px;\";\n return `<td style=\"padding:12px 16px;border:1px solid #e6e1da;text-align:center;\">\n <div style=\"font-weight:600;${valueStyle}\">${escapeHtml(display)}</div>\n <div style=\"font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:#57544f;margin-top:4px;\">${escapeHtml(label)}</div>\n </td>`;\n }).join(\"\");\n return `<table role=\"presentation\" style=\"border-collapse:collapse;margin:16px 0;\"><tr>${cells}</tr></table>`;\n}\n\nfunction notMeasuredHtml(lines: NotMeasuredLine[]): string {\n if (lines.length === 0) return \"\";\n const items = lines\n .map((l) => `<li><strong>${escapeHtml(l.label)}</strong> — ${escapeHtml(l.reason)}</li>`)\n .join(\"\");\n return `<h2 style=\"font-size:16px;margin:24px 0 8px;\">Not measured</h2><ul style=\"margin:0;padding-left:20px;\">${items}</ul>`;\n}\n\n/** analyze.error and fix titles/why are untrusted (LLM output, or text lifted\n * from the prospect's own site) — every interpolation below is escaped. */\nfunction fixesHtml(analyze: StageResult<AnalyzeResult>): string {\n if (!analyze.ok) {\n return `<p style=\"color:#8a857e;margin:8px 0 0;\">Fixes not available — ${escapeHtml(analyze.error)}</p>`;\n }\n const top = [...analyze.data.fixes]\n .sort((a, b) => IMPACT_ORDER[a.impact] - IMPACT_ORDER[b.impact])\n .slice(0, TOP_FIX_COUNT);\n if (top.length === 0) {\n return `<p style=\"margin:8px 0 0;\">Nothing urgent — the essentials are already in place.</p>`;\n }\n const items = top\n .map(\n (f) =>\n `<li><strong>${escapeHtml(f.title)}</strong> — ${escapeHtml(f.why)} <span style=\"color:#8a857e;\">(${escapeHtml(f.impact)} impact, ${escapeHtml(f.effort)} effort)</span></li>`,\n )\n .join(\"\");\n return `<ol style=\"margin:8px 0 0;padding-left:20px;\">${items}</ol>`;\n}\n\nfunction linkHtml(link: string | null): string {\n if (!link) {\n return `<p style=\"margin:24px 0 0;color:#8a857e;\">No shareable link — the audit could not be saved to the database. The full report is attached to this email instead.</p>`;\n }\n const safe = safeUrl(link);\n return `<p style=\"margin:24px 0 0;\"><strong>Full report:</strong> <a href=\"${escapeHtml(safe)}\">${escapeHtml(link)}</a></p>`;\n}\n\n/** This sheet is for us, and it says so. It carries the real reasons a stage did\n * not run — useful internally, wrong in front of a prospect — so it has to be\n * obvious that the shareable artefact is the attachment and the link, not this\n * body. Without the label, the natural thing to do with a useful email is\n * forward it. */\nconst INTERNAL_NOTE = `<p style=\"margin:24px 0 0;padding:12px 14px;background:#f4f2ef;border-left:3px solid #d71920;color:#57544f;font-size:14px;\">\n Internal sheet — it names the reasons a section could not be measured. The\n version to send a prospect is the linked report, also attached to this email.\n</p>`;\n\n/**\n * Build the internal sheet — a SHORT summary for Tucker/Tim/Erik, distinct\n * from the client-facing report render.ts builds. Pure: no IO, so it's safe\n * to call from a test with no network. `attachmentHtml` is the full report\n * (render.ts's `renderProspectReport`, itself pure), included here so\n * `sendAuditEmail`'s default path doesn't need a second render call.\n *\n * Every value that can trace back to the prospect's site or the model —\n * the business name, fixes, stage error text, the narrative-adjacent bits —\n * is escaped with `escapeHtml`/`safeUrl`, the same honesty-and-safety rule\n * render.ts follows for the client-facing report.\n */\nexport function buildAuditEmail(\n result: ProspectAuditResult,\n opts: { link: string | null },\n): AuditEmailContent {\n const name = resolveName(result);\n const subject = `Prospect audit — ${subjectSafe(name)}`;\n\n const html = `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /></head>\n<body style=\"margin:0;padding:24px;background:#faf8f5;color:#1a1a1a;font:15px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;\">\n <h1 style=\"font-size:22px;margin:0 0 4px;\">Prospect audit — ${escapeHtml(name)}</h1>\n <p style=\"margin:0 0 16px;color:#57544f;\">\n <a href=\"${escapeHtml(safeUrl(result.url))}\">${escapeHtml(result.url)}</a> · audited ${escapeHtml(formatIsoDate(result.generatedAt))}\n </p>\n ${scoresTableHtml(result.scores)}\n ${notMeasuredHtml(notMeasuredLines(result))}\n <h2 style=\"font-size:16px;margin:24px 0 8px;\">Top fixes</h2>\n ${fixesHtml(result.analyze)}\n ${linkHtml(opts.link)}\n ${INTERNAL_NOTE}\n</body>\n</html>`;\n\n return { subject, html, attachmentHtml: renderProspectReport(result) };\n}\n\n/**\n * Parse `PROSPECT_AUDIT_RECIPIENTS` (comma-separated) into a clean address\n * list: trimmed, empties dropped, case-insensitively deduped — mirrors\n * `reports/send/orchestrate.ts`'s `parseAddresses` for the same reasons (an\n * operator-typed env var, same typo surface). No address is ever hardcoded\n * anywhere in this module; an unset or empty var yields `[]`, which\n * `sendAuditEmail` treats as \"skip, and say why\" rather than an error.\n */\nexport function parseProspectAuditRecipients(raw: string | undefined | null): string[] {\n if (!raw) return [];\n const seen = new Set<string>();\n const list: string[] = [];\n for (const part of raw.split(\",\")) {\n const trimmed = part.trim().toLowerCase();\n if (!trimmed) continue;\n if (seen.has(trimmed)) continue;\n seen.add(trimmed);\n list.push(trimmed);\n }\n return list;\n}\n\n/** Filesystem-safe attachment-filename fragment. Tiny local copy of the CLI's\n * private `slugify` (prospect-audit.ts) — not worth exporting/sharing one\n * three-line predicate across a module boundary. */\nfunction slugify(s: string): string {\n const slug = s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"prospect\";\n}\n\nexport type SendAuditEmailOptions = {\n link: string | null;\n recipients: string[];\n /**\n * The persisted `prospect_audits` row id (from `createProspectAudit`), when\n * persistence succeeded. Mirrors orchestrate.ts's\n * `idempotencyKey: \\`report:${report.id}\\`` pattern, so re-sending for the\n * SAME already-persisted audit dedupes at Resend instead of double-mailing.\n * Null when persistence failed — the sheet can still be sent (it's built to\n * survive independently of Turso), but then there is no stable row id, so\n * the key falls back to `url`+`generatedAt`: that still protects a\n * same-process double-call, though — unlike a DB id — it can't survive an\n * entire pipeline rerun, which mints genuinely new content anyway.\n */\n auditId?: string | null;\n /**\n * The print-designed PDF leave-behind, when one was rendered.\n *\n * Optional on purpose: rendering it needs a live page and a headless browser,\n * and neither is worth losing a delivered report over. Absent, the email goes\n * exactly as it did before — the HTML sheet and the link — and the caller has\n * already recorded a warning saying why.\n */\n pdf?: Buffer | null;\n /** Defaults to `defaultResendClient()`. */\n client?: ResendClient;\n /** Test seam for the ATTACHMENT content only (production omits it and gets\n * the real `renderProspectReport` output `buildAuditEmail` already\n * computed) — lets a test of this function's sending mechanics (recipients,\n * idempotency key, attachment shape) skip re-deriving the full report. */\n renderReport?: (result: ProspectAuditResult) => string;\n};\n\nexport type SendAuditEmailResult =\n { sent: true; messageId: string; recipients: string[] } | { sent: false; reason: string };\n\n/**\n * Send the audit sheet with the full rendered report attached as an `.html`\n * file (base64), so the sheet survives independently of the link and of\n * Turso. Empty `recipients` is a legitimate, non-exceptional outcome (no\n * `PROSPECT_AUDIT_RECIPIENTS` configured) — returns `{sent:false}` rather\n * than throwing, so a caller can report it as a warning; a genuine send\n * failure (a throwing `client.send`) still propagates as a real exception.\n */\nexport async function sendAuditEmail(\n result: ProspectAuditResult,\n opts: SendAuditEmailOptions,\n): Promise<SendAuditEmailResult> {\n if (opts.recipients.length === 0) {\n return {\n sent: false,\n reason: \"no recipients configured (PROSPECT_AUDIT_RECIPIENTS is unset or empty)\",\n };\n }\n\n const built = buildAuditEmail(result, { link: opts.link });\n const attachmentHtml = opts.renderReport ? opts.renderReport(result) : built.attachmentHtml;\n const client = opts.client ?? defaultResendClient();\n const idempotencyKey = `prospect-audit:${opts.auditId ?? `${result.url}#${result.generatedAt}`}`;\n const filename = `prospect-audit-${slugify(hostnameOf(result.url))}.html`;\n\n const { messageId } = await client.send({\n from: FROM_ADDRESS,\n to: opts.recipients,\n subject: built.subject,\n html: built.html,\n attachments: [\n {\n filename,\n content: Buffer.from(attachmentHtml, \"utf-8\").toString(\"base64\"),\n contentType: \"text/html\",\n },\n // The PDF goes second so the HTML sheet stays the first attachment it has\n // always been, and appears only when one was actually rendered — an empty\n // or broken attachment is worse than none.\n ...(opts.pdf\n ? [\n {\n filename: filename.replace(/\\.html$/, \".pdf\"),\n content: opts.pdf.toString(\"base64\"),\n contentType: \"application/pdf\",\n },\n ]\n : []),\n ],\n idempotencyKey,\n });\n\n return { sent: true, messageId, recipients: opts.recipients };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/prospect/email.ts"],"sourcesContent":["import { escapeHtml, safeUrl } from \"../util/html.js\";\nimport { hostnameOf } from \"../util/url.js\";\nimport { renderProspectReport } from \"./render.js\";\nimport { defaultResendClient, type ResendClient } from \"../reports/send/resend.js\";\nimport type { AnalyzeResult, Fix, ProspectAuditResult, StageResult } from \"./types.js\";\n\n/**\n * Mirrors `reports/send/orchestrate.ts`'s `FROM_ADDRESS` (its line 18) — copied,\n * not imported, exactly like `digest.ts` and `recipes/selftest-email.ts` already\n * do. orchestrate.ts's own `FROM_ADDRESS` isn't exported, and importing it would\n * drag its whole Airtable/MJML dependency chain into this module's graph, which\n * stays lazy-loaded on purpose (see bin.ts's central-dep-blocker comment — this\n * module reaches `resend`, a devDependency, and must only ever be reached via a\n * dynamic `import()` from the CLI).\n */\nconst FROM_ADDRESS = \"Reddoor Reports <reports@reddoorla.com>\";\n\nconst IMPACT_ORDER: Record<Fix[\"impact\"], number> = { high: 0, medium: 1, low: 2 };\n\n/** \"top few fixes\" per the design — enough to be useful on an internal sheet\n * without turning it into the full client report. */\nconst TOP_FIX_COUNT = 3;\n\nexport type AuditEmailContent = {\n subject: string;\n html: string;\n /** The FULL client-facing report (render.ts's `renderProspectReport`, pure\n * and deterministic like this function), so `sendAuditEmail` can attach it\n * without a second render call on the default path. */\n attachmentHtml: string;\n};\n\n/** The searchable business name when the pipeline resolved one, else the\n * hostname — mirrors render.ts's identical fallback (types.ts's businessName\n * doc: \"Null when no name was ever resolved... itself a finding, not an\n * error\"). Used for both the subject line and the sheet's own heading. */\nfunction resolveName(result: ProspectAuditResult): string {\n return result.businessName && result.businessName.trim()\n ? result.businessName\n : hostnameOf(result.url);\n}\n\n/**\n * Mail subject headers are plain text, never HTML-rendered by a client — no\n * `escapeHtml` needed there (that would wrongly show literal \"<\" etc. in\n * the subject line). A raw newline/control character IS worth collapsing:\n * a business name that embeds one (scraped page title, model output) would\n * otherwise visibly break the subject, and a stray CR/LF in a header value\n * is the classic email-header-injection vector — defense-in-depth even\n * though the Resend API's JSON transport already neutralizes it here.\n */\nfunction subjectSafe(s: string): string {\n return s.replace(/\\s+/g, \" \").trim();\n}\n\n/** Local copy of render.ts's date formatter — small enough that importing it\n * (render.ts doesn't export it) isn't worth the coupling. */\nfunction formatIsoDate(iso: string): string {\n const d = new Date(iso);\n if (Number.isNaN(d.getTime())) return iso;\n return d.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n timeZone: \"UTC\",\n });\n}\n\ntype NotMeasuredLine = { label: string; reason: string };\n\n/**\n * Everything on the sheet that reads \"not measured\", and why — in plain\n * words. Unlike render.ts's client-facing report (which collapses every\n * stage failure to STAGE_FAILED_MESSAGE so a stranger never sees operator\n * vocabulary), this email goes to Tucker/Tim/Erik: the real diagnostic\n * (a fetch error, an LLM timeout) is more useful to them than a sanitized\n * phrase, so stage `error` strings are surfaced verbatim (still escaped —\n * they can embed arbitrary text from a fetch response).\n *\n * The four-Scores branches mirror checks.ts's `computeScores` exactly: which\n * stage feeds which score, and the cases where that stage's OWN data still\n * nulls the score even though the stage itself succeeded (crawlerAccessMeasured\n * false, avgMissing null, empty buyerQuestions, a null visibilityScore). If\n * computeScores' conditions change, this needs the matching update.\n */\nfunction notMeasuredLines(result: ProspectAuditResult): NotMeasuredLine[] {\n const lines: NotMeasuredLine[] = [];\n\n if (!result.checks.ok) {\n lines.push({\n label: \"Findability\",\n reason: `the checks stage failed — ${result.checks.error}`,\n });\n lines.push({\n label: \"Readability\",\n reason: `the checks stage failed — ${result.checks.error}`,\n });\n } else {\n const c = result.checks.data;\n if (result.scores.findability === null) {\n lines.push({\n label: \"Findability\",\n reason: !c.crawlerAccessMeasured\n ? \"robots.txt could not be fetched, so crawler access wasn't measured\"\n : \"every crawled page failed to produce readable content\",\n });\n }\n if (result.scores.readability === null) {\n lines.push({\n label: \"Readability\",\n reason: \"no page produced a comparable raw/rendered pair to measure JavaScript dependence\",\n });\n }\n }\n\n if (!result.lighthouse.ok) {\n lines.push({ label: \"Lighthouse\", reason: result.lighthouse.error });\n }\n\n if (!result.analyze.ok) {\n lines.push({ label: \"Answers\", reason: result.analyze.error });\n } else if (result.scores.answers === null) {\n lines.push({ label: \"Answers\", reason: \"the model found no buyer questions to check\" });\n }\n\n // Named for what the stage measures (the receipts section), not for the\n // removed score label — the email no longer presents an AI Visibility score.\n if (!result.probes.ok) {\n lines.push({ label: \"AI engine answers\", reason: result.probes.error });\n } else if (result.scores.aiVisibility === null) {\n lines.push({ label: \"AI engine answers\", reason: \"no category (buyer-question) query ran\" });\n }\n\n return lines;\n}\n\n/** The three SITE scores. aiVisibility is deliberately absent — the report\n * reorganised around what the client controls, and a visibility number in a\n * score row reads as ours to move. It stays computed and stored; the report\n * page presents the receipts. */\nconst SCORE_FIELDS: { key: keyof ProspectAuditResult[\"scores\"]; label: string }[] = [\n { key: \"findability\", label: \"Findability\" },\n { key: \"readability\", label: \"Readability\" },\n { key: \"answers\", label: \"Answers\" },\n];\n\nfunction scoresTableHtml(scores: ProspectAuditResult[\"scores\"]): string {\n const cells = SCORE_FIELDS.map(({ key, label }) => {\n const value = scores[key];\n const display = value === null ? \"Not measured\" : String(value);\n const valueStyle =\n value === null ? \"color:#8a857e;font-size:14px;\" : \"color:#d71920;font-size:28px;\";\n return `<td style=\"padding:12px 16px;border:1px solid #e6e1da;text-align:center;\">\n <div style=\"font-weight:600;${valueStyle}\">${escapeHtml(display)}</div>\n <div style=\"font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:#57544f;margin-top:4px;\">${escapeHtml(label)}</div>\n </td>`;\n }).join(\"\");\n return `<table role=\"presentation\" style=\"border-collapse:collapse;margin:16px 0;\"><tr>${cells}</tr></table>`;\n}\n\nfunction notMeasuredHtml(lines: NotMeasuredLine[]): string {\n if (lines.length === 0) return \"\";\n const items = lines\n .map((l) => `<li><strong>${escapeHtml(l.label)}</strong> — ${escapeHtml(l.reason)}</li>`)\n .join(\"\");\n return `<h2 style=\"font-size:16px;margin:24px 0 8px;\">Not measured</h2><ul style=\"margin:0;padding-left:20px;\">${items}</ul>`;\n}\n\n/** analyze.error and fix titles/why are untrusted (LLM output, or text lifted\n * from the prospect's own site) — every interpolation below is escaped. */\nfunction fixesHtml(analyze: StageResult<AnalyzeResult>): string {\n if (!analyze.ok) {\n return `<p style=\"color:#8a857e;margin:8px 0 0;\">Fixes not available — ${escapeHtml(analyze.error)}</p>`;\n }\n const top = [...analyze.data.fixes]\n .sort((a, b) => IMPACT_ORDER[a.impact] - IMPACT_ORDER[b.impact])\n .slice(0, TOP_FIX_COUNT);\n if (top.length === 0) {\n return `<p style=\"margin:8px 0 0;\">Nothing urgent — the essentials are already in place.</p>`;\n }\n const items = top\n .map(\n (f) =>\n `<li><strong>${escapeHtml(f.title)}</strong> — ${escapeHtml(f.why)} <span style=\"color:#8a857e;\">(${escapeHtml(f.impact)} impact, ${escapeHtml(f.effort)} effort)</span></li>`,\n )\n .join(\"\");\n return `<ol style=\"margin:8px 0 0;padding-left:20px;\">${items}</ol>`;\n}\n\nfunction linkHtml(link: string | null): string {\n if (!link) {\n return `<p style=\"margin:24px 0 0;color:#8a857e;\">No shareable link — the audit could not be saved to the database. The full report is attached to this email instead.</p>`;\n }\n const safe = safeUrl(link);\n return `<p style=\"margin:24px 0 0;\"><strong>Full report:</strong> <a href=\"${escapeHtml(safe)}\">${escapeHtml(link)}</a></p>`;\n}\n\n/** This sheet is for us, and it says so. It carries the real reasons a stage did\n * not run — useful internally, wrong in front of a prospect — so it has to be\n * obvious that the shareable artefact is the attachment and the link, not this\n * body. Without the label, the natural thing to do with a useful email is\n * forward it. */\nconst INTERNAL_NOTE = `<p style=\"margin:24px 0 0;padding:12px 14px;background:#f4f2ef;border-left:3px solid #d71920;color:#57544f;font-size:14px;\">\n Internal sheet — it names the reasons a section could not be measured. The\n version to send a prospect is the linked report, also attached to this email.\n</p>`;\n\n/**\n * Build the internal sheet — a SHORT summary for Tucker/Tim/Erik, distinct\n * from the client-facing report render.ts builds. Pure: no IO, so it's safe\n * to call from a test with no network. `attachmentHtml` is the full report\n * (render.ts's `renderProspectReport`, itself pure), included here so\n * `sendAuditEmail`'s default path doesn't need a second render call.\n *\n * Every value that can trace back to the prospect's site or the model —\n * the business name, fixes, stage error text, the narrative-adjacent bits —\n * is escaped with `escapeHtml`/`safeUrl`, the same honesty-and-safety rule\n * render.ts follows for the client-facing report.\n */\nexport function buildAuditEmail(\n result: ProspectAuditResult,\n opts: { link: string | null },\n): AuditEmailContent {\n const name = resolveName(result);\n const subject = `Prospect audit — ${subjectSafe(name)}`;\n\n const html = `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /></head>\n<body style=\"margin:0;padding:24px;background:#faf8f5;color:#1a1a1a;font:15px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;\">\n <h1 style=\"font-size:22px;margin:0 0 4px;\">Prospect audit — ${escapeHtml(name)}</h1>\n <p style=\"margin:0 0 16px;color:#57544f;\">\n <a href=\"${escapeHtml(safeUrl(result.url))}\">${escapeHtml(result.url)}</a> · audited ${escapeHtml(formatIsoDate(result.generatedAt))}\n </p>\n ${scoresTableHtml(result.scores)}\n ${notMeasuredHtml(notMeasuredLines(result))}\n <h2 style=\"font-size:16px;margin:24px 0 8px;\">Top fixes</h2>\n ${fixesHtml(result.analyze)}\n ${linkHtml(opts.link)}\n ${INTERNAL_NOTE}\n</body>\n</html>`;\n\n return { subject, html, attachmentHtml: renderProspectReport(result) };\n}\n\n/**\n * Parse `PROSPECT_AUDIT_RECIPIENTS` (comma-separated) into a clean address\n * list: trimmed, empties dropped, case-insensitively deduped — mirrors\n * `reports/send/orchestrate.ts`'s `parseAddresses` for the same reasons (an\n * operator-typed env var, same typo surface). No address is ever hardcoded\n * anywhere in this module; an unset or empty var yields `[]`, which\n * `sendAuditEmail` treats as \"skip, and say why\" rather than an error.\n */\nexport function parseProspectAuditRecipients(raw: string | undefined | null): string[] {\n if (!raw) return [];\n const seen = new Set<string>();\n const list: string[] = [];\n for (const part of raw.split(\",\")) {\n const trimmed = part.trim().toLowerCase();\n if (!trimmed) continue;\n if (seen.has(trimmed)) continue;\n seen.add(trimmed);\n list.push(trimmed);\n }\n return list;\n}\n\n/** Filesystem-safe attachment-filename fragment. Tiny local copy of the CLI's\n * private `slugify` (prospect-audit.ts) — not worth exporting/sharing one\n * three-line predicate across a module boundary. */\nfunction slugify(s: string): string {\n const slug = s\n .toLowerCase()\n .replace(/[^a-z0-9.-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"prospect\";\n}\n\nexport type SendAuditEmailOptions = {\n link: string | null;\n recipients: string[];\n /**\n * The persisted `prospect_audits` row id (from `createProspectAudit`), when\n * persistence succeeded. Mirrors orchestrate.ts's\n * `idempotencyKey: \\`report:${report.id}\\`` pattern, so re-sending for the\n * SAME already-persisted audit dedupes at Resend instead of double-mailing.\n * Null when persistence failed — the sheet can still be sent (it's built to\n * survive independently of Turso), but then there is no stable row id, so\n * the key falls back to `url`+`generatedAt`: that still protects a\n * same-process double-call, though — unlike a DB id — it can't survive an\n * entire pipeline rerun, which mints genuinely new content anyway.\n */\n auditId?: string | null;\n /**\n * The print-designed PDF leave-behind, when one was rendered.\n *\n * Optional on purpose: rendering it needs a live page and a headless browser,\n * and neither is worth losing a delivered report over. Absent, the email goes\n * exactly as it did before — the HTML sheet and the link — and the caller has\n * already recorded a warning saying why.\n */\n pdf?: Buffer | null;\n /** Defaults to `defaultResendClient()`. */\n client?: ResendClient;\n /** Test seam for the ATTACHMENT content only (production omits it and gets\n * the real `renderProspectReport` output `buildAuditEmail` already\n * computed) — lets a test of this function's sending mechanics (recipients,\n * idempotency key, attachment shape) skip re-deriving the full report. */\n renderReport?: (result: ProspectAuditResult) => string;\n};\n\nexport type SendAuditEmailResult =\n { sent: true; messageId: string; recipients: string[] } | { sent: false; reason: string };\n\n/**\n * Send the audit sheet with the full rendered report attached as an `.html`\n * file (base64), so the sheet survives independently of the link and of\n * Turso. Empty `recipients` is a legitimate, non-exceptional outcome (no\n * `PROSPECT_AUDIT_RECIPIENTS` configured) — returns `{sent:false}` rather\n * than throwing, so a caller can report it as a warning; a genuine send\n * failure (a throwing `client.send`) still propagates as a real exception.\n */\nexport async function sendAuditEmail(\n result: ProspectAuditResult,\n opts: SendAuditEmailOptions,\n): Promise<SendAuditEmailResult> {\n if (opts.recipients.length === 0) {\n return {\n sent: false,\n reason: \"no recipients configured (PROSPECT_AUDIT_RECIPIENTS is unset or empty)\",\n };\n }\n\n const built = buildAuditEmail(result, { link: opts.link });\n const attachmentHtml = opts.renderReport ? opts.renderReport(result) : built.attachmentHtml;\n const client = opts.client ?? defaultResendClient();\n const idempotencyKey = `prospect-audit:${opts.auditId ?? `${result.url}#${result.generatedAt}`}`;\n const filename = `prospect-audit-${slugify(hostnameOf(result.url))}.html`;\n\n const { messageId } = await client.send({\n from: FROM_ADDRESS,\n to: opts.recipients,\n subject: built.subject,\n html: built.html,\n attachments: [\n {\n filename,\n content: Buffer.from(attachmentHtml, \"utf-8\").toString(\"base64\"),\n contentType: \"text/html\",\n },\n // The PDF goes second so the HTML sheet stays the first attachment it has\n // always been, and appears only when one was actually rendered — an empty\n // or broken attachment is worse than none.\n ...(opts.pdf\n ? [\n {\n filename: filename.replace(/\\.html$/, \".pdf\"),\n content: opts.pdf.toString(\"base64\"),\n contentType: \"application/pdf\",\n },\n ]\n : []),\n ],\n idempotencyKey,\n });\n\n return { sent: true, messageId, recipients: opts.recipients };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -66,7 +66,7 @@ async function runEnsureSiteCommand(slug, opts) {
|
|
|
66
66
|
if (!slug) return { output: "Provide a <slug> (e.g. `ensure-site roalson`).", code: 2 };
|
|
67
67
|
try {
|
|
68
68
|
const base = openBase(readAirtableConfig());
|
|
69
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
69
|
+
const { makeSiteMirror } = await import("./site-mirror-3E7JYTJG.js");
|
|
70
70
|
const result = await ensureSite(
|
|
71
71
|
base,
|
|
72
72
|
{
|
|
@@ -93,4 +93,4 @@ async function runEnsureSiteCommand(slug, opts) {
|
|
|
93
93
|
export {
|
|
94
94
|
runEnsureSiteCommand
|
|
95
95
|
};
|
|
96
|
-
//# sourceMappingURL=ensure-site-
|
|
96
|
+
//# sourceMappingURL=ensure-site-SF6KICTN.js.map
|
|
@@ -143,7 +143,7 @@ async function runFormsNotifyTargetCommand(site, opts) {
|
|
|
143
143
|
return { output: `--set must be 'on' or 'off' (got '${opts.set}')`, code: 2 };
|
|
144
144
|
}
|
|
145
145
|
try {
|
|
146
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
146
|
+
const { makeSiteMirror } = await import("./site-mirror-3E7JYTJG.js");
|
|
147
147
|
const result = await formsNotifyTarget({
|
|
148
148
|
siteMirror: await makeSiteMirror(),
|
|
149
149
|
site: site.trim(),
|
|
@@ -163,4 +163,4 @@ export {
|
|
|
163
163
|
formatNotifyTarget,
|
|
164
164
|
runFormsNotifyTargetCommand
|
|
165
165
|
};
|
|
166
|
-
//# sourceMappingURL=forms-notify-target-
|
|
166
|
+
//# sourceMappingURL=forms-notify-target-YCZUAHTY.js.map
|
|
@@ -24,9 +24,9 @@ import {
|
|
|
24
24
|
} from "./chunk-XHHMM5HX.js";
|
|
25
25
|
import {
|
|
26
26
|
recordFleetEventsBestEffort
|
|
27
|
-
} from "./chunk-
|
|
28
|
-
import "./chunk-
|
|
29
|
-
import "./chunk-
|
|
27
|
+
} from "./chunk-DMPHP7UP.js";
|
|
28
|
+
import "./chunk-22GKJO5F.js";
|
|
29
|
+
import "./chunk-OCNSIBMP.js";
|
|
30
30
|
import "./chunk-LBYOLBW7.js";
|
|
31
31
|
import "./chunk-6VAL7XJT.js";
|
|
32
32
|
|
|
@@ -91,7 +91,7 @@ async function runGitHubSignalsCommand(opts, deps = {}) {
|
|
|
91
91
|
);
|
|
92
92
|
const sweptAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
93
|
const makeMirror = deps.makeMirror ?? (async () => {
|
|
94
|
-
const { makeHealthMirrorBestEffort } = await import("./health-mirror-
|
|
94
|
+
const { makeHealthMirrorBestEffort } = await import("./health-mirror-55RRU6BI.js");
|
|
95
95
|
return makeHealthMirrorBestEffort();
|
|
96
96
|
});
|
|
97
97
|
const mirror = await makeMirror();
|
|
@@ -156,4 +156,4 @@ export {
|
|
|
156
156
|
githubSignalsExitCode,
|
|
157
157
|
runGitHubSignalsCommand
|
|
158
158
|
};
|
|
159
|
-
//# sourceMappingURL=github-signals-
|
|
159
|
+
//# sourceMappingURL=github-signals-3WLIJ5RR.js.map
|
|
@@ -102,7 +102,7 @@ async function runHeaderImageCommand(site, opts) {
|
|
|
102
102
|
}
|
|
103
103
|
let withStore = opts;
|
|
104
104
|
if (opts.writeAirtable && !opts.storeDb && process.env.TURSO_DATABASE_URL) {
|
|
105
|
-
const { openDb, readDbConfig } = await import("./client-
|
|
105
|
+
const { openDb, readDbConfig } = await import("./client-M2V6FHH5.js");
|
|
106
106
|
const { storeHeaderImage } = await import("./header-images-SQBCE5OD.js");
|
|
107
107
|
const db = await openDb(readDbConfig());
|
|
108
108
|
withStore = { ...opts, storeDb: (id, img) => storeHeaderImage(db, id, img) };
|
|
@@ -115,4 +115,4 @@ export {
|
|
|
115
115
|
resolveTargets,
|
|
116
116
|
runHeaderImageCommand
|
|
117
117
|
};
|
|
118
|
-
//# sourceMappingURL=header-image-
|
|
118
|
+
//# sourceMappingURL=header-image-DOCDFFRO.js.map
|
|
@@ -12,8 +12,8 @@ import "./chunk-7NYN2P6I.js";
|
|
|
12
12
|
import {
|
|
13
13
|
openDb,
|
|
14
14
|
readDbConfig
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-22GKJO5F.js";
|
|
16
|
+
import "./chunk-OCNSIBMP.js";
|
|
17
17
|
import "./chunk-6VAL7XJT.js";
|
|
18
18
|
|
|
19
19
|
// src/audits/health-mirror.ts
|
|
@@ -43,4 +43,4 @@ export {
|
|
|
43
43
|
makeHealthMirrorBestEffort,
|
|
44
44
|
makeScheduleMirrorBestEffort
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=health-mirror-
|
|
46
|
+
//# sourceMappingURL=health-mirror-55RRU6BI.js.map
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DEFAULT_INIT_STEPS,
|
|
11
11
|
a11yFixturesPage,
|
|
12
12
|
init
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-UXJZU23T.js";
|
|
14
14
|
import {
|
|
15
15
|
convertToPnpm
|
|
16
16
|
} from "./chunk-XIDUYLSR.js";
|
|
@@ -29,10 +29,10 @@ import {
|
|
|
29
29
|
bumpDeps
|
|
30
30
|
} from "./chunk-2NJQLSEU.js";
|
|
31
31
|
import "./chunk-R53VL3RI.js";
|
|
32
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-MVDMKKBN.js";
|
|
33
33
|
import {
|
|
34
34
|
approveBlockers
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-E22WAIF2.js";
|
|
36
36
|
import {
|
|
37
37
|
findDueReports
|
|
38
38
|
} from "./chunk-PZR4EBPY.js";
|
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
parseAddresses,
|
|
41
41
|
sendApprovedReports,
|
|
42
42
|
withGlobalCc
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-FCS36FDT.js";
|
|
44
44
|
import {
|
|
45
45
|
isPendingApproval
|
|
46
46
|
} from "./chunk-F3JWOPTG.js";
|
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
isHealthGateClear
|
|
51
51
|
} from "./chunk-VAD43MOR.js";
|
|
52
52
|
import "./chunk-BRYXFH77.js";
|
|
53
|
-
import "./chunk-
|
|
53
|
+
import "./chunk-7MKT4I5T.js";
|
|
54
54
|
import {
|
|
55
55
|
fromAirtableBase
|
|
56
56
|
} from "./chunk-PR343K7F.js";
|
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
import "./chunk-WEKL2HYJ.js";
|
|
85
85
|
import {
|
|
86
86
|
syncConfigs
|
|
87
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-A6T2R63Z.js";
|
|
88
88
|
import "./chunk-3G25KIWW.js";
|
|
89
89
|
import "./chunk-MLT4QY5X.js";
|
|
90
90
|
import "./chunk-OTGB6YQO.js";
|
|
@@ -96,7 +96,7 @@ import {
|
|
|
96
96
|
runAudits,
|
|
97
97
|
runAuditsAcross,
|
|
98
98
|
securityAudit
|
|
99
|
-
} from "./chunk-
|
|
99
|
+
} from "./chunk-QD427NIO.js";
|
|
100
100
|
import "./chunk-LKGVSM2O.js";
|
|
101
101
|
import "./chunk-GASBX52O.js";
|
|
102
102
|
import "./chunk-XHHMM5HX.js";
|
|
@@ -104,11 +104,11 @@ import {
|
|
|
104
104
|
fromJsonFile,
|
|
105
105
|
localPath
|
|
106
106
|
} from "./chunk-Z3JXLULD.js";
|
|
107
|
-
import "./chunk-
|
|
108
|
-
import "./chunk-
|
|
107
|
+
import "./chunk-DMPHP7UP.js";
|
|
108
|
+
import "./chunk-22GKJO5F.js";
|
|
109
109
|
import "./chunk-UDRHAQT7.js";
|
|
110
110
|
import "./chunk-RARREDQE.js";
|
|
111
|
-
import "./chunk-
|
|
111
|
+
import "./chunk-OCNSIBMP.js";
|
|
112
112
|
import "./chunk-A3WDWNN5.js";
|
|
113
113
|
import {
|
|
114
114
|
isLeadFormType
|
|
@@ -122,7 +122,7 @@ import "./chunk-LBYOLBW7.js";
|
|
|
122
122
|
import "./chunk-XXTZBPUY.js";
|
|
123
123
|
import "./chunk-6VAL7XJT.js";
|
|
124
124
|
import "./chunk-NMAWXBJA.js";
|
|
125
|
-
import "./chunk-
|
|
125
|
+
import "./chunk-YLYXW5PH.js";
|
|
126
126
|
|
|
127
127
|
// src/recipes/index.ts
|
|
128
128
|
var ALL_RECIPE_NAMES = [
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-Q47DU255.js";
|
|
9
9
|
import {
|
|
10
10
|
init
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-UXJZU23T.js";
|
|
12
12
|
import "./chunk-XIDUYLSR.js";
|
|
13
13
|
import "./chunk-IYSEC543.js";
|
|
14
14
|
import "./chunk-J7SZQUCW.js";
|
|
@@ -16,11 +16,11 @@ import "./chunk-BQRFQSGY.js";
|
|
|
16
16
|
import "./chunk-Y53WOQIJ.js";
|
|
17
17
|
import "./chunk-7RJ2HMMJ.js";
|
|
18
18
|
import "./chunk-R53VL3RI.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-A6T2R63Z.js";
|
|
20
20
|
import "./chunk-3G25KIWW.js";
|
|
21
21
|
import "./chunk-MLT4QY5X.js";
|
|
22
22
|
import "./chunk-OTGB6YQO.js";
|
|
23
|
-
import "./chunk-
|
|
23
|
+
import "./chunk-QD427NIO.js";
|
|
24
24
|
import "./chunk-LKGVSM2O.js";
|
|
25
25
|
import "./chunk-GASBX52O.js";
|
|
26
26
|
import "./chunk-XHHMM5HX.js";
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
siteLabel
|
|
38
38
|
} from "./chunk-XXTZBPUY.js";
|
|
39
39
|
import "./chunk-NMAWXBJA.js";
|
|
40
|
-
import "./chunk-
|
|
40
|
+
import "./chunk-YLYXW5PH.js";
|
|
41
41
|
|
|
42
42
|
// src/cli/commands/init.ts
|
|
43
43
|
import { resolve } from "path";
|
|
@@ -110,4 +110,4 @@ async function runInitCommand(site, opts) {
|
|
|
110
110
|
export {
|
|
111
111
|
runInitCommand
|
|
112
112
|
};
|
|
113
|
-
//# sourceMappingURL=init-
|
|
113
|
+
//# sourceMappingURL=init-NXZ4NXTL.js.map
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
import "./chunk-MLT4QY5X.js";
|
|
43
43
|
import {
|
|
44
44
|
runAudits
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-QD427NIO.js";
|
|
46
46
|
import "./chunk-LKGVSM2O.js";
|
|
47
47
|
import "./chunk-GASBX52O.js";
|
|
48
48
|
import "./chunk-XHHMM5HX.js";
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
} from "./chunk-XXTZBPUY.js";
|
|
61
61
|
import "./chunk-6VAL7XJT.js";
|
|
62
62
|
import "./chunk-NMAWXBJA.js";
|
|
63
|
-
import "./chunk-
|
|
63
|
+
import "./chunk-YLYXW5PH.js";
|
|
64
64
|
|
|
65
65
|
// src/cli/commands/launch.ts
|
|
66
66
|
import { resolve } from "path";
|
|
@@ -241,8 +241,8 @@ async function runLaunchCommand(site, opts) {
|
|
|
241
241
|
if (!target) {
|
|
242
242
|
return { output: `No site resolved for "${site}".`, code: 1 };
|
|
243
243
|
}
|
|
244
|
-
const { makeReportMirror } = await import("./report-mirror-
|
|
245
|
-
const { makeSiteMirror } = await import("./site-mirror-
|
|
244
|
+
const { makeReportMirror } = await import("./report-mirror-6ZDCAQZB.js");
|
|
245
|
+
const { makeSiteMirror } = await import("./site-mirror-3E7JYTJG.js");
|
|
246
246
|
const result = await launch(target, {
|
|
247
247
|
reportMirror: await makeReportMirror(),
|
|
248
248
|
siteMirror: await makeSiteMirror()
|
|
@@ -252,4 +252,4 @@ async function runLaunchCommand(site, opts) {
|
|
|
252
252
|
export {
|
|
253
253
|
runLaunchCommand
|
|
254
254
|
};
|
|
255
|
-
//# sourceMappingURL=launch-
|
|
255
|
+
//# sourceMappingURL=launch-WPEQ3TY2.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
parseAddresses,
|
|
5
5
|
sendApprovedReports,
|
|
6
6
|
withGlobalCc
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-FCS36FDT.js";
|
|
8
8
|
import "./chunk-F3JWOPTG.js";
|
|
9
9
|
import "./chunk-VAD43MOR.js";
|
|
10
10
|
import "./chunk-IY6256GK.js";
|
|
@@ -16,10 +16,10 @@ import "./chunk-5UINJNAJ.js";
|
|
|
16
16
|
import "./chunk-BGTYPVLT.js";
|
|
17
17
|
import "./chunk-7NYN2P6I.js";
|
|
18
18
|
import "./chunk-WEKL2HYJ.js";
|
|
19
|
-
import "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-DMPHP7UP.js";
|
|
20
|
+
import "./chunk-22GKJO5F.js";
|
|
21
21
|
import "./chunk-RARREDQE.js";
|
|
22
|
-
import "./chunk-
|
|
22
|
+
import "./chunk-OCNSIBMP.js";
|
|
23
23
|
import "./chunk-6VAL7XJT.js";
|
|
24
24
|
export {
|
|
25
25
|
GLOBAL_REPORT_CC,
|
|
@@ -28,4 +28,4 @@ export {
|
|
|
28
28
|
sendApprovedReports,
|
|
29
29
|
withGlobalCc
|
|
30
30
|
};
|
|
31
|
-
//# sourceMappingURL=orchestrate-
|
|
31
|
+
//# sourceMappingURL=orchestrate-KMQIUP3H.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ACCURACY_SKIPPED,
|
|
3
|
+
ANALYZE_SKIPPED,
|
|
4
|
+
ASSETS_SKIPPED,
|
|
5
|
+
ASSET_ACCEPT,
|
|
6
|
+
GOAL_UNRESOLVED,
|
|
7
|
+
PROBES_SKIPPED,
|
|
8
|
+
defaultAssetProbe,
|
|
9
|
+
envAccuracyDeps,
|
|
10
|
+
envAnalyzeDeps,
|
|
11
|
+
envEngines,
|
|
12
|
+
runProspectAudit
|
|
13
|
+
} from "./chunk-WVVT6SJK.js";
|
|
14
|
+
import "./chunk-RQLXEMEE.js";
|
|
15
|
+
import "./chunk-RARREDQE.js";
|
|
16
|
+
export {
|
|
17
|
+
ACCURACY_SKIPPED,
|
|
18
|
+
ANALYZE_SKIPPED,
|
|
19
|
+
ASSETS_SKIPPED,
|
|
20
|
+
ASSET_ACCEPT,
|
|
21
|
+
GOAL_UNRESOLVED,
|
|
22
|
+
PROBES_SKIPPED,
|
|
23
|
+
defaultAssetProbe,
|
|
24
|
+
envAccuracyDeps,
|
|
25
|
+
envAnalyzeDeps,
|
|
26
|
+
envEngines,
|
|
27
|
+
runProspectAudit
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=pipeline-HQJYZ443.js.map
|