fimo 0.14.0 → 0.15.0-staging.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 (199) hide show
  1. package/README.md +1 -0
  2. package/assets/astro/components/Boolean.astro +14 -0
  3. package/assets/astro/components/Date.astro +23 -0
  4. package/assets/astro/components/Image.astro +79 -0
  5. package/assets/astro/components/Json.astro +14 -0
  6. package/assets/astro/components/RichText.astro +17 -0
  7. package/assets/astro/components/StaticImage.astro +66 -0
  8. package/assets/astro/components/Text.astro +23 -0
  9. package/assets/astro/components/Video.astro +47 -0
  10. package/assets/astro/components/index.d.ts +8 -0
  11. package/assets/astro/components/index.js +8 -0
  12. package/assets/content-templates/shared/server.eta +56 -0
  13. package/assets/content-templates/sveltekit/content.eta +86 -0
  14. package/assets/skills/fimo/SKILL.md +13 -4
  15. package/assets/skills/fimo-cli/SKILL.md +28 -28
  16. package/assets/skills/fimo-cli/references/account.md +0 -2
  17. package/assets/skills/fimo-cli/references/agent-setup.md +7 -9
  18. package/assets/skills/fimo-cli/references/agents.md +0 -2
  19. package/assets/skills/fimo-cli/references/analytics.md +0 -2
  20. package/assets/skills/fimo-cli/references/assets.md +2 -4
  21. package/assets/skills/fimo-cli/references/branches.md +14 -15
  22. package/assets/skills/fimo-cli/references/content.md +9 -11
  23. package/assets/skills/fimo-cli/references/credits.md +0 -2
  24. package/assets/skills/fimo-cli/references/deploy.md +6 -7
  25. package/assets/skills/fimo-cli/references/describe.md +1 -3
  26. package/assets/skills/fimo-cli/references/domains.md +0 -2
  27. package/assets/skills/fimo-cli/references/forms.md +5 -7
  28. package/assets/skills/fimo-cli/references/history.md +0 -2
  29. package/assets/skills/fimo-cli/references/init.md +34 -26
  30. package/assets/skills/fimo-cli/references/integrations/firecrawl.md +0 -2
  31. package/assets/skills/fimo-cli/references/integrations.md +0 -2
  32. package/assets/skills/fimo-cli/references/invite.md +0 -2
  33. package/assets/skills/fimo-cli/references/labels.md +5 -7
  34. package/assets/skills/fimo-cli/references/locales.md +2 -4
  35. package/assets/skills/fimo-cli/references/mcp.md +2 -4
  36. package/assets/skills/fimo-cli/references/media.md +0 -2
  37. package/assets/skills/fimo-cli/references/preview.md +0 -2
  38. package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +1 -3
  39. package/assets/skills/fimo-cli/references/projects.md +1 -3
  40. package/assets/skills/fimo-cli/references/schedules.md +0 -2
  41. package/assets/skills/fimo-cli/references/support.md +0 -2
  42. package/assets/skills/fimo-cli/references/webhooks.md +0 -2
  43. package/assets/skills/fimo-code/SKILL.md +11 -3
  44. package/assets/skills/fimo-code/references/assets.md +2 -2
  45. package/assets/skills/fimo-code/references/content/clients.md +5 -1
  46. package/assets/skills/fimo-code/references/content.md +3 -1
  47. package/assets/skills/fimo-code/references/frameworks/astro.md +127 -0
  48. package/assets/skills/fimo-code/references/frameworks/compatibility.md +19 -0
  49. package/assets/skills/fimo-code/references/frameworks/hosting.md +15 -3
  50. package/assets/skills/fimo-code/references/frameworks/react-router.md +37 -209
  51. package/assets/skills/fimo-code/references/frameworks/sveltekit.md +110 -0
  52. package/assets/skills/fimo-code/references/frameworks/vite-react.md +35 -166
  53. package/assets/skills/fimo-code/references/labels.md +16 -6
  54. package/assets/skills/fimo-code/references/locales.md +3 -1
  55. package/assets/skills/fimo-code/references/ui.md +19 -15
  56. package/assets/skills/fimo-migration/SKILL.md +99 -146
  57. package/assets/skills/fimo-migration/references/frameworks/nextjs.md +26 -0
  58. package/assets/skills/fimo-migration/references/inventory/coordinator.md +40 -0
  59. package/assets/skills/fimo-migration/references/inventory/locales/next-intl.md +39 -0
  60. package/assets/skills/fimo-migration/references/inventory/locales.md +41 -0
  61. package/assets/skills/fimo-migration/references/workflow.md +3 -2
  62. package/assets/skills/fimo-migration/scripts/inventory/locales/next-intl.mjs +459 -0
  63. package/assets/skills/fimo-studio/SKILL.md +1 -1
  64. package/assets/skills/fimo-studio/references/project-creation.md +10 -18
  65. package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +0 -1
  66. package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +0 -1
  67. package/assets/sveltekit/components/Boolean.svelte +19 -0
  68. package/assets/sveltekit/components/Date.svelte +33 -0
  69. package/assets/sveltekit/components/FimoProvider.svelte +35 -0
  70. package/assets/sveltekit/components/Image.svelte +80 -0
  71. package/assets/sveltekit/components/Json.svelte +18 -0
  72. package/assets/sveltekit/components/Label.svelte +16 -0
  73. package/assets/sveltekit/components/Number.svelte +21 -0
  74. package/assets/sveltekit/components/RichText.svelte +19 -0
  75. package/assets/sveltekit/components/Text.svelte +30 -0
  76. package/assets/sveltekit/components/Video.svelte +37 -0
  77. package/assets/sveltekit/components/index.d.ts +10 -0
  78. package/assets/sveltekit/components/index.js +10 -0
  79. package/dist/build/astro/config.d.ts +4 -0
  80. package/dist/build/astro/config.js +64 -0
  81. package/dist/build/next/config.js +16 -0
  82. package/dist/build/sveltekit/config.d.ts +6 -0
  83. package/dist/build/sveltekit/config.js +8 -0
  84. package/dist/build/sveltekit/data-id.d.ts +3 -0
  85. package/dist/build/sveltekit/data-id.js +171 -0
  86. package/dist/build/sveltekit/vite.d.ts +13 -0
  87. package/dist/build/sveltekit/vite.js +15 -0
  88. package/dist/build/vite/index.js +2 -0
  89. package/dist/build/vite/plugins/data-id.d.ts +9 -0
  90. package/dist/build/vite/plugins/data-id.js +6 -1
  91. package/dist/build/vite/plugins/runtime-env.d.ts +21 -0
  92. package/dist/build/vite/plugins/runtime-env.js +68 -0
  93. package/dist/cli/bundle.json +1 -1
  94. package/dist/cli/index.js +108 -101
  95. package/dist/runtime/app/FimoProviders.d.ts +1 -1
  96. package/dist/runtime/app/FimoProviders.js +11 -5
  97. package/dist/runtime/astro/internal.d.ts +6 -0
  98. package/dist/runtime/astro/internal.js +16 -0
  99. package/dist/runtime/astro/preview.d.ts +14 -0
  100. package/dist/runtime/astro/preview.js +103 -0
  101. package/dist/runtime/astro/render-rich-text.d.ts +2 -0
  102. package/dist/runtime/astro/render-rich-text.js +1 -0
  103. package/dist/runtime/astro/server-runtime.d.ts +3 -0
  104. package/dist/runtime/astro/server-runtime.js +20 -0
  105. package/dist/runtime/astro/server.d.ts +15 -0
  106. package/dist/runtime/astro/server.js +14 -0
  107. package/dist/runtime/content/client/collection-core.d.ts +2 -17
  108. package/dist/runtime/content/client/collection-core.js +15 -8
  109. package/dist/runtime/content/client/core-types.d.ts +96 -0
  110. package/dist/runtime/content/client/core-types.js +1 -0
  111. package/dist/runtime/content/client/internal.d.ts +4 -4
  112. package/dist/runtime/content/client/internal.js +4 -3
  113. package/dist/runtime/content/client/runtime.d.ts +1 -1
  114. package/dist/runtime/content/client/server-content-client.d.ts +3 -3
  115. package/dist/runtime/content/client/server-content-client.js +2 -29
  116. package/dist/runtime/content/client/singleton-core.d.ts +2 -9
  117. package/dist/runtime/content/client/singleton-core.js +9 -4
  118. package/dist/runtime/content/client/types.d.ts +5 -115
  119. package/dist/runtime/content/index.d.ts +3 -3
  120. package/dist/runtime/labels/fetch.js +5 -3
  121. package/dist/runtime/next/client.d.ts +2 -1
  122. package/dist/runtime/next/facade.d.ts +1 -1
  123. package/dist/runtime/next/provider.js +1 -1
  124. package/dist/runtime/next/server-runtime.js +1 -1
  125. package/dist/runtime/next/server.d.ts +5 -4
  126. package/dist/runtime/paths/get-fimo-paths.js +8 -3
  127. package/dist/runtime/preview/bridge.d.ts +8 -0
  128. package/dist/runtime/preview/bridge.js +20 -0
  129. package/dist/runtime/preview/location.d.ts +23 -0
  130. package/dist/runtime/preview/location.js +93 -0
  131. package/dist/runtime/preview/routes.d.ts +10 -0
  132. package/dist/runtime/preview/routes.js +29 -0
  133. package/dist/runtime/primitives/index.d.ts +4 -1
  134. package/dist/runtime/primitives/index.js +1 -0
  135. package/dist/runtime/primitives/lib/image-optimization.d.ts +27 -2
  136. package/dist/runtime/primitives/lib/image-optimization.js +40 -0
  137. package/dist/runtime/primitives/lib/index.d.ts +1 -1
  138. package/dist/runtime/primitives/lib/primitives.d.ts +0 -54
  139. package/dist/runtime/primitives/lib/primitives.js +6 -0
  140. package/dist/runtime/primitives/lib/render-rich-text.d.ts +3 -0
  141. package/dist/runtime/primitives/lib/render-rich-text.js +112 -0
  142. package/dist/runtime/react/primitives/contracts.d.ts +40 -2
  143. package/dist/runtime/react/primitives/renderers/ImageRenderer.js +15 -34
  144. package/dist/runtime/react/primitives/renderers/RichTextRenderer.d.ts +2 -1
  145. package/dist/runtime/react/primitives/renderers/StaticImage.js +13 -30
  146. package/dist/runtime/routing/AppRoutesProvider.js +20 -8
  147. package/dist/runtime/shared/env-identity.d.ts +43 -0
  148. package/dist/runtime/shared/env-identity.js +88 -0
  149. package/dist/runtime/shared/local-runtime-env.d.ts +65 -0
  150. package/dist/runtime/shared/local-runtime-env.js +169 -0
  151. package/dist/runtime/{react-router → shared}/preview-location.d.ts +7 -13
  152. package/dist/runtime/{react-router → shared}/preview-location.js +26 -27
  153. package/dist/runtime/sveltekit/content.d.ts +25 -0
  154. package/dist/runtime/sveltekit/content.js +33 -0
  155. package/dist/runtime/sveltekit/context-contract.d.ts +5 -0
  156. package/dist/runtime/sveltekit/context-contract.js +2 -0
  157. package/dist/runtime/sveltekit/context.d.ts +8 -0
  158. package/dist/runtime/sveltekit/context.js +25 -0
  159. package/dist/runtime/sveltekit/index.d.ts +7 -0
  160. package/dist/runtime/sveltekit/index.js +4 -0
  161. package/dist/runtime/sveltekit/internal.d.ts +6 -0
  162. package/dist/runtime/sveltekit/internal.js +12 -0
  163. package/dist/runtime/sveltekit/labels.d.ts +16 -0
  164. package/dist/runtime/sveltekit/labels.js +13 -0
  165. package/dist/runtime/sveltekit/preview.d.ts +19 -0
  166. package/dist/runtime/sveltekit/preview.js +77 -0
  167. package/dist/runtime/sveltekit/runtime.d.ts +8 -0
  168. package/dist/runtime/sveltekit/runtime.js +17 -0
  169. package/dist/runtime/templates.d.ts +1 -1
  170. package/dist/runtime/templates.js +1 -1
  171. package/dist/runtime/vite/Label.d.ts +13 -0
  172. package/dist/runtime/vite/Label.js +16 -0
  173. package/dist/scripts/extract-translations.js +103 -43
  174. package/dist/scripts/sync-forms.js +1 -1
  175. package/dist/scripts/sync-schemas.js +83 -16
  176. package/package.json +57 -2
  177. package/release.json +2 -2
  178. package/templates/nextjs/AGENTS.md +10 -5
  179. package/templates/nextjs/_gitignore +1 -0
  180. package/templates/react-router/AGENTS.md +4 -3
  181. package/templates/react-router/package.json +1 -1
  182. package/templates/sveltekit/.fimo/config.json +15 -0
  183. package/templates/sveltekit/AGENTS.md +36 -0
  184. package/templates/sveltekit/CLAUDE.md +5 -0
  185. package/templates/sveltekit/_gitignore +16 -0
  186. package/templates/sveltekit/package.json +28 -0
  187. package/templates/sveltekit/pnpm-workspace.yaml +4 -0
  188. package/templates/sveltekit/src/app.html +11 -0
  189. package/templates/sveltekit/src/routes/+layout.server.ts +8 -0
  190. package/templates/sveltekit/src/routes/+layout.svelte +12 -0
  191. package/templates/sveltekit/src/routes/+page.svelte +48 -0
  192. package/templates/sveltekit/svelte.config.js +5 -0
  193. package/templates/sveltekit/tsconfig.json +14 -0
  194. package/templates/sveltekit/vite.config.ts +5 -0
  195. /package/assets/content-templates/{form-template.eta → forms/react.eta} +0 -0
  196. /package/assets/content-templates/{next-client-template.eta → next/client.eta} +0 -0
  197. /package/assets/content-templates/{next-server-template.eta → next/server.eta} +0 -0
  198. /package/assets/content-templates/{hooks-template.eta → react/client.eta} +0 -0
  199. /package/assets/content-templates/{types-template.eta → shared/types.eta} +0 -0
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Which Fimo env a local checkout is on, and how to reach it — derived, not
3
+ * stored.
4
+ *
5
+ * The env is the git branch. The project id and tenant API base URL sit in
6
+ * `.fimo/project.json`, written once at link and never on a switch. The tenant
7
+ * URL for the current env is a pure function of the three, so nothing has to
8
+ * be rewritten when the branch changes: `git checkout` alone is a complete env
9
+ * switch, offline.
10
+ *
11
+ * This module only DERIVES. Precedence against explicit values is the caller's
12
+ * job and is the same everywhere: a variable the user (or the sandbox) already
13
+ * set is left alone; derivation fills the rest, one variable at a time.
14
+ *
15
+ * Shared by the CLI, `fimo/vite`, and `fimo/next/config` so the three can
16
+ * never disagree about which env the app is calling.
17
+ */
18
+ import { execFileSync } from 'node:child_process';
19
+ import fs from 'node:fs';
20
+ import path from 'node:path';
21
+ import { CURRENT_PROJECT_LAYOUT, LEGACY_PROJECT_LAYOUT } from '../project-layout.js';
22
+ import { DEFAULT_ENV, LOCAL_CONTENT_API_ENV_KEYS, buildLocalRuntimeEnv, buildTenantApiUrl, isValidEnv, } from './env-identity.js';
23
+ /**
24
+ * Derive the local runtime env, or `null` when this directory is not a linked
25
+ * Fimo project (or the link predates `tenantApiBaseUrl` — nothing safe to
26
+ * derive, so nothing is).
27
+ */
28
+ export function resolveLocalRuntimeEnv(options) {
29
+ const root = findProjectRoot(options.cwd);
30
+ const link = root ? readProjectLink(root) : null;
31
+ if (!root || !link) {
32
+ return null;
33
+ }
34
+ const branch = currentBranch(root);
35
+ const env = branch ?? DEFAULT_ENV;
36
+ const tenantApiUrl = buildTenantApiUrl(link.projectId, env, link.tenantApiBaseUrl);
37
+ return {
38
+ env,
39
+ tenantApiUrl,
40
+ source: branch ? 'branch' : 'fallback',
41
+ values: buildLocalRuntimeEnv({ tenantApiUrl, env }),
42
+ link,
43
+ };
44
+ }
45
+ /**
46
+ * The one case where "explicit wins" pins the app to the WRONG env: an
47
+ * explicit tenant URL that Fimo itself minted for this project (old-CLI
48
+ * `fimo clone` seeded the clone-time env's URL into `.env`) and that disagrees
49
+ * with the branch. A URL the user typed — `http://localhost:4000` — is never
50
+ * reported; that is the override the design exists to protect.
51
+ */
52
+ export function findStaleFimoOverride(explicit, derived) {
53
+ for (const key of LOCAL_CONTENT_API_ENV_KEYS) {
54
+ const value = explicit[key];
55
+ if (value && value !== derived.tenantApiUrl && isFimoMintedTenantUrl(value, derived.link)) {
56
+ return { key, explicit: value, derived: derived.tenantApiUrl };
57
+ }
58
+ }
59
+ return null;
60
+ }
61
+ /** One message for every surface (dev-server console, `fimo checkout`) so the fix reads the same everywhere. */
62
+ export function staleOverrideMessage(stale) {
63
+ return (`[fimo] An env file pins the app to ${stale.explicit} (seeded by an older CLI), ` +
64
+ `so it will NOT follow the git branch (${stale.derived}). ` +
65
+ 'Run `fimo link` to remove that line, or delete it yourself.');
66
+ }
67
+ /** Whether `value` is a tenant URL Fimo minted for `link`'s project — for any env. */
68
+ export function isFimoMintedTenantUrl(value, link) {
69
+ try {
70
+ const candidate = new URL(value);
71
+ const base = new URL(link.tenantApiBaseUrl);
72
+ return (candidate.protocol === base.protocol &&
73
+ candidate.port === base.port &&
74
+ candidate.hostname.startsWith(`${link.projectId}-`) &&
75
+ candidate.hostname.endsWith(`.${base.hostname}`));
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * `.env` then `.env.local` (later wins), the way every supported framework
83
+ * layers them. For CLI commands, which — unlike Vite and Next — do not load
84
+ * these files themselves but must see the same explicit values the app will.
85
+ */
86
+ export function readLocalEnvFiles(cwd) {
87
+ const root = findProjectRoot(cwd) ?? cwd;
88
+ const values = {};
89
+ for (const file of ['.env', '.env.local']) {
90
+ const filePath = path.join(root, file);
91
+ if (!fs.existsSync(filePath)) {
92
+ continue;
93
+ }
94
+ for (const line of fs.readFileSync(filePath, 'utf-8').split(/\r?\n/)) {
95
+ const trimmed = line.trim();
96
+ if (!trimmed || trimmed.startsWith('#')) {
97
+ continue;
98
+ }
99
+ const eq = trimmed.indexOf('=');
100
+ if (eq <= 0) {
101
+ continue;
102
+ }
103
+ values[trimmed.slice(0, eq).trim()] = trimmed
104
+ .slice(eq + 1)
105
+ .trim()
106
+ .replace(/^['"]|['"]$/g, '');
107
+ }
108
+ }
109
+ return values;
110
+ }
111
+ /**
112
+ * The branch, when it names a valid env; otherwise `null` (detached HEAD, no
113
+ * repo, no git). Tags are deliberately ignored: the CLI maps a `release/*` tag
114
+ * to `prod` so `fimo status` on a checked-out release reads as read-only, but a
115
+ * dev server is never run to look at prod — locally you always develop toward
116
+ * a branch.
117
+ *
118
+ * Synchronous on purpose — this runs inside framework config hooks at startup,
119
+ * where a spawn of `git` is cheap and an async boundary is not always
120
+ * available.
121
+ */
122
+ function currentBranch(root) {
123
+ try {
124
+ const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
125
+ cwd: root,
126
+ stdio: ['ignore', 'pipe', 'ignore'],
127
+ encoding: 'utf-8',
128
+ }).trim();
129
+ return branch && branch !== 'HEAD' && isValidEnv(branch) ? branch : null;
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
135
+ function findProjectRoot(start) {
136
+ let dir = path.resolve(start);
137
+ for (;;) {
138
+ if (fs.existsSync(path.join(dir, CURRENT_PROJECT_LAYOUT.project)) ||
139
+ fs.existsSync(path.join(dir, LEGACY_PROJECT_LAYOUT.project))) {
140
+ return dir;
141
+ }
142
+ const parent = path.dirname(dir);
143
+ if (parent === dir) {
144
+ return null;
145
+ }
146
+ dir = parent;
147
+ }
148
+ }
149
+ function readProjectLink(root) {
150
+ for (const layout of [CURRENT_PROJECT_LAYOUT, LEGACY_PROJECT_LAYOUT]) {
151
+ const file = path.join(root, layout.project);
152
+ if (!fs.existsSync(file)) {
153
+ continue;
154
+ }
155
+ try {
156
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
157
+ if (typeof parsed.projectId === 'string' &&
158
+ parsed.projectId.length > 0 &&
159
+ typeof parsed.tenantApiBaseUrl === 'string' &&
160
+ parsed.tenantApiBaseUrl.length > 0) {
161
+ return { projectId: parsed.projectId, tenantApiBaseUrl: parsed.tenantApiBaseUrl };
162
+ }
163
+ }
164
+ catch {
165
+ return null;
166
+ }
167
+ }
168
+ return null;
169
+ }
@@ -9,20 +9,14 @@ export interface PreviewLocationChange {
9
9
  /** Exact app location used to keep the parent's iframe URL in sync. */
10
10
  navigationPath: string;
11
11
  }
12
- /**
13
- * `ancestorOrigins[0]` identifies the immediate embedding document and stays
14
- * stable when the preview document itself navigates or reloads. The referrer
15
- * is the fallback for browsers without ancestor origins (Firefox), where it
16
- * can go stale after in-app navigation — which is why the runtime wrapper
17
- * below remembers the first successful resolution.
18
- */
12
+ export interface PreviewBrowserContext {
13
+ readonly window: Pick<Window, 'location' | 'sessionStorage'>;
14
+ readonly document: Pick<Document, 'referrer'>;
15
+ }
16
+ /** Prefer the immediate embedding document over a potentially stale referrer. */
19
17
  export declare function resolvePreviewParentOrigin(referrer: string | undefined, ancestorOrigins: readonly string[] | undefined): string | null;
20
- /**
21
- * Resolve the parent origin for the iframe's authenticated preview channel
22
- * from the live browser context. Standalone app loads have no trusted parent
23
- * origin and must not receive query/hash-bearing location data.
24
- */
25
- export declare function getPreviewParentOrigin(): string | null;
18
+ /** Resolve and remember the trusted Studio origin across full-page navigation. */
19
+ export declare function getPreviewParentOrigin(browser?: PreviewBrowserContext): string | null;
26
20
  /** Keep sandbox credentials and the iframe cache-buster out of app navigation state. */
27
21
  export declare function getPreviewLocationPath(location: PreviewLocationLike): string;
28
22
  export declare function getPreviewLocationChange(location: PreviewLocationLike): PreviewLocationChange;
@@ -13,65 +13,64 @@ const getOrigin = (value) => {
13
13
  return null;
14
14
  }
15
15
  };
16
- /**
17
- * `ancestorOrigins[0]` identifies the immediate embedding document and stays
18
- * stable when the preview document itself navigates or reloads. The referrer
19
- * is the fallback for browsers without ancestor origins (Firefox), where it
20
- * can go stale after in-app navigation — which is why the runtime wrapper
21
- * below remembers the first successful resolution.
22
- */
16
+ /** Prefer the immediate embedding document over a potentially stale referrer. */
23
17
  export function resolvePreviewParentOrigin(referrer, ancestorOrigins) {
24
18
  return getOrigin(ancestorOrigins?.[0]) ?? getOrigin(referrer);
25
19
  }
26
- /**
27
- * Resolve the parent origin for the iframe's authenticated preview channel
28
- * from the live browser context. Standalone app loads have no trusted parent
29
- * origin and must not receive query/hash-bearing location data.
30
- */
31
- export function getPreviewParentOrigin() {
32
- if (typeof window === 'undefined') {
20
+ /** Resolve and remember the trusted Studio origin across full-page navigation. */
21
+ export function getPreviewParentOrigin(browser) {
22
+ const browserWindow = browser?.window ?? (typeof window === 'undefined' ? undefined : window);
23
+ const browserDocument = browser?.document ?? (typeof document === 'undefined' ? undefined : document);
24
+ if (!browserWindow) {
33
25
  return null;
34
26
  }
35
- const ancestorOrigin = getOrigin(window.location.ancestorOrigins?.[0]);
27
+ const storage = readSessionStorage(browserWindow);
28
+ const ancestorOrigin = getOrigin(browserWindow.location.ancestorOrigins?.[0]);
36
29
  if (ancestorOrigin) {
37
- rememberPreviewParentOrigin(ancestorOrigin);
30
+ rememberPreviewParentOrigin(ancestorOrigin, storage);
38
31
  return ancestorOrigin;
39
32
  }
40
- // The referrer becomes the previous preview URL after in-app navigation, so
41
- // a remembered first resolution outranks it on reloads.
42
- const rememberedOrigin = readRememberedPreviewParentOrigin();
33
+ const rememberedOrigin = readRememberedPreviewParentOrigin(storage);
43
34
  if (rememberedOrigin) {
44
35
  return rememberedOrigin;
45
36
  }
46
- const referrerOrigin = getOrigin(typeof document === 'undefined' ? undefined : document.referrer);
37
+ const referrerOrigin = getOrigin(browserDocument?.referrer);
47
38
  if (referrerOrigin) {
48
- rememberPreviewParentOrigin(referrerOrigin);
39
+ rememberPreviewParentOrigin(referrerOrigin, storage);
49
40
  }
50
41
  return referrerOrigin;
51
42
  }
52
- function readRememberedPreviewParentOrigin() {
43
+ function readSessionStorage(browserWindow) {
44
+ try {
45
+ return browserWindow.sessionStorage;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ function readRememberedPreviewParentOrigin(storage) {
53
52
  if (rememberedPreviewParentOrigin !== undefined) {
54
53
  return rememberedPreviewParentOrigin;
55
54
  }
56
- if (typeof sessionStorage === 'undefined') {
55
+ if (!storage) {
57
56
  rememberedPreviewParentOrigin = null;
58
57
  return null;
59
58
  }
60
59
  try {
61
- rememberedPreviewParentOrigin = getOrigin(sessionStorage.getItem(PREVIEW_PARENT_ORIGIN_STORAGE_KEY) ?? undefined);
60
+ rememberedPreviewParentOrigin = getOrigin(storage.getItem(PREVIEW_PARENT_ORIGIN_STORAGE_KEY) ?? undefined);
62
61
  }
63
62
  catch {
64
63
  rememberedPreviewParentOrigin = null;
65
64
  }
66
65
  return rememberedPreviewParentOrigin;
67
66
  }
68
- function rememberPreviewParentOrigin(origin) {
67
+ function rememberPreviewParentOrigin(origin, storage) {
69
68
  rememberedPreviewParentOrigin = origin;
70
- if (typeof sessionStorage === 'undefined') {
69
+ if (!storage) {
71
70
  return;
72
71
  }
73
72
  try {
74
- sessionStorage.setItem(PREVIEW_PARENT_ORIGIN_STORAGE_KEY, origin);
73
+ storage.setItem(PREVIEW_PARENT_ORIGIN_STORAGE_KEY, origin);
75
74
  }
76
75
  catch {
77
76
  // Storage availability must not block the preview message channel.
@@ -0,0 +1,25 @@
1
+ import type { CollectionContentCore, ContentClientConfig, ContentClientHandle, SingletonContentCore } from '../content/client/core-types.js';
2
+ export interface SvelteKitContentClientConfig extends ContentClientConfig {
3
+ apiUrl: string | undefined;
4
+ defaultLocale?: string;
5
+ }
6
+ export interface SvelteKitEvent {
7
+ fetch: typeof globalThis.fetch;
8
+ }
9
+ export interface SvelteKitContentModule {
10
+ default: ContentClientHandle;
11
+ }
12
+ export type SvelteKitContentRegistry = Readonly<Record<string, SvelteKitContentModule>>;
13
+ export interface CreateFimoOptions<TContent extends SvelteKitContentRegistry> {
14
+ content: TContent;
15
+ event: SvelteKitEvent;
16
+ locale?: string;
17
+ }
18
+ export declare function createContentClient<T extends {
19
+ id: string;
20
+ }, TInput extends object>(config: SvelteKitContentClientConfig): CollectionContentCore<T, TInput>;
21
+ export declare function createSingletonClient<T extends {
22
+ id: string;
23
+ }, TInput extends object>(config: SvelteKitContentClientConfig): SingletonContentCore<T, TInput>;
24
+ export declare function createFimo<const TContent extends SvelteKitContentRegistry>({ content, event, locale, }: CreateFimoOptions<TContent>): TContent;
25
+ //# sourceMappingURL=content.d.ts.map
@@ -0,0 +1,33 @@
1
+ import { createServerContentClient, createServerSingletonClient } from '../content/client/server-content-client.js';
2
+ import { createSvelteKitContentRuntime } from './runtime.js';
3
+ const requestClientFactories = new WeakMap();
4
+ export function createContentClient(config) {
5
+ const create = (requestFetch, locale) => createServerContentClient(config, createSvelteKitContentRuntime({
6
+ apiUrl: config.apiUrl,
7
+ defaultLocale: locale ?? config.defaultLocale,
8
+ fetch: requestFetch,
9
+ }));
10
+ const client = create(globalThis.fetch);
11
+ requestClientFactories.set(client, create);
12
+ return client;
13
+ }
14
+ export function createSingletonClient(config) {
15
+ const create = (requestFetch, locale) => createServerSingletonClient(config, createSvelteKitContentRuntime({
16
+ apiUrl: config.apiUrl,
17
+ defaultLocale: locale ?? config.defaultLocale,
18
+ fetch: requestFetch,
19
+ }));
20
+ const client = create(globalThis.fetch);
21
+ requestClientFactories.set(client, create);
22
+ return client;
23
+ }
24
+ export function createFimo({ content, event, locale, }) {
25
+ return Object.fromEntries(Object.entries(content).map(([key, contentModule]) => {
26
+ const create = requestClientFactories.get(contentModule.default);
27
+ if (!create) {
28
+ throw new Error(`Content module "${key}" was not created by fimo/sveltekit.`);
29
+ }
30
+ const client = create(event.fetch, locale);
31
+ return [key, { ...contentModule, ...client, default: client }];
32
+ }));
33
+ }
@@ -0,0 +1,5 @@
1
+ import type { LabelsSnapshot } from '../labels/core.js';
2
+ /** Shared by duplicated `fimo` package instances in the same Svelte tree. */
3
+ export declare const LABELS_KEY: unique symbol;
4
+ export type LabelsSource = () => LabelsSnapshot;
5
+ //# sourceMappingURL=context-contract.d.ts.map
@@ -0,0 +1,2 @@
1
+ /** Shared by duplicated `fimo` package instances in the same Svelte tree. */
2
+ export const LABELS_KEY = Symbol.for('fimo.sveltekit.labels');
@@ -0,0 +1,8 @@
1
+ import { type Labels } from '../labels/core.js';
2
+ /**
3
+ * Read the labels installed by `FimoProvider`. Call it while a component
4
+ * initializes; the returned `t()` is usable at any time afterwards and returns
5
+ * a source-tracked `FimoString`, empty for keys the project has no value for.
6
+ */
7
+ export declare function useLabels(): Labels;
8
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1,25 @@
1
+ import { getContext } from 'svelte';
2
+ import { createLabels } from '../labels/core.js';
3
+ import { LABELS_KEY } from './context-contract.js';
4
+ /**
5
+ * Read the labels installed by `FimoProvider`. Call it while a component
6
+ * initializes; the returned `t()` is usable at any time afterwards and returns
7
+ * a source-tracked `FimoString`, empty for keys the project has no value for.
8
+ */
9
+ export function useLabels() {
10
+ const readSnapshot = getContext(LABELS_KEY);
11
+ if (!readSnapshot) {
12
+ throw new Error('useLabels() found no Fimo labels. Render FimoProvider with the label snapshot from your root layout load.');
13
+ }
14
+ // Every member reads the snapshot on access: binding one `createLabels` result
15
+ // here would freeze the labels at initialization and stop tracking `data`.
16
+ return {
17
+ get locale() {
18
+ return readSnapshot().locale;
19
+ },
20
+ get snapshot() {
21
+ return readSnapshot();
22
+ },
23
+ t: (key) => createLabels(readSnapshot()).t(key),
24
+ };
25
+ }
@@ -0,0 +1,7 @@
1
+ export { createContentClient, createFimo, createSingletonClient, type CreateFimoOptions, type SvelteKitContentClientConfig, type SvelteKitContentModule, type SvelteKitContentRegistry, type SvelteKitEvent, } from './content.js';
2
+ export { useLabels } from './context.js';
3
+ export { getLabels, type GetLabelsOptions } from './labels.js';
4
+ export type { Labels, LabelsSnapshot } from '../labels/core.js';
5
+ export type { CollectionContentCore as CollectionContentClient, SingletonContentCore as SingletonContentClient, } from '../content/client/core-types.js';
6
+ export * from '../content/index.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,4 @@
1
+ export { createContentClient, createFimo, createSingletonClient, } from './content.js';
2
+ export { useLabels } from './context.js';
3
+ export { getLabels } from './labels.js';
4
+ export * from '../content/index.js';
@@ -0,0 +1,6 @@
1
+ export { renderRichTextHtml } from '../primitives/lib/render-rich-text.js';
2
+ export { resolveImagePresentation, type ImageFit } from '../primitives/lib/image-optimization.js';
3
+ export { LABELS_KEY, type LabelsSource } from './context-contract.js';
4
+ export { connectPreview, ensurePreviewScript, sendPreviewLocation, type PreviewNavigation } from './preview.js';
5
+ export declare function mergeSvelteImageStyle(style: string | null | undefined, objectFit: string): string;
6
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1,12 @@
1
+ export { renderRichTextHtml } from '../primitives/lib/render-rich-text.js';
2
+ export { resolveImagePresentation } from '../primitives/lib/image-optimization.js';
3
+ export { LABELS_KEY } from './context-contract.js';
4
+ export { connectPreview, ensurePreviewScript, sendPreviewLocation } from './preview.js';
5
+ export function mergeSvelteImageStyle(style, objectFit) {
6
+ if (style && /(?:^|;)\s*object-fit\s*:/i.test(style)) {
7
+ return style;
8
+ }
9
+ const value = style?.trim() ?? '';
10
+ const separator = value === '' || value.endsWith(';') ? '' : ';';
11
+ return `${value}${separator}object-fit:${objectFit}`;
12
+ }
@@ -0,0 +1,16 @@
1
+ import type { LabelsSnapshot } from '../labels/core.js';
2
+ import type { SvelteKitEvent } from './content.js';
3
+ export interface GetLabelsOptions {
4
+ apiUrl: string | undefined;
5
+ event: SvelteKitEvent;
6
+ /** The application resolves the active locale; Fimo never infers it from the URL. */
7
+ locale: string;
8
+ }
9
+ /**
10
+ * Read the label dictionary for one locale through the request `fetch`.
11
+ *
12
+ * Returns plain data because SvelteKit serializes whatever a `load` returns:
13
+ * pass it to `FimoProvider` in a layout to expose `Label` and `useLabels()`.
14
+ */
15
+ export declare function getLabels({ apiUrl, event, locale }: GetLabelsOptions): Promise<LabelsSnapshot>;
16
+ //# sourceMappingURL=labels.d.ts.map
@@ -0,0 +1,13 @@
1
+ import { fetchLabels } from '../labels/fetch.js';
2
+ import { createSvelteKitContentRuntime } from './runtime.js';
3
+ /**
4
+ * Read the label dictionary for one locale through the request `fetch`.
5
+ *
6
+ * Returns plain data because SvelteKit serializes whatever a `load` returns:
7
+ * pass it to `FimoProvider` in a layout to expose `Label` and `useLabels()`.
8
+ */
9
+ export async function getLabels({ apiUrl, event, locale }) {
10
+ const runtime = createSvelteKitContentRuntime({ apiUrl, defaultLocale: locale, fetch: event.fetch });
11
+ const labels = await fetchLabels(runtime, { locale });
12
+ return labels.snapshot;
13
+ }
@@ -0,0 +1,19 @@
1
+ export interface PreviewNavigation {
2
+ /** SvelteKit `goto` — the app router stays in charge of the transition. */
3
+ goto: (to: string) => unknown;
4
+ /** SvelteKit `invalidateAll` — reruns every `load` against fresh content. */
5
+ invalidateAll: () => Promise<unknown>;
6
+ }
7
+ /** Load the platform preview script once. Empty configuration keeps preview disabled. */
8
+ export declare function ensurePreviewScript(src: string | undefined): boolean;
9
+ /** Report where the app is now. Studio keeps its URL and route selector in sync from this. */
10
+ export declare function sendPreviewLocation(): void;
11
+ /**
12
+ * Listen for the live Studio preview protocol and answer it with SvelteKit's
13
+ * own navigation APIs. Returns the disconnect function.
14
+ *
15
+ * Only messages from the established parent origin are honored. History stays
16
+ * native, so Studio's back/forward controls need no protocol of their own.
17
+ */
18
+ export declare function connectPreview({ goto, invalidateAll }: PreviewNavigation): () => void;
19
+ //# sourceMappingURL=preview.d.ts.map
@@ -0,0 +1,77 @@
1
+ import { getPreviewLocationChange, getPreviewParentOrigin } from '../shared/preview-location.js';
2
+ /** Load the platform preview script once. Empty configuration keeps preview disabled. */
3
+ export function ensurePreviewScript(src) {
4
+ if (!src) {
5
+ return false;
6
+ }
7
+ const exists = Array.from(document.scripts).some((script) => script.getAttribute('src') === src);
8
+ if (!exists) {
9
+ const script = document.createElement('script');
10
+ script.src = src;
11
+ script.defer = true;
12
+ document.head.appendChild(script);
13
+ }
14
+ return true;
15
+ }
16
+ /** Report where the app is now. Studio keeps its URL and route selector in sync from this. */
17
+ export function sendPreviewLocation() {
18
+ const parentOrigin = getPreviewParentOrigin();
19
+ const locationChange = getPreviewLocationChange(window.location);
20
+ window.parent.postMessage({
21
+ type: 'nav/locationChange',
22
+ // Query and hash state only travels to an established parent origin.
23
+ payload: parentOrigin ? locationChange : { path: locationChange.path },
24
+ }, parentOrigin ?? '*');
25
+ }
26
+ /** Reliable-delivery reply: the parent retries an unacknowledged message forever. */
27
+ function acknowledge(message, result) {
28
+ const parentOrigin = getPreviewParentOrigin();
29
+ if (!message.requiresAck || typeof message.messageId !== 'string' || !parentOrigin) {
30
+ return;
31
+ }
32
+ window.parent.postMessage({ type: 'ack', payload: { messageId: message.messageId, ...result } }, parentOrigin);
33
+ }
34
+ async function refreshContent(message, invalidateAll) {
35
+ try {
36
+ await invalidateAll();
37
+ acknowledge(message, { ok: true });
38
+ }
39
+ catch (error) {
40
+ console.error(error);
41
+ acknowledge(message, { ok: false, error: error instanceof Error ? error.message : String(error) });
42
+ }
43
+ }
44
+ /**
45
+ * Listen for the live Studio preview protocol and answer it with SvelteKit's
46
+ * own navigation APIs. Returns the disconnect function.
47
+ *
48
+ * Only messages from the established parent origin are honored. History stays
49
+ * native, so Studio's back/forward controls need no protocol of their own.
50
+ */
51
+ export function connectPreview({ goto, invalidateAll }) {
52
+ const onMessage = (event) => {
53
+ const parentOrigin = getPreviewParentOrigin();
54
+ if (event.source !== window.parent || !parentOrigin || event.origin !== parentOrigin) {
55
+ return;
56
+ }
57
+ const message = (event.data ?? {});
58
+ switch (message.type) {
59
+ case 'nav/navigate':
60
+ if (typeof message.payload?.to === 'string') {
61
+ void goto(message.payload.to);
62
+ }
63
+ return;
64
+ case 'nav/read-routes':
65
+ // The app owns its route table; answer with where it actually is.
66
+ sendPreviewLocation();
67
+ return;
68
+ case 'content/refresh':
69
+ void refreshContent(message, invalidateAll);
70
+ return;
71
+ }
72
+ };
73
+ window.addEventListener('message', onMessage);
74
+ return () => {
75
+ window.removeEventListener('message', onMessage);
76
+ };
77
+ }
@@ -0,0 +1,8 @@
1
+ import { type ContentRuntime } from '../content/client/runtime.js';
2
+ export interface SvelteKitContentRuntimeOptions {
3
+ apiUrl: string | undefined;
4
+ defaultLocale?: string;
5
+ fetch: typeof globalThis.fetch;
6
+ }
7
+ export declare function createSvelteKitContentRuntime(options: SvelteKitContentRuntimeOptions): ContentRuntime;
8
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { requireContentApiUrl } from '../content/client/runtime.js';
2
+ export function createSvelteKitContentRuntime(options) {
3
+ return {
4
+ apiBase() {
5
+ return requireContentApiUrl(options.apiUrl, 'PUBLIC_FIMO_API_URL');
6
+ },
7
+ defaultLocale() {
8
+ return options.defaultLocale?.trim() || 'en';
9
+ },
10
+ headers(extra) {
11
+ return { ...extra };
12
+ },
13
+ fetch(input, init) {
14
+ return options.fetch(input, init);
15
+ },
16
+ };
17
+ }
@@ -1,4 +1,4 @@
1
- export declare const TEMPLATE_FRAMEWORKS: readonly ['react-router', 'nextjs'];
1
+ export declare const TEMPLATE_FRAMEWORKS: readonly ['react-router', 'nextjs', 'sveltekit'];
2
2
  export type TemplateFramework = (typeof TEMPLATE_FRAMEWORKS)[number];
3
3
  export declare const DEFAULT_TEMPLATE: TemplateFramework;
4
4
  /**
@@ -27,7 +27,7 @@ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
27
27
  const PACKAGE_ROOT = path.resolve(MODULE_DIR, '..', '..');
28
28
  const RELEASE_FILE = path.join(PACKAGE_ROOT, 'release.json');
29
29
  const PACKAGE_JSON_FILE = path.join(PACKAGE_ROOT, 'package.json');
30
- export const TEMPLATE_FRAMEWORKS = ['react-router', 'nextjs'];
30
+ export const TEMPLATE_FRAMEWORKS = ['react-router', 'nextjs', 'sveltekit'];
31
31
  export const DEFAULT_TEMPLATE = 'react-router';
32
32
  /**
33
33
  * Absolute path to the requested framework's template directory.
@@ -0,0 +1,13 @@
1
+ import type { ElementType } from 'react';
2
+ import type { LabelProps } from '../react/primitives/contracts.js';
3
+ export type { LabelProps } from '../react/primitives/contracts.js';
4
+ /**
5
+ * Render one editable label from the Vite/React Router label context.
6
+ *
7
+ * For attributes, metadata, and composed strings, use `useLabels().t()`.
8
+ *
9
+ * @example
10
+ * <Label value="hero.title" as="h1" />
11
+ */
12
+ export declare function Label<T extends ElementType = 'span'>({ value, ...props }: LabelProps<T>): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=Label.d.ts.map
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Text } from '../react/primitives/editable/Text.js';
3
+ import { useLabels } from './translations.js';
4
+ /**
5
+ * Render one editable label from the Vite/React Router label context.
6
+ *
7
+ * For attributes, metadata, and composed strings, use `useLabels().t()`.
8
+ *
9
+ * @example
10
+ * <Label value="hero.title" as="h1" />
11
+ */
12
+ export function Label({ value, ...props }) {
13
+ const { t } = useLabels();
14
+ const textProps = { ...props, value: t(value) };
15
+ return _jsx(Text, { ...textProps });
16
+ }