mnfst-render 0.4.7 → 0.4.9

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.
File without changes
@@ -229,23 +229,18 @@ function resolveConfig() {
229
229
  locales: pre.locales,
230
230
  redirects: Array.isArray(pre.redirects) ? pre.redirects : [],
231
231
  wait: cli.wait ?? pre.wait ?? null,
232
- waitAfterIdle: Math.max(0, cli.waitAfterIdle ?? pre.waitAfterIdle ?? 0),
232
+ waitAfterIdle: 0,
233
233
  concurrency: Math.max(1, cli.concurrency ?? pre.concurrency ?? Math.max(4, cpus().length - 1)),
234
- /** Default: generate locale variant pages via Node.js text substitution rather than Puppeteer.
235
- * Set manifest.prerender.localeSubstitution=false to always use Puppeteer for every locale. */
236
- localeSubstitution: pre.localeSubstitution !== false,
237
- /** Locales to always render with Puppeteer even when localeSubstitution is enabled (e.g. RTL). */
238
- localeSubstitutionExclude: Array.isArray(pre.localeSubstitutionExclude)
239
- ? pre.localeSubstitutionExclude.map(String)
240
- : [],
234
+ localeSubstitution: true,
235
+ localeSubstitutionExclude: [],
241
236
  /** Explicit locale-neutral paths to render in addition to those discovered automatically.
242
237
  * Each entry is expanded to all locale variants (e.g. "legal/privacy" → "cs/legal/privacy", ...) */
243
238
  paths: Array.isArray(pre.paths)
244
239
  ? pre.paths.map((p) => String(p).replace(/^\/+|\/+$/g, '')).filter(Boolean)
245
240
  : [],
246
241
  dryRun: !!cli.dryRun,
247
- debugPrerender: !!(cli.debugPrerender ?? pre.debugPrerender),
248
- pipelineTimeout: Math.max(3000, Number(pre.pipelineTimeout) || 25000),
242
+ debugPrerender: !!cli.debugPrerender,
243
+ pipelineTimeout: 25000,
249
244
  };
250
245
  }
251
246
 
@@ -694,13 +689,11 @@ function promptContinueWithRuntimeTailwind(rootDir) {
694
689
 
695
690
  /**
696
691
  * Build a static Tailwind stylesheet via @tailwindcss/cli (v4+), scanning project sources.
697
- * Only runs when the project opts in (data-tailwind on manifest script) or manifest.prerender.tailwind === true.
692
+ * Only runs when the project uses data-tailwind on the manifest script tag (auto-detected).
693
+ * Set manifest.prerender.tailwindInput to a custom CSS entry file if needed.
698
694
  */
699
695
  function runTailwindCliForPrerender(rootDir, outputDir, pre) {
700
- const explicit = pre?.tailwind;
701
- if (explicit === false) return false;
702
- const usesTailwind = explicit === true || indexHtmlUsesTailwind(rootDir);
703
- if (!usesTailwind) return false;
696
+ if (!indexHtmlUsesTailwind(rootDir)) return false;
704
697
 
705
698
  const outCss = join(outputDir, 'prerender.tailwind.css');
706
699
  try {
@@ -708,7 +701,7 @@ function runTailwindCliForPrerender(rootDir, outputDir, pre) {
708
701
  } catch {
709
702
  const proceed = promptContinueWithRuntimeTailwind(rootDir);
710
703
  if (!proceed) {
711
- throw new Error('prerender aborted: install tailwindcss/@tailwindcss/cli or disable prerender.tailwind.');
704
+ throw new Error('prerender aborted: install tailwindcss/@tailwindcss/cli or remove data-tailwind from your manifest script tag.');
712
705
  }
713
706
  process.stdout.write('prerender: continuing with runtime data-tailwind behavior.\n');
714
707
  return false;
@@ -726,15 +719,12 @@ function runTailwindCliForPrerender(rootDir, outputDir, pre) {
726
719
  }
727
720
 
728
721
  const outputBasename = basename(outputDir);
729
- const defaultContent = [
722
+ const contentGlobs = [
730
723
  '**/*.html',
731
724
  '!**/node_modules/**',
732
725
  '!**/dist/**',
733
726
  `!**/${outputBasename}/**`,
734
727
  ];
735
- const contentGlobs = Array.isArray(pre?.tailwindContent) && pre.tailwindContent.length > 0
736
- ? pre.tailwindContent
737
- : defaultContent;
738
728
 
739
729
  const args = [
740
730
  '--yes',
@@ -762,7 +752,7 @@ function runTailwindCliForPrerender(rootDir, outputDir, pre) {
762
752
  }
763
753
  }
764
754
  if (r.status !== 0) {
765
- console.error('prerender: Tailwind CLI failed; install with `npm i -D tailwindcss @tailwindcss/cli` or fix tailwindInput/tailwindContent in manifest.prerender.');
755
+ console.error('prerender: Tailwind CLI failed; install with `npm i -D tailwindcss @tailwindcss/cli` or check tailwindInput in manifest.prerender.');
766
756
  if (r.stderr) console.error(r.stderr);
767
757
  if (r.stdout) console.error(r.stdout);
768
758
  return false;
@@ -1144,6 +1134,9 @@ function loadAllLocaleContentData(manifest, rootDir, locales) {
1144
1134
  target[key] = (target[key] && typeof target[key] === 'object') ? target[key] : {};
1145
1135
  deepMerge(target[key], source[key]);
1146
1136
  } else {
1137
+ // Don't overwrite an existing nested object with a primitive — that creates
1138
+ // type asymmetry across locales and causes '[object Object]' in substitution pairs
1139
+ if (target[key] && typeof target[key] === 'object') continue;
1147
1140
  target[key] = source[key];
1148
1141
  }
1149
1142
  }
@@ -1152,12 +1145,25 @@ function loadAllLocaleContentData(manifest, rootDir, locales) {
1152
1145
  const result = new Map();
1153
1146
  for (const locale of locales) result.set(locale, {});
1154
1147
 
1148
+ // Read just the header row of a CSV to check which locale columns it contains.
1149
+ function csvLocaleColumns(csvPath) {
1150
+ if (!existsSync(csvPath)) return new Set();
1151
+ try {
1152
+ const firstLine = readFileSync(csvPath, 'utf8').split(/\r?\n/)[0] || '';
1153
+ return new Set(splitCsvLine(firstLine).slice(1)); // skip key column
1154
+ } catch { return new Set(); }
1155
+ }
1156
+
1155
1157
  for (const [, value] of Object.entries(data)) {
1156
1158
  if (typeof value === 'string') {
1157
1159
  // Single CSV with locale columns (all locales in one file)
1158
1160
  if (value.endsWith('.csv')) {
1159
1161
  const csvPath = join(rootDir, value.startsWith('/') ? value.slice(1) : value);
1162
+ const cols = csvLocaleColumns(csvPath);
1160
1163
  for (const locale of locales) {
1164
+ // Only include locales the CSV actually declares; falling back to the English
1165
+ // column for a missing locale silently poisons substitution pairs with English values.
1166
+ if (!cols.has(locale)) continue;
1161
1167
  deepMerge(result.get(locale), parseCsvToKeyValue(csvPath, locale));
1162
1168
  }
1163
1169
  }
@@ -1168,7 +1174,9 @@ function loadAllLocaleContentData(manifest, rootDir, locales) {
1168
1174
  for (const ref of refs) {
1169
1175
  if (typeof ref !== 'string' || !ref.endsWith('.csv')) continue;
1170
1176
  const csvPath = join(rootDir, ref.startsWith('/') ? ref.slice(1) : ref);
1177
+ const cols = csvLocaleColumns(csvPath);
1171
1178
  for (const locale of locales) {
1179
+ if (!cols.has(locale)) continue;
1172
1180
  deepMerge(result.get(locale), parseCsvToKeyValue(csvPath, locale));
1173
1181
  }
1174
1182
  }
@@ -1215,6 +1223,8 @@ function buildSubstitutionPairs(defaultLocaleData, targetLocaleData) {
1215
1223
  // Recurse into nested objects (produced by setNestedKey for dotted CSV keys)
1216
1224
  collectPairs(defaultVal, targetVal && typeof targetVal === 'object' ? targetVal : {});
1217
1225
  } else {
1226
+ // Skip if target is a non-primitive — String(obj) === '[object Object]' is never useful
1227
+ if (targetVal !== null && typeof targetVal === 'object') continue;
1218
1228
  const from = String(defaultVal ?? '').trim();
1219
1229
  const to = String(targetVal ?? '').trim();
1220
1230
  if (!from || from === to) continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst-render",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Render Manifest sites to static HTML for SEO",
5
5
  "type": "module",
6
6
  "bin": {