hubspot-cms-sync 0.5.2 → 0.5.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hubspot-cms-sync",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "Git-backed bidirectional sync for HubSpot CMS themes, content, blogs, forms, and assets.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,7 +41,7 @@ async function loadTagSlugs(siteDir) {
41
41
  * trackingPortalId, if set, injects the HubSpot tracking script into the footer so
42
42
  * forms keep de-anonymizing (`standard_footer_includes`). Returns counts.
43
43
  */
44
- export async function buildStatic({ siteDir, outDir, baseUrl = '', assetBase = '/assets', trackingPortalId } = {}) {
44
+ export async function buildStatic({ siteDir, outDir, baseUrl = '', assetBase = '/assets', trackingPortalId, blogPageSize = 20 } = {}) {
45
45
  const tagMap = await loadTagSlugs(siteDir);
46
46
  const tagSlugFor = (name) => tagMap[name] || slugify(name);
47
47
  const footerIncludes = trackingPortalId
@@ -76,7 +76,17 @@ export async function buildStatic({ siteDir, outDir, baseUrl = '', assetBase = '
76
76
  fileCount++;
77
77
  }
78
78
 
79
- await emit('/blog', renderBlogListing(posts, { ...opts, route: '/blog' }));
79
+ // Paginate listings to match HubSpot (20 posts/page; page 1 = base, page N = base/page/N).
80
+ // Without this the static listing renders every post on one page.
81
+ async function emitListing(basePath, items) {
82
+ const totalPages = Math.max(1, Math.ceil(items.length / blogPageSize));
83
+ for (let pageNum = 1; pageNum <= totalPages; pageNum += 1) {
84
+ const route = pageNum === 1 ? basePath : `${basePath}/page/${pageNum}`;
85
+ await emit(route, renderBlogListing(items, { ...opts, route, basePath, pageNum, pageSize: blogPageSize }));
86
+ }
87
+ }
88
+
89
+ await emitListing('/blog', posts);
80
90
 
81
91
  // One listing per tag, posts grouped by tag slug (preserves newest-first order).
82
92
  const byTag = new Map();
@@ -88,7 +98,7 @@ export async function buildStatic({ siteDir, outDir, baseUrl = '', assetBase = '
88
98
  }
89
99
  }
90
100
  for (const [slug, tagPosts] of byTag) {
91
- await emit(`/blog/tag/${slug}`, renderBlogListing(tagPosts, { ...opts, route: `/blog/tag/${slug}` }));
101
+ await emitListing(`/blog/tag/${slug}`, tagPosts);
92
102
  }
93
103
 
94
104
  // Assets. get_asset_url maps ../css|js|images -> /css|js|images; @asset:<p> -> /assets/<p>.
@@ -306,13 +306,23 @@ export function renderPage(page, { siteDir, site, baseUrl = '', assetBase = '/as
306
306
  // by contents.total_page_count > 1) is inert.
307
307
  // ---------------------------------------------------------------------------
308
308
  export function renderBlogListing(posts, { siteDir, site, baseUrl = '', assetBase = '/assets', lang = 'en',
309
- headerIncludes = '', footerIncludes = '', tagSlugFor, route = '/blog' } = {}) {
309
+ headerIncludes = '', footerIncludes = '', tagSlugFor, route = '/blog',
310
+ basePath = null, pageNum = 1, pageSize = 0 } = {}) {
310
311
  const opts = { baseUrl, assetBase, lang, headerIncludes, footerIncludes, tagSlugFor };
311
312
  const env = makeEnv(siteDir, { site, opts });
313
+ // Pagination: pageSize <= 0 keeps the whole list on one page (back-compat). Otherwise
314
+ // slice to the page window. blog_page_link mirrors HubSpot — page 1 is the listing
315
+ // base (e.g. /blog), page N is <base>/page/N — and drives the template's paginator.
316
+ const base = basePath || route;
317
+ const totalPages = pageSize > 0 ? Math.max(1, Math.ceil(posts.length / pageSize)) : 1;
318
+ const pageItems = pageSize > 0 ? posts.slice((pageNum - 1) * pageSize, pageNum * pageSize) : posts;
319
+ env.addGlobal('blog_page_link', (n) => (Number(n) <= 1 ? base : `${base}/page/${n}`));
320
+ const contents = pageItems.map((p) => postContent(p, opts));
321
+ contents.total_page_count = totalPages;
312
322
  const context = {
313
- contents: posts.map((p) => postContent(p, opts)),
323
+ contents,
314
324
  content: { absolute_url: baseUrl + route, canonical_url: baseUrl + route },
315
- current_page_num: 1,
325
+ current_page_num: pageNum,
316
326
  nav_active: null,
317
327
  nav_hide_cta: false,
318
328
  };
package/src/republish.mjs CHANGED
@@ -73,10 +73,17 @@ async function republish(argv = process.argv.slice(2), opts = {}) {
73
73
  const fut = future();
74
74
  let ok = 0;
75
75
  let fail = 0;
76
+ let skipped = 0;
76
77
  async function schedule(kind, id, publishDate) {
77
78
  const body = { id: String(id), publishDate: publishDate || fut };
78
79
  const { status } = await hub('POST', `/cms/v3/${kind}/schedule`, body);
79
- status === 204 ? ok++ : (fail++, console.error(` ${kind} ${id} -> ${status}`));
80
+ // 409 = the page already has a scheduled publish (e.g. just published by a
81
+ // `push --publish`, or an orphan portal page) — the desired end state, not a
82
+ // failure. `--all` legitimately touches pages outside our manifest; don't let
83
+ // their conflicts fail the deploy. Log for visibility.
84
+ if (status === 204) ok++;
85
+ else if (status === 409) { skipped++; console.error(` ${kind} ${id} -> 409 already scheduled (skip)`); }
86
+ else { fail++; console.error(` ${kind} ${id} -> ${status}`); }
80
87
  }
81
88
 
82
89
  const pages = await getAll('/cms/v3/pages/site-pages?property=id,slug,state');
@@ -91,7 +98,7 @@ async function republish(argv = process.argv.slice(2), opts = {}) {
91
98
  console.log(`republishing ${posts.length} blog post(s) (preserving publishDate)`);
92
99
  for (const p of posts) await schedule('blogs/posts', p.id, p.publishDate);
93
100
  }
94
- console.log(`scheduled ${ok} | failed ${fail} (live in ~90s)`);
101
+ console.log(`scheduled ${ok} | already-scheduled ${skipped} | failed ${fail} (live in ~90s)`);
95
102
  return fail ? 1 : 0;
96
103
  }
97
104