w-funnel/scripts/bake-funnels.mjs
dev.daminik00 aa956adebb add funnel
2025-10-06 02:41:09 +02:00

102 lines
3.3 KiB
JavaScript

#!/usr/bin/env node
import { promises as fs } from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
const funnelsDir = path.join(projectRoot, "public", "funnels");
const outputFile = path.join(projectRoot, "src", "lib", "funnel", "bakedFunnels.ts");
/**
* Нормализует данные воронки перед запеканием
* Удаляет поля которые не соответствуют типам TypeScript
*/
function normalizeFunnelData(funnelData) {
return {
...funnelData,
screens: funnelData.screens.map((screen) => {
const normalizedScreen = { ...screen };
// Удаляем variables из экранов, которые не поддерживают это поле
// variables поддерживается только в info экранах
if ('variables' in normalizedScreen && normalizedScreen.template !== 'info') {
delete normalizedScreen.variables;
}
return normalizedScreen;
}),
};
}
function formatFunnelRecord(funnels) {
const entries = Object.entries(funnels)
.map(([funnelId, definition]) => {
const serialized = JSON.stringify(definition, null, 2);
const indented = serialized
.split("\n")
.map((line, index) => (index === 0 ? line : ` ${line}`))
.join("\n");
return ` "${funnelId}": ${indented}`;
})
.join(",\n\n");
return `{
${entries}\n}`;
}
async function bakeFunnels() {
const dirExists = await fs
.access(funnelsDir)
.then(() => true)
.catch(() => false);
if (!dirExists) {
throw new Error(`Funnels directory not found: ${funnelsDir}`);
}
const files = (await fs.readdir(funnelsDir)).sort((a, b) => a.localeCompare(b));
const funnels = {};
for (const file of files) {
if (!file.endsWith(".json")) continue;
const filePath = path.join(funnelsDir, file);
const raw = await fs.readFile(filePath, "utf8");
let parsed;
try {
parsed = JSON.parse(raw);
} catch (error) {
throw new Error(`Failed to parse ${file}: ${error.message}`);
}
const funnelId = parsed?.meta?.id ?? parsed?.id ?? file.replace(/\.json$/, "");
if (!funnelId || typeof funnelId !== "string") {
throw new Error(
`Unable to determine funnel id for '${file}'. Ensure the file contains an 'id' or 'meta.id' field.`
);
}
// Нормализуем данные перед запеканием
funnels[funnelId] = normalizeFunnelData(parsed);
}
const headerComment = `/**\n * This file is auto-generated by scripts/bake-funnels.mjs.\n * Do not edit this file manually; update the source JSON files instead.\n */`;
const recordLiteral = formatFunnelRecord(funnels);
const contents = `${headerComment}\n\nimport type { FunnelDefinition } from "./types";\n\nexport const BAKED_FUNNELS: Record<string, FunnelDefinition> = ${recordLiteral};\n`;
await fs.mkdir(path.dirname(outputFile), { recursive: true });
await fs.writeFile(outputFile, contents, "utf8");
console.log(`Baked ${Object.keys(funnels).length} funnel(s) into ${path.relative(projectRoot, outputFile)}`);
}
bakeFunnels().catch((error) => {
console.error(error);
process.exit(1);
});