@techninja/clearstack 0.3.41 → 0.3.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/build-og-images.js +11 -4
- package/lib/build-og.js +1 -1
- package/lib/og-image-template.js +15 -1
- package/package.json +1 -1
package/lib/build-og-images.js
CHANGED
|
@@ -17,6 +17,8 @@ import { resolveTemplate, renderTemplate, buildContext, loadTokens, WIDTH, HEIGH
|
|
|
17
17
|
* @property {string} [logo] - Path or URL to logo
|
|
18
18
|
* @property {string} [siteName] - Site name for badge
|
|
19
19
|
* @property {Record<string, unknown>} [context] - Extra interpolation data
|
|
20
|
+
* @property {(slug: string) => boolean} [filter] - Only render matching slugs
|
|
21
|
+
* @property {(slug: string, n: number, total: number) => void} [onProgress]
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -25,7 +27,7 @@ import { resolveTemplate, renderTemplate, buildContext, loadTokens, WIDTH, HEIGH
|
|
|
25
27
|
* @returns {Promise<{ images: number, routes: number }>}
|
|
26
28
|
*/
|
|
27
29
|
export async function buildOGImages(opts) {
|
|
28
|
-
const { projectDir, outDir = 'dist', logo, siteName, context = {} } = opts;
|
|
30
|
+
const { projectDir, outDir = 'dist', logo, siteName, context = {}, filter, onProgress } = opts;
|
|
29
31
|
const routes = loadRoutes(projectDir);
|
|
30
32
|
if (!routes) {
|
|
31
33
|
console.log('⚠ No routes config found. Create clearstack.routes.json');
|
|
@@ -37,7 +39,8 @@ export async function buildOGImages(opts) {
|
|
|
37
39
|
const start = performance.now();
|
|
38
40
|
|
|
39
41
|
const { chromium } = await import('playwright');
|
|
40
|
-
const browser = await chromium.launch();
|
|
42
|
+
const browser = await chromium.launch({ executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH });
|
|
43
|
+
console.log(' Browser ready, rendering...');
|
|
41
44
|
const page = await browser.newPage({ viewport: { width: WIDTH, height: HEIGHT } });
|
|
42
45
|
const out = resolve(projectDir, outDir);
|
|
43
46
|
let images = 0;
|
|
@@ -47,6 +50,7 @@ export async function buildOGImages(opts) {
|
|
|
47
50
|
const isDynamic = pattern.includes(':');
|
|
48
51
|
|
|
49
52
|
if (!isDynamic) {
|
|
53
|
+
if (filter) continue;
|
|
50
54
|
const data = { app: { name: siteName }, ...context };
|
|
51
55
|
const ctx = buildContext(config, data, site);
|
|
52
56
|
await screenshot(page, renderTemplate(template, ctx, projectDir), out, pattern);
|
|
@@ -57,11 +61,13 @@ export async function buildOGImages(opts) {
|
|
|
57
61
|
for (const item of items) {
|
|
58
62
|
const slug = item.slug || item.id || item.sku || item[paramName];
|
|
59
63
|
if (!slug) continue;
|
|
64
|
+
if (filter && !filter(String(slug))) continue;
|
|
60
65
|
const path = pattern.replace(`:${paramName}`, String(slug));
|
|
61
66
|
const data = { [paramName]: item, item, app: { name: siteName }, ...context };
|
|
62
67
|
const ctx = buildContext(config, data, site);
|
|
63
68
|
await screenshot(page, renderTemplate(template, ctx, projectDir), out, path);
|
|
64
69
|
images++;
|
|
70
|
+
if (onProgress) onProgress(String(slug), images, -1);
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
73
|
}
|
|
@@ -85,7 +91,7 @@ export async function buildOneOGImage(opts) {
|
|
|
85
91
|
const tokens = loadTokens(projectDir);
|
|
86
92
|
const site = { tokens, logo, siteName };
|
|
87
93
|
const { chromium } = await import('playwright');
|
|
88
|
-
const browser = await chromium.launch();
|
|
94
|
+
const browser = await chromium.launch({ executablePath: process.env.PLAYWRIGHT_EXECUTABLE_PATH });
|
|
89
95
|
const page = await browser.newPage({ viewport: { width: WIDTH, height: HEIGHT } });
|
|
90
96
|
const out = resolve(projectDir, outDir);
|
|
91
97
|
let result = '';
|
|
@@ -113,7 +119,8 @@ export async function buildOneOGImage(opts) {
|
|
|
113
119
|
|
|
114
120
|
/** Render HTML content and save screenshot to disk. */
|
|
115
121
|
async function screenshot(page, html, outDir, routePath) {
|
|
116
|
-
await page.setContent(html, { waitUntil: '
|
|
122
|
+
await page.setContent(html, { waitUntil: 'domcontentloaded' });
|
|
123
|
+
await page.waitForTimeout(html.includes('fonts.googleapis') ? 2500 : 800);
|
|
117
124
|
const imgPath = routePath === '/'
|
|
118
125
|
? resolve(outDir, 'og-image.png')
|
|
119
126
|
: resolve(outDir, routePath.slice(1) + '.png');
|
package/lib/build-og.js
CHANGED
|
@@ -114,7 +114,7 @@ function resolveMeta(config, data, baseUrl, path) {
|
|
|
114
114
|
function writePage(outDir, routePath, shell, meta) {
|
|
115
115
|
const filePath = routePath === '/'
|
|
116
116
|
? resolve(outDir, 'index.html')
|
|
117
|
-
: resolve(outDir, routePath.slice(1)
|
|
117
|
+
: resolve(outDir, routePath.slice(1) + '.html');
|
|
118
118
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
119
119
|
writeFileSync(filePath, injectMeta(shell, meta));
|
|
120
120
|
}
|
package/lib/og-image-template.js
CHANGED
|
@@ -75,6 +75,14 @@ export function buildContext(config, itemData, site) {
|
|
|
75
75
|
const resolvedImage = image && !image.startsWith('http') ? `${baseUrl}${image}` : image;
|
|
76
76
|
const item = itemData.item || {};
|
|
77
77
|
const emoji = item.emoji || itemData.emoji || '';
|
|
78
|
+
const tags = item.tags || itemData.tags || [];
|
|
79
|
+
const tagsHtml = tags.map((t) => `<div class="tag">#${t}</div>`).join('');
|
|
80
|
+
const postTitle = item.title || item.caption || item.name || '';
|
|
81
|
+
const logoSrc = item.logo?.wordmark || item.logo?.mark || '';
|
|
82
|
+
const itemLogoHtml = logoSrc ? `<img src="${logoSrc}" alt="${postTitle}">` : '';
|
|
83
|
+
const dateFormatted = item.date ? new Date(item.date).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : '';
|
|
84
|
+
const mediaUrl = resolvedImage || (item.files?.[0] ? `https://data.tn42.com/assets-media/${item.files[0]}` : '');
|
|
85
|
+
const isVideo = item.type === 'video' || (item.files?.[0] || '').endsWith('.mp4');
|
|
78
86
|
|
|
79
87
|
return {
|
|
80
88
|
...itemData, ...item,
|
|
@@ -87,12 +95,18 @@ export function buildContext(config, itemData, site) {
|
|
|
87
95
|
text: t['color-text'] || '#e2e8f0',
|
|
88
96
|
textMuted: t['color-text-muted'] || '#a8b8cc',
|
|
89
97
|
emoji, image: resolvedImage,
|
|
98
|
+
tagsHtml, postTitle, dateFormatted, mediaUrl,
|
|
99
|
+
logoHtml: itemLogoHtml,
|
|
100
|
+
noLogoMark: itemLogoHtml ? '' : '<div class="mark">42</div>',
|
|
101
|
+
mediaHtml: isVideo
|
|
102
|
+
? `<video src="${mediaUrl}" autoplay muted playsinline style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover"></video>`
|
|
103
|
+
: mediaUrl ? `<img src="${mediaUrl}" style="position:absolute;inset:0;width:100%;height:100%;object-fit:cover">` : '',
|
|
90
104
|
variantsFormatted: formatNum(item.expected_variants),
|
|
91
105
|
uniqueFormatted: formatNum(item.estimated_unique_variants),
|
|
92
106
|
priceFormatted: item.price ? `$${(item.price / 100).toFixed(2)}` : '',
|
|
93
107
|
imageHtml: resolvedImage ? `<img class="hero" src="${resolvedImage}">` : '',
|
|
94
108
|
cardClass: resolvedImage ? 'card-with-image' : '',
|
|
95
|
-
|
|
109
|
+
siteLogo: site.logo ? `<img class="logo" src="${site.logo}">` : '',
|
|
96
110
|
badgeHtml: site.siteName ? `<div class="badge">${site.siteName}</div>` : '',
|
|
97
111
|
emojiHtml: emoji ? `<div class="emoji">${emoji}</div>` : '',
|
|
98
112
|
};
|
package/package.json
CHANGED