fimo 0.8.0 → 0.9.0-experimental.2

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 (190) hide show
  1. package/assets/content-templates/hooks-template.eta +4 -2
  2. package/assets/content-templates/types-template.eta +66 -1
  3. package/assets/skills/fimo/SKILL.md +1 -1
  4. package/assets/skills/fimo/references/content.md +86 -15
  5. package/assets/skills/fimo-cli/SKILL.md +6 -2
  6. package/assets/skills/fimo-cli/references/content.md +61 -2
  7. package/assets/skills/fimo-cli/references/integrations/firecrawl.md +20 -4
  8. package/assets/skills/fimo-cli/references/integrations.md +5 -1
  9. package/assets/skills/fimo-cli/references/webhooks.md +162 -0
  10. package/assets/skills/fimo-migration/SKILL.md +22 -2
  11. package/assets/skills/fimo-migration/references/content.md +24 -0
  12. package/assets/skills/fimo-migration/references/manifest.md +35 -0
  13. package/assets/skills/fimo-migration/references/qa.md +24 -2
  14. package/assets/skills/fimo-migration/references/workflow.md +12 -1
  15. package/assets/skills/fimo-migration/references/wrap.md +617 -0
  16. package/assets/skills/fimo-studio/SKILL.md +5 -3
  17. package/assets/skills/fimo-studio/references/cms.md +9 -1
  18. package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
  19. package/assets/skills/fimo-studio/references/webhooks.md +42 -0
  20. package/dist/build/vite/plugins/translations.d.ts +2 -8
  21. package/dist/build/vite/plugins/translations.js +3 -121
  22. package/dist/cli/bundle.json +1 -1
  23. package/dist/cli/index.js +1051 -144
  24. package/dist/runtime/app/FimoProviders.d.ts +1 -11
  25. package/dist/runtime/app/FimoProviders.js +2 -23
  26. package/dist/runtime/astro/index.d.ts +2 -0
  27. package/dist/runtime/astro/index.js +3 -0
  28. package/dist/runtime/astro/server.d.ts +3 -0
  29. package/dist/runtime/astro/server.js +6 -0
  30. package/dist/runtime/content/client/internal.js +1 -0
  31. package/dist/runtime/content/client/types.d.ts +16 -0
  32. package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
  33. package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
  34. package/dist/runtime/next/FimoNextProviders.js +20 -0
  35. package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
  36. package/dist/runtime/next/FimoNextScripts.js +19 -0
  37. package/dist/runtime/next/index.d.ts +14 -0
  38. package/dist/runtime/next/index.js +14 -0
  39. package/dist/runtime/next/server.d.ts +2 -0
  40. package/dist/runtime/next/server.js +4 -0
  41. package/dist/runtime/nuxt/composables.d.ts +9 -0
  42. package/dist/runtime/nuxt/composables.js +57 -0
  43. package/dist/runtime/nuxt/index.d.ts +3 -0
  44. package/dist/runtime/nuxt/index.js +6 -0
  45. package/dist/runtime/nuxt/server.d.ts +3 -0
  46. package/dist/runtime/nuxt/server.js +6 -0
  47. package/dist/runtime/primitives/labels-hook.d.ts +33 -0
  48. package/dist/runtime/primitives/labels-hook.js +41 -0
  49. package/dist/runtime/primitives/labels-store.d.ts +12 -0
  50. package/dist/runtime/primitives/labels-store.js +60 -0
  51. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
  52. package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
  53. package/dist/runtime/primitives/translations.d.ts +1 -27
  54. package/dist/runtime/primitives/translations.js +7 -85
  55. package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
  56. package/dist/runtime/react/FimoSpaProviders.js +63 -0
  57. package/dist/runtime/react/index.d.ts +33 -0
  58. package/dist/runtime/react/index.js +39 -0
  59. package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
  60. package/dist/runtime/remix/FimoRemixProviders.js +12 -0
  61. package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
  62. package/dist/runtime/remix/FimoRemixScripts.js +19 -0
  63. package/dist/runtime/remix/index.d.ts +12 -0
  64. package/dist/runtime/remix/index.js +11 -0
  65. package/dist/runtime/remix/server.d.ts +2 -0
  66. package/dist/runtime/remix/server.js +2 -0
  67. package/dist/runtime/shared/display-routes.d.ts +14 -0
  68. package/dist/runtime/shared/display-routes.js +25 -0
  69. package/dist/runtime/shared/fimo-config.server.js +1 -0
  70. package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
  71. package/dist/runtime/shared/i18n-messages.d.ts +29 -0
  72. package/dist/runtime/shared/i18n-messages.js +79 -0
  73. package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
  74. package/dist/runtime/shared/labels-fetch.pure.js +71 -0
  75. package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
  76. package/dist/runtime/shared/labels-fetch.server.js +97 -0
  77. package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
  78. package/dist/runtime/shared/labels-t.pure.js +10 -0
  79. package/dist/runtime/strapi/client.d.ts +30 -0
  80. package/dist/runtime/strapi/client.js +89 -0
  81. package/dist/runtime/strapi/index.d.ts +5 -0
  82. package/dist/runtime/strapi/index.js +6 -0
  83. package/dist/runtime/strapi/reshape.d.ts +46 -0
  84. package/dist/runtime/strapi/reshape.js +114 -0
  85. package/dist/runtime/svelte/index.d.ts +3 -0
  86. package/dist/runtime/svelte/index.js +7 -0
  87. package/dist/runtime/svelte/server.d.ts +3 -0
  88. package/dist/runtime/svelte/server.js +6 -0
  89. package/dist/runtime/vue/index.d.ts +3 -0
  90. package/dist/runtime/vue/index.js +5 -0
  91. package/dist/runtime/vue/server.d.ts +3 -0
  92. package/dist/runtime/vue/server.js +6 -0
  93. package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
  94. package/dist/runtime/wrapped/FimoFileLink.js +19 -0
  95. package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
  96. package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
  97. package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
  98. package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
  99. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +23 -0
  100. package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
  101. package/dist/runtime/wrapped/FimoText.d.ts +22 -0
  102. package/dist/runtime/wrapped/FimoText.js +20 -0
  103. package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
  104. package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
  105. package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
  106. package/dist/runtime/wrapped/WrappedProviders.js +101 -0
  107. package/dist/runtime/wrapped/dom.d.ts +4 -0
  108. package/dist/runtime/wrapped/dom.js +23 -0
  109. package/dist/runtime/wrapped/server.d.ts +77 -0
  110. package/dist/runtime/wrapped/server.js +237 -0
  111. package/dist/scripts/apply-wrap-templates.d.ts +61 -0
  112. package/dist/scripts/apply-wrap-templates.js +132 -0
  113. package/dist/scripts/apply-wrap.d.ts +46 -0
  114. package/dist/scripts/apply-wrap.js +278 -0
  115. package/dist/scripts/cms/discover.d.ts +2 -0
  116. package/dist/scripts/cms/discover.js +17 -0
  117. package/dist/scripts/cms/example-content.d.ts +33 -0
  118. package/dist/scripts/cms/example-content.js +163 -0
  119. package/dist/scripts/cms/strapi/detect.d.ts +2 -0
  120. package/dist/scripts/cms/strapi/detect.js +186 -0
  121. package/dist/scripts/cms/strapi/index.d.ts +3 -0
  122. package/dist/scripts/cms/strapi/index.js +25 -0
  123. package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
  124. package/dist/scripts/cms/strapi/list-source.js +90 -0
  125. package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
  126. package/dist/scripts/cms/strapi/schema-map.js +279 -0
  127. package/dist/scripts/cms/strapi/shapes.d.ts +23 -0
  128. package/dist/scripts/cms/strapi/shapes.js +38 -0
  129. package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
  130. package/dist/scripts/cms/strapi/touchpoints.js +145 -0
  131. package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
  132. package/dist/scripts/cms/strapi/wire-client.js +107 -0
  133. package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
  134. package/dist/scripts/cms/strapi/wiring.js +46 -0
  135. package/dist/scripts/cms/types.d.ts +145 -0
  136. package/dist/scripts/cms/types.js +11 -0
  137. package/dist/scripts/detect-framework.d.ts +39 -0
  138. package/dist/scripts/detect-framework.js +133 -0
  139. package/dist/scripts/draft-rows.d.ts +19 -0
  140. package/dist/scripts/draft-rows.js +135 -0
  141. package/dist/scripts/draft-schemas.d.ts +27 -0
  142. package/dist/scripts/draft-schemas.js +188 -0
  143. package/dist/scripts/extract-translations.js +16 -11
  144. package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
  145. package/dist/scripts/frameworks/astro/routes.js +55 -0
  146. package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
  147. package/dist/scripts/frameworks/astro/template-style.js +19 -0
  148. package/dist/scripts/frameworks/discover.d.ts +70 -0
  149. package/dist/scripts/frameworks/discover.js +178 -0
  150. package/dist/scripts/frameworks/next/rewire.d.ts +30 -0
  151. package/dist/scripts/frameworks/next/rewire.js +258 -0
  152. package/dist/scripts/frameworks/next/routes.d.ts +17 -0
  153. package/dist/scripts/frameworks/next/routes.js +134 -0
  154. package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
  155. package/dist/scripts/frameworks/next/wiring.js +186 -0
  156. package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
  157. package/dist/scripts/frameworks/nuxt/routes.js +54 -0
  158. package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
  159. package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
  160. package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
  161. package/dist/scripts/frameworks/remix/routes.js +50 -0
  162. package/dist/scripts/frameworks/support.d.ts +75 -0
  163. package/dist/scripts/frameworks/support.js +165 -0
  164. package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
  165. package/dist/scripts/frameworks/sveltekit/routes.js +50 -0
  166. package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
  167. package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
  168. package/dist/scripts/frameworks/vite/routes.d.ts +15 -0
  169. package/dist/scripts/frameworks/vite/routes.js +93 -0
  170. package/dist/scripts/frameworks/vite/wiring.d.ts +10 -0
  171. package/dist/scripts/frameworks/vite/wiring.js +99 -0
  172. package/dist/scripts/i18n-namespace.d.ts +27 -0
  173. package/dist/scripts/i18n-namespace.js +155 -0
  174. package/dist/scripts/lib/validation-report.d.ts +1 -0
  175. package/dist/scripts/lib/validation-report.js +3 -0
  176. package/dist/scripts/render-parity/capture.d.ts +56 -0
  177. package/dist/scripts/render-parity/capture.js +366 -0
  178. package/dist/scripts/render-parity/compare.d.ts +59 -0
  179. package/dist/scripts/render-parity/compare.js +0 -0
  180. package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
  181. package/dist/scripts/render-parity/normalize-html.js +223 -0
  182. package/dist/scripts/render-parity/types.d.ts +41 -0
  183. package/dist/scripts/render-parity/types.js +1 -0
  184. package/dist/scripts/scan-wrappable.d.ts +196 -0
  185. package/dist/scripts/scan-wrappable.js +1232 -0
  186. package/dist/scripts/validate-route-metadata.js +11 -0
  187. package/dist/scripts/validate-schemas.js +42 -1
  188. package/package.json +84 -4
  189. package/release.json +2 -2
  190. package/templates/react-router/package.json +1 -1
@@ -0,0 +1,223 @@
1
+ import * as cheerio from 'cheerio';
2
+ // `title` included: Next dev streams document metadata into the body end
3
+ // until the shell settles — title text is never visible page content, and
4
+ // whether it lands in head or body depends on layout asyncness (a wrap
5
+ // migration legitimately makes the root layout async).
6
+ const REMOVED_TAGS = new Set([
7
+ 'script',
8
+ 'style',
9
+ 'noscript',
10
+ 'template',
11
+ 'link',
12
+ 'meta',
13
+ 'svg',
14
+ 'iframe',
15
+ 'head',
16
+ 'title',
17
+ ]);
18
+ const BLOCK_TAGS = new Set([
19
+ 'p',
20
+ 'h1',
21
+ 'h2',
22
+ 'h3',
23
+ 'h4',
24
+ 'h5',
25
+ 'h6',
26
+ 'li',
27
+ 'td',
28
+ 'th',
29
+ 'dt',
30
+ 'dd',
31
+ 'blockquote',
32
+ 'figcaption',
33
+ 'pre',
34
+ 'button',
35
+ 'label',
36
+ 'summary',
37
+ 'caption',
38
+ 'option',
39
+ ]);
40
+ const FALLBACK_BLOCK_TAGS = new Set(['div', 'section', 'article', 'main', 'aside', 'header', 'footer', 'nav', 'body']);
41
+ const MARK_KIND = {
42
+ strong: 'strong',
43
+ b: 'strong',
44
+ em: 'em',
45
+ i: 'em',
46
+ u: 'u',
47
+ s: 's',
48
+ del: 's',
49
+ strike: 's',
50
+ code: 'code',
51
+ a: 'a',
52
+ };
53
+ function collapse(text) {
54
+ return text.replace(/ /g, ' ').replace(/\s+/g, ' ').trim();
55
+ }
56
+ /** Strip the capture origin so differing ports between runs compare equal. */
57
+ export function normalizeHref(href, origin) {
58
+ if (href.startsWith(origin)) {
59
+ const stripped = href.slice(origin.length);
60
+ return stripped.startsWith('/') ? stripped : `/${stripped}`;
61
+ }
62
+ return href;
63
+ }
64
+ /**
65
+ * Media src normalization: origin strip + Next image-optimizer decode, so a
66
+ * pre-wrap `next/image` (`/_next/image?url=…&w=…`) and a post-wrap plain
67
+ * `<img src>` of the same asset compare equal.
68
+ */
69
+ export function normalizeMediaSrc(src, origin) {
70
+ let value = normalizeHref(src, origin);
71
+ const optimizerMatch = value.match(/^\/_next\/image\?(.+)$/);
72
+ if (optimizerMatch) {
73
+ const params = new URLSearchParams(optimizerMatch[1]);
74
+ const url = params.get('url');
75
+ if (url) {
76
+ value = url;
77
+ }
78
+ }
79
+ return value;
80
+ }
81
+ function isExcluded(node) {
82
+ const tag = node.name?.toLowerCase() ?? '';
83
+ if (REMOVED_TAGS.has(tag)) {
84
+ return true;
85
+ }
86
+ // Hidden elements are not visible content — except Next streaming
87
+ // containers (div[hidden][id^="S:"]), whose content replaces Suspense
88
+ // holes; kept in document order so streamed content is captured
89
+ // symmetrically between baseline and check.
90
+ if (node.attribs && 'hidden' in node.attribs) {
91
+ const isStreamingContainer = tag === 'div' && (node.attribs.id ?? '').startsWith('S:');
92
+ if (!isStreamingContainer) {
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ }
98
+ function nearestBlock(node) {
99
+ let current = node.parent;
100
+ let fallback = null;
101
+ while (current) {
102
+ const tag = current.name?.toLowerCase();
103
+ if (tag && BLOCK_TAGS.has(tag)) {
104
+ return current;
105
+ }
106
+ if (!fallback && tag && FALLBACK_BLOCK_TAGS.has(tag)) {
107
+ fallback = current;
108
+ }
109
+ current = current.parent;
110
+ }
111
+ return fallback;
112
+ }
113
+ function collectText(node) {
114
+ if (node.type === 'text') {
115
+ return String(node.data ?? '');
116
+ }
117
+ if (node.type !== 'tag' || isExcluded(node)) {
118
+ return '';
119
+ }
120
+ return (node.children ?? []).map(collectText).join('');
121
+ }
122
+ export function extractRouteSignature(html, opts) {
123
+ const $ = cheerio.load(html);
124
+ const bodyNode = $('body').get(0);
125
+ const blocks = [];
126
+ const blockIndex = new Map();
127
+ const images = [];
128
+ const videos = [];
129
+ const links = [];
130
+ const visit = (node) => {
131
+ if (node.type === 'text') {
132
+ const text = collapse(String(node.data ?? ''));
133
+ if (!text) {
134
+ return;
135
+ }
136
+ const block = nearestBlock(node);
137
+ if (!block) {
138
+ return;
139
+ }
140
+ const existing = blockIndex.get(block);
141
+ if (existing === undefined) {
142
+ blockIndex.set(block, blocks.length);
143
+ blocks.push({ tag: block.name?.toLowerCase() ?? 'div', text, marks: [] });
144
+ }
145
+ else {
146
+ blocks[existing].text = collapse(`${blocks[existing].text} ${text}`);
147
+ }
148
+ return;
149
+ }
150
+ if (node.type !== 'tag' && node.type !== 'root') {
151
+ return;
152
+ }
153
+ if (node.type === 'tag' && isExcluded(node)) {
154
+ return;
155
+ }
156
+ const tag = node.name?.toLowerCase() ?? '';
157
+ const attribs = node.attribs ?? {};
158
+ // Inline-marks signature: the formatting that must survive wrapping and
159
+ // the TipTap seed round-trip. Attached to the nearest block.
160
+ const markKind = MARK_KIND[tag];
161
+ if (markKind) {
162
+ const text = collapse(collectText(node));
163
+ if (text) {
164
+ const block = nearestBlock(node);
165
+ if (block) {
166
+ if (!blockIndex.has(block)) {
167
+ blockIndex.set(block, blocks.length);
168
+ blocks.push({ tag: block.name?.toLowerCase() ?? 'div', text: '', marks: [] });
169
+ }
170
+ const mark = { kind: markKind, text };
171
+ if (markKind === 'a') {
172
+ if (attribs.href) {
173
+ mark.href = normalizeHref(attribs.href, opts.origin);
174
+ blocks[blockIndex.get(block)].marks.push(mark);
175
+ }
176
+ }
177
+ else {
178
+ blocks[blockIndex.get(block)].marks.push(mark);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ if (tag === 'img' && attribs.src) {
184
+ images.push(normalizeMediaSrc(attribs.src, opts.origin));
185
+ }
186
+ if (tag === 'source' && attribs.src) {
187
+ const parentTag = node.parent?.name?.toLowerCase();
188
+ if (parentTag === 'picture') {
189
+ images.push(normalizeMediaSrc(attribs.src, opts.origin));
190
+ }
191
+ else if (parentTag === 'video') {
192
+ videos.push(normalizeMediaSrc(attribs.src, opts.origin));
193
+ }
194
+ }
195
+ if (tag === 'video') {
196
+ if (attribs.src) {
197
+ videos.push(normalizeMediaSrc(attribs.src, opts.origin));
198
+ }
199
+ if (attribs.poster) {
200
+ videos.push(normalizeMediaSrc(attribs.poster, opts.origin));
201
+ }
202
+ }
203
+ if (tag === 'a' && attribs.href) {
204
+ links.push({ href: normalizeHref(attribs.href, opts.origin), text: collapse(collectText(node)) });
205
+ }
206
+ for (const child of node.children ?? []) {
207
+ visit(child);
208
+ }
209
+ };
210
+ if (bodyNode) {
211
+ for (const child of bodyNode.children ?? []) {
212
+ visit(child);
213
+ }
214
+ }
215
+ // Blocks created empty by a mark preceding any direct text (e.g. a <p>
216
+ // starting with <strong>) get their text from the block's full content.
217
+ for (const [block, index] of blockIndex) {
218
+ if (!blocks[index].text) {
219
+ blocks[index].text = collapse(collectText(block));
220
+ }
221
+ }
222
+ return { status: opts.status, blocks: blocks.filter((block) => block.text.length > 0), images, videos, links };
223
+ }
@@ -0,0 +1,41 @@
1
+ export declare const PARITY_BASELINE_VERSION = 1;
2
+ export interface InlineMark {
3
+ kind: 'strong' | 'em' | 'u' | 's' | 'code' | 'a';
4
+ text: string;
5
+ href?: string;
6
+ }
7
+ export interface TextBlock {
8
+ tag: string;
9
+ text: string;
10
+ marks: InlineMark[];
11
+ }
12
+ export interface RouteSignature {
13
+ status: number;
14
+ blocks: TextBlock[];
15
+ images: string[];
16
+ videos: string[];
17
+ links: Array<{
18
+ href: string;
19
+ text: string;
20
+ }>;
21
+ }
22
+ export type RouteCapture = {
23
+ signature: RouteSignature;
24
+ } | {
25
+ skipped: 'dynamic-params' | 'fetch-failed';
26
+ error?: string;
27
+ };
28
+ /**
29
+ * Normalized pre-wrap snapshot of every route's rendered content. Written to
30
+ * `.fimo/migration/parity-baseline.json`; `fimo migrate wrap --check`
31
+ * recaptures with the identical pipeline and compares.
32
+ */
33
+ export interface ParityBaseline {
34
+ version: typeof PARITY_BASELINE_VERSION;
35
+ capturedAt: string;
36
+ engine: 'dev-server';
37
+ framework: string | null;
38
+ command: string;
39
+ unwrappedAtCapture?: number;
40
+ routes: Record<string, RouteCapture>;
41
+ }
@@ -0,0 +1 @@
1
+ export const PARITY_BASELINE_VERSION = 1;
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Inverse of extract-translations: finds content that is NOT yet wrapped with
3
+ * Fimo primitives — hardcoded JSX text, image/video sources, file links, and
4
+ * user-visible attribute strings. Drives the wrap migration's coverage gate:
5
+ * every item must end wrapped, ignored-with-a-reason, or unresolved.
6
+ */
7
+ export type WrappableKind = 'text' | 'image' | 'video' | 'file-link' | 'attribute';
8
+ export interface WrappableItem {
9
+ kind: WrappableKind;
10
+ file: string;
11
+ line: number;
12
+ preview: string;
13
+ /** AST span info for the --apply codemod (offsets into the file). */
14
+ span?: {
15
+ start: number;
16
+ end: number;
17
+ parentTag?: string;
18
+ openNameStart?: number;
19
+ openNameEnd?: number;
20
+ closeNameStart?: number;
21
+ closeNameEnd?: number;
22
+ };
23
+ }
24
+ /**
25
+ * In-code collection candidate: an array literal of same-shaped objects with
26
+ * user-visible string fields (services cards, FAQ items, testimonials, …).
27
+ * Flat labels are the wrong shape for these — they belong in a Fimo
28
+ * collection (schema + entries), migrated by the agent.
29
+ */
30
+ export interface CollectionCandidate {
31
+ name: string;
32
+ file: string;
33
+ line: number;
34
+ count: number;
35
+ fields: string[];
36
+ sample: string;
37
+ /**
38
+ * Cross-collection references detected by value matching: this candidate's
39
+ * field values point at another candidate's identity values. Migrated as
40
+ * Fimo `reference` fields (`target`/`targets` + `many`), never as strings.
41
+ */
42
+ references?: CandidateReference[];
43
+ /** Per-field string values (capped) — internal input to reference
44
+ * detection; stripped before the plan is written. */
45
+ fieldValues?: Record<string, string[]>;
46
+ /** First row with NATIVE value types — internal input to schema-draft
47
+ * type inference; stripped before the plan is written. */
48
+ typedSample?: Record<string, unknown>;
49
+ /** All rows (capped) with NATIVE value types — internal input to the
50
+ * rows-staging generator; stripped before the plan is written. */
51
+ typedRows?: Record<string, unknown>[];
52
+ manyFields?: string[];
53
+ }
54
+ export interface CandidateReference {
55
+ field: string;
56
+ /** Candidate name(s) the values point at: one = fixed target, several = polymorphic. */
57
+ targets: string[];
58
+ many: boolean;
59
+ }
60
+ interface ReferenceMatchable {
61
+ name: string;
62
+ references?: CandidateReference[];
63
+ fieldValues?: Record<string, string[]>;
64
+ manyFields?: string[];
65
+ }
66
+ /**
67
+ * Annotate candidates whose field values point at another candidate's
68
+ * identity values: one full-coverage target = fixed reference, several
69
+ * partial targets covering together = polymorphic. Mutates in place.
70
+ *
71
+ * Coverage is OCCURRENCE-weighted and dangling-adjusted: values matching no
72
+ * candidate's identity set are dangling references by definition (deleted
73
+ * targets, sentinels) and drop out of the denominator, weighted by how
74
+ * often they occur — 1,700 case studies pointing at 12 clients with 190
75
+ * distinct ghost values is still a reference field, because 89% of the ROWS
76
+ * resolve. Guards against coincidence: at least 2 distinct matched values
77
+ * and at least half the occurrences must resolve somewhere.
78
+ */
79
+ export declare function annotateCandidateReferences(candidates: ReferenceMatchable[]): void;
80
+ /**
81
+ * External live data source: content fetched at render time from a
82
+ * third-party API (GitHub stats, dev.to posts, a headless CMS). Invisible to
83
+ * the wrap scan by nature — the migration must ASK the user what to do with
84
+ * it (keep live vs ingest) instead of guessing.
85
+ */
86
+ export interface ExternalSource {
87
+ host: string;
88
+ files: string[];
89
+ count: number;
90
+ /** 'cms' when detected via a headless-CMS SDK dependency: the site's
91
+ * CONTENT lives there — migrating it is the point, not an option. */
92
+ kind?: 'api' | 'cms';
93
+ }
94
+ /** Identities of already-wrapped content, for the seeding gate. */
95
+ export interface WrappedIds {
96
+ labels: string[];
97
+ media: string[];
98
+ richText: string[];
99
+ files: string[];
100
+ }
101
+ export interface WrapScanResult {
102
+ items: WrappableItem[];
103
+ wrappedCount: number;
104
+ filesScanned: number;
105
+ wrappedIds: WrappedIds;
106
+ collections: CollectionCandidate[];
107
+ externalSources: ExternalSource[];
108
+ dataFiles: DataCopyFile[];
109
+ /** `t()` calls bound to a FOREIGN i18n library (next-intl, i18next, …), not
110
+ * Fimo — surfaced so the migration never claims them as editable coverage. */
111
+ foreignI18nCount: number;
112
+ }
113
+ /**
114
+ * A .json data file whose values are site copy (config-driven sites keep ALL
115
+ * their text there and templates render only expressions, so template/JSX
116
+ * scanning finds nothing). Each detected file must be triaged in the manifest
117
+ * (`wrap.dataFiles`) — otherwise a copy-in-config site would pass the
118
+ * coverage gate with zero editable content.
119
+ */
120
+ export interface DataCopyFile {
121
+ file: string;
122
+ strings: number;
123
+ samples: string[];
124
+ collections: DataFileCollection[];
125
+ /** 'markdown' groups: prose documents (md/mdx) aggregated per directory —
126
+ * content the wrap cannot label-ify (Tier 1b ingestion territory), but the
127
+ * triage decision must still be explicit. */
128
+ kind?: 'markdown';
129
+ /** Number of documents behind a markdown group entry. */
130
+ count?: number;
131
+ }
132
+ export interface DataFileCollection {
133
+ path: string;
134
+ length: number;
135
+ copyFields: string[];
136
+ references?: CandidateReference[];
137
+ /** Internal input to reference detection; stripped before the plan is written. */
138
+ fieldValues?: Record<string, string[]>;
139
+ /** First row with NATIVE value types — internal input to schema-draft
140
+ * type inference; stripped before the plan is written. */
141
+ typedSample?: Record<string, unknown>;
142
+ /** All rows (capped) with NATIVE value types — internal to rows staging. */
143
+ typedRows?: Record<string, unknown>[];
144
+ manyFields?: string[];
145
+ /** Fields forced to `string` regardless of inferred type — markdown/MDX
146
+ * bodies stay verbatim so the user's existing renderer keeps working
147
+ * (wrap mode never reshapes what the page already renders). */
148
+ verbatimFields?: string[];
149
+ }
150
+ export declare function scanWrappableFile(filePath: string, code: string, relPath: string, opts?: {
151
+ i18nFimoWired?: boolean;
152
+ }): {
153
+ items: WrappableItem[];
154
+ wrapped: number;
155
+ wrappedIds: WrappedIds;
156
+ collections: CollectionCandidate[];
157
+ foreignI18n?: number;
158
+ };
159
+ /**
160
+ * Parse ONE markdown/MDX document into a typed row: YAML frontmatter fields
161
+ * (natively typed — dates, booleans, numbers, arrays) plus a `slug` (from
162
+ * frontmatter or the filename stem) and a `body` holding the raw markdown
163
+ * after the frontmatter, verbatim. The body stays a string so the user's
164
+ * existing markdown renderer keeps rendering it unchanged (wrap mode).
165
+ */
166
+ export declare function parseMarkdownDoc(fileName: string, raw: string): Record<string, unknown> | null;
167
+ /**
168
+ * Read every markdown/MDX document in a directory (non-recursively — one
169
+ * group per directory) into typed rows. Shared by the scanner (sampling for
170
+ * schema drafts + reference detection) and the rows stager (full dataset).
171
+ */
172
+ export declare function readMarkdownGroupRows(dir: string): Array<Record<string, unknown>>;
173
+ export declare function scanDataJson(relPath: string, raw: string): DataCopyFile | null;
174
+ /**
175
+ * The label keys a `decision: "labels"` data file is expected to seed —
176
+ * one per prose string, keyed by JSON path under the recorded prefix. The
177
+ * seeding gate verifies each exists in the database.
178
+ */
179
+ export declare function dataFileLabelKeys(raw: string, prefix: string, filePath?: string): string[];
180
+ /** Find render-time fetches of third-party APIs (fetch with an http(s) literal or template prefix). */
181
+ export declare function scanExternalSources(rootDir: string, dirs: string[]): ExternalSource[];
182
+ /**
183
+ * Scan a project's source directories for unwrapped content. `dirs` defaults
184
+ * to the common Next/React layouts that exist under `rootDir`.
185
+ */
186
+ export declare function scanWrappable(rootDir: string, opts?: {
187
+ dirs?: string[];
188
+ excludePrefixes?: string[];
189
+ dataDirs?: string[];
190
+ keepInternal?: boolean;
191
+ /** The app's i18n message loader is wired to Fimo (imports
192
+ * `loadFimoMessages`), so its foreign `t()` calls ARE Fimo-backed and
193
+ * editable — count them as covered instead of excluding them. */
194
+ i18nFimoWired?: boolean;
195
+ }): WrapScanResult;
196
+ export {};