← back to Corkwallcovering
scripts/gen-selfcontained-prod.mjs
48 lines
#!/usr/bin/env node
/**
* Generator (runs on Mac2, where git history exists): computes the exact cork PROD
* tag-apply plan (Scope A + Scope B, same logic as apply-shopify-tags-PROD.mjs) and
* injects it into scripts/_cork-apply-template.mjs at the __PLAN__ marker, emitting
* a fully SELF-CONTAINED runner (scripts/apply-cork-tags-SELFCONTAINED.mjs).
*
* The emitted runner needs ONLY the 3 prod creds (env) + the `pg` package — no git,
* no data/products.json, no reports/ — so it runs on Kamatera's /var/www copy
* (which has no .git) with zero repo dependencies.
*/
import fs from 'node:fs/promises';
import { execFileSync } from 'node:child_process';
const STYLE_VOCAB = ['Traditional','Contemporary','Modern','Coastal','Tropical','Art Deco','Mid-Century Modern','Bohemian','Transitional','Victorian','Chinoiserie','Minimalist','Maximalist','Rustic','Industrial','Scandinavian','Hollywood Regency','Japandi','Farmhouse'];
const PATTERN_VOCAB = ['Botanical','Floral','Geometric','Damask','Palm','Trellis','Stripe','Abstract','Toile','Scenic','Paisley','Plaid','Animal Print','Medallion','Chevron','Herringbone','Solid','Textured','Faux','Mural'];
const SCOPE_B_VOCAB = new Set([...STYLE_VOCAB, ...PATTERN_VOCAB]);
const ENRICH_COMMIT = 'ee5ed29';
const products = JSON.parse(await fs.readFile('data/products.json', 'utf8'));
const plan = new Map(); // handle -> Set(tags)
const want = (h, t) => { if (!plan.has(h)) plan.set(h, new Set()); plan.get(h).add(t); };
// Scope A — deterministic adds from the staged diff
const diff = JSON.parse(await fs.readFile('reports/cork-tag-diff.json', 'utf8'));
let scopeA = 0;
for (const r of diff.rows) { if (!r.add_tags?.length) continue; const h = r.handle || r.sku; for (const t of r.add_tags) { want(h, t); scopeA++; } }
// Scope B — Gemini-enriched vocab style/pattern tags: diff vs pre-enrich version (git)
let scopeB = 0;
const beforeRaw = execFileSync('git', ['show', `${ENRICH_COMMIT}~1:data/products.json`], { encoding: 'utf8', maxBuffer: 32 * 1024 * 1024 });
const before = JSON.parse(beforeRaw);
const beforeTags = new Map(before.map(p => [p.handle || p.sku, new Set((p.tags || []).map(String))]));
for (const p of products) {
const h = p.handle || p.sku;
const prev = beforeTags.get(h) || new Set();
for (const t of (p.tags || []).map(String)) { if (!prev.has(t) && SCOPE_B_VOCAB.has(t)) { want(h, t); scopeB++; } }
}
const PLAN = [...plan.entries()].map(([handle, set]) => ({ handle, add: [...set] }));
console.error(`plan: ${PLAN.length} products (Scope A adds=${scopeA}, Scope B adds=${scopeB})`);
const template = await fs.readFile('scripts/_cork-apply-template.mjs', 'utf8');
if (!template.includes('__PLAN__')) throw new Error('template missing __PLAN__ marker');
const out = template.replace('__PLAN__', JSON.stringify(PLAN, null, 2));
await fs.writeFile('scripts/apply-cork-tags-SELFCONTAINED.mjs', out);
console.error('wrote scripts/apply-cork-tags-SELFCONTAINED.mjs');