mnfst-render 0.5.46 → 0.5.48
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/manifest.render.mjs +89 -16
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -3114,6 +3114,75 @@ ${urlEntries.join('\n')}
|
|
|
3114
3114
|
writeLlmsFiles(outputDir, pathList, base, ctx);
|
|
3115
3115
|
}
|
|
3116
3116
|
|
|
3117
|
+
/**
|
|
3118
|
+
* Group llms entries into sections by first URL segment ("Blog" for /blog/*),
|
|
3119
|
+
* root page as "Overview", in first-encounter order. Wherever a path holds two
|
|
3120
|
+
* or more sub-areas that each have their own page and children, each sub-area
|
|
3121
|
+
* becomes its own section labelled by its page title, recursively: /docs/ai/*
|
|
3122
|
+
* and /docs/framework/* split into "AI Plugins" and "Framework", and groups
|
|
3123
|
+
* inside them into "Framework / Core Plugins" and so on. A lone sub-area
|
|
3124
|
+
* (/fr/docs under /fr) is walked through when a split lies below it, and the
|
|
3125
|
+
* first segment then leads the label ("Fr / AI Plugins").
|
|
3126
|
+
*/
|
|
3127
|
+
function groupLlmsSections(entries, siteName = '') {
|
|
3128
|
+
const titleCase = (s) => s.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3129
|
+
const paths = new Set(entries.map((e) => e.pathSeg));
|
|
3130
|
+
const parents = new Set();
|
|
3131
|
+
for (const p of paths) {
|
|
3132
|
+
const segs = p.split('/');
|
|
3133
|
+
for (let i = 1; i < segs.length; i++) parents.add(segs.slice(0, i).join('/'));
|
|
3134
|
+
}
|
|
3135
|
+
const childAreas = new Map();
|
|
3136
|
+
for (const p of paths) {
|
|
3137
|
+
if (!parents.has(p) || !p.includes('/')) continue;
|
|
3138
|
+
const parent = p.slice(0, p.lastIndexOf('/'));
|
|
3139
|
+
if (!childAreas.has(parent)) childAreas.set(parent, new Set());
|
|
3140
|
+
childAreas.get(parent).add(p);
|
|
3141
|
+
}
|
|
3142
|
+
const splits = (prefix) => (childAreas.get(prefix)?.size || 0) >= 2;
|
|
3143
|
+
const below = new Map();
|
|
3144
|
+
const splitsBelow = (prefix) => {
|
|
3145
|
+
if (!below.has(prefix)) {
|
|
3146
|
+
const kids = childAreas.get(prefix);
|
|
3147
|
+
below.set(prefix, splits(prefix) || (kids?.size === 1 && splitsBelow(kids.values().next().value)));
|
|
3148
|
+
}
|
|
3149
|
+
return below.get(prefix);
|
|
3150
|
+
};
|
|
3151
|
+
const suffix = siteName ? new RegExp(`\\s+[—–|-]\\s+${siteName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}$`) : null;
|
|
3152
|
+
const titles = new Map(entries.map((e) => [e.pathSeg, e.title || '']));
|
|
3153
|
+
const labelCache = new Map();
|
|
3154
|
+
const titleOf = (area) => {
|
|
3155
|
+
if (labelCache.has(area)) return labelCache.get(area);
|
|
3156
|
+
let t = titles.get(area) || '';
|
|
3157
|
+
if (suffix) t = t.replace(suffix, '');
|
|
3158
|
+
t = t.trim();
|
|
3159
|
+
if (!t || t === siteName || t === area) t = titleCase(area.split('/').pop());
|
|
3160
|
+
labelCache.set(area, t);
|
|
3161
|
+
return t;
|
|
3162
|
+
};
|
|
3163
|
+
const sections = new Map();
|
|
3164
|
+
for (const entry of entries) {
|
|
3165
|
+
const segs = entry.pathSeg.split('/');
|
|
3166
|
+
const first = segs[0] || '';
|
|
3167
|
+
let key = first || 'Overview';
|
|
3168
|
+
const parts = first && !splits(first) && splitsBelow(first) ? [titleCase(first)] : [];
|
|
3169
|
+
let prefix = first;
|
|
3170
|
+
for (let i = 1; first && i < segs.length && splitsBelow(prefix); i++) {
|
|
3171
|
+
const child = segs.slice(0, i + 1).join('/');
|
|
3172
|
+
if (!childAreas.get(prefix).has(child)) break;
|
|
3173
|
+
if (splits(prefix)) {
|
|
3174
|
+
key = child;
|
|
3175
|
+
parts.push(titleOf(child));
|
|
3176
|
+
}
|
|
3177
|
+
prefix = child;
|
|
3178
|
+
}
|
|
3179
|
+
const label = parts.length > 1 || (parts.length && splits(first)) ? parts.join(' / ') : (first ? titleCase(first) : 'Overview');
|
|
3180
|
+
if (!sections.has(key)) sections.set(key, { label, entries: [] });
|
|
3181
|
+
sections.get(key).entries.push(entry);
|
|
3182
|
+
}
|
|
3183
|
+
return sections;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3117
3186
|
/**
|
|
3118
3187
|
* Write llms.txt (curated index) and llms-full.txt (concatenated full content)
|
|
3119
3188
|
* per the llmstxt.org convention. Read each prerendered HTML file in pathList
|
|
@@ -3121,9 +3190,7 @@ ${urlEntries.join('\n')}
|
|
|
3121
3190
|
* injectMetaInDom + smart defaults, so the output reflects the same layered
|
|
3122
3191
|
* precedence (data-head → prerender.meta → smart defaults) without re-deriving.
|
|
3123
3192
|
*
|
|
3124
|
-
*
|
|
3125
|
-
* for /docs/getting-started/*, etc.) so the index is browseable. The root /
|
|
3126
|
-
* page is treated as the site overview.
|
|
3193
|
+
* Sections come from groupLlmsSections.
|
|
3127
3194
|
*/
|
|
3128
3195
|
function writeLlmsFiles(outputDir, pathList, liveBase, ctx = {}) {
|
|
3129
3196
|
const siteName = ctx.siteName || 'Site';
|
|
@@ -3144,18 +3211,7 @@ function writeLlmsFiles(outputDir, pathList, liveBase, ctx = {}) {
|
|
|
3144
3211
|
});
|
|
3145
3212
|
}
|
|
3146
3213
|
|
|
3147
|
-
|
|
3148
|
-
// "Overview". Sections are presented in first-encounter order to preserve
|
|
3149
|
-
// whatever order the project's manifest.json or yaml index dictated.
|
|
3150
|
-
const sections = new Map();
|
|
3151
|
-
const titleCase = (s) => s.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3152
|
-
for (const entry of entries) {
|
|
3153
|
-
const first = entry.pathSeg.split('/')[0] || '';
|
|
3154
|
-
const sectionKey = first || 'Overview';
|
|
3155
|
-
const sectionLabel = first ? titleCase(first) : 'Overview';
|
|
3156
|
-
if (!sections.has(sectionKey)) sections.set(sectionKey, { label: sectionLabel, entries: [] });
|
|
3157
|
-
sections.get(sectionKey).entries.push(entry);
|
|
3158
|
-
}
|
|
3214
|
+
const sections = groupLlmsSections(entries, siteName);
|
|
3159
3215
|
|
|
3160
3216
|
// --- llms.txt: short curated index ---
|
|
3161
3217
|
let llms = `# ${siteName}\n`;
|
|
@@ -3467,13 +3523,20 @@ const COPY_EXCLUDE = new Set([
|
|
|
3467
3523
|
'CLAUDE.md', 'AGENTS.md', 'manifest.schema.json',
|
|
3468
3524
|
]);
|
|
3469
3525
|
|
|
3526
|
+
// Capacitor-wrapped projects: native projects and config stay out of the web
|
|
3527
|
+
// output, else each `cap sync` nests ios/App/App/public/ios/… deeper.
|
|
3528
|
+
const CAPACITOR_CONFIGS = ['capacitor.config.json', 'capacitor.config.ts', 'capacitor.config.js'];
|
|
3529
|
+
const CAPACITOR_EXCLUDE = new Set(['ios', 'android', ...CAPACITOR_CONFIGS]);
|
|
3530
|
+
|
|
3470
3531
|
function copyProjectIntoDist(rootResolved, outputResolved) {
|
|
3471
3532
|
const outputDirName = basename(outputResolved);
|
|
3472
3533
|
COPY_EXCLUDE.add(outputDirName);
|
|
3534
|
+
const wrapped = CAPACITOR_CONFIGS.some((f) => existsSync(join(rootResolved, f)));
|
|
3473
3535
|
const entries = readdirSync(rootResolved, { withFileTypes: true });
|
|
3474
3536
|
for (const ent of entries) {
|
|
3475
3537
|
const name = ent.name;
|
|
3476
3538
|
if (COPY_EXCLUDE.has(name) || name.startsWith('.')) continue;
|
|
3539
|
+
if (wrapped && CAPACITOR_EXCLUDE.has(name)) continue;
|
|
3477
3540
|
const src = join(rootResolved, name);
|
|
3478
3541
|
const dest = join(outputResolved, name);
|
|
3479
3542
|
cpSync(src, dest, {
|
|
@@ -3671,11 +3734,19 @@ async function runPrerender(config) {
|
|
|
3671
3734
|
const lockBasename = basename(outputLockPath(finalOutput));
|
|
3672
3735
|
COPY_EXCLUDE.add(finalBasename);
|
|
3673
3736
|
COPY_EXCLUDE.add(lockBasename);
|
|
3737
|
+
// An --out render (e.g. a Capacitor webDir) also skips the configured web output.
|
|
3738
|
+
const renderBlock = config.manifest?.render ?? config.manifest?.prerender ?? {};
|
|
3739
|
+
const configuredOutput = resolve(rootResolved, renderBlock.output ?? 'website');
|
|
3740
|
+
const configuredBasename = dirname(configuredOutput) === rootResolved && !COPY_EXCLUDE.has(basename(configuredOutput))
|
|
3741
|
+
? basename(configuredOutput)
|
|
3742
|
+
: null;
|
|
3743
|
+
if (configuredBasename) COPY_EXCLUDE.add(configuredBasename);
|
|
3674
3744
|
try {
|
|
3675
3745
|
copyProjectIntoDist(rootResolved, outputResolved);
|
|
3676
3746
|
} finally {
|
|
3677
3747
|
COPY_EXCLUDE.delete(finalBasename);
|
|
3678
3748
|
COPY_EXCLUDE.delete(lockBasename);
|
|
3749
|
+
if (configuredBasename) COPY_EXCLUDE.delete(configuredBasename);
|
|
3679
3750
|
}
|
|
3680
3751
|
// Env placeholders (${VAR}) only resolve at runtime via window.env (populated
|
|
3681
3752
|
// by the mnfst-run dev server from .env). A static prod build has no
|
|
@@ -4508,7 +4579,7 @@ async function runPrerender(config) {
|
|
|
4508
4579
|
// observer state at runtime and therefore need the live Alpine scope.
|
|
4509
4580
|
const INTERACTIVE_DIRECTIVES = new Set([
|
|
4510
4581
|
'x-color', 'x-dropdown', 'x-tooltip', 'x-tab', 'x-tabpanel',
|
|
4511
|
-
'x-toast', 'x-carousel', 'x-
|
|
4582
|
+
'x-toast', 'x-carousel', 'x-anchors', 'x-model',
|
|
4512
4583
|
'x-files', 'x-data-files',
|
|
4513
4584
|
]);
|
|
4514
4585
|
// Runtime-only Alpine magics whose values change after the prerender
|
|
@@ -5486,8 +5557,10 @@ if (_isDirectEntry) {
|
|
|
5486
5557
|
export {
|
|
5487
5558
|
main,
|
|
5488
5559
|
discoverRoutes,
|
|
5560
|
+
groupLlmsSections,
|
|
5489
5561
|
acquireOutputLock,
|
|
5490
5562
|
outputLockPath,
|
|
5563
|
+
copyProjectIntoDist,
|
|
5491
5564
|
markPrerenderedManifestComponents,
|
|
5492
5565
|
normalizeLocaleRouteExclude,
|
|
5493
5566
|
isLocaleRouteExcluded,
|