@reddoorla/maintenance 0.75.0 → 0.76.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-V6EYPPUR.js → announce-RUT247HS.js} +9 -6
- package/dist/{announce-V6EYPPUR.js.map → announce-RUT247HS.js.map} +1 -1
- package/dist/{blux-C24OVDDW.js → blux-RIJBRVCF.js} +1867 -277
- package/dist/blux-RIJBRVCF.js.map +1 -0
- package/dist/chunk-37S6L6G4.js +170 -0
- package/dist/chunk-37S6L6G4.js.map +1 -0
- package/dist/{chunk-PERP7LSS.js → chunk-3U3IGVAQ.js} +89 -13
- package/dist/chunk-3U3IGVAQ.js.map +1 -0
- package/dist/{chunk-IDBMYDVH.js → chunk-4VAUSRSS.js} +5 -3
- package/dist/{chunk-IDBMYDVH.js.map → chunk-4VAUSRSS.js.map} +1 -1
- package/dist/{run-migration-LJNNZWIV.js → chunk-6DOFNDXU.js} +5 -3
- package/dist/chunk-6DOFNDXU.js.map +1 -0
- package/dist/{chunk-NUIBQPB3.js → chunk-6SZBNALD.js} +62 -9
- package/dist/chunk-6SZBNALD.js.map +1 -0
- package/dist/{chunk-6OYICQSJ.js → chunk-CZAMYVOP.js} +9 -4
- package/dist/chunk-CZAMYVOP.js.map +1 -0
- package/dist/chunk-DRDAEELJ.js +197 -0
- package/dist/chunk-DRDAEELJ.js.map +1 -0
- package/dist/{chunk-KKL3RYFQ.js → chunk-DYKG53IR.js} +2 -2
- package/dist/{chunk-HBFMTM2E.js → chunk-JTRTG5CU.js} +3 -3
- package/dist/{chunk-JCITWZQR.js → chunk-MNE374CZ.js} +2 -50
- package/dist/chunk-MNE374CZ.js.map +1 -0
- package/dist/chunk-MPBXOJ3N.js +51 -0
- package/dist/chunk-MPBXOJ3N.js.map +1 -0
- package/dist/{chunk-I55YWV6T.js → chunk-QWFHC3VH.js} +3 -3
- package/dist/{chunk-KLVMJYZZ.js → chunk-SEQA7MC3.js} +2 -2
- package/dist/chunk-UWJZXL2D.js +9 -0
- package/dist/chunk-UWJZXL2D.js.map +1 -0
- package/dist/cli/bin.js +36 -9
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/{digest-LEMZS5ZK.js → digest-75FWSC74.js} +9 -6
- package/dist/{digest-LEMZS5ZK.js.map → digest-75FWSC74.js.map} +1 -1
- package/dist/favicon-SRHSJZ62.js +42 -0
- package/dist/favicon-SRHSJZ62.js.map +1 -0
- package/dist/freeze-Y5OJFOST.js +442 -0
- package/dist/freeze-Y5OJFOST.js.map +1 -0
- package/dist/header-image-H4N54DHN.js +95 -0
- package/dist/header-image-H4N54DHN.js.map +1 -0
- package/dist/index.d.ts +63 -1
- package/dist/index.js +64 -16
- package/dist/index.js.map +1 -1
- package/dist/{init-7PTP4O2K.js → init-2KOISBCZ.js} +3 -3
- package/dist/{launch-W33FWWOZ.js → launch-K4EHKTXC.js} +7 -5
- package/dist/{launch-W33FWWOZ.js.map → launch-K4EHKTXC.js.map} +1 -1
- package/dist/{orchestrate-NYRYEKQM.js → orchestrate-CPR6KVS2.js} +4 -3
- package/dist/{preflight-JCQFKB6M.js → preflight-QRRMPI7K.js} +5 -4
- package/dist/{preflight-JCQFKB6M.js.map → preflight-QRRMPI7K.js.map} +1 -1
- package/dist/{report-POHQEV6F.js → report-SLGBDJ6O.js} +8 -6
- package/dist/{report-POHQEV6F.js.map → report-SLGBDJ6O.js.map} +1 -1
- package/dist/reports/header-image/assets/plate.png +0 -0
- package/dist/run-migration-WP57U5XO.js +9 -0
- package/dist/run-migration-WP57U5XO.js.map +1 -0
- package/dist/{selftest-OO2WDSDM.js → selftest-RVASRO3Z.js} +8 -6
- package/dist/{selftest-OO2WDSDM.js.map → selftest-RVASRO3Z.js.map} +1 -1
- package/dist/webflow-624FEBFD.js +548 -0
- package/dist/webflow-624FEBFD.js.map +1 -0
- package/package.json +3 -3
- package/dist/blux-C24OVDDW.js.map +0 -1
- package/dist/chunk-6OYICQSJ.js.map +0 -1
- package/dist/chunk-JCITWZQR.js.map +0 -1
- package/dist/chunk-NUIBQPB3.js.map +0 -1
- package/dist/chunk-PERP7LSS.js.map +0 -1
- package/dist/run-migration-LJNNZWIV.js.map +0 -1
- /package/dist/{chunk-KKL3RYFQ.js.map → chunk-DYKG53IR.js.map} +0 -0
- /package/dist/{chunk-HBFMTM2E.js.map → chunk-JTRTG5CU.js.map} +0 -0
- /package/dist/{chunk-I55YWV6T.js.map → chunk-QWFHC3VH.js.map} +0 -0
- /package/dist/{chunk-KLVMJYZZ.js.map → chunk-SEQA7MC3.js.map} +0 -0
- /package/dist/{init-7PTP4O2K.js.map → init-2KOISBCZ.js.map} +0 -0
- /package/dist/{orchestrate-NYRYEKQM.js.map → orchestrate-CPR6KVS2.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/commands/header-image.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { openBase, readAirtableConfig } from \"../../reports/airtable/client.js\";\nimport { listWebsites, siteSlug, ACTIVE_STATUSES } from \"../../reports/airtable/websites.js\";\nimport type { WebsiteRow } from \"../../reports/airtable/websites.js\";\nimport { uploadAttachment } from \"../../reports/airtable/attachments.js\";\nimport { generateHeaderImage } from \"../../reports/header-image/index.js\";\n\nexport type HeaderImageOptions = {\n all?: boolean;\n force?: boolean;\n writeAirtable?: boolean;\n outDir?: string;\n settleMs?: string;\n};\n\n/** Which rows this invocation should act on. Pure, so it is unit-tested. */\nexport function resolveTargets(\n rows: readonly WebsiteRow[],\n // `| undefined` spelled out because `exactOptionalPropertyTypes` otherwise\n // rejects the caller passing through possibly-undefined CLI flags.\n opts: { site?: string | undefined; all?: boolean | undefined; force?: boolean | undefined },\n): WebsiteRow[] {\n if (opts.site) {\n const want = siteSlug(opts.site);\n return rows.filter((r) => siteSlug(r.name) === want);\n }\n if (!opts.all) return [];\n return rows.filter((r) => {\n if (!r.url) return false;\n if (r.status === null || !ACTIVE_STATUSES.has(r.status)) return false;\n return opts.force ? true : !r.headerImage;\n });\n}\n\n/** Parse the `--settle-ms <ms>` flag. Unset → undefined (the generator's own\n * default settle). `Number(\"abc\")` is NaN, which would sail through as a NaN\n * timeout and silently skip the settle entirely, so anything non-finite or\n * negative is rejected up front rather than producing a subtly-wrong capture. */\nexport function parseSettleMs(value: string | undefined): number | undefined {\n if (value === undefined) return undefined;\n const n = Number(value);\n if (!Number.isFinite(n) || n < 0) {\n throw Object.assign(\n new Error(`--settle-ms must be a non-negative number of milliseconds, got \"${value}\"`),\n { exitCode: 2 },\n );\n }\n return n;\n}\n\n/** Injected in tests so the file-writing branch can be exercised without\n * driving a real browser (mirrors `GenerateInput.shooter`). */\ntype Generate = typeof generateHeaderImage;\n\n/**\n * Generate a header image per target row and either upload it to Airtable or\n * write it locally for review. Split out of {@link runHeaderImageCommand} so\n * the local-output path is testable without an Airtable base.\n */\nexport async function generateForTargets(\n targets: readonly WebsiteRow[],\n opts: HeaderImageOptions,\n generate: Generate = generateHeaderImage,\n): Promise<{ output: string; code: number }> {\n const outDir = resolve(opts.outDir ?? \"reports\");\n const settleMs = parseSettleMs(opts.settleMs);\n // `reports/` does not exist on a fresh checkout, so the writeFile() below\n // would throw ENOENT for the first operator to run the default (local) path.\n // Create it once, before the loop, rather than per site.\n if (!opts.writeAirtable) await mkdir(outDir, { recursive: true });\n\n const lines: string[] = [];\n let failed = 0;\n\n for (const row of targets) {\n try {\n const gen = await generate({\n url: row.url,\n slug: siteSlug(row.name),\n // `exactOptionalPropertyTypes` rejects an explicit `settleMs: undefined`\n // against `settleMs?: number`, so omit the key entirely when unset.\n ...(settleMs === undefined ? {} : { settleMs }),\n });\n if (opts.writeAirtable) {\n await uploadAttachment(row.id, \"Header image\", gen.bytes, gen.filename, gen.contentType);\n lines.push(\n `✔ ${row.name} — uploaded ${gen.filename} (${(gen.bytes.byteLength / 1024 / 1024).toFixed(2)} MB)`,\n );\n } else {\n const path = resolve(outDir, gen.filename);\n await writeFile(path, gen.bytes);\n lines.push(`✔ ${row.name} — wrote ${path} (review, then re-run with --write-airtable)`);\n }\n } catch (err) {\n failed++;\n lines.push(`✖ ${row.name} — ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n lines.push(`${targets.length - failed}/${targets.length} generated.`);\n return { output: lines.join(\"\\n\"), code: failed > 0 ? 1 : 0 };\n}\n\n/**\n * `header-image [site]` — capture a site's live homepage and composite its\n * report header image. Defaults to writing the JPEG locally so the operator can\n * eyeball it; `--write-airtable` uploads it to the Websites row's Header image\n * field. `--all` backfills every live site that has no header image yet.\n */\nexport async function runHeaderImageCommand(\n site: string | undefined,\n opts: HeaderImageOptions,\n): Promise<{ output: string; code: number }> {\n const base = openBase(readAirtableConfig());\n const rows = await listWebsites(base);\n const targets = resolveTargets(rows, { site, all: opts.all, force: opts.force });\n if (targets.length === 0) {\n return {\n output: site ? `No site matched \"${site}\".` : \"No sites need a header image.\",\n code: 1,\n };\n }\n return generateForTargets(targets, opts);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA,SAAS,OAAO,iBAAiB;AACjC,SAAS,eAAe;AAgBjB,SAAS,eACd,MAGA,MACc;AACd,MAAI,KAAK,MAAM;AACb,UAAM,OAAO,SAAS,KAAK,IAAI;AAC/B,WAAO,KAAK,OAAO,CAAC,MAAM,SAAS,EAAE,IAAI,MAAM,IAAI;AAAA,EACrD;AACA,MAAI,CAAC,KAAK,IAAK,QAAO,CAAC;AACvB,SAAO,KAAK,OAAO,CAAC,MAAM;AACxB,QAAI,CAAC,EAAE,IAAK,QAAO;AACnB,QAAI,EAAE,WAAW,QAAQ,CAAC,gBAAgB,IAAI,EAAE,MAAM,EAAG,QAAO;AAChE,WAAO,KAAK,QAAQ,OAAO,CAAC,EAAE;AAAA,EAChC,CAAC;AACH;AAMO,SAAS,cAAc,OAA+C;AAC3E,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,IAAI,OAAO,KAAK;AACtB,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,GAAG;AAChC,UAAM,OAAO;AAAA,MACX,IAAI,MAAM,mEAAmE,KAAK,GAAG;AAAA,MACrF,EAAE,UAAU,EAAE;AAAA,IAChB;AAAA,EACF;AACA,SAAO;AACT;AAWA,eAAsB,mBACpB,SACA,MACA,WAAqB,qBACsB;AAC3C,QAAM,SAAS,QAAQ,KAAK,UAAU,SAAS;AAC/C,QAAM,WAAW,cAAc,KAAK,QAAQ;AAI5C,MAAI,CAAC,KAAK,cAAe,OAAM,MAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AAEhE,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS;AAEb,aAAW,OAAO,SAAS;AACzB,QAAI;AACF,YAAM,MAAM,MAAM,SAAS;AAAA,QACzB,KAAK,IAAI;AAAA,QACT,MAAM,SAAS,IAAI,IAAI;AAAA;AAAA;AAAA,QAGvB,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,MAC/C,CAAC;AACD,UAAI,KAAK,eAAe;AACtB,cAAM,iBAAiB,IAAI,IAAI,gBAAgB,IAAI,OAAO,IAAI,UAAU,IAAI,WAAW;AACvF,cAAM;AAAA,UACJ,UAAK,IAAI,IAAI,oBAAe,IAAI,QAAQ,MAAM,IAAI,MAAM,aAAa,OAAO,MAAM,QAAQ,CAAC,CAAC;AAAA,QAC9F;AAAA,MACF,OAAO;AACL,cAAM,OAAO,QAAQ,QAAQ,IAAI,QAAQ;AACzC,cAAM,UAAU,MAAM,IAAI,KAAK;AAC/B,cAAM,KAAK,UAAK,IAAI,IAAI,iBAAY,IAAI,8CAA8C;AAAA,MACxF;AAAA,IACF,SAAS,KAAK;AACZ;AACA,YAAM,KAAK,UAAK,IAAI,IAAI,WAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IAClF;AAAA,EACF;AAEA,QAAM,KAAK,GAAG,QAAQ,SAAS,MAAM,IAAI,QAAQ,MAAM,aAAa;AACpE,SAAO,EAAE,QAAQ,MAAM,KAAK,IAAI,GAAG,MAAM,SAAS,IAAI,IAAI,EAAE;AAC9D;AAQA,eAAsB,sBACpB,MACA,MAC2C;AAC3C,QAAM,OAAO,SAAS,mBAAmB,CAAC;AAC1C,QAAM,OAAO,MAAM,aAAa,IAAI;AACpC,QAAM,UAAU,eAAe,MAAM,EAAE,MAAM,KAAK,KAAK,KAAK,OAAO,KAAK,MAAM,CAAC;AAC/E,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,MACL,QAAQ,OAAO,oBAAoB,IAAI,OAAO;AAAA,MAC9C,MAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO,mBAAmB,SAAS,IAAI;AACzC;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -71,6 +71,11 @@ type RouteResult = {
|
|
|
71
71
|
title: string | null;
|
|
72
72
|
/** The chromium `meta[name="description"]` content, or null when absent/not captured. */
|
|
73
73
|
metaDescription: string | null;
|
|
74
|
+
/** Unsubstituted SvelteKit template placeholders (e.g. "%sveltekit.body%") the SERVED HTML
|
|
75
|
+
* carried — a broken app.html (a placeholder token trapped in a comment, a malformed template)
|
|
76
|
+
* ships these literally. Empty = clean. Optional: a runner that didn't capture it is treated as
|
|
77
|
+
* clean (undefined ≠ failure), but a token that IS seen is unambiguous corruption. */
|
|
78
|
+
leakedTemplateTokens?: string[];
|
|
74
79
|
};
|
|
75
80
|
type LinkResult = {
|
|
76
81
|
url: string;
|
|
@@ -88,9 +93,18 @@ type PageFetch = {
|
|
|
88
93
|
title: string | null;
|
|
89
94
|
metaDescription: string | null;
|
|
90
95
|
};
|
|
96
|
+
/** What a no-follow GET of the base URL observed: the raw status (null = network error/timeout)
|
|
97
|
+
* and the raw Location header (null when absent). See canonicalizeBaseUrl. */
|
|
98
|
+
type RedirectFetch = {
|
|
99
|
+
status: number | null;
|
|
100
|
+
location: string | null;
|
|
101
|
+
};
|
|
91
102
|
/** Injected plain-fetch re-verification IO (see reverifyRoutes). Tests pass a fake. */
|
|
92
103
|
type VerifyDeps = {
|
|
93
104
|
fetchPage: (url: string) => Promise<PageFetch>;
|
|
105
|
+
/** Optional single `redirect:"manual"` GET, for canonicalizeBaseUrl's base-URL preflight.
|
|
106
|
+
* Optional so existing fakes stay valid; absent → canonicalization is a no-op. */
|
|
107
|
+
fetchRedirect?: (url: string) => Promise<RedirectFetch>;
|
|
94
108
|
};
|
|
95
109
|
|
|
96
110
|
/** Outcome of driving one site's contact form. `formPresent:false` ⇒ n/a
|
|
@@ -756,6 +770,47 @@ type ReportRow = {
|
|
|
756
770
|
overrideAt: string | null;
|
|
757
771
|
};
|
|
758
772
|
|
|
773
|
+
type ShootOptions = {
|
|
774
|
+
url: string;
|
|
775
|
+
width: number;
|
|
776
|
+
height: number;
|
|
777
|
+
deviceScaleFactor: number;
|
|
778
|
+
settleMs: number;
|
|
779
|
+
};
|
|
780
|
+
/** Injected browser IO. The real impl drives Playwright; tests pass a fake. */
|
|
781
|
+
type Shooter = {
|
|
782
|
+
shoot: (opts: ShootOptions) => Promise<Uint8Array>;
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
type GenerateInput = {
|
|
786
|
+
/** The site's production URL. */
|
|
787
|
+
url: string;
|
|
788
|
+
/** Airtable site slug; drives the attachment filename. */
|
|
789
|
+
slug?: string;
|
|
790
|
+
/** Injected browser IO (tests). */
|
|
791
|
+
shooter?: Shooter;
|
|
792
|
+
/** Per-site settle override for animation-heavy or consent-gated homepages. */
|
|
793
|
+
settleMs?: number;
|
|
794
|
+
};
|
|
795
|
+
type GeneratedHeaderImage = {
|
|
796
|
+
bytes: Uint8Array;
|
|
797
|
+
/** The domain as printed on the plate. */
|
|
798
|
+
domain: string;
|
|
799
|
+
filename: string;
|
|
800
|
+
contentType: "image/jpeg";
|
|
801
|
+
};
|
|
802
|
+
/** Strip scheme, leading www and any path — the plate prints a bare domain. */
|
|
803
|
+
declare function domainFromUrl(url: string): string;
|
|
804
|
+
/** Capture a site's homepage and composite its header image. */
|
|
805
|
+
declare function generateHeaderImage(input: GenerateInput): Promise<GeneratedHeaderImage>;
|
|
806
|
+
|
|
807
|
+
type RefreshHeaderDeps = {
|
|
808
|
+
generate?: (input: {
|
|
809
|
+
url: string;
|
|
810
|
+
slug?: string;
|
|
811
|
+
}) => Promise<GeneratedHeaderImage>;
|
|
812
|
+
upload?: (recordId: string, field: string, bytes: Uint8Array, filename: string, contentType: string) => Promise<void>;
|
|
813
|
+
};
|
|
759
814
|
type DraftOptions = {
|
|
760
815
|
/** Where to write the local preview HTML when `previewOnly`. Defaults to `reports/<slug>/draft.html`. */
|
|
761
816
|
previewPath?: string;
|
|
@@ -772,6 +827,10 @@ type DraftOptions = {
|
|
|
772
827
|
/** The mapped ReportRow being completed, returned as `reportRow` from the
|
|
773
828
|
* complete path so callers keep the same shape they get on the create path. */
|
|
774
829
|
existingRow?: ReportRow;
|
|
830
|
+
/** Injected deps for the draft-time header refresh, or `false` to skip it
|
|
831
|
+
* entirely. Tests set `false` (or a stub) so a unit suite never launches a
|
|
832
|
+
* browser or resolves DNS. Production leaves it unset and gets the real thing. */
|
|
833
|
+
refreshHeader?: RefreshHeaderDeps | false;
|
|
775
834
|
};
|
|
776
835
|
/** An enrichment fetch that *errored* (not one that was legitimately skipped
|
|
777
836
|
* because it isn't configured / the site lacks the inputs). Surfaced so a
|
|
@@ -1142,4 +1201,7 @@ declare function renderCockpitHtml(model: CockpitModel): string;
|
|
|
1142
1201
|
*/
|
|
1143
1202
|
declare function verifyBasicAuth(authHeader: string | null | undefined, expectedPassword: string | null): boolean;
|
|
1144
1203
|
|
|
1145
|
-
|
|
1204
|
+
/** Read the bundled 2400x3200 plate. */
|
|
1205
|
+
declare function loadPlate(): Promise<Uint8Array>;
|
|
1206
|
+
|
|
1207
|
+
export { ALL_AUDIT_NAMES, ALL_RECIPE_NAMES, type AirtableInventoryOptions, AuditName, AuditResult, BLURRED_CID, type BumpDepsOptions, type BundledImage, CHECK_CID, type ConvertToPnpmOptions, DEFAULT_INIT_STEPS, type DraftOptions, type DraftResult, type DueItem, type GenerateInput, type GeneratedHeaderImage, type HeaderImage, type InitOptions, type InitResult, type InitStep, type InitStepResult, InventoryProvider, type LighthouseScores, type LocalPathOptions, type OnboardAudit, type OnboardOptions, type OrchestrateOptions, RecipeName, RecipeResult, type RefreshHeaderDeps, type RenderResult, type ReportData, type ReportType, Site, type UpgradeSvelte4to5Options, a11yAudit, a11yFixturesPage, bumpDeps, convertToPnpm, depsAudit, domainFromUrl, draftReportForSite, findDueReports, fromAirtableBase, fromJsonFile, generateHeaderImage, init, isRecipeName, lighthouseAudit, lintAudit, loadBundledImages, loadPlate, localPath, onboard, renderCockpitHtml, renderReportHtml, renderSiteDashboardHtml, runAudits, runAuditsAcross, securityAudit, selfCaretRange, selfPackageVersion, sendApprovedReports, svelteCodemods, upgradeSvelte4to5, verifyBasicAuth };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
|
+
import "./chunk-CZAMYVOP.js";
|
|
1
2
|
import {
|
|
2
3
|
draftReportForSite
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import
|
|
6
|
-
|
|
4
|
+
} from "./chunk-6SZBNALD.js";
|
|
5
|
+
import "./chunk-SEQA7MC3.js";
|
|
6
|
+
import {
|
|
7
|
+
domainFromUrl,
|
|
8
|
+
generateHeaderImage,
|
|
9
|
+
loadPlate
|
|
10
|
+
} from "./chunk-37S6L6G4.js";
|
|
7
11
|
import {
|
|
8
12
|
approveBlockers
|
|
9
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-JTRTG5CU.js";
|
|
10
14
|
import {
|
|
11
15
|
findDueReports
|
|
12
16
|
} from "./chunk-D4FVAIEJ.js";
|
|
17
|
+
import "./chunk-RHPDQW6D.js";
|
|
13
18
|
import {
|
|
14
19
|
upgradeSvelte4to5
|
|
15
20
|
} from "./chunk-ZO7WNY57.js";
|
|
@@ -17,7 +22,7 @@ import {
|
|
|
17
22
|
DEFAULT_INIT_STEPS,
|
|
18
23
|
a11yFixturesPage,
|
|
19
24
|
init
|
|
20
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-DYKG53IR.js";
|
|
21
26
|
import {
|
|
22
27
|
convertToPnpm
|
|
23
28
|
} from "./chunk-2X322ZES.js";
|
|
@@ -37,7 +42,7 @@ import {
|
|
|
37
42
|
parseAddresses,
|
|
38
43
|
sendApprovedReports,
|
|
39
44
|
withGlobalCc
|
|
40
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-4VAUSRSS.js";
|
|
41
46
|
import "./chunk-GECESMVS.js";
|
|
42
47
|
import {
|
|
43
48
|
BLURRED_CID,
|
|
@@ -50,7 +55,8 @@ import {
|
|
|
50
55
|
loadBundledImages,
|
|
51
56
|
renderReportHtml,
|
|
52
57
|
safeUrl
|
|
53
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-MNE374CZ.js";
|
|
59
|
+
import "./chunk-MPBXOJ3N.js";
|
|
54
60
|
import "./chunk-WEKL2HYJ.js";
|
|
55
61
|
import {
|
|
56
62
|
bumpDeps
|
|
@@ -73,7 +79,7 @@ import {
|
|
|
73
79
|
runAudits,
|
|
74
80
|
runAuditsAcross,
|
|
75
81
|
securityAudit
|
|
76
|
-
} from "./chunk-
|
|
82
|
+
} from "./chunk-3U3IGVAQ.js";
|
|
77
83
|
import "./chunk-GASBX52O.js";
|
|
78
84
|
import "./chunk-YEGHUODU.js";
|
|
79
85
|
import "./chunk-LKGVSM2O.js";
|
|
@@ -606,8 +612,23 @@ th, td { text-align: left; padding: 0.5rem; border-bottom: 1px solid #eee; }
|
|
|
606
612
|
@media (prefers-color-scheme: dark) { th, td { border-color: #2a2a2a; } }
|
|
607
613
|
.muted { color: #999; }
|
|
608
614
|
.empty { color: #999; padding: 1rem; border: 1px dashed #ccc; border-radius: 6px; text-align: center; }
|
|
609
|
-
button.approve { font: inherit; padding: 0.35rem 0.85rem; border: 1px solid #2c7; border-radius: 6px; background: #2c7; color: #fff; cursor: pointer; }
|
|
615
|
+
button.approve { font: inherit; padding: 0.35rem 0.85rem; border: 1px solid #2c7; border-radius: 6px; background: #2c7; color: #fff; cursor: pointer; position: relative; transition: background-color 0.12s ease, border-color 0.12s ease; }
|
|
616
|
+
button.approve:hover:not(:disabled) { background: #25b36a; border-color: #25b36a; }
|
|
617
|
+
button.approve:active:not(:disabled) { background: #1e9a5b; border-color: #1e9a5b; }
|
|
618
|
+
button.approve:focus-visible { outline: 2px solid #2c7; outline-offset: 2px; }
|
|
610
619
|
button.approve:disabled { opacity: 0.6; cursor: default; }
|
|
620
|
+
/* In-flight: the label goes transparent rather than being removed, so the button keeps
|
|
621
|
+
its exact width and the row never reflows mid-request. The spinner is a pseudo-element,
|
|
622
|
+
NOT injected markup \u2014 this handler is deliberately textContent/title-only so server
|
|
623
|
+
strings can never become HTML, and a ::after keeps that property intact. */
|
|
624
|
+
button.approve.is-loading { color: transparent; cursor: progress; }
|
|
625
|
+
button.approve.is-loading::after { content: ""; position: absolute; top: 50%; left: 50%; width: 0.85em; height: 0.85em; margin: -0.45em 0 0 -0.45em; border: 2px solid #ffffff80; border-top-color: transparent; border-radius: 50%; animation: approve-spin 0.8s linear infinite; }
|
|
626
|
+
@keyframes approve-spin { to { transform: rotate(360deg); } }
|
|
627
|
+
/* Never remove the spin outright \u2014 a frozen ring reads as "hung". Slow it instead. */
|
|
628
|
+
@media (prefers-reduced-motion: reduce) { button.approve.is-loading::after { animation-duration: 2.4s; } }
|
|
629
|
+
/* Terminal success. Wins over :disabled's dimming (the button stays disabled after a
|
|
630
|
+
successful approve, and a 0.6-opacity "Approved" reads as not-quite-done). */
|
|
631
|
+
button.approve.is-approved, button.approve.is-approved:disabled { background: #14663c; border-color: #14663c; color: #fff; opacity: 1; }
|
|
611
632
|
.pending-info { display: flex; flex-wrap: wrap; gap: 0.35rem 1rem; font-size: 0.82rem; margin: 0.3rem 0 0.15rem; }
|
|
612
633
|
.recipients-missing { color: #e57373; }
|
|
613
634
|
.preflight { font-size: 0.78rem; padding: 0.1rem 0.45rem; border-radius: 999px; white-space: nowrap; }
|
|
@@ -677,12 +698,17 @@ button.approve:disabled { opacity: 0.6; cursor: default; }
|
|
|
677
698
|
}
|
|
678
699
|
.pill { font-size: 0.75rem; padding: 0.1rem 0.5rem; border-radius: 999px; font-weight: 700; }
|
|
679
700
|
.override { margin: 0.4rem 0 0.15rem 0.25rem; }
|
|
680
|
-
button.override-toggle { font: inherit; font-size: 0.78rem; padding: 0.15rem 0.6rem; border: 1px solid #b00; border-radius: 6px; background: transparent; color: #b00; cursor: pointer; }
|
|
681
|
-
|
|
701
|
+
button.override-toggle { font: inherit; font-size: 0.78rem; padding: 0.15rem 0.6rem; border: 1px solid #b00; border-radius: 6px; background: transparent; color: #b00; cursor: pointer; transition: background-color 0.12s ease; }
|
|
702
|
+
button.override-toggle:hover { background: #bb00001a; }
|
|
703
|
+
button.override-toggle:focus-visible { outline: 2px solid #b00; outline-offset: 2px; }
|
|
704
|
+
@media (prefers-color-scheme: dark) { button.override-toggle { border-color: #ff8a80; color: #ff8a80; } button.override-toggle:hover { background: #ff8a801f; } button.override-toggle:focus-visible { outline-color: #ff8a80; } }
|
|
682
705
|
.override-form { margin-top: 0.35rem; display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
|
|
683
706
|
.override-reason { font: inherit; font-size: 0.85rem; padding: 0.25rem 0.45rem; border: 1px solid #ccc; border-radius: 4px; background: transparent; color: inherit; min-width: 220px; flex: 1 1 220px; }
|
|
684
707
|
@media (prefers-color-scheme: dark) { .override-reason { border-color: #444; } }
|
|
685
|
-
button.override-submit { font: inherit; font-size: 0.8rem; padding: 0.25rem 0.7rem; border: 1px solid #b00; border-radius: 6px; background: #b00; color: #fff; cursor: pointer; }
|
|
708
|
+
button.override-submit { font: inherit; font-size: 0.8rem; padding: 0.25rem 0.7rem; border: 1px solid #b00; border-radius: 6px; background: #b00; color: #fff; cursor: pointer; transition: background-color 0.12s ease, border-color 0.12s ease; }
|
|
709
|
+
button.override-submit:hover:not(:disabled) { background: #900; border-color: #900; }
|
|
710
|
+
button.override-submit:active:not(:disabled) { background: #7a0000; border-color: #7a0000; }
|
|
711
|
+
button.override-submit:focus-visible { outline: 2px solid #b00; outline-offset: 2px; }
|
|
686
712
|
button.override-submit:disabled { opacity: 0.6; cursor: default; }
|
|
687
713
|
.override-status { font-size: 0.78rem; color: #999; }
|
|
688
714
|
.vuln-list { list-style: none; padding: 0; margin: 0; }
|
|
@@ -704,7 +730,10 @@ button.override-submit:disabled { opacity: 0.6; cursor: default; }
|
|
|
704
730
|
.detail dd input, .detail dd select, .detail dd textarea { width: 100%; box-sizing: border-box; font: inherit; padding: 0.25rem 0.4rem; border: 1px solid #ccc; border-radius: 4px; background: transparent; color: inherit; }
|
|
705
731
|
.detail dd textarea { min-height: 3.5rem; resize: vertical; }
|
|
706
732
|
.detail-saved { font-size: 0.8rem; color: #2a8; }
|
|
707
|
-
button.trigger-renovate { font: inherit; font-size: 0.8rem; padding: 0.15rem 0.6rem; margin-left: 0.5rem; border: 1px solid #888; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; }
|
|
733
|
+
button.trigger-renovate { font: inherit; font-size: 0.8rem; padding: 0.15rem 0.6rem; margin-left: 0.5rem; border: 1px solid #888; border-radius: 6px; background: transparent; color: inherit; cursor: pointer; transition: background-color 0.12s ease, border-color 0.12s ease; }
|
|
734
|
+
button.trigger-renovate:hover:not(:disabled) { background: #88888822; border-color: #666; }
|
|
735
|
+
button.trigger-renovate:focus-visible { outline: 2px solid #888; outline-offset: 2px; }
|
|
736
|
+
button.trigger-renovate:disabled { opacity: 0.6; cursor: default; }
|
|
708
737
|
/* Alarm chip strip \u2014 mirrors fleet-render.ts chip styles (NOT the page's .pill classes). */
|
|
709
738
|
.chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
|
|
710
739
|
.chip { font-size: 0.8rem; padding: 0.1rem 0.5rem; border-radius: 6px; background: #f0f0f0; }
|
|
@@ -779,18 +808,31 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
779
808
|
document.querySelectorAll("button.approve").forEach((b) => {
|
|
780
809
|
b.addEventListener("click", async () => {
|
|
781
810
|
b.disabled = true;
|
|
811
|
+
// is-loading swaps the label for a spinner; aria-busy is the same news for a
|
|
812
|
+
// screen reader, which cannot see it. Both are cleared in the finally below so
|
|
813
|
+
// no exit path can strand the button spinning forever.
|
|
814
|
+
b.classList.add("is-loading");
|
|
815
|
+
b.setAttribute("aria-busy", "true");
|
|
782
816
|
try {
|
|
783
817
|
const res = await fetch(b.dataset.approveUrl, { method: "POST" });
|
|
784
818
|
if (res.ok) {
|
|
785
819
|
b.textContent = "Approved";
|
|
820
|
+
// Terminal success: darker green, and it stays disabled (approving twice is
|
|
821
|
+
// a no-op server-side, but the button should not invite the second click).
|
|
822
|
+
b.classList.add("is-approved");
|
|
786
823
|
} else {
|
|
787
824
|
// A 409 carries { reason, blockers } \u2014 surface WHY instead of a bare
|
|
788
825
|
// "Failed" next to a possibly-stale green chip. textContent/title
|
|
789
826
|
// assignment only (no innerHTML), so server strings stay inert.
|
|
790
827
|
const data = await res.json().catch(() => null);
|
|
791
828
|
b.textContent = data && data.reason === "send-blocked" ? "Blocked" : "Failed";
|
|
792
|
-
|
|
793
|
-
|
|
829
|
+
// Double-backslash, NOT single: this whole block lives inside the renderer's
|
|
830
|
+
// template literal, so a single-backslash escape is consumed HERE at build
|
|
831
|
+
// time and emits a real newline into the served HTML \u2014 an unterminated string
|
|
832
|
+
// literal that kills the parse of the entire script element, taking every
|
|
833
|
+
// handler on the page with it (exactly how approve/override/renovate went
|
|
834
|
+
// dead). Note this comment must also avoid backticks for the same reason.
|
|
835
|
+
if (data && Array.isArray(data.blockers)) b.title = data.blockers.join("\\n");
|
|
794
836
|
b.disabled = false;
|
|
795
837
|
}
|
|
796
838
|
} catch {
|
|
@@ -798,6 +840,9 @@ function renderSiteDashboardHtml(site, reports, submissions = [], spamTotals = n
|
|
|
798
840
|
// the button doesn't sit permanently disabled reading "Approve".
|
|
799
841
|
b.textContent = "Failed";
|
|
800
842
|
b.disabled = false;
|
|
843
|
+
} finally {
|
|
844
|
+
b.classList.remove("is-loading");
|
|
845
|
+
b.removeAttribute("aria-busy");
|
|
801
846
|
}
|
|
802
847
|
});
|
|
803
848
|
});
|
|
@@ -1614,15 +1659,18 @@ export {
|
|
|
1614
1659
|
bumpDeps,
|
|
1615
1660
|
convertToPnpm,
|
|
1616
1661
|
depsAudit,
|
|
1662
|
+
domainFromUrl,
|
|
1617
1663
|
draftReportForSite,
|
|
1618
1664
|
findDueReports,
|
|
1619
1665
|
fromAirtableBase,
|
|
1620
1666
|
fromJsonFile,
|
|
1667
|
+
generateHeaderImage,
|
|
1621
1668
|
init,
|
|
1622
1669
|
isRecipeName,
|
|
1623
1670
|
lighthouseAudit,
|
|
1624
1671
|
lintAudit,
|
|
1625
1672
|
loadBundledImages,
|
|
1673
|
+
loadPlate,
|
|
1626
1674
|
localPath,
|
|
1627
1675
|
onboard,
|
|
1628
1676
|
renderCockpitHtml,
|