207 lines
6.9 KiB
TypeScript
207 lines
6.9 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* Scans the repo and writes machine-usable facts.json to conductor/artifacts.
|
|
* No code execution; text-only heuristics. Safe for CI.
|
|
*/
|
|
import fg from "fast-glob";
|
|
import { promises as fs } from "fs";
|
|
|
|
const readText = async (p: string) => {
|
|
try {
|
|
return await fs.readFile(p, "utf8");
|
|
} catch {
|
|
return "";
|
|
}
|
|
};
|
|
const has = (s: string, ...needles: string[]) =>
|
|
needles.every((n) => s.toLowerCase().includes(n.toLowerCase()));
|
|
|
|
(async () => {
|
|
const out = "conductor/artifacts/facts.json";
|
|
const files = await fg(
|
|
[
|
|
"app/**/*",
|
|
"src/**/*",
|
|
"components/**/*",
|
|
"lib/**/*",
|
|
"services/**/*",
|
|
"public/**/*",
|
|
"next.config.*",
|
|
"proxy.ts",
|
|
"middleware.ts",
|
|
"package.json",
|
|
"prisma/**/*",
|
|
"conductor/**/*",
|
|
"gemini.md",
|
|
],
|
|
{ dot: true }
|
|
);
|
|
|
|
const textByFile = await Promise.all(files.map(readText));
|
|
const joined = textByFile.join("\n");
|
|
|
|
const findOne = async (globs: string[]) =>
|
|
(await fg(globs, { dot: true }))[0];
|
|
|
|
const nextConfigPath = await findOne(["next.config.ts", "next.config.js"]);
|
|
const nextConfig = nextConfigPath ? await readText(nextConfigPath) : "";
|
|
|
|
const facts = {
|
|
timestamp: new Date().toISOString(),
|
|
files,
|
|
next: {
|
|
hasAppRouter: files.some(
|
|
(f) =>
|
|
/^app\/layout\.(t|j)sx$/.test(f) ||
|
|
/^src\/app\/layout\.(t|j)sx$/.test(f)
|
|
),
|
|
hasProxyTs: !!(await findOne(["proxy.ts", "src/proxy.ts"])),
|
|
hasMiddlewareTs: !!(await findOne([
|
|
"middleware.ts",
|
|
"src/middleware.ts",
|
|
])),
|
|
nextConfigPath,
|
|
permissionsPolicy: (nextConfig.match(
|
|
/Permissions-Policy[^"'\n]*["'`](.*)["'`]/
|
|
) || [, ""])[1],
|
|
hasHSTS: nextConfig.includes("Strict-Transport-Security"),
|
|
hasCOOP: nextConfig.includes("Cross-Origin-Opener-Policy"),
|
|
hasCOEP:
|
|
nextConfig.includes("Cross-Origin-Embedder-Policy") ||
|
|
nextConfig.includes("Cross-Origin-Resource-Policy"),
|
|
hasCORP: nextConfig.includes("Cross-Origin-Resource-Policy"),
|
|
},
|
|
security: {
|
|
cspMentioned: has(joined, "content-security-policy", "csp") || false,
|
|
},
|
|
auth: {
|
|
hasInviteRoute: files.some(
|
|
(f) =>
|
|
/app\/invite\/\[code]\/page\.(t|j)sx$/.test(f) ||
|
|
/app\/.*invite.*\/page\.(t|j)sx$/.test(f)
|
|
),
|
|
hasOtpLogic:
|
|
/otp/i.test(joined) || /one[- ]?time[- ]?password/i.test(joined),
|
|
otpWindow5mMentioned: /5 ?min|300000/.test(joined),
|
|
hasTrustedDeviceLogic:
|
|
/trusted device/i.test(joined) || /trustedDevices/.test(joined),
|
|
trustedSessionThroughMar2_2026: /Mar(ch)? ?2,? ?2026|2026-03-02/.test(
|
|
joined
|
|
),
|
|
},
|
|
geofence: {
|
|
mentioned: /geofence|allowlist|allowed countries/i.test(joined),
|
|
allowlistCountries: [
|
|
"US",
|
|
"AU",
|
|
"PG",
|
|
"CA",
|
|
"GB",
|
|
"ZW",
|
|
"ZM",
|
|
"ZA",
|
|
].filter((c) => new RegExp(`\\b${c}\\b`).test(joined)),
|
|
hasGuidancePage: /guidance|vpn|allowed regions/i.test(joined),
|
|
},
|
|
rsvp: {
|
|
hasRsvpRoute: files.some((f) =>
|
|
/app\/\(auth\)\/rsvp\/page\.(t|j)sx$/.test(f)
|
|
),
|
|
couplePrefillMentioned: /couple|primary.*secondary|prefill/i.test(joined),
|
|
lockDateJan31_2026: /Jan(uary)? ?31,? ?2026|2026-01-31/.test(joined),
|
|
adminOverrideAudited: /override|audit/i.test(joined),
|
|
},
|
|
guestbook: {
|
|
hasGuestbookRoute: files.some((f) =>
|
|
/app\/\(auth\)\/dashboard\/guestbook\/page\.(t|j)sx$/.test(f)
|
|
),
|
|
textAudioVideo: /text.*audio.*video|audio.*video.*text/i.test(joined),
|
|
videoMax120s: /120 ?s|120 seconds|2 ?min/i.test(joined),
|
|
audioMax180s: /180 ?s|180 seconds|3 ?min/i.test(joined),
|
|
hasModerationQueue: /moderation|approve|hide/i.test(joined),
|
|
},
|
|
gallery: {
|
|
hasGalleryRoute: files.some((f) =>
|
|
/app\/\(auth\)\/dashboard\/gallery\/page\.(t|j)sx$/.test(f)
|
|
),
|
|
hasQrUpload: /qr|scan/i.test(joined),
|
|
hasEditor: /react-easy-crop|canvas|OffscreenCanvas|WebGL|editor/i.test(
|
|
joined
|
|
),
|
|
editorPresets5:
|
|
/Garden Soft|Ivory Film|Dusk Glow|Classic Noir|Vivid Bloom/i.test(
|
|
joined
|
|
),
|
|
editorAdjustments:
|
|
/crop|rotate|brightness|contrast|saturation|vibrance|sharpen|vignette|grain|blur/i.test(
|
|
joined
|
|
),
|
|
downscale2048: /2048px|downscale|resize/i.test(joined),
|
|
feeds: ["Latest", "Highlights", "Mine", "By Guest"].filter((n) =>
|
|
joined.includes(n)
|
|
),
|
|
hasModeration: /moderation|approve|hide/i.test(joined),
|
|
hasSlideshow: /slideshow|mosaic/i.test(joined),
|
|
tableBadges: /table badge|table QR/i.test(joined),
|
|
},
|
|
music: {
|
|
appleMusicMentioned: /Apple Music|MusicKit|music\.apple/i.test(joined),
|
|
top25Mentioned: /Top ?25/i.test(joined),
|
|
previewPlaybackMentioned: /preview|30 ?s|audio preview/i.test(joined),
|
|
perInviteQuotaMentioned: /quota|per[- ]invite|limit/i.test(joined),
|
|
dedupeMentioned: /dedupe|duplicate/i.test(joined),
|
|
},
|
|
sections: {
|
|
hasWeddingParty:
|
|
files.some((f) => /wedding[-]?party/i.test(f)) ||
|
|
/Wedding Party/i.test(joined),
|
|
hasTribute:
|
|
files.some((f) =>
|
|
/app\/\(auth\)\/dashboard\/tribute\/page\.(t|j)sx$/.test(f)
|
|
) || /Tribute/i.test(joined),
|
|
tributeMemorialStylingMentioned:
|
|
/ivory|charcoal|gold|candle|memorial/i.test(joined),
|
|
hasRegistry: /registry/i.test(joined),
|
|
hasVenue: /venue/i.test(joined),
|
|
hasWeather: /weather/i.test(joined),
|
|
hasTraffic: /traffic/i.test(joined),
|
|
hasDirectionsAndUber: /Apple Maps|Google Maps|Waze|Uber/i.test(joined),
|
|
hasProfileWhosWho: /Who'?s Who|guest-directory|profile consent/i.test(
|
|
joined
|
|
),
|
|
},
|
|
admin: {
|
|
shadcnMentioned: /shadcn|@\/components\/ui/.test(joined),
|
|
hasModerationQueues: /moderation queue|approve|hide/i.test(joined),
|
|
csvImportExport: /CSV|import|export/i.test(joined),
|
|
deviceManagement: /trusted device|replace device/i.test(joined),
|
|
},
|
|
media: {
|
|
ffmpegMentioned: /ffmpeg/i.test(joined),
|
|
hlsMentioned: /HLS|m3u8/i.test(joined),
|
|
aacOrOpusMentioned: /AAC|Opus/i.test(joined),
|
|
thumbnailsOrWaveformsMentioned: /thumbnail|poster|waveform/i.test(joined),
|
|
},
|
|
pwa: {
|
|
hasManifest: !!(await findOne([
|
|
"public/manifest.webmanifest",
|
|
"public/manifest.json",
|
|
])),
|
|
hasServiceWorker: !!(await findOne([
|
|
"public/service-worker.js",
|
|
"public/sw.js",
|
|
])),
|
|
},
|
|
observability: {
|
|
auditLogMentioned: /AuditLog/i.test(joined),
|
|
accessEventMentioned: /AccessEvent/i.test(joined),
|
|
analyticsEventsMentioned: /analytics|event map/i.test(joined),
|
|
o11yOrTracingMentioned: /OpenTelemetry|trace|metrics|logs/i.test(joined),
|
|
},
|
|
};
|
|
|
|
await fs.mkdir("conductor/artifacts", { recursive: true });
|
|
await fs.writeFile(out, JSON.stringify(facts, null, 2), "utf8");
|
|
console.log(`✅ wrote ${out}`);
|
|
})();
|