fimo 0.8.0 → 0.9.0-experimental.10

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 (218) hide show
  1. package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +66 -0
  2. package/assets/agents-starter/claude/settings.json +19 -0
  3. package/assets/content-templates/hooks-template.eta +4 -2
  4. package/assets/content-templates/types-template.eta +67 -1
  5. package/assets/skills/fimo/SKILL.md +1 -1
  6. package/assets/skills/fimo/references/content.md +86 -15
  7. package/assets/skills/fimo/references/translations.md +75 -0
  8. package/assets/skills/fimo-cli/SKILL.md +6 -2
  9. package/assets/skills/fimo-cli/references/content.md +61 -2
  10. package/assets/skills/fimo-cli/references/integrations/firecrawl.md +20 -4
  11. package/assets/skills/fimo-cli/references/integrations.md +5 -1
  12. package/assets/skills/fimo-cli/references/webhooks.md +162 -0
  13. package/assets/skills/fimo-migration/SKILL.md +24 -2
  14. package/assets/skills/fimo-migration/references/content.md +24 -0
  15. package/assets/skills/fimo-migration/references/manifest.md +46 -0
  16. package/assets/skills/fimo-migration/references/pages.md +234 -0
  17. package/assets/skills/fimo-migration/references/qa.md +24 -2
  18. package/assets/skills/fimo-migration/references/workflow.md +12 -1
  19. package/assets/skills/fimo-migration/references/wrap.md +662 -0
  20. package/assets/skills/fimo-studio/SKILL.md +5 -3
  21. package/assets/skills/fimo-studio/references/cms.md +9 -1
  22. package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
  23. package/assets/skills/fimo-studio/references/webhooks.md +42 -0
  24. package/dist/build/vite/plugins/translations.d.ts +2 -8
  25. package/dist/build/vite/plugins/translations.js +31 -131
  26. package/dist/cli/bundle.json +1 -1
  27. package/dist/cli/index.js +1337 -189
  28. package/dist/runtime/app/FimoProviders.d.ts +1 -11
  29. package/dist/runtime/app/FimoProviders.js +31 -30
  30. package/dist/runtime/astro/index.d.ts +2 -0
  31. package/dist/runtime/astro/index.js +3 -0
  32. package/dist/runtime/astro/server.d.ts +3 -0
  33. package/dist/runtime/astro/server.js +6 -0
  34. package/dist/runtime/content/client/internal.js +1 -0
  35. package/dist/runtime/content/client/types.d.ts +16 -0
  36. package/dist/runtime/core/BuiltWithBadge.d.ts +2 -0
  37. package/dist/runtime/core/BuiltWithBadge.js +68 -0
  38. package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
  39. package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
  40. package/dist/runtime/next/FimoNextProviders.js +20 -0
  41. package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
  42. package/dist/runtime/next/FimoNextScripts.js +19 -0
  43. package/dist/runtime/next/index.d.ts +14 -0
  44. package/dist/runtime/next/index.js +14 -0
  45. package/dist/runtime/next/server.d.ts +2 -0
  46. package/dist/runtime/next/server.js +4 -0
  47. package/dist/runtime/nuxt/composables.d.ts +9 -0
  48. package/dist/runtime/nuxt/composables.js +57 -0
  49. package/dist/runtime/nuxt/index.d.ts +3 -0
  50. package/dist/runtime/nuxt/index.js +6 -0
  51. package/dist/runtime/nuxt/server.d.ts +3 -0
  52. package/dist/runtime/nuxt/server.js +6 -0
  53. package/dist/runtime/primitives/components/RichText.d.ts +5 -1
  54. package/dist/runtime/primitives/components/RichText.js +2 -2
  55. package/dist/runtime/primitives/labels-hook.d.ts +33 -0
  56. package/dist/runtime/primitives/labels-hook.js +41 -0
  57. package/dist/runtime/primitives/labels-store.d.ts +12 -0
  58. package/dist/runtime/primitives/labels-store.js +60 -0
  59. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
  60. package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
  61. package/dist/runtime/primitives/translations.d.ts +1 -27
  62. package/dist/runtime/primitives/translations.js +7 -85
  63. package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
  64. package/dist/runtime/react/FimoSpaProviders.js +122 -0
  65. package/dist/runtime/react/index.d.ts +35 -0
  66. package/dist/runtime/react/index.js +40 -0
  67. package/dist/runtime/react/spa-content-cache.d.ts +38 -0
  68. package/dist/runtime/react/spa-content-cache.js +64 -0
  69. package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
  70. package/dist/runtime/remix/FimoRemixProviders.js +12 -0
  71. package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
  72. package/dist/runtime/remix/FimoRemixScripts.js +19 -0
  73. package/dist/runtime/remix/index.d.ts +12 -0
  74. package/dist/runtime/remix/index.js +11 -0
  75. package/dist/runtime/remix/server.d.ts +2 -0
  76. package/dist/runtime/remix/server.js +2 -0
  77. package/dist/runtime/seo/neutral.d.ts +22 -0
  78. package/dist/runtime/seo/neutral.js +17 -0
  79. package/dist/runtime/shared/display-routes.d.ts +14 -0
  80. package/dist/runtime/shared/display-routes.js +25 -0
  81. package/dist/runtime/shared/fimo-config.server.d.ts +0 -1
  82. package/dist/runtime/shared/fimo-config.server.js +1 -13
  83. package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
  84. package/dist/runtime/shared/i18n-messages.d.ts +29 -0
  85. package/dist/runtime/shared/i18n-messages.js +79 -0
  86. package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
  87. package/dist/runtime/shared/labels-fetch.pure.js +71 -0
  88. package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
  89. package/dist/runtime/shared/labels-fetch.server.js +97 -0
  90. package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
  91. package/dist/runtime/shared/labels-t.pure.js +10 -0
  92. package/dist/runtime/strapi/client.d.ts +30 -0
  93. package/dist/runtime/strapi/client.js +132 -0
  94. package/dist/runtime/strapi/index.d.ts +5 -0
  95. package/dist/runtime/strapi/index.js +6 -0
  96. package/dist/runtime/strapi/reshape.d.ts +50 -0
  97. package/dist/runtime/strapi/reshape.js +121 -0
  98. package/dist/runtime/svelte/index.d.ts +3 -0
  99. package/dist/runtime/svelte/index.js +7 -0
  100. package/dist/runtime/svelte/server.d.ts +3 -0
  101. package/dist/runtime/svelte/server.js +6 -0
  102. package/dist/runtime/vue/index.d.ts +3 -0
  103. package/dist/runtime/vue/index.js +5 -0
  104. package/dist/runtime/vue/server.d.ts +3 -0
  105. package/dist/runtime/vue/server.js +6 -0
  106. package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
  107. package/dist/runtime/wrapped/FimoFileLink.js +19 -0
  108. package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
  109. package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
  110. package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
  111. package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
  112. package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +26 -0
  113. package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
  114. package/dist/runtime/wrapped/FimoText.d.ts +22 -0
  115. package/dist/runtime/wrapped/FimoText.js +20 -0
  116. package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
  117. package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
  118. package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
  119. package/dist/runtime/wrapped/WrappedProviders.js +101 -0
  120. package/dist/runtime/wrapped/content-fetch.pure.d.ts +20 -0
  121. package/dist/runtime/wrapped/content-fetch.pure.js +106 -0
  122. package/dist/runtime/wrapped/dom.d.ts +4 -0
  123. package/dist/runtime/wrapped/dom.js +23 -0
  124. package/dist/runtime/wrapped/server.d.ts +80 -0
  125. package/dist/runtime/wrapped/server.js +207 -0
  126. package/dist/scripts/apply-wrap-templates.d.ts +61 -0
  127. package/dist/scripts/apply-wrap-templates.js +137 -0
  128. package/dist/scripts/apply-wrap.d.ts +73 -0
  129. package/dist/scripts/apply-wrap.js +340 -0
  130. package/dist/scripts/cms/discover.d.ts +2 -0
  131. package/dist/scripts/cms/discover.js +17 -0
  132. package/dist/scripts/cms/example-content.d.ts +33 -0
  133. package/dist/scripts/cms/example-content.js +165 -0
  134. package/dist/scripts/cms/strapi/detect.d.ts +2 -0
  135. package/dist/scripts/cms/strapi/detect.js +186 -0
  136. package/dist/scripts/cms/strapi/index.d.ts +3 -0
  137. package/dist/scripts/cms/strapi/index.js +25 -0
  138. package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
  139. package/dist/scripts/cms/strapi/list-source.js +90 -0
  140. package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
  141. package/dist/scripts/cms/strapi/schema-map.js +289 -0
  142. package/dist/scripts/cms/strapi/shapes.d.ts +25 -0
  143. package/dist/scripts/cms/strapi/shapes.js +48 -0
  144. package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
  145. package/dist/scripts/cms/strapi/touchpoints.js +145 -0
  146. package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
  147. package/dist/scripts/cms/strapi/wire-client.js +107 -0
  148. package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
  149. package/dist/scripts/cms/strapi/wiring.js +46 -0
  150. package/dist/scripts/cms/types.d.ts +145 -0
  151. package/dist/scripts/cms/types.js +11 -0
  152. package/dist/scripts/detect-framework.d.ts +39 -0
  153. package/dist/scripts/detect-framework.js +139 -0
  154. package/dist/scripts/draft-rows.d.ts +19 -0
  155. package/dist/scripts/draft-rows.js +178 -0
  156. package/dist/scripts/draft-schemas.d.ts +27 -0
  157. package/dist/scripts/draft-schemas.js +247 -0
  158. package/dist/scripts/extract-translations.js +16 -11
  159. package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
  160. package/dist/scripts/frameworks/astro/routes.js +44 -0
  161. package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
  162. package/dist/scripts/frameworks/astro/template-style.js +19 -0
  163. package/dist/scripts/frameworks/discover.d.ts +83 -0
  164. package/dist/scripts/frameworks/discover.js +288 -0
  165. package/dist/scripts/frameworks/discovered-route.d.ts +9 -0
  166. package/dist/scripts/frameworks/discovered-route.js +17 -0
  167. package/dist/scripts/frameworks/next/rewire.d.ts +43 -0
  168. package/dist/scripts/frameworks/next/rewire.js +282 -0
  169. package/dist/scripts/frameworks/next/routes.d.ts +30 -0
  170. package/dist/scripts/frameworks/next/routes.js +176 -0
  171. package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
  172. package/dist/scripts/frameworks/next/wiring.js +186 -0
  173. package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
  174. package/dist/scripts/frameworks/nuxt/routes.js +38 -0
  175. package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
  176. package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
  177. package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
  178. package/dist/scripts/frameworks/remix/routes.js +36 -0
  179. package/dist/scripts/frameworks/support.d.ts +75 -0
  180. package/dist/scripts/frameworks/support.js +165 -0
  181. package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
  182. package/dist/scripts/frameworks/sveltekit/routes.js +35 -0
  183. package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
  184. package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
  185. package/dist/scripts/frameworks/vite/routes.d.ts +20 -0
  186. package/dist/scripts/frameworks/vite/routes.js +143 -0
  187. package/dist/scripts/frameworks/vite/wiring.d.ts +22 -0
  188. package/dist/scripts/frameworks/vite/wiring.js +144 -0
  189. package/dist/scripts/i18n-namespace.d.ts +27 -0
  190. package/dist/scripts/i18n-namespace.js +155 -0
  191. package/dist/scripts/lib/humanize.d.ts +14 -0
  192. package/dist/scripts/lib/humanize.js +54 -0
  193. package/dist/scripts/lib/validation-report.d.ts +1 -3
  194. package/dist/scripts/lib/validation-report.js +3 -9
  195. package/dist/scripts/render-parity/capture.d.ts +64 -0
  196. package/dist/scripts/render-parity/capture.js +436 -0
  197. package/dist/scripts/render-parity/compare.d.ts +59 -0
  198. package/dist/scripts/render-parity/compare.js +0 -0
  199. package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
  200. package/dist/scripts/render-parity/normalize-html.js +223 -0
  201. package/dist/scripts/render-parity/types.d.ts +41 -0
  202. package/dist/scripts/render-parity/types.js +1 -0
  203. package/dist/scripts/routes/bindings.d.ts +93 -0
  204. package/dist/scripts/routes/bindings.js +117 -0
  205. package/dist/scripts/routes/label-prefixes.d.ts +49 -0
  206. package/dist/scripts/routes/label-prefixes.js +136 -0
  207. package/dist/scripts/routes/module-resolve.d.ts +20 -0
  208. package/dist/scripts/routes/module-resolve.js +78 -0
  209. package/dist/scripts/routes/page-structure.d.ts +72 -0
  210. package/dist/scripts/routes/page-structure.js +234 -0
  211. package/dist/scripts/scan-wrappable.d.ts +200 -0
  212. package/dist/scripts/scan-wrappable.js +1355 -0
  213. package/dist/scripts/validate-route-metadata.js +11 -0
  214. package/dist/scripts/validate-schemas.js +42 -1
  215. package/package.json +88 -4
  216. package/release.json +2 -2
  217. package/templates/react-router/package.json +1 -1
  218. package/templates/react-router/translations/en.json +1 -0
@@ -0,0 +1,44 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { pushRoute, toSegment } from '../discovered-route.js';
4
+ const PAGE_EXT = /\.(astro|md|mdx|html)$/;
5
+ /** Astro file-based routes under src/pages (index files map to the dir path). */
6
+ export function discoverAstroRoutes(pagesDir) {
7
+ const routes = [];
8
+ const walk = (dir, segments) => {
9
+ let entries;
10
+ try {
11
+ entries = readdirSync(dir);
12
+ }
13
+ catch {
14
+ return;
15
+ }
16
+ for (const entry of entries) {
17
+ const full = join(dir, entry);
18
+ let isDir = false;
19
+ try {
20
+ isDir = statSync(full).isDirectory();
21
+ }
22
+ catch {
23
+ continue;
24
+ }
25
+ // Astro's convention: underscore-prefixed files AND directories are
26
+ // excluded from routing (component folders inside src/pages).
27
+ if (entry.startsWith('_')) {
28
+ continue;
29
+ }
30
+ if (isDir) {
31
+ walk(full, [...segments, toSegment(entry)]);
32
+ continue;
33
+ }
34
+ if (!PAGE_EXT.test(entry)) {
35
+ continue;
36
+ }
37
+ const base = entry.replace(PAGE_EXT, '');
38
+ pushRoute(routes, base === 'index' ? segments : [...segments, toSegment(base)], full);
39
+ }
40
+ };
41
+ walk(pagesDir, []);
42
+ routes.sort((a, b) => a.path.localeCompare(b.path));
43
+ return routes;
44
+ }
@@ -0,0 +1,7 @@
1
+ import type { TemplateWrapStyle } from '../../apply-wrap-templates.js';
2
+ /**
3
+ * Astro template wraps: t() resolves in the frontmatter (server-only, so the
4
+ * server barrel is safe to import there), values interpolate as {t(...)}.
5
+ * Files without a frontmatter fence get one — Astro allows an empty fence.
6
+ */
7
+ export declare const astroTemplateWrapStyle: TemplateWrapStyle;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Astro template wraps: t() resolves in the frontmatter (server-only, so the
3
+ * server barrel is safe to import there), values interpolate as {t(...)}.
4
+ * Files without a frontmatter fence get one — Astro allows an empty fence.
5
+ */
6
+ export const astroTemplateWrapStyle = {
7
+ ext: '.astro',
8
+ head: 'import { loadFimoT } from "fimo/astro/server";\nconst t = await loadFimoT();',
9
+ headMarker: /loadFimoT/,
10
+ open: '{t(',
11
+ close: ')}',
12
+ injectHead: (code, head) => {
13
+ const fence = code.match(/^---\r?\n/);
14
+ if (fence) {
15
+ return `${code.slice(0, fence[0].length)}${head}\n${code.slice(fence[0].length)}`;
16
+ }
17
+ return `---\n${head}\n---\n${code}`;
18
+ },
19
+ };
@@ -0,0 +1,83 @@
1
+ import type { TemplateWrapStyle } from '../apply-wrap-templates.js';
2
+ import type { FrameworkDetection } from '../detect-framework.js';
3
+ import { type RouteFileResolution, type RouteLike } from '../routes/module-resolve.js';
4
+ import { type CollectionRewireResult } from './next/rewire.js';
5
+ import { type DiscoveredRoute } from './next/routes.js';
6
+ import { type RootWiringResult } from './next/wiring.js';
7
+ /**
8
+ * Route discovery for any detected framework — the single registry generic
9
+ * code calls. Frameworks without a file convention (vue SPA, plain vite)
10
+ * return [] and rely on fimo-config routes.
11
+ */
12
+ export declare function discoverFrameworkRoutes(cwd: string, detection: FrameworkDetection): DiscoveredRoute[];
13
+ /**
14
+ * Extra directories the framework keeps page COPY in, outside any source
15
+ * root (data-file scanning). Part of the framework registry so generic
16
+ * scanners never carry framework names.
17
+ */
18
+ export declare function frameworkDataDirs(framework: string | null | undefined): string[];
19
+ /**
20
+ * The wrapper-import module for frameworks whose .jsx/.tsx is guaranteed
21
+ * React — the only ones the --apply JSX codemod may touch. Template
22
+ * frameworks return null: a .tsx island there may be Solid or Preact, and
23
+ * injecting a React wrapper import breaks the build (bento dogfood). Bare
24
+ * Vite qualifies only with the detection's `react` flag (a react dependency
25
+ * pins the JSX runtime); the SPA primitives live in fimo/react and are
26
+ * fallback-safe before the providers are wired.
27
+ */
28
+ export declare function frameworkJsxWrapperModule(detection: Pick<FrameworkDetection, 'framework' | 'react'>): string | null;
29
+ /**
30
+ * The template-wrap style for frameworks whose template files the --apply
31
+ * codemod may touch (plain text nodes only). Vue SPA stays null: without a
32
+ * server data path there is no deterministic t() wiring to inject.
33
+ */
34
+ export declare function frameworkTemplateWrapStyle(framework: string | null | undefined): TemplateWrapStyle | null;
35
+ export declare function frameworkRouteFiles(framework: string | null): ((cwd: string, routes: Record<string, RouteLike>) => RouteFileResolution[]) | null;
36
+ export interface SectionStyle {
37
+ /** Module extensions a section import may resolve to. */
38
+ extensions: string[];
39
+ /** Wrapped-key scope for a component file (must match the wrap codemod's
40
+ * own scoping so drafted sections join their staged keys). */
41
+ fileScope: (relFile: string) => string;
42
+ /** Directories tried for auto-imported components (Nuxt convention) when
43
+ * a section tag has no import statement. */
44
+ fallbackComponentDirs: string[];
45
+ }
46
+ /**
47
+ * How to read a page's SECTION STRUCTURE for this framework — the seam the
48
+ * page-structure drafter builds on. Template frameworks share the ES-import
49
+ * + capitalized-tag shape, differing only in extensions and key scoping.
50
+ */
51
+ export declare function frameworkSectionStyle(framework: string | null): SectionStyle;
52
+ /** Client-rendered frameworks: no server pass — reads happen in the browser
53
+ * (the prompt's collection recipe and the SPA providers key off this). */
54
+ export declare function isClientRenderedFramework(framework: string | null | undefined): boolean;
55
+ export declare function frameworkRootWiring(detection: Pick<FrameworkDetection, 'framework' | 'react' | 'appDir'>): ((cwd: string) => RootWiringResult) | null;
56
+ /**
57
+ * Collection-page rewiring: pages that render a machine-staged collection get
58
+ * their data source switched to `wrapCollection` (Fimo overlay over the
59
+ * retained source rows) mechanically, with per-page bail reasons. Runs during
60
+ * --apply after drafts are staged; adopt-drafts renames the call-site uids.
61
+ * Next.js app router only for now.
62
+ */
63
+ export declare function frameworkCollectionRewire(detection: Pick<FrameworkDetection, 'framework' | 'appDir'>): ((cwd: string, scan: unknown, drafts: Array<{
64
+ uid: string;
65
+ sourceName: string;
66
+ source: string;
67
+ }>) => CollectionRewireResult) | null;
68
+ export interface WiringMarker {
69
+ /** Candidate files, relative to the project root; ONE must contain the marker. */
70
+ files: string[];
71
+ marker: RegExp;
72
+ /** Human hint rendered when the marker is missing. */
73
+ hint: string;
74
+ /** Missing marker warns instead of failing (no automated recipe yet). */
75
+ warnOnly?: boolean;
76
+ }
77
+ /**
78
+ * How `wrap --check` proves the root wiring exists: without providers the
79
+ * wrapped values render fallbacks forever, and neither coverage (counts
80
+ * wraps), parity (fallbacks render identically), nor seeding (DB-side)
81
+ * notices — editability would ship silently broken.
82
+ */
83
+ export declare function frameworkWiringMarker(detection: Pick<FrameworkDetection, 'framework' | 'react' | 'appDir'>, cwd?: string): WiringMarker | null;
@@ -0,0 +1,288 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { basename, join, relative } from 'node:path';
3
+ import { keyScope, slugify } from '../apply-wrap.js';
4
+ import { DEFAULT_COMPONENT_EXTENSIONS } from '../routes/module-resolve.js';
5
+ import { discoverAstroRoutes } from './astro/routes.js';
6
+ import { astroTemplateWrapStyle } from './astro/template-style.js';
7
+ import { rewireNextCollectionPages } from './next/rewire.js';
8
+ import { discoverNextPagesRoutes, discoverNextRoutes, resolveNextRouteFiles, } from './next/routes.js';
9
+ import { ensureNextLayoutWiring, nextRootLayoutFiles } from './next/wiring.js';
10
+ import { discoverNuxtRoutes } from './nuxt/routes.js';
11
+ import { nuxtTemplateWrapStyle } from './nuxt/template-style.js';
12
+ import { discoverRemixRoutes } from './remix/routes.js';
13
+ import { discoverSvelteKitRoutes } from './sveltekit/routes.js';
14
+ import { svelteTemplateWrapStyle } from './sveltekit/template-style.js';
15
+ import { discoverReactRouterDomRoutes, resolveSpaRouteFiles } from './vite/routes.js';
16
+ import { ensureSpaEntryWiring } from './vite/wiring.js';
17
+ /**
18
+ * Route discovery for any detected framework — the single registry generic
19
+ * code calls. Frameworks without a file convention (vue SPA, plain vite)
20
+ * return [] and rely on fimo-config routes.
21
+ */
22
+ export function discoverFrameworkRoutes(cwd, detection) {
23
+ if (detection.framework === 'vite' && detection.react) {
24
+ return discoverReactRouterDomRoutes(cwd);
25
+ }
26
+ // Library-mode React Router (react-router dep on Vite, no app/ convention)
27
+ // declares routes in code exactly like a vite+react SPA — the Fimo-site
28
+ // dogfood detected `react-router` and fell to `default: []`, forcing 22
29
+ // manual `pages set` calls while the SPA scanner would have found them all.
30
+ if (detection.framework === 'react-router' && !detection.pagesDir) {
31
+ return discoverReactRouterDomRoutes(cwd);
32
+ }
33
+ switch (detection.framework) {
34
+ case 'next':
35
+ return [
36
+ ...(detection.appDir ? discoverNextRoutes(join(cwd, detection.appDir)) : []),
37
+ ...(detection.pagesDir ? discoverNextPagesRoutes(join(cwd, detection.pagesDir)) : []),
38
+ ];
39
+ case 'remix':
40
+ return detection.pagesDir ? discoverRemixRoutes(join(cwd, detection.pagesDir)) : [];
41
+ case 'astro':
42
+ return detection.pagesDir ? discoverAstroRoutes(join(cwd, detection.pagesDir)) : [];
43
+ case 'nuxt':
44
+ return detection.pagesDir ? discoverNuxtRoutes(join(cwd, detection.pagesDir)) : [];
45
+ case 'sveltekit':
46
+ return detection.pagesDir ? discoverSvelteKitRoutes(join(cwd, detection.pagesDir)) : [];
47
+ default:
48
+ return [];
49
+ }
50
+ }
51
+ /**
52
+ * Extra directories the framework keeps page COPY in, outside any source
53
+ * root (data-file scanning). Part of the framework registry so generic
54
+ * scanners never carry framework names.
55
+ */
56
+ export function frameworkDataDirs(framework) {
57
+ switch (framework) {
58
+ case 'nuxt':
59
+ return ['content']; // Nuxt Content convention (.yml/.md page documents)
60
+ default:
61
+ return [];
62
+ }
63
+ }
64
+ /**
65
+ * The wrapper-import module for frameworks whose .jsx/.tsx is guaranteed
66
+ * React — the only ones the --apply JSX codemod may touch. Template
67
+ * frameworks return null: a .tsx island there may be Solid or Preact, and
68
+ * injecting a React wrapper import breaks the build (bento dogfood). Bare
69
+ * Vite qualifies only with the detection's `react` flag (a react dependency
70
+ * pins the JSX runtime); the SPA primitives live in fimo/react and are
71
+ * fallback-safe before the providers are wired.
72
+ */
73
+ export function frameworkJsxWrapperModule(detection) {
74
+ switch (detection.framework) {
75
+ case 'next':
76
+ return 'fimo/next';
77
+ case 'remix':
78
+ return 'fimo/remix';
79
+ case 'react-router':
80
+ // Library-mode React Router SPAs (react-router dep on Vite) wrap with
81
+ // the client-side fimo/react primitives — same shape as vite+react.
82
+ // Proven on the Fimo marketing site (1211 items auto-wrapped).
83
+ return 'fimo/react';
84
+ case 'vite':
85
+ return detection.react ? 'fimo/react' : null;
86
+ default:
87
+ return null;
88
+ }
89
+ }
90
+ /**
91
+ * The template-wrap style for frameworks whose template files the --apply
92
+ * codemod may touch (plain text nodes only). Vue SPA stays null: without a
93
+ * server data path there is no deterministic t() wiring to inject.
94
+ */
95
+ export function frameworkTemplateWrapStyle(framework) {
96
+ switch (framework) {
97
+ case 'astro':
98
+ return astroTemplateWrapStyle;
99
+ case 'nuxt':
100
+ return nuxtTemplateWrapStyle;
101
+ case 'sveltekit':
102
+ return svelteTemplateWrapStyle;
103
+ default:
104
+ return null;
105
+ }
106
+ }
107
+ /**
108
+ * The one-time root-wiring codemod for frameworks whose providers mount in a
109
+ * single well-known file (Next.js app router: the root layout; Vite React
110
+ * SPA: the entry). Runs during --apply, AFTER the baseline capture — the
111
+ * pre-wrap dev-server boot must not require the fimo package, and providers
112
+ * are render-transparent so the parity check stays clean. Template
113
+ * frameworks wire through their TemplateWrapStyle.ensureWiring hooks instead.
114
+ */
115
+ /**
116
+ * Registry route ids → the component files that render them — the join the
117
+ * label-ownership prefill and the page-structure drafter build on. Framework
118
+ * knowledge stays HERE: core modules call this and never test a framework
119
+ * name themselves. File-based frameworks resolve straight from their
120
+ * discovery output (the walk knows every route's file); code-declared
121
+ * routers (SPA) resolve through import analysis.
122
+ */
123
+ function resolveByDiscovery(discover, conventionalDirs) {
124
+ return (cwd, routes) => {
125
+ const pagesDir = conventionalDirs.map((dir) => join(cwd, dir)).find((dir) => existsSync(dir));
126
+ if (!pagesDir) {
127
+ return [];
128
+ }
129
+ const byPath = new Map();
130
+ for (const [routeId, route] of Object.entries(routes)) {
131
+ if (typeof route?.path === 'string') {
132
+ byPath.set(route.path, routeId);
133
+ }
134
+ }
135
+ const resolutions = [];
136
+ for (const discovered of discover(pagesDir)) {
137
+ const routeId = byPath.get(discovered.path);
138
+ if (routeId && discovered.file) {
139
+ resolutions.push({ routeId, file: discovered.file });
140
+ }
141
+ }
142
+ return resolutions;
143
+ };
144
+ }
145
+ export function frameworkRouteFiles(framework) {
146
+ switch (framework) {
147
+ case 'next':
148
+ return resolveNextRouteFiles;
149
+ case 'react-router':
150
+ case 'vite':
151
+ return resolveSpaRouteFiles;
152
+ case 'astro':
153
+ return resolveByDiscovery(discoverAstroRoutes, ['src/pages', 'pages']);
154
+ case 'nuxt':
155
+ return resolveByDiscovery(discoverNuxtRoutes, ['pages', 'src/pages']);
156
+ case 'sveltekit':
157
+ return resolveByDiscovery(discoverSvelteKitRoutes, ['src/routes']);
158
+ case 'remix':
159
+ return resolveByDiscovery(discoverRemixRoutes, ['app/routes']);
160
+ default:
161
+ return null;
162
+ }
163
+ }
164
+ /**
165
+ * How to read a page's SECTION STRUCTURE for this framework — the seam the
166
+ * page-structure drafter builds on. Template frameworks share the ES-import
167
+ * + capitalized-tag shape, differing only in extensions and key scoping.
168
+ */
169
+ export function frameworkSectionStyle(framework) {
170
+ const templateScope = (relFile) => {
171
+ const style = frameworkTemplateWrapStyle(framework);
172
+ return style?.fileScope?.(relFile) ?? slugify(basename(relFile).replace(/\.[a-z0-9]+$/i, '')) ?? 'shared';
173
+ };
174
+ switch (framework) {
175
+ case 'astro':
176
+ return {
177
+ extensions: ['.astro', '.tsx', '.jsx', '.ts', '.js'],
178
+ fileScope: templateScope,
179
+ fallbackComponentDirs: [],
180
+ };
181
+ case 'nuxt':
182
+ return {
183
+ extensions: ['.vue', '.ts', '.js'],
184
+ fileScope: templateScope,
185
+ fallbackComponentDirs: ['components'],
186
+ };
187
+ case 'sveltekit':
188
+ return { extensions: ['.svelte', '.ts', '.js'], fileScope: templateScope, fallbackComponentDirs: [] };
189
+ default:
190
+ return { extensions: DEFAULT_COMPONENT_EXTENSIONS.slice(), fileScope: keyScope, fallbackComponentDirs: [] };
191
+ }
192
+ }
193
+ /** Client-rendered frameworks: no server pass — reads happen in the browser
194
+ * (the prompt's collection recipe and the SPA providers key off this). */
195
+ export function isClientRenderedFramework(framework) {
196
+ return framework === 'vite' || framework === 'react-router';
197
+ }
198
+ export function frameworkRootWiring(detection) {
199
+ switch (detection.framework) {
200
+ case 'next':
201
+ return (cwd) => ensureNextLayoutWiring(cwd, detection);
202
+ case 'react-router':
203
+ // Same createRoot(...).render entry shape as vite+react; framework-mode
204
+ // RR apps have no src/main.* and bail with a reason (agent §0).
205
+ return ensureSpaEntryWiring;
206
+ case 'vite':
207
+ return detection.react ? ensureSpaEntryWiring : null;
208
+ default:
209
+ return null;
210
+ }
211
+ }
212
+ /**
213
+ * Collection-page rewiring: pages that render a machine-staged collection get
214
+ * their data source switched to `wrapCollection` (Fimo overlay over the
215
+ * retained source rows) mechanically, with per-page bail reasons. Runs during
216
+ * --apply after drafts are staged; adopt-drafts renames the call-site uids.
217
+ * Next.js app router only for now.
218
+ */
219
+ export function frameworkCollectionRewire(detection) {
220
+ switch (detection.framework) {
221
+ case 'next':
222
+ return (cwd, scan, drafts) => rewireNextCollectionPages(cwd, detection, scan, drafts);
223
+ default:
224
+ return null;
225
+ }
226
+ }
227
+ /**
228
+ * How `wrap --check` proves the root wiring exists: without providers the
229
+ * wrapped values render fallbacks forever, and neither coverage (counts
230
+ * wraps), parity (fallbacks render identically), nor seeding (DB-side)
231
+ * notices — editability would ship silently broken.
232
+ */
233
+ export function frameworkWiringMarker(detection, cwd) {
234
+ switch (detection.framework) {
235
+ case 'next': {
236
+ if (!detection.appDir) {
237
+ return {
238
+ files: ['pages/_app.tsx', 'pages/_app.jsx', 'src/pages/_app.tsx', 'src/pages/_app.jsx'],
239
+ marker: /FimoNextProviders/,
240
+ hint: 'mount FimoNextProviders in pages/_app (pages router has no automated recipe yet)',
241
+ warnOnly: true,
242
+ };
243
+ }
244
+ // Resolve the real root layout(s), including the next-intl `[locale]`
245
+ // segment; fall back to the direct path when nothing is on disk yet.
246
+ const resolved = cwd ? nextRootLayoutFiles(cwd, detection.appDir).map((file) => relative(cwd, file)) : [];
247
+ const files = resolved.length > 0
248
+ ? resolved
249
+ : [`${detection.appDir}/layout.tsx`, `${detection.appDir}/layout.jsx`, `${detection.appDir}/layout.js`];
250
+ return {
251
+ files,
252
+ marker: /FimoNextProviders/,
253
+ hint: `mount FimoNextProviders + FimoNextScripts in ${detection.appDir}/layout (agent prompt section 0)`,
254
+ };
255
+ }
256
+ case 'vite':
257
+ if (!detection.react) {
258
+ return null;
259
+ }
260
+ return {
261
+ files: ['src/main.tsx', 'src/main.jsx', 'src/index.tsx', 'src/index.jsx'],
262
+ marker: /FimoSpaProviders/,
263
+ hint: 'wrap the root render with FimoSpaProviders from fimo/react (agent prompt section 0)',
264
+ };
265
+ case 'react-router':
266
+ // Accept either provider: FimoSpaProviders (the vite+react codemod) or
267
+ // FimoProviders (the fimo/react-router adapter, hand-wired on RR sites).
268
+ return {
269
+ files: ['src/main.tsx', 'src/main.jsx', 'src/index.tsx', 'src/index.jsx', 'app/root.tsx', 'app/root.jsx'],
270
+ marker: /Fimo(Spa)?Providers/,
271
+ hint: 'wrap the root render with FimoSpaProviders (fimo/react) or FimoProviders (fimo/react-router) — agent prompt section 0',
272
+ };
273
+ case 'remix':
274
+ return {
275
+ files: ['app/root.tsx', 'app/root.jsx'],
276
+ marker: /FimoRemixProviders/,
277
+ hint: 'mount FimoRemixProviders in app/root (agent prompt section 0)',
278
+ };
279
+ case 'sveltekit':
280
+ return {
281
+ files: ['src/routes/+layout.server.js', 'src/routes/+layout.server.ts'],
282
+ marker: /fimoLabels/,
283
+ hint: 'return fimoLabels from the root +layout.server load (wrap.md SvelteKit notes)',
284
+ };
285
+ default:
286
+ return null;
287
+ }
288
+ }
@@ -0,0 +1,9 @@
1
+ import type { DiscoveredRoute } from './next/routes.js';
2
+ /**
3
+ * Shared helpers for file-based route discovery — one implementation of the
4
+ * segment→route push and the bracket-param normalization that astro, nuxt,
5
+ * sveltekit, and remix previously each carried a copy of.
6
+ */
7
+ export declare function pushRoute(routes: DiscoveredRoute[], parts: string[], file?: string): void;
8
+ /** `[slug]` → `:slug`, `[...rest]` → `:rest` (bracket file conventions). */
9
+ export declare const toSegment: (name: string) => string;
@@ -0,0 +1,17 @@
1
+ import { discoveredRouteLabel } from '../lib/humanize.js';
2
+ /**
3
+ * Shared helpers for file-based route discovery — one implementation of the
4
+ * segment→route push and the bracket-param normalization that astro, nuxt,
5
+ * sveltekit, and remix previously each carried a copy of.
6
+ */
7
+ export function pushRoute(routes, parts, file) {
8
+ const routePath = `/${parts.join('/')}`.replace(/\/+/g, '/');
9
+ routes.push({
10
+ id: routePath === '/' ? 'home' : parts.map((part) => part.replace(/^:/, '')).join('-'),
11
+ path: routePath,
12
+ label: discoveredRouteLabel(routePath),
13
+ ...(file ? { file } : {}),
14
+ });
15
+ }
16
+ /** `[slug]` → `:slug`, `[...rest]` → `:rest` (bracket file conventions). */
17
+ export const toSegment = (name) => name.replace(/^\[\.\.\.(.+)\]$/, ':$1').replace(/^\[(.+)\]$/, ':$1');
@@ -0,0 +1,43 @@
1
+ import type { FrameworkDetection } from '../../detect-framework.js';
2
+ import type { WrapScanResult } from '../../scan-wrappable.js';
3
+ /**
4
+ * Page rewiring for wrap-migrated collections: make each page that renders a
5
+ * machine-staged collection read from Fimo through `wrapCollection` while
6
+ * KEEPING its source rows as the render fallback. Two safe shapes:
7
+ *
8
+ * - an in-code array literal (`const clients = [...]`) rendered by the
9
+ * page's default-export function → the literal is extracted byte-faithful
10
+ * to `src/data/<name>.json` (it becomes the fallback file) and the
11
+ * component fetches `wrapCollection('<Uid>', clientsSource)`;
12
+ * - a data-file default import (`import posts from '../src/data/posts.json'`)
13
+ * → the import becomes `postsSource` and the component fetches
14
+ * `wrapCollection('<Uid>', postsSource)`.
15
+ *
16
+ * Everything else bails with a reason that flows into the agent prompt.
17
+ * Uids are the PROVISIONAL draft names; `schemas adopt-drafts` rewrites the
18
+ * call sites through its renames.
19
+ */
20
+ export interface RewiredPage {
21
+ file: string;
22
+ /** Collection uid the page reads through `wrapCollection` (provisional until
23
+ * `adopt-drafts` renames it). */
24
+ uid: string;
25
+ /** The page's registry route path (`:param` normalized), null when the file
26
+ * sits in a non-routable location. Joins the page back to the page registry
27
+ * as binding evidence. */
28
+ routePath: string | null;
29
+ }
30
+ export interface CollectionRewireResult {
31
+ changes: string[];
32
+ warnings: string[];
33
+ /** Pages successfully rewired. */
34
+ rewired: number;
35
+ /** Every page known to read a collection through `wrapCollection` — rewired
36
+ * this run or found already wired on a re-run. */
37
+ pages: RewiredPage[];
38
+ }
39
+ export declare function rewireNextCollectionPages(cwd: string, detection: Pick<FrameworkDetection, 'appDir'>, scan: WrapScanResult, drafts: Array<{
40
+ uid: string;
41
+ sourceName: string;
42
+ source: string;
43
+ }>): CollectionRewireResult;