fimo 0.9.0-experimental.1 → 0.9.0-experimental.11
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/assets/agent-templates/content-translator/scripts/write-locale-files.ts +66 -0
- package/assets/agents-starter/claude/settings.json +19 -0
- package/assets/content-templates/types-template.eta +1 -0
- package/assets/skills/fimo/references/translations.md +75 -0
- package/assets/skills/fimo-migration/SKILL.md +2 -0
- package/assets/skills/fimo-migration/references/manifest.md +11 -0
- package/assets/skills/fimo-migration/references/pages.md +298 -0
- package/assets/skills/fimo-migration/references/wrap.md +45 -0
- package/dist/build/vite/plugins/translations.js +28 -10
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +577 -269
- package/dist/runtime/app/FimoProviders.js +29 -7
- package/dist/runtime/core/BuiltWithBadge.d.ts +2 -0
- package/dist/runtime/core/BuiltWithBadge.js +68 -0
- package/dist/runtime/primitives/components/RichText.d.ts +5 -1
- package/dist/runtime/primitives/components/RichText.js +2 -2
- package/dist/runtime/react/FimoSpaProviders.js +83 -24
- package/dist/runtime/react/index.d.ts +2 -0
- package/dist/runtime/react/index.js +1 -0
- package/dist/runtime/react/spa-content-cache.d.ts +38 -0
- package/dist/runtime/react/spa-content-cache.js +64 -0
- package/dist/runtime/seo/neutral.d.ts +22 -0
- package/dist/runtime/seo/neutral.js +17 -0
- package/dist/runtime/shared/fimo-config.server.d.ts +0 -1
- package/dist/runtime/shared/fimo-config.server.js +0 -13
- package/dist/runtime/strapi/client.js +45 -2
- package/dist/runtime/strapi/reshape.d.ts +10 -6
- package/dist/runtime/strapi/reshape.js +7 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +4 -1
- package/dist/runtime/wrapped/FimoRichTextBlock.js +2 -2
- package/dist/runtime/wrapped/content-fetch.pure.d.ts +20 -0
- package/dist/runtime/wrapped/content-fetch.pure.js +106 -0
- package/dist/runtime/wrapped/server.d.ts +5 -2
- package/dist/runtime/wrapped/server.js +15 -45
- package/dist/scripts/apply-wrap-templates.js +10 -5
- package/dist/scripts/apply-wrap.d.ts +29 -2
- package/dist/scripts/apply-wrap.js +69 -7
- package/dist/scripts/cms/example-content.js +16 -13
- package/dist/scripts/cms/strapi/schema-map.js +16 -4
- package/dist/scripts/cms/strapi/shapes.d.ts +7 -5
- package/dist/scripts/cms/strapi/shapes.js +10 -0
- package/dist/scripts/cms/strapi/wiring.js +1 -1
- package/dist/scripts/detect-framework.js +6 -0
- package/dist/scripts/draft-rows.js +44 -1
- package/dist/scripts/draft-schemas.js +64 -5
- package/dist/scripts/frameworks/astro/routes.js +8 -19
- package/dist/scripts/frameworks/discover.d.ts +19 -6
- package/dist/scripts/frameworks/discover.js +113 -3
- package/dist/scripts/frameworks/discovered-route.d.ts +9 -0
- package/dist/scripts/frameworks/discovered-route.js +17 -0
- package/dist/scripts/frameworks/next/rewire.d.ts +13 -0
- package/dist/scripts/frameworks/next/rewire.js +26 -2
- package/dist/scripts/frameworks/next/routes.d.ts +13 -0
- package/dist/scripts/frameworks/next/routes.js +55 -13
- package/dist/scripts/frameworks/nuxt/routes.js +2 -18
- package/dist/scripts/frameworks/remix/routes.js +4 -18
- package/dist/scripts/frameworks/sveltekit/routes.js +4 -19
- package/dist/scripts/frameworks/vite/routes.d.ts +5 -0
- package/dist/scripts/frameworks/vite/routes.js +59 -9
- package/dist/scripts/frameworks/vite/wiring.d.ts +12 -0
- package/dist/scripts/frameworks/vite/wiring.js +68 -23
- package/dist/scripts/lib/humanize.d.ts +14 -0
- package/dist/scripts/lib/humanize.js +54 -0
- package/dist/scripts/lib/validation-report.d.ts +0 -3
- package/dist/scripts/lib/validation-report.js +0 -9
- package/dist/scripts/render-parity/capture.d.ts +12 -4
- package/dist/scripts/render-parity/capture.js +94 -24
- package/dist/scripts/routes/bindings.d.ts +93 -0
- package/dist/scripts/routes/bindings.js +117 -0
- package/dist/scripts/routes/label-prefixes.d.ts +49 -0
- package/dist/scripts/routes/label-prefixes.js +136 -0
- package/dist/scripts/routes/module-resolve.d.ts +20 -0
- package/dist/scripts/routes/module-resolve.js +78 -0
- package/dist/scripts/routes/page-structure.d.ts +136 -0
- package/dist/scripts/routes/page-structure.js +393 -0
- package/dist/scripts/scan-wrappable.d.ts +5 -1
- package/dist/scripts/scan-wrappable.js +126 -3
- package/package.json +5 -1
- package/templates/react-router/package.json +1 -1
- package/templates/react-router/translations/en.json +1 -0
|
@@ -118,6 +118,36 @@ const WRAPPABLE_ATTRIBUTES = new Set(['placeholder', 'title', 'aria-label']);
|
|
|
118
118
|
const FILE_LINK_PATTERN = /\.(pdf|zip|docx?|xlsx?|pptx?|csv|mp4|mov|webm)$/i;
|
|
119
119
|
const SKIP_DIRS = new Set(['node_modules', '.next', '.git', 'dist', 'build', '.fimo']);
|
|
120
120
|
const MIN_TEXT_LENGTH = 3;
|
|
121
|
+
/**
|
|
122
|
+
* Prose containers whose children mix raw text with inline elements or
|
|
123
|
+
* expressions. Wrapping such text node-by-node produces FRAGMENT labels
|
|
124
|
+
* (", and agencies building…") that nobody can find or edit as a sentence —
|
|
125
|
+
* the whole block merges into ONE `formatted-text` item instead (agent work:
|
|
126
|
+
* the fimo`` tagged template or RichText, never the auto-wrap codemod).
|
|
127
|
+
*/
|
|
128
|
+
const PROSE_TAGS = new Set([
|
|
129
|
+
'p',
|
|
130
|
+
'h1',
|
|
131
|
+
'h2',
|
|
132
|
+
'h3',
|
|
133
|
+
'h4',
|
|
134
|
+
'h5',
|
|
135
|
+
'h6',
|
|
136
|
+
'li',
|
|
137
|
+
'blockquote',
|
|
138
|
+
'figcaption',
|
|
139
|
+
'dt',
|
|
140
|
+
'dd',
|
|
141
|
+
'label',
|
|
142
|
+
'legend',
|
|
143
|
+
'td',
|
|
144
|
+
'th',
|
|
145
|
+
'caption',
|
|
146
|
+
'summary',
|
|
147
|
+
]);
|
|
148
|
+
function meaningfulLength(text) {
|
|
149
|
+
return text.replace(/[^\p{L}\p{N}]/gu, '').length;
|
|
150
|
+
}
|
|
121
151
|
const PREVIEW_LENGTH = 60;
|
|
122
152
|
function toPreview(value) {
|
|
123
153
|
const collapsed = value.replace(/\s+/g, ' ').trim();
|
|
@@ -212,7 +242,7 @@ function extractTemplate(ext, code) {
|
|
|
212
242
|
}
|
|
213
243
|
function scanTemplateFile(ext, code, relPath) {
|
|
214
244
|
const items = [];
|
|
215
|
-
const wrappedIds = { labels: [], media: [], richText: [], files: [] };
|
|
245
|
+
const wrappedIds = { labels: [], media: [], richText: [], files: [], ordered: [] };
|
|
216
246
|
let wrapped = 0;
|
|
217
247
|
const { template, offset, frontmatter } = extractTemplate(ext, code);
|
|
218
248
|
// t('key', ...) in frontmatter/script and in template expressions = wrapped.
|
|
@@ -220,6 +250,7 @@ function scanTemplateFile(ext, code, relPath) {
|
|
|
220
250
|
for (const match of source.matchAll(/\bt\(\s*['"]([^'"]+)['"]/g)) {
|
|
221
251
|
wrapped += 1;
|
|
222
252
|
wrappedIds.labels.push(match[1]);
|
|
253
|
+
wrappedIds.ordered.push(match[1]);
|
|
223
254
|
}
|
|
224
255
|
}
|
|
225
256
|
// data-fimo-source attributes = wrapped media/labels rendered via attrs.
|
|
@@ -444,7 +475,7 @@ export function scanWrappableFile(filePath, code, relPath, opts = {}) {
|
|
|
444
475
|
const items = [];
|
|
445
476
|
let wrapped = 0;
|
|
446
477
|
let foreignI18n = 0;
|
|
447
|
-
const wrappedIds = { labels: [], media: [], richText: [], files: [] };
|
|
478
|
+
const wrappedIds = { labels: [], media: [], richText: [], files: [], ordered: [] };
|
|
448
479
|
const collections = [];
|
|
449
480
|
const templateExt = filePath.match(/\.(astro|vue|svelte)$/)?.[1];
|
|
450
481
|
if (templateExt) {
|
|
@@ -465,6 +496,50 @@ export function scanWrappableFile(filePath, code, relPath, opts = {}) {
|
|
|
465
496
|
function detectCollection(declarator) {
|
|
466
497
|
detectCollectionFromDeclarator(declarator, code, 0, relPath, collections);
|
|
467
498
|
}
|
|
499
|
+
// Text nodes absorbed into a merged formatted-text item (keyed by offset) —
|
|
500
|
+
// the JSXText visitor skips them so fragments never become labels.
|
|
501
|
+
const mergedText = new Set();
|
|
502
|
+
function containsWrappedContent(node) {
|
|
503
|
+
if (!node || typeof node !== 'object') {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
if (node.type === 'CallExpression' && node.callee?.type === 'Identifier' && node.callee.name === 't') {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
if (node.type === 'TaggedTemplateExpression' && node.tag?.type === 'Identifier' && node.tag.name === 'fimo') {
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
if (node.type === 'JSXElement') {
|
|
513
|
+
const name = jsxElementName(node);
|
|
514
|
+
if (name && WRAPPING_COMPONENTS.has(name)) {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
for (const key of Object.keys(node)) {
|
|
519
|
+
const child = node[key];
|
|
520
|
+
if (Array.isArray(child)) {
|
|
521
|
+
if (child.some((item) => containsWrappedContent(item))) {
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
else if (typeof child === 'object' && child !== null && containsWrappedContent(child)) {
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
/** Every JSXText inside a prose element, recursing through inline children
|
|
532
|
+
* (inside a `<p>`, nested elements are inline by HTML rules). */
|
|
533
|
+
function collectProseTextNodes(node, out) {
|
|
534
|
+
for (const child of node.children ?? []) {
|
|
535
|
+
if (child.type === 'JSXText') {
|
|
536
|
+
out.push(child);
|
|
537
|
+
}
|
|
538
|
+
else if (child.type === 'JSXElement') {
|
|
539
|
+
collectProseTextNodes(child, out);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
468
543
|
function visit(node, insideWrapped, parentTag, scopes) {
|
|
469
544
|
if (!node || typeof node !== 'object') {
|
|
470
545
|
return;
|
|
@@ -486,6 +561,7 @@ export function scanWrappableFile(filePath, code, relPath, opts = {}) {
|
|
|
486
561
|
const id = attributeValue(node, 'id');
|
|
487
562
|
if (kind && id) {
|
|
488
563
|
wrappedIds[kind].push(id.value);
|
|
564
|
+
wrappedIds.ordered.push(id.value);
|
|
489
565
|
}
|
|
490
566
|
}
|
|
491
567
|
if (!insideWrapped && name) {
|
|
@@ -548,7 +624,43 @@ export function scanWrappableFile(filePath, code, relPath, opts = {}) {
|
|
|
548
624
|
}
|
|
549
625
|
}
|
|
550
626
|
}
|
|
551
|
-
|
|
627
|
+
// Mixed prose detection runs on the ELEMENT (before its children are
|
|
628
|
+
// visited): a prose tag whose direct children interleave raw text with
|
|
629
|
+
// inline elements/expressions becomes ONE formatted-text item, and its
|
|
630
|
+
// text nodes are recorded so the JSXText visitor below skips them.
|
|
631
|
+
if (node.type === 'JSXElement' && !insideWrapped) {
|
|
632
|
+
const proseName = jsxElementName(node);
|
|
633
|
+
if (proseName && PROSE_TAGS.has(proseName)) {
|
|
634
|
+
const children = node.children ?? [];
|
|
635
|
+
const directText = children.filter((child) => child.type === 'JSXText' && meaningfulLength(String(child.value ?? '')) > 0);
|
|
636
|
+
const inline = children.filter((child) => child.type === 'JSXElement' ||
|
|
637
|
+
(child.type === 'JSXExpressionContainer' && child.expression?.type !== 'JSXEmptyExpression'));
|
|
638
|
+
if (directText.length > 0 &&
|
|
639
|
+
inline.length > 0 &&
|
|
640
|
+
!directText.some((child) => mergedText.has(child.start)) &&
|
|
641
|
+
!children.some((child) => containsWrappedContent(child))) {
|
|
642
|
+
const textNodes = [];
|
|
643
|
+
collectProseTextNodes(node, textNodes);
|
|
644
|
+
const combined = textNodes.map((textNode) => String(textNode.value ?? '')).join(' ');
|
|
645
|
+
if (meaningfulLength(combined) >= MIN_TEXT_LENGTH) {
|
|
646
|
+
for (const textNode of textNodes) {
|
|
647
|
+
if (typeof textNode.start === 'number') {
|
|
648
|
+
mergedText.add(textNode.start);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
const inner = code.slice(node.openingElement?.end ?? node.start ?? 0, node.closingElement?.start ?? node.end ?? 0);
|
|
652
|
+
items.push({
|
|
653
|
+
kind: 'formatted-text',
|
|
654
|
+
file: relPath,
|
|
655
|
+
line: lineOf(code, node.start ?? 0),
|
|
656
|
+
preview: toPreview(inner),
|
|
657
|
+
span: { start: node.start, end: node.end, parentTag: proseName },
|
|
658
|
+
});
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (node.type === 'JSXText' && !insideWrapped && !mergedText.has(node.start)) {
|
|
552
664
|
const text = String(node.value ?? '');
|
|
553
665
|
const meaningful = text.replace(/[^\p{L}\p{N}]/gu, '');
|
|
554
666
|
if (meaningful.length >= MIN_TEXT_LENGTH) {
|
|
@@ -584,6 +696,7 @@ export function scanWrappableFile(filePath, code, relPath, opts = {}) {
|
|
|
584
696
|
const resolved = resolveScopedKey(node.callee.name, firstArg.value, childScopes);
|
|
585
697
|
if (!resolved.unresolvable) {
|
|
586
698
|
wrappedIds.labels.push(resolved.key);
|
|
699
|
+
wrappedIds.ordered.push(resolved.key);
|
|
587
700
|
}
|
|
588
701
|
}
|
|
589
702
|
}
|
|
@@ -1066,6 +1179,8 @@ export function scanWrappable(rootDir, opts = {}) {
|
|
|
1066
1179
|
richText: new Set(),
|
|
1067
1180
|
files: new Set(),
|
|
1068
1181
|
};
|
|
1182
|
+
const orderedIds = [];
|
|
1183
|
+
const orderedSeen = new Set();
|
|
1069
1184
|
for (const file of files) {
|
|
1070
1185
|
let code;
|
|
1071
1186
|
try {
|
|
@@ -1086,6 +1201,12 @@ export function scanWrappable(rootDir, opts = {}) {
|
|
|
1086
1201
|
idSets[kind].add(id);
|
|
1087
1202
|
}
|
|
1088
1203
|
}
|
|
1204
|
+
for (const id of result.wrappedIds.ordered) {
|
|
1205
|
+
if (!orderedSeen.has(id)) {
|
|
1206
|
+
orderedSeen.add(id);
|
|
1207
|
+
orderedIds.push(id);
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1089
1210
|
}
|
|
1090
1211
|
const dataFiles = [];
|
|
1091
1212
|
// Markdown/MDX documents: group per directory so a 40-post blog is ONE
|
|
@@ -1227,6 +1348,8 @@ export function scanWrappable(rootDir, opts = {}) {
|
|
|
1227
1348
|
media: [...idSets.media].sort(),
|
|
1228
1349
|
richText: [...idSets.richText].sort(),
|
|
1229
1350
|
files: [...idSets.files].sort(),
|
|
1351
|
+
// Encounter order, never sorted — the on-page reading order.
|
|
1352
|
+
ordered: orderedIds,
|
|
1230
1353
|
},
|
|
1231
1354
|
};
|
|
1232
1355
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "0.9.0-experimental.
|
|
3
|
+
"version": "0.9.0-experimental.11",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fimo": "dist/cli/index.js"
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"types": "./dist/runtime/strapi/index.d.ts",
|
|
59
59
|
"import": "./dist/runtime/strapi/index.js"
|
|
60
60
|
},
|
|
61
|
+
"./seo": {
|
|
62
|
+
"types": "./dist/runtime/seo/neutral.d.ts",
|
|
63
|
+
"import": "./dist/runtime/seo/neutral.js"
|
|
64
|
+
},
|
|
61
65
|
"./remix": {
|
|
62
66
|
"types": "./dist/runtime/remix/index.d.ts",
|
|
63
67
|
"import": "./dist/runtime/remix/index.js"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|