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
@@ -6,9 +6,9 @@ description: >-
6
6
  Triggers: "in Fimo Studio", "in the dashboard", "in the web app",
7
7
  "how do I X in the UI", or screenshots of the dashboard. Topics
8
8
  covered: dashboard content and CMS, publishing from the dashboard,
9
- dashboard SEO/domain settings, visual editing, version history UI,
10
- dashboard sharing/billing, and dashboard chat. Never describe UI
11
- locations, buttons, or panels from memory — always read the relevant
9
+ dashboard SEO/domain settings, outbound webhooks, visual editing, version
10
+ history UI, dashboard sharing/billing, and dashboard chat. Never describe
11
+ UI locations, buttons, or panels from memory — always read the relevant
12
12
  reference file first.
13
13
  ---
14
14
 
@@ -37,6 +37,7 @@ Read the relevant reference file when the user's question matches a topic.
37
37
  | AI Chat | references/ai-chat.md | "What can the AI do?", "How do I give context?" |
38
38
  | Code Editor | references/code-editor.md | "How do I edit code?", "Where are my files?" |
39
39
  | SEO Settings | references/seo-settings.md | "How do I set up SEO?", "Favicon, title, share image" |
40
+ | Webhooks | references/webhooks.md | "React to forms", "Send events to another service" |
40
41
  | Preview & Responsive | references/preview-and-responsive.md | "How does it look on mobile?", "How do I test sizes?" |
41
42
  | Visual Editing | references/visual-editing.md | "Can I edit visually?", "How do I change colors?" |
42
43
  | Version History | references/version-history.md | "How do I undo?", "How do I go back?" |
@@ -53,6 +54,7 @@ Read the relevant reference file when the user's question matches a topic.
53
54
  | Custom domains | Publishing (must publish first), Billing (premium required) |
54
55
  | Adding images | Media Library, AI Chat, Edit Mode |
55
56
  | Content/CMS | AI Chat, Media Library |
57
+ | Webhooks | Project secrets, Forms, Publishing, Agents |
56
58
  | Visual changes | Edit Mode (quick tweaks), AI Chat (bigger changes), Code Editor (full control) |
57
59
  | Something went wrong | Version History, AI Chat |
58
60
  | Working with others | Sharing, Collaboration |
@@ -42,7 +42,7 @@ When you open a Collection, the entry list provides several tools:
42
42
  2. Click the **+** (plus) icon button at the top of the Collection (labeled **New entry** on hover). If the Collection is empty, use the **Create entry** button in the center of the empty state instead.
43
43
  3. Fill in the form fields (text, images, dates, whatever the collection defines).
44
44
 
45
- Changes save automatically as you fill in each field (the save is debounced), so there is no Save button. The save status appears in the top bar (Saving / Saved). The entry is now part of your Collection. If the Collection is wired to a page on your site, the new content shows up automatically.
45
+ Changes save automatically as you fill in each field (the save is debounced), so there is no Save button. The save status appears in the top bar (Saving / Saved). An incomplete new entry waits until its required fields are valid before it becomes part of the Collection. If the Collection is wired to a page on your site, new content shows up automatically.
46
46
 
47
47
  ## Editing an Entry
48
48
 
@@ -51,6 +51,14 @@ Changes save automatically as you fill in each field (the save is debounced), so
51
51
 
52
52
  Your edits save automatically as you change each field (the save is debounced), so there is no Save button. Watch the top bar for the save status (Saving / Saved). The entry editor's only header actions are delete (trash icon) and copy link.
53
53
 
54
+ If a field is invalid, the editor keeps the change unsaved and pins a status bar below the header. Choose **Review issues** to jump to the first invalid field, then **Next issue** to move through the remaining fields. Collapsed blocks open automatically. Once every issue is fixed, the editor confirms that all fields are valid and resumes saving.
55
+
56
+ ## Editing Text
57
+
58
+ - Short text such as titles, names, and slugs uses one compact line.
59
+ - Plain multiline text such as summaries and descriptions starts taller and grows as you write.
60
+ - Formatted content uses the rich-text editor for headings, links, lists, and media.
61
+
54
62
  ## Bulk Operations
55
63
 
56
64
  Need to update or remove several entries at once?
@@ -12,7 +12,9 @@ For text elements, the side panel shows editable fields where you can change the
12
12
 
13
13
  ### Editing Styles
14
14
 
15
- The side panel organizes style controls into these categories:
15
+ Style controls are only available on projects created before July 2026. Newer projects edit styles through the AI chat instead (for example: "make the hero title larger and dark blue"). If the user does not see the style controls described below, their project is a newer one -- point them to the chat.
16
+
17
+ On projects that have them, the side panel organizes style controls into these categories:
16
18
 
17
19
  - **Typography** -- Font family, font size, font weight, line height, letter spacing, text alignment, and bold/italic/underline/strikethrough toggles.
18
20
  - **Color** -- Text and foreground colors.
@@ -31,5 +33,5 @@ Click the **Edit mode** toggle again or close the side panel. The side editor cl
31
33
 
32
34
  ## When to Use Edit Mode vs. Chat
33
35
 
34
- - **Edit mode** is best for quick visual tweaks -- changing a color, adjusting spacing, updating text, or trying a different font.
35
- - **Chat** is better for bigger changes -- adding new sections, restructuring a page layout, or building new functionality.
36
+ - **Edit mode** is best for content changes -- updating text (including bold, italic, underline, and links), and replacing images. On older projects it also covers quick style tweaks like colors and spacing.
37
+ - **Chat** is better for style and structural changes -- changing colors or spacing (on newer projects), adding new sections, restructuring a page layout, or building new functionality.
@@ -0,0 +1,42 @@
1
+ # Webhooks
2
+
3
+ > **Beta:** Test each destination and review its delivery history before depending on it in production.
4
+
5
+ Webhooks send project activity to an external HTTPS endpoint. Use them for focused reactions such as adding a waitlist submission to an email tool, notifying Slack after a publish, or starting a GitHub workflow after an agent review.
6
+
7
+ Only project members with Full Access can see Webhooks in Settings or manage its destinations and delivery history.
8
+
9
+ ## Create one
10
+
11
+ 1. Open the project and choose **Settings**.
12
+ 2. Select **Webhooks**.
13
+ 3. Choose **New webhook**.
14
+ 4. Name it, select one event, and enter the destination URL.
15
+ 5. Optionally narrow form, agent, media, or entry events with the filter shown for that event.
16
+ 6. Review the request and select **Save**.
17
+
18
+ The event groups are Entries, Forms, Publish, Agents, Analytics, and Media. Webhooks belong to the project, with up to 20 configurations. They listen to main by default or can include every environment.
19
+
20
+ Entry webhooks show **Combine rapid changes**. The default waits 5 seconds after direct editing stops and sends one delivery with the combined changes. Immediate, 30-second, and 1-minute windows are also available. Bulk edits and environment merges already produce batches and send immediately.
21
+
22
+ ## Configure the request
23
+
24
+ The default payload is the complete Fimo event, including its type, project, environment, timestamp, and event-specific data. Keep this when the destination can accept it.
25
+
26
+ Choose **Custom template** when a service expects a specific JSON shape. Use **Insert variable** to pick fields for the selected event. Variables use paths such as `{{ event.data.submission.data.email }}`. The preview catches invalid JSON and missing variables before save.
27
+
28
+ When Studio recognizes a Resend, Slack, or GitHub URL, it offers a request setup for that service. The suggestion never changes the request automatically. Accept it to prefill relevant headers and JSON, then review every field.
29
+
30
+ ## Add authentication
31
+
32
+ Under **Headers**, add a header name and choose its value type. Prefer a project secret for API keys and tokens; choose whether the destination needs the raw secret, a `Bearer ` prefix, or a `token ` prefix. Fixed values are suitable only for non-sensitive headers.
33
+
34
+ Fimo signs requests by default. Save the signing secret shown after creation so a custom receiver can verify `X-Fimo-Signature`. Rotate it from the webhook configuration if it is exposed.
35
+
36
+ ## Test and monitor
37
+
38
+ Use **Send test** before depending on a workflow. Tests use documented sample data and travel through the same delivery path as real events.
39
+
40
+ Open **Deliveries** to see the event ID, masked destination, request metadata and payload excerpt, queue message ID, status, HTTP response, duration, and individual attempts. Fimo automatically retries temporary failures. After all automatic retries are exhausted, open the failed delivery and choose **Retry now**. A delivery accepts up to five manual retries.
41
+
42
+ Destination credentials are masked in configuration and delivery details. A successful test means the destination returned a successful HTTP response; it does not necessarily mean the provider completed its downstream action, so check the provider when that distinction matters.
@@ -1,11 +1,5 @@
1
1
  import type { Plugin } from 'vite';
2
- interface LabelBundle {
3
- dictionaries: Record<string, Record<string, string>>;
4
- defaultLocale: string;
5
- locales: string[];
6
- updatedAt: string | null;
7
- }
2
+ import { loadLabelBundle } from '../../../runtime/shared/labels-fetch.server.js';
8
3
  export default function translationsPlugin(): Plugin;
9
- export declare function loadLabelBundle(rootDir: string): Promise<LabelBundle>;
10
- export {};
4
+ export { loadLabelBundle };
11
5
  //# sourceMappingURL=translations.d.ts.map
@@ -1,10 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { Buffer } from 'node:buffer';
3
2
  import { readFile } from 'node:fs/promises';
4
3
  import { homedir } from 'node:os';
5
4
  import { join } from 'node:path';
6
- import { getFimoDefaultLocale, getFimoLocales } from '../../../runtime/i18n/locale.js';
7
- import { getConfigServer } from '../../../runtime/shared/fimo-config.server.js';
5
+ import { emptyLabelBundle, labelSignature, loadLabelBundle, readProjectEnv, resolveTenantApiUrl, serializeBundle, } from '../../../runtime/shared/labels-fetch.server.js';
8
6
  const VIRTUAL_ID = 'virtual:translations';
9
7
  const RESOLVED_ID = '\0' + VIRTUAL_ID;
10
8
  export default function translationsPlugin() {
@@ -16,6 +14,12 @@ export default function translationsPlugin() {
16
14
  let reconnectTimer;
17
15
  let refreshTimer;
18
16
  let pollTimer;
17
+ // Set on closeBundle. Without it, closing a socket fires its own onclose,
18
+ // which schedules a reconnect AFTER the cleanup ran — resurrecting the
19
+ // event sockets forever and pinning the event loop. That is exactly how a
20
+ // prerender script using `createServer({ middlewareMode: true })` finished
21
+ // its work and then never exited.
22
+ let disposed = false;
19
23
  const refreshLabels = async () => {
20
24
  bundle = await loadLabelBundle(rootDir);
21
25
  return bundle;
@@ -51,6 +55,9 @@ export default function translationsPlugin() {
51
55
  });
52
56
  };
53
57
  const scheduleRefresh = () => {
58
+ if (disposed) {
59
+ return;
60
+ }
54
61
  if (refreshTimer) {
55
62
  clearTimeout(refreshTimer);
56
63
  }
@@ -58,6 +65,17 @@ export default function translationsPlugin() {
58
65
  void refreshLabelsAndInvalidateIfChanged().catch(() => undefined);
59
66
  }, 150);
60
67
  };
68
+ const adoptEventSockets = (sockets) => {
69
+ if (disposed) {
70
+ // The server closed while the async connect was in flight — sockets
71
+ // landing now must close immediately or they outlive the cleanup.
72
+ for (const socket of sockets) {
73
+ socket.close();
74
+ }
75
+ return;
76
+ }
77
+ eventSockets = sockets;
78
+ };
61
79
  return {
62
80
  name: 'fimo-virtual-translations',
63
81
  enforce: 'pre',
@@ -78,11 +96,7 @@ export default function translationsPlugin() {
78
96
  devServer = server;
79
97
  server.watcher.add([join(rootDir, 'fimo-config.json'), join(rootDir, '.env'), join(rootDir, '.env.local')]);
80
98
  void refreshLabelsAndInvalidateIfChanged().catch(() => undefined);
81
- connectLabelEvents(rootDir, scheduleRefresh)
82
- .then((sockets) => {
83
- eventSockets = sockets;
84
- })
85
- .catch(() => undefined);
99
+ connectLabelEvents(rootDir, scheduleRefresh).then(adoptEventSockets).catch(() => undefined);
86
100
  pollTimer = setInterval(() => {
87
101
  void refreshLabelsAndInvalidateIfChanged().catch(() => undefined);
88
102
  }, 2000);
@@ -128,6 +142,9 @@ export default function translationsPlugin() {
128
142
  return [];
129
143
  },
130
144
  closeBundle() {
145
+ // Order matters: dispose FIRST so the onclose handlers fired by the
146
+ // socket closes below (and any in-flight connect) cannot reschedule.
147
+ disposed = true;
131
148
  for (const socket of eventSockets) {
132
149
  socket.close();
133
150
  }
@@ -145,7 +162,7 @@ export default function translationsPlugin() {
145
162
  };
146
163
  async function connectLabelEvents(root, onLabelsChanged) {
147
164
  const env = await readProjectEnv(root);
148
- const apiUrl = env.VITE_API_URL;
165
+ const apiUrl = resolveTenantApiUrl(env);
149
166
  if (!apiUrl) {
150
167
  return [];
151
168
  }
@@ -194,12 +211,11 @@ export default function translationsPlugin() {
194
211
  }
195
212
  };
196
213
  socket.onclose = () => {
214
+ if (disposed) {
215
+ return;
216
+ }
197
217
  reconnectTimer = setTimeout(() => {
198
- void connectLabelEvents(root, onLabelsChanged)
199
- .then((nextSockets) => {
200
- eventSockets = nextSockets;
201
- })
202
- .catch(() => undefined);
218
+ void connectLabelEvents(root, onLabelsChanged).then(adoptEventSockets).catch(() => undefined);
203
219
  }, 2000);
204
220
  };
205
221
  socket.onerror = () => undefined;
@@ -229,24 +245,6 @@ function parseContentChangePayload(payload) {
229
245
  updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : undefined,
230
246
  };
231
247
  }
232
- function serializeBundle(bundle) {
233
- return JSON.stringify({
234
- dictionaries: bundle.dictionaries,
235
- defaultLocale: bundle.defaultLocale,
236
- locales: bundle.locales,
237
- });
238
- }
239
- function emptyLabelBundle(rootDir) {
240
- const config = getConfigServer(rootDir);
241
- const defaultLocale = getFimoDefaultLocale(config);
242
- const locales = getFimoLocales(config);
243
- return {
244
- dictionaries: Object.fromEntries(locales.map((locale) => [locale, {}])),
245
- defaultLocale,
246
- locales,
247
- updatedAt: null,
248
- };
249
- }
250
248
  function findVirtualTranslationModules(server) {
251
249
  const direct = server.moduleGraph.getModuleById(RESOLVED_ID);
252
250
  const matches = new Set(direct ? [direct] : []);
@@ -257,73 +255,13 @@ function findVirtualTranslationModules(server) {
257
255
  }
258
256
  return [...matches];
259
257
  }
260
- function labelSignature(bundle) {
261
- return Buffer.from(serializeBundle(bundle)).toString('base64');
262
- }
263
258
  function labelUpdatePayload(bundle) {
264
259
  return {
265
260
  ...bundle,
266
261
  signature: labelSignature(bundle),
267
262
  };
268
263
  }
269
- export async function loadLabelBundle(rootDir) {
270
- const config = getConfigServer(rootDir);
271
- const defaultLocale = getFimoDefaultLocale(config);
272
- const locales = getFimoLocales(config);
273
- const env = await readProjectEnv(rootDir);
274
- const apiUrl = env.VITE_API_URL;
275
- if (!apiUrl) {
276
- return {
277
- dictionaries: Object.fromEntries(locales.map((locale) => [locale, {}])),
278
- defaultLocale,
279
- locales,
280
- updatedAt: null,
281
- };
282
- }
283
- try {
284
- const loaded = await Promise.all(locales.map((locale) => fetchLabelDictionary(apiUrl, locale).catch(() => ({ locale, labels: {}, updatedAt: null }))));
285
- const updatedAt = loaded
286
- .map((entry) => entry.updatedAt)
287
- .filter((value) => typeof value === 'string')
288
- .sort()
289
- .at(-1);
290
- return {
291
- dictionaries: Object.fromEntries(loaded.map((entry) => [entry.locale, entry.labels])),
292
- defaultLocale,
293
- locales,
294
- updatedAt: updatedAt ?? null,
295
- };
296
- }
297
- catch {
298
- return {
299
- dictionaries: Object.fromEntries(locales.map((locale) => [locale, {}])),
300
- defaultLocale,
301
- locales,
302
- updatedAt: null,
303
- };
304
- }
305
- }
306
- async function fetchLabelDictionary(apiUrl, locale) {
307
- const url = new URL('/labels', apiUrl);
308
- url.searchParams.set('locale', locale);
309
- const response = await fetchWithTimeout(url, 4000);
310
- if (!response.ok) {
311
- return { locale, labels: {}, updatedAt: null };
312
- }
313
- const body = (await response.json());
314
- if (Array.isArray(body.data)) {
315
- return {
316
- locale,
317
- labels: Object.fromEntries(body.data.map((row) => [row.key, row.value ?? row.defaultValue ?? ''])),
318
- updatedAt: null,
319
- };
320
- }
321
- return {
322
- locale,
323
- labels: body.data?.labels ?? {},
324
- updatedAt: body.data?.updatedAt ?? null,
325
- };
326
- }
264
+ export { loadLabelBundle };
327
265
  async function loadRuntimeConfig(apiUrl) {
328
266
  try {
329
267
  const response = await fetchWithTimeout(new URL('/runtime-config', apiUrl), 2000);
@@ -358,44 +296,6 @@ async function fetchWithTimeout(url, timeoutMs) {
358
296
  clearTimeout(timeout);
359
297
  }
360
298
  }
361
- async function readProjectEnv(rootDir) {
362
- const values = {
363
- ...(await readEnvFile(join(rootDir, '.env'))),
364
- ...(await readEnvFile(join(rootDir, '.env.local'))),
365
- };
366
- for (const key of ['VITE_API_URL']) {
367
- if (process.env[key]) {
368
- values[key] = process.env[key];
369
- }
370
- }
371
- return values;
372
- }
373
- async function readEnvFile(filePath) {
374
- try {
375
- const content = await readFile(filePath, 'utf8');
376
- const values = {};
377
- for (const line of content.split(/\r?\n/)) {
378
- const trimmed = line.trim();
379
- if (!trimmed || trimmed.startsWith('#')) {
380
- continue;
381
- }
382
- const index = trimmed.indexOf('=');
383
- if (index === -1) {
384
- continue;
385
- }
386
- const key = trimmed.slice(0, index).trim();
387
- const value = trimmed
388
- .slice(index + 1)
389
- .trim()
390
- .replace(/^['"]|['"]$/g, '');
391
- values[key] = value;
392
- }
393
- return values;
394
- }
395
- catch {
396
- return {};
397
- }
398
- }
399
299
  async function readProjectSettings(rootDir) {
400
300
  try {
401
301
  const raw = await readFile(join(rootDir, '.fimo.settings.json'), 'utf8');
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-07-23T08:29:03.159Z",
4
+ "bundledAt": "2026-07-29T15:37:14.073Z",
5
5
  "cliVersion": "0.8.0",
6
6
  "external": [
7
7
  "oxc-parser",