Overhaul logging

This commit is contained in:
Dominic Ferrando
2026-06-23 22:30:56 -04:00
parent 449b08971f
commit d415685a81
6 changed files with 170 additions and 115 deletions
+3
View File
@@ -5,5 +5,8 @@
"type": "module",
"exports": {
".": "./src/index.ts"
},
"dependencies": {
"pino": "^10.3.1"
}
}
+85 -80
View File
@@ -3,12 +3,10 @@ import { formatSlug, type DeepPartial } from "./util/misc";
import { WebflowClient } from "./webflow";
import { PrintfulClient } from "./printful";
import { redis } from "bun";
import pino from "pino";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const R2_WORKER_URL = "https://r2-worker.xominus.workers.dev";
export const WEBSITE_MEDIA_URL = "https://website-media.bladeandbrawn.com";
type MainProduct = {
name: string;
externalId: string;
@@ -25,144 +23,149 @@ type SyncState = {
};
export class ProductSyncer {
private log: pino.Logger;
constructor(
private printful: PrintfulClient,
private webflow: WebflowClient,
) { }
log?: pino.Logger,
) {
this.log = (log ?? pino({ level: "silent" })).child({ component: "ProductSyncer" });
}
async sync(printfulProductId?: number) {
const syncStart = Date.now();
try {
if (printfulProductId) this.log.info({ printfulProductId }, "sync started");
else this.log.info("sync started");
await this.setState({ isSyncing: true, syncingIds: [] });
// Populate products
const mainPrintfulProducts: MainProduct[] = [];
const webflowProducts: Webflow.Products.ProductAndSkus[] = [];
this.log.debug("populating webflow products");
const webflowProducts: Webflow.Products.ProductAndSkus[] = await this.webflow.Products.getAll();
this.log.debug("populating printful products");
let printfulProducts = await this.printful.Products.getAll();
if (printfulProductId) {
const printfulProduct = printfulProducts.find(
(p) => p.id === printfulProductId,
);
const printfulProduct = printfulProducts.find((p) => p.id === printfulProductId);
if (printfulProduct) {
const mainProductName = this.getMainProductName(
const metaProductName = this.getMainProductName(
printfulProduct.name,
);
printfulProducts = printfulProducts.filter((p) =>
p.name.includes(mainProductName),
p.name.includes(metaProductName),
);
}
}
webflowProducts.push(...(await this.webflow.Products.getAll()));
// Generate main printful products
console.log("Generating main products...");
this.log.info({ count: printfulProducts.length }, "generating meta printful products");
const metaPrintfulProducts: MainProduct[] = [];
for (const printfulProduct of printfulProducts) {
const mainProductName = this.getMainProductName(
printfulProduct.name,
);
let mainPrintfulProduct = mainPrintfulProducts.find((mp) =>
let metaPrintfulProduct = metaPrintfulProducts.find((mp) =>
mp.name.includes(mainProductName),
);
if (!mainPrintfulProduct) {
mainPrintfulProduct = {
if (!metaPrintfulProduct) {
metaPrintfulProduct = {
name: mainProductName,
variants: [],
externalId: printfulProduct.external_id,
colors: new Set(),
};
mainPrintfulProducts.push(mainPrintfulProduct);
metaPrintfulProducts.push(metaPrintfulProduct);
}
const productColor = this.findColorInProductName(
printfulProduct.name,
);
mainPrintfulProduct.variants.push({
metaPrintfulProduct.variants.push({
color: productColor,
product: (await this.printful.Products.get(printfulProduct.id))!,
});
mainPrintfulProduct.colors.add(productColor);
metaPrintfulProduct.colors.add(productColor);
}
// TODO: Validate main printful products
// Sync the main printful products
console.log("Syncing main products...");
for (const mainPrintfulProduct of mainPrintfulProducts) {
for (const metaPrintfulProduct of metaPrintfulProducts) {
this.log.info({ name: metaPrintfulProduct.name }, "syncing meta printful product");
await this.setState({
isSyncing: true,
syncingIds: mainPrintfulProduct.variants.map(
syncingIds: metaPrintfulProduct.variants.map(
(v) => v.product.sync_product.id,
),
});
const foundColors = mainPrintfulProduct.colors;
const foundColors = metaPrintfulProduct.colors;
const foundSizes: Set<string> = new Set();
const webflowSkus: DeepPartial<Webflow.Products.Skus.Sku>[] = [];
for (const specialVariant of mainPrintfulProduct.variants) {
for (const specialVariant of metaPrintfulProduct.variants) {
for (const printfulVariant of specialVariant.product.sync_variants) {
// check if this variant size is in all other special variants
this.log.debug(
{ size: printfulVariant.size, color: printfulVariant.color },
"generating webflow SKU from printful variant"
);
// check if this variant size is in all other special variants, if its not then skip it
let sizeIsInAllVariants = true;
for (const specialVariant of mainPrintfulProduct.variants) {
const sizes = specialVariant.product.sync_variants.map(
(v) => v.size,
);
for (const metaVariant of metaPrintfulProduct.variants) {
const sizes = metaVariant.product.sync_variants.map((v) => v.size);
if (!sizes.includes(printfulVariant.size))
sizeIsInAllVariants = false;
}
if (!sizeIsInAllVariants) continue;
if (sizeIsInAllVariants) {
foundSizes.add(printfulVariant.size);
foundSizes.add(printfulVariant.size);
webflowSkus.push({
id: printfulVariant.external_id,
fieldData: {
name: printfulVariant.name,
slug: formatSlug(printfulVariant.name),
"sku-values": {
color: specialVariant.color,
size: printfulVariant.size,
},
price: {
value: Math.floor(
+printfulVariant.retail_price * 100,
),
unit: printfulVariant.currency,
currency: printfulVariant.currency,
},
"main-image": PrintfulClient.Util.getVariantMainImage(
printfulVariant,
),
webflowSkus.push({
id: printfulVariant.external_id,
fieldData: {
name: printfulVariant.name,
slug: formatSlug(printfulVariant.name),
"sku-values": {
color: specialVariant.color,
size: printfulVariant.size,
},
});
}
price: {
value: Math.floor(
+printfulVariant.retail_price * 100,
),
unit: printfulVariant.currency,
currency: printfulVariant.currency,
},
"main-image": PrintfulClient.Util.getVariantMainImage(
printfulVariant,
),
},
});
}
}
// SYNC PRODUCT DATA
let existingWebflowProduct = webflowProducts.find(
(webflowProduct) =>
webflowProduct.product.id ===
mainPrintfulProduct.externalId.split("-")[0],
(webflowProduct) => webflowProduct.product.id === metaPrintfulProduct.externalId.split("-")[0],
);
if (existingWebflowProduct) {
// SYNC PRODUCT UPDATE
this.log.info("existing webflow product found, updating it");
// do not update images
for (const webflowSku of webflowSkus) {
for (const webflowSku of webflowSkus)
delete webflowSku.fieldData?.["main-image"];
}
const webflowProductId =
mainPrintfulProduct.externalId.split("-")[0];
if (!webflowProductId)
throw new Error("Malformed printful product ID");
const webflowProductId = metaPrintfulProduct.externalId.split("-")[0];
if (!webflowProductId) throw new Error("Malformed printful product ID");
await this.webflow.Products.update(webflowProductId, {
product: {
fieldData: {
name: mainPrintfulProduct.name,
slug: formatSlug(mainPrintfulProduct.name),
name: metaPrintfulProduct.name,
slug: formatSlug(metaPrintfulProduct.name),
shippable: true,
"tax-category": "standard-taxable",
"sku-properties": [
@@ -209,11 +212,13 @@ export class ProductSyncer {
}
} else {
// SYNC PRODUCT CREATE
this.log.info("existing webflow product not found, creating it");
const webflowProductId = await this.webflow.Products.create({
product: {
fieldData: {
name: mainPrintfulProduct.name,
slug: formatSlug(mainPrintfulProduct.name),
name: metaPrintfulProduct.name,
slug: formatSlug(metaPrintfulProduct.name),
shippable: true,
"tax-category": "standard-taxable",
"sku-properties": [
@@ -249,12 +254,11 @@ export class ProductSyncer {
existingWebflowProduct = await this.webflow.Products.get(webflowProductId);
if (!existingWebflowProduct)
throw new Error("Webflow product missing after create");
throw new Error("webflow product missing after create");
for (const specialVariant of mainPrintfulProduct.variants) {
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] =
[];
for (const printfulVariant of specialVariant.product.sync_variants) {
for (const metaVariant of metaPrintfulProduct.variants) {
const newPrintfulVariants: DeepPartial<Printful.Products.SyncVariant>[] = [];
for (const printfulVariant of metaVariant.product.sync_variants) {
const associatedWebflowSku = existingWebflowProduct.skus.find(
(sku) =>
sku.fieldData["sku-values"]?.["color"] === printfulVariant.color &&
@@ -269,12 +273,13 @@ export class ProductSyncer {
}
await sleep(10000);
this.log.info({ printfulProduct: metaVariant.product.sync_product.id }, "updating printful product");
await this.printful.Products.update(
specialVariant.product.sync_product.id,
metaVariant.product.sync_product.id,
{
sync_product: {
id: specialVariant.product.sync_product.id,
external_id: `${webflowProductId}-${specialVariant.color}`,
id: metaVariant.product.sync_product.id,
external_id: `${webflowProductId}-${metaVariant.color}`,
},
sync_variants: newPrintfulVariants,
},
@@ -282,13 +287,13 @@ export class ProductSyncer {
}
}
}
console.log("Done");
}
finally {
this.log.info({ durationMs: Date.now() - syncStart }, "sync complete");
} finally {
try {
await this.setState({ isSyncing: false, syncingIds: [] });
} catch {
console.error("Failed to reset sync state");
this.log.error("failed to reset sync state");
}
}
}