mnfst-render 0.5.16 → 0.5.18

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.
Files changed (2) hide show
  1. package/manifest.render.mjs +53 -15
  2. package/package.json +1 -1
@@ -363,7 +363,7 @@ function parseYamlPaths(filePath) {
363
363
  let currentGroup = '';
364
364
  const lines = text.split(/\r?\n/);
365
365
  for (const line of lines) {
366
- const groupMatch = line.match(/^group:\s*["']?([^"'\n]+)["']?/);
366
+ const groupMatch = line.match(/^\s*-?\s*group:\s*["']?([^"'\n]+)["']?/);
367
367
  if (groupMatch) {
368
368
  currentGroup = groupMatch[1].trim().toLowerCase().replace(/\s+/g, '-');
369
369
  continue;
@@ -372,12 +372,15 @@ function parseYamlPaths(filePath) {
372
372
  if (pathMatch && currentGroup) {
373
373
  const segment = pathMatch[1].trim();
374
374
  paths.push(`${currentGroup}/${segment}`);
375
- }
376
- const genericPathMatch = line.match(/^\s*(?:-\s*)?(?:path|slug):\s*["']?([^"'\n#]+)["']?/);
377
- if (genericPathMatch) {
378
- const v = genericPathMatch[1].trim().replace(/^\/+|\/+$/g, '');
379
- if (v && !v.includes('*') && !/\.[a-z0-9]+$/i.test(v)) {
380
- paths.push(v);
375
+ } else {
376
+ // No group context — fall back to a bare path/slug. Used by data files
377
+ // whose entries are flat (e.g. articles list with `path:` per item).
378
+ const genericPathMatch = line.match(/^\s*(?:-\s*)?(?:path|slug):\s*["']?([^"'\n#]+)["']?/);
379
+ if (genericPathMatch) {
380
+ const v = genericPathMatch[1].trim().replace(/^\/+|\/+$/g, '');
381
+ if (v && !v.includes('*') && !/\.[a-z0-9]+$/i.test(v)) {
382
+ paths.push(v);
383
+ }
381
384
  }
382
385
  }
383
386
  }
@@ -1474,6 +1477,26 @@ function loadContentForPrerender(manifest, rootDir, locale) {
1474
1477
  content = parseCsvToKeyValue(join(rootDir, data.slice(1)), loc);
1475
1478
  } else if (data && typeof data === 'object' && data.locales && typeof data.locales === 'string') {
1476
1479
  content = parseCsvToKeyValue(join(rootDir, data.locales.slice(1)), loc);
1480
+ } else if (data && typeof data === 'object' && !Array.isArray(data)) {
1481
+ // Per-locale files: { "en": "/data/content.en.yaml", "fr": "/data/content.fr.yaml", ... }
1482
+ const filePath = data[loc] || data[Object.keys(data)[0]];
1483
+ if (typeof filePath === 'string') {
1484
+ const fullPath = join(rootDir, filePath.startsWith('/') ? filePath.slice(1) : filePath);
1485
+ if (existsSync(fullPath)) {
1486
+ try {
1487
+ const raw = readFileSync(fullPath, 'utf8');
1488
+ if (filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
1489
+ let jsYaml = null;
1490
+ try { jsYaml = require('js-yaml'); } catch { /* skip */ }
1491
+ if (jsYaml) content = jsYaml.load(raw) || {};
1492
+ } else if (filePath.endsWith('.json')) {
1493
+ content = JSON.parse(raw);
1494
+ } else if (filePath.endsWith('.csv')) {
1495
+ content = parseCsvToKeyValue(fullPath, loc);
1496
+ }
1497
+ } catch { /* ignore parse errors */ }
1498
+ }
1499
+ }
1477
1500
  }
1478
1501
  if (manifest.description !== undefined && content.description === undefined) {
1479
1502
  content.description = manifest.description;
@@ -1546,15 +1569,27 @@ function resolveHeadXBindings(html, xData) {
1546
1569
  .replace(/>/g, '>')
1547
1570
  .replace(/"/g, '"');
1548
1571
  return html.replace(/<head>([\s\S]*?)<\/head>/i, (_, headContent) => {
1549
- let out = headContent.replace(
1550
- /(\s)(?::|x-bind:)(\w+)=["'](\$x\.[^"']+)["']/g,
1551
- (_, space, attr, expr) => {
1572
+ // Process each tag in <head> that has a :attr or x-bind:attr binding
1573
+ const out = headContent.replace(/<[^>]+>/g, (tag) => {
1574
+ // Find all :attr="$x...." or x-bind:attr="$x...." bindings in this tag
1575
+ const bindingRe = /\s(?::|x-bind:)(\w+)=["'](\$x\.[^"']+)["']/g;
1576
+ let m;
1577
+ let newTag = tag;
1578
+ while ((m = bindingRe.exec(tag)) !== null) {
1579
+ const attr = m[1];
1580
+ const expr = m[2];
1552
1581
  const path = expr.replace(/^\$x\./, '').trim();
1553
1582
  const value = getXPath(xData, path);
1554
- if (value === undefined) return _;
1555
- return `${space}${attr}="${esc(value)}"`;
1583
+ if (value === undefined) continue;
1584
+ // Remove the binding
1585
+ newTag = newTag.replace(m[0], '');
1586
+ // Remove existing static fallback for this attr
1587
+ newTag = newTag.replace(new RegExp(`\\s${attr}=["'][^"']*["']`), '');
1588
+ // Insert the resolved attr before the closing >
1589
+ newTag = newTag.replace(/>$/, ` ${attr}="${esc(value)}">`);
1556
1590
  }
1557
- );
1591
+ return newTag;
1592
+ });
1558
1593
  return `<head>${out}</head>`;
1559
1594
  });
1560
1595
  }
@@ -1781,8 +1816,11 @@ async function runPrerender(config) {
1781
1816
  paths.add(`${locale}/${seg}`);
1782
1817
  }
1783
1818
  }
1784
- // Default locale also under its slug (e.g. /en/, /en/page-1) so linking is symmetric; canonical points to root
1785
- if (defaultLocale) {
1819
+ // Default locale also under its slug (e.g. /en/, /en/page-1) so linking is
1820
+ // symmetric with other locales; canonical points to root. Skip this when
1821
+ // there's only one locale — the duplicates serve no purpose and bloat the
1822
+ // output (every page would be written twice: at root AND under /en/).
1823
+ if (defaultLocale && locales.length > 1) {
1786
1824
  paths.add(defaultLocale);
1787
1825
  for (const seg of localeNeutralSegments) {
1788
1826
  if (seg !== '') paths.add(`${defaultLocale}/${seg}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.5.16",
3
+ "version": "0.5.18",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {