fake.ts
4.1 kB · typescript · 107 lines
1import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";2import { join, resolve } from "node:path";3import site from "../site.json";45const org = resolve(import.meta.dir, "..");6const shop = resolve(org, "../shop");7const data = join(org, "data");8const DESIGNS = Number(process.env.FAKE_DESIGNS ?? 2);9const HOUR = 60 * 60 * 1000;10const CDN = "https://cdn.shopify.com/s/files/1/0000/0001/files";1112let state = 20260906;1314function next() {15 state = (state * 1103515245 + 12345) % 2147483648;16 return state / 2147483648;17}1819const hex = () => Array.from({ length: 8 }, () => "0123456789abcdef"[Math.floor(next() * 16)]).join("");2021const read = (path: string) => JSON.parse(readFileSync(path, "utf8"));2223const write = (path: string, body: unknown) => {24 mkdirSync(join(path, ".."), { recursive: true });25 writeFileSync(path, JSON.stringify(body, null, 2) + "\n");26};2728const STUB = {29 variants: [30 { id: 1, size: "S", cost: "24.00" },31 { id: 2, size: "M", cost: "24.00" },32 { id: 3, size: "L", cost: "24.00" },33 ],34 mockups: [35 { id: 1, category: "Flat", title: "Front" },36 { id: 2, category: "Lifestyle", title: "Worn" },37 ],38 placements: [{ width: 20, height: 20, dpi: 300 }],39};4041const catalog = read(join(shop, "files", "catalog.json")) as { id: number; live: boolean; category: string; title: string }[];42const products: unknown[] = [];43let stamp = Date.now();4445for (const entry of catalog) {46 const file = join(shop, "data", "products", `${entry.id}.json`);47 if (!existsSync(file) && !entry.live) continue;48 const source = existsSync(file) ? read(file) : STUB;49 const live = (source.variants ?? []).filter((v: { is_ignored: boolean }) => !v.is_ignored);50 const variants = (live.length ? live : source.variants ?? []).filter(51 (v: { size: string }, i: number, all: { size: string }[]) => all.findIndex((o) => o.size === v.size) === i,52 );53 const mockups = (source.mockups ?? []).filter((m: { is_ignored: boolean }) => !m.is_ignored);54 const placements = new Map<string, { width: number; height: number; dpi: number }>();55 for (const place of source.placements ?? []) {56 const id = `${Math.round(place.width * 100)}-${Math.round(place.height * 100)}-${Math.round(place.dpi)}`;57 if (!placements.has(id)) placements.set(id, place);58 }59 if (!variants.length || !mockups.length) {60 console.warn(`fake: ${entry.id} has no live variants or mockups, skipped`);61 continue;62 }63 for (let n = 0; n < DESIGNS; n++) {64 const key = hex();65 stamp -= HOUR;66 const created = new Date(stamp).toISOString();67 const files = [...placements.keys()].map((id) => ({ id, key: hex(), name: "", url: "" }));68 for (const [i, one] of files.entries()) {69 one.name = files.length === 1 ? "printfile" : `printfile-${i + 1}`;70 one.url = `${site.root}/cdn/printful/${key}/${one.name}.png`;71 }72 products.push({73 key,74 type: String(entry.id),75 created,76 available: true,77 variants: variants.map((v: { id: number; size: string; cost: string }) => ({78 id: String(4000000000000 + Math.floor(next() * 1000000000)),79 size: v.size,80 price: Number(v.cost || "20").toFixed(2),81 available: true,82 })),83 images: mockups.map((m: { id: number; category: string; title: string }) => {84 const name = `${key}-${hex()}`;85 return { url: `${CDN}/${name}.png?v=1`, alt: `${m.id} - ${m.category} - ${m.title}`, style: String(m.id) };86 }),87 files: files.map((one) => one.name),88 });89 write(join(data, "tasks", key, `${key}.json`), {90 key,91 step: "ARCHIVE",92 seed: Math.floor(next() * 1000000),93 created_at: stamp,94 updated_at: stamp,95 product: { id: entry.id, category: entry.category, title: entry.title },96 variation: {},97 printfiles: files.map((one) => ({ ...one, width: 20, height: 20, dpi: 300 })),98 placements: [...placements.values()],99 variants: [],100 mockups: [],101 metadata: {},102 });103 }104}105106write(join(data, "shop.json"), { at: Date.now(), products });107console.log(`fake: ${products.length} products from ${catalog.length} catalog rows into data/shop.json`);