@rmdes/indiekit-endpoint-syndicate 1.0.0-beta.43 → 1.0.0-beta.45
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/controllers/syndicate.js +4 -2
- package/lib/utils.js +49 -50
- package/package.json +1 -1
|
@@ -239,14 +239,16 @@ export const syndicateController = {
|
|
|
239
239
|
`[syndication] Syndicated: ${result.url} (${result.syndicatedUrls.length} target(s))`,
|
|
240
240
|
);
|
|
241
241
|
} catch (error) {
|
|
242
|
+
const message =
|
|
243
|
+
error instanceof Error ? error.message : String(error);
|
|
242
244
|
results.push({
|
|
243
245
|
url: postData.properties?.url,
|
|
244
246
|
success: false,
|
|
245
|
-
error:
|
|
247
|
+
error: message,
|
|
246
248
|
});
|
|
247
249
|
|
|
248
250
|
console.error(
|
|
249
|
-
`[syndication] Failed: ${postData.properties?.url} - ${
|
|
251
|
+
`[syndication] Failed: ${postData.properties?.url} - ${message}`,
|
|
250
252
|
);
|
|
251
253
|
}
|
|
252
254
|
|
package/lib/utils.js
CHANGED
|
@@ -221,38 +221,6 @@ export const syndicateToTargets = async (
|
|
|
221
221
|
};
|
|
222
222
|
};
|
|
223
223
|
|
|
224
|
-
/**
|
|
225
|
-
* Derive OG image slug from a post URL path.
|
|
226
|
-
* Matches Eleventy theme's ogSlug filter logic.
|
|
227
|
-
*
|
|
228
|
-
* Date-based: /type/YYYY/MM/DD/slug → YYYY-MM-DD-slug
|
|
229
|
-
* Non-date: /about/ → about
|
|
230
|
-
* @param {string} postUrl - Full post URL
|
|
231
|
-
* @returns {string|null} OG slug or null
|
|
232
|
-
*/
|
|
233
|
-
export function urlToOgSlug(postUrl) {
|
|
234
|
-
try {
|
|
235
|
-
const pathname = new URL(postUrl).pathname;
|
|
236
|
-
const segments = pathname.split("/").filter(Boolean);
|
|
237
|
-
|
|
238
|
-
// Date-based URL: /type/yyyy/MM/dd/slug → 5 segments
|
|
239
|
-
if (segments.length === 5) {
|
|
240
|
-
const [, year, month, day, slug] = segments;
|
|
241
|
-
if (
|
|
242
|
-
/^\d{4}$/.test(year) &&
|
|
243
|
-
/^\d{2}$/.test(month) &&
|
|
244
|
-
/^\d{2}$/.test(day)
|
|
245
|
-
) {
|
|
246
|
-
return `${year}-${month}-${day}-${slug}`;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Fallback: last segment
|
|
251
|
-
return segments.at(-1);
|
|
252
|
-
} catch {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
224
|
|
|
257
225
|
/**
|
|
258
226
|
* HEAD a URL and report its status, 0 on network error or timeout
|
|
@@ -276,30 +244,61 @@ const headCheck = async (url) => {
|
|
|
276
244
|
};
|
|
277
245
|
|
|
278
246
|
/**
|
|
279
|
-
*
|
|
280
|
-
*
|
|
247
|
+
* GET a page and return its status plus the og:image it declares.
|
|
248
|
+
* @param {string} url - Page URL
|
|
249
|
+
* @returns {Promise<{status: number, ogImage: string|undefined}>}
|
|
250
|
+
*/
|
|
251
|
+
const fetchOgImage = async (url) => {
|
|
252
|
+
try {
|
|
253
|
+
const controller = new AbortController();
|
|
254
|
+
const timeoutId = setTimeout(() => controller.abort(), 5000);
|
|
255
|
+
const response = await fetch(url, { redirect: "follow", signal: controller.signal });
|
|
256
|
+
clearTimeout(timeoutId);
|
|
257
|
+
if (response.status !== 200) return { status: response.status };
|
|
258
|
+
const html = await response.text();
|
|
259
|
+
const match = html.match(/<meta\s+property="og:image"\s+content="([^"]+)"/);
|
|
260
|
+
return { status: 200, ogImage: match?.[1] };
|
|
261
|
+
} catch {
|
|
262
|
+
return { status: 0 };
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Check if a post and its OG card are live on the public site.
|
|
268
|
+
*
|
|
269
|
+
* Reads the og:image the POST ITSELF declares rather than re-deriving its
|
|
270
|
+
* filename. The removed helper's own docstring said "Matches Eleventy theme's
|
|
271
|
+
* ogSlug filter logic" — a hand-copied convention from another repo, and on
|
|
272
|
+
* 2026-09-13 that convention changed: the theme added the post type to the
|
|
273
|
+
* cache key so a like and a reply sharing a basename stop colliding. This gate
|
|
274
|
+
* kept asking for the old type-less name, got 404 for cards that were
|
|
275
|
+
* demonstrably live, and silently blocked syndication for every new post.
|
|
276
|
+
*
|
|
277
|
+
* Reading the page removes the coupling: whatever the page says its card is, we
|
|
278
|
+
* check that. It also makes `skipOg` unnecessary in principle — a photo post
|
|
279
|
+
* declares its photo as og:image and that HEADs the same way — though the flag
|
|
280
|
+
* is kept for callers that still pass it.
|
|
281
|
+
*
|
|
282
|
+
* Costs one GET instead of one HEAD, for posts awaiting syndication only.
|
|
281
283
|
* @param {string} postUrl - Full public URL of the post
|
|
282
|
-
* @param {string} me - Publication URL (
|
|
284
|
+
* @param {string} me - Publication URL (no longer used to build the card URL; kept for the signature)
|
|
283
285
|
* @param {object} [options] - Options
|
|
284
|
-
* @param {boolean} [options.skipOg] -
|
|
285
|
-
* where Eleventy uses the photo itself as og:image instead of generating /og/*.png)
|
|
286
|
+
* @param {boolean} [options.skipOg] - Don't wait on any og:image
|
|
286
287
|
* @returns {Promise<{ready: boolean, postStatus: number, ogStatus: number}>}
|
|
287
288
|
*/
|
|
288
289
|
export async function isPostReady(postUrl, me, options = {}) {
|
|
289
290
|
const { skipOg = false } = options;
|
|
290
|
-
const
|
|
291
|
-
const meNorm = me?.replace(/\/$/, "");
|
|
292
|
-
const ogUrl =
|
|
293
|
-
!skipOg && ogSlug && meNorm ? `${meNorm}/og/${ogSlug}.png` : undefined;
|
|
291
|
+
const { status: postStatus, ogImage } = await fetchOgImage(postUrl);
|
|
294
292
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
293
|
+
if (postStatus !== 200) {
|
|
294
|
+
return { ready: false, postStatus, ogStatus: 0 };
|
|
295
|
+
}
|
|
296
|
+
// No card declared (or explicitly skipped) → nothing to wait for.
|
|
297
|
+
if (skipOg || !ogImage) {
|
|
298
|
+
return { ready: true, postStatus, ogStatus: 200 };
|
|
299
|
+
}
|
|
299
300
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
postStatus,
|
|
303
|
-
ogStatus,
|
|
304
|
-
};
|
|
301
|
+
const ogStatus = await headCheck(ogImage);
|
|
302
|
+
return { ready: ogStatus === 200, postStatus, ogStatus };
|
|
305
303
|
}
|
|
304
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmdes/indiekit-endpoint-syndicate",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.45",
|
|
4
4
|
"description": "Syndication endpoint for Indiekit. Fork of @indiekit/endpoint-syndicate with batch syndication support and bug fixes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"indiekit",
|