fimo 0.8.0 → 0.9.0-experimental.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/content-templates/hooks-template.eta +4 -2
- package/assets/content-templates/types-template.eta +66 -1
- package/assets/skills/fimo/SKILL.md +1 -1
- package/assets/skills/fimo/references/content.md +86 -15
- package/assets/skills/fimo-cli/SKILL.md +6 -2
- package/assets/skills/fimo-cli/references/content.md +61 -2
- package/assets/skills/fimo-cli/references/integrations/firecrawl.md +20 -4
- package/assets/skills/fimo-cli/references/integrations.md +5 -1
- package/assets/skills/fimo-cli/references/webhooks.md +162 -0
- package/assets/skills/fimo-migration/SKILL.md +22 -2
- package/assets/skills/fimo-migration/references/content.md +24 -0
- package/assets/skills/fimo-migration/references/manifest.md +35 -0
- package/assets/skills/fimo-migration/references/qa.md +24 -2
- package/assets/skills/fimo-migration/references/workflow.md +12 -1
- package/assets/skills/fimo-migration/references/wrap.md +617 -0
- package/assets/skills/fimo-studio/SKILL.md +5 -3
- package/assets/skills/fimo-studio/references/cms.md +9 -1
- package/assets/skills/fimo-studio/references/visual-editing.md +5 -3
- package/assets/skills/fimo-studio/references/webhooks.md +42 -0
- package/dist/build/vite/plugins/translations.d.ts +2 -8
- package/dist/build/vite/plugins/translations.js +3 -121
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +1051 -144
- package/dist/runtime/app/FimoProviders.d.ts +1 -11
- package/dist/runtime/app/FimoProviders.js +2 -23
- package/dist/runtime/astro/index.d.ts +2 -0
- package/dist/runtime/astro/index.js +3 -0
- package/dist/runtime/astro/server.d.ts +3 -0
- package/dist/runtime/astro/server.js +6 -0
- package/dist/runtime/content/client/internal.js +1 -0
- package/dist/runtime/content/client/types.d.ts +16 -0
- package/dist/runtime/i18n/FimoLocaleContext.js +1 -0
- package/dist/runtime/next/FimoNextProviders.d.ts +24 -0
- package/dist/runtime/next/FimoNextProviders.js +20 -0
- package/dist/runtime/next/FimoNextScripts.d.ts +10 -0
- package/dist/runtime/next/FimoNextScripts.js +19 -0
- package/dist/runtime/next/index.d.ts +14 -0
- package/dist/runtime/next/index.js +14 -0
- package/dist/runtime/next/server.d.ts +2 -0
- package/dist/runtime/next/server.js +4 -0
- package/dist/runtime/nuxt/composables.d.ts +9 -0
- package/dist/runtime/nuxt/composables.js +57 -0
- package/dist/runtime/nuxt/index.d.ts +3 -0
- package/dist/runtime/nuxt/index.js +6 -0
- package/dist/runtime/nuxt/server.d.ts +3 -0
- package/dist/runtime/nuxt/server.js +6 -0
- package/dist/runtime/primitives/labels-hook.d.ts +33 -0
- package/dist/runtime/primitives/labels-hook.js +41 -0
- package/dist/runtime/primitives/labels-store.d.ts +12 -0
- package/dist/runtime/primitives/labels-store.js +60 -0
- package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts +5 -3
- package/dist/runtime/primitives/lib/useFimoLiveValue.js +11 -4
- package/dist/runtime/primitives/translations.d.ts +1 -27
- package/dist/runtime/primitives/translations.js +7 -85
- package/dist/runtime/react/FimoSpaProviders.d.ts +36 -0
- package/dist/runtime/react/FimoSpaProviders.js +63 -0
- package/dist/runtime/react/index.d.ts +33 -0
- package/dist/runtime/react/index.js +39 -0
- package/dist/runtime/remix/FimoRemixProviders.d.ts +16 -0
- package/dist/runtime/remix/FimoRemixProviders.js +12 -0
- package/dist/runtime/remix/FimoRemixScripts.d.ts +10 -0
- package/dist/runtime/remix/FimoRemixScripts.js +19 -0
- package/dist/runtime/remix/index.d.ts +12 -0
- package/dist/runtime/remix/index.js +11 -0
- package/dist/runtime/remix/server.d.ts +2 -0
- package/dist/runtime/remix/server.js +2 -0
- package/dist/runtime/shared/display-routes.d.ts +14 -0
- package/dist/runtime/shared/display-routes.js +25 -0
- package/dist/runtime/shared/fimo-config.server.js +1 -0
- package/dist/runtime/shared/fimo-config.types.d.ts +2 -0
- package/dist/runtime/shared/i18n-messages.d.ts +29 -0
- package/dist/runtime/shared/i18n-messages.js +79 -0
- package/dist/runtime/shared/labels-fetch.pure.d.ts +17 -0
- package/dist/runtime/shared/labels-fetch.pure.js +71 -0
- package/dist/runtime/shared/labels-fetch.server.d.ts +7 -0
- package/dist/runtime/shared/labels-fetch.server.js +97 -0
- package/dist/runtime/shared/labels-t.pure.d.ts +2 -0
- package/dist/runtime/shared/labels-t.pure.js +10 -0
- package/dist/runtime/strapi/client.d.ts +30 -0
- package/dist/runtime/strapi/client.js +89 -0
- package/dist/runtime/strapi/index.d.ts +5 -0
- package/dist/runtime/strapi/index.js +6 -0
- package/dist/runtime/strapi/reshape.d.ts +46 -0
- package/dist/runtime/strapi/reshape.js +114 -0
- package/dist/runtime/svelte/index.d.ts +3 -0
- package/dist/runtime/svelte/index.js +7 -0
- package/dist/runtime/svelte/server.d.ts +3 -0
- package/dist/runtime/svelte/server.js +6 -0
- package/dist/runtime/vue/index.d.ts +3 -0
- package/dist/runtime/vue/index.js +5 -0
- package/dist/runtime/vue/server.d.ts +3 -0
- package/dist/runtime/vue/server.js +6 -0
- package/dist/runtime/wrapped/FimoFileLink.d.ts +17 -0
- package/dist/runtime/wrapped/FimoFileLink.js +19 -0
- package/dist/runtime/wrapped/FimoMediaImage.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaImage.js +20 -0
- package/dist/runtime/wrapped/FimoMediaVideo.d.ts +14 -0
- package/dist/runtime/wrapped/FimoMediaVideo.js +20 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.d.ts +23 -0
- package/dist/runtime/wrapped/FimoRichTextBlock.js +22 -0
- package/dist/runtime/wrapped/FimoText.d.ts +22 -0
- package/dist/runtime/wrapped/FimoText.js +20 -0
- package/dist/runtime/wrapped/WrappedContentContext.d.ts +28 -0
- package/dist/runtime/wrapped/WrappedContentContext.js +30 -0
- package/dist/runtime/wrapped/WrappedProviders.d.ts +33 -0
- package/dist/runtime/wrapped/WrappedProviders.js +101 -0
- package/dist/runtime/wrapped/dom.d.ts +4 -0
- package/dist/runtime/wrapped/dom.js +23 -0
- package/dist/runtime/wrapped/server.d.ts +77 -0
- package/dist/runtime/wrapped/server.js +237 -0
- package/dist/scripts/apply-wrap-templates.d.ts +61 -0
- package/dist/scripts/apply-wrap-templates.js +132 -0
- package/dist/scripts/apply-wrap.d.ts +46 -0
- package/dist/scripts/apply-wrap.js +278 -0
- package/dist/scripts/cms/discover.d.ts +2 -0
- package/dist/scripts/cms/discover.js +17 -0
- package/dist/scripts/cms/example-content.d.ts +33 -0
- package/dist/scripts/cms/example-content.js +162 -0
- package/dist/scripts/cms/strapi/detect.d.ts +2 -0
- package/dist/scripts/cms/strapi/detect.js +186 -0
- package/dist/scripts/cms/strapi/index.d.ts +3 -0
- package/dist/scripts/cms/strapi/index.js +25 -0
- package/dist/scripts/cms/strapi/list-source.d.ts +17 -0
- package/dist/scripts/cms/strapi/list-source.js +90 -0
- package/dist/scripts/cms/strapi/schema-map.d.ts +9 -0
- package/dist/scripts/cms/strapi/schema-map.js +277 -0
- package/dist/scripts/cms/strapi/shapes.d.ts +23 -0
- package/dist/scripts/cms/strapi/shapes.js +38 -0
- package/dist/scripts/cms/strapi/touchpoints.d.ts +31 -0
- package/dist/scripts/cms/strapi/touchpoints.js +145 -0
- package/dist/scripts/cms/strapi/wire-client.d.ts +15 -0
- package/dist/scripts/cms/strapi/wire-client.js +107 -0
- package/dist/scripts/cms/strapi/wiring.d.ts +17 -0
- package/dist/scripts/cms/strapi/wiring.js +46 -0
- package/dist/scripts/cms/types.d.ts +145 -0
- package/dist/scripts/cms/types.js +11 -0
- package/dist/scripts/detect-framework.d.ts +39 -0
- package/dist/scripts/detect-framework.js +133 -0
- package/dist/scripts/draft-rows.d.ts +19 -0
- package/dist/scripts/draft-rows.js +135 -0
- package/dist/scripts/draft-schemas.d.ts +27 -0
- package/dist/scripts/draft-schemas.js +188 -0
- package/dist/scripts/extract-translations.js +16 -11
- package/dist/scripts/frameworks/astro/routes.d.ts +3 -0
- package/dist/scripts/frameworks/astro/routes.js +55 -0
- package/dist/scripts/frameworks/astro/template-style.d.ts +7 -0
- package/dist/scripts/frameworks/astro/template-style.js +19 -0
- package/dist/scripts/frameworks/discover.d.ts +70 -0
- package/dist/scripts/frameworks/discover.js +178 -0
- package/dist/scripts/frameworks/next/rewire.d.ts +30 -0
- package/dist/scripts/frameworks/next/rewire.js +258 -0
- package/dist/scripts/frameworks/next/routes.d.ts +17 -0
- package/dist/scripts/frameworks/next/routes.js +134 -0
- package/dist/scripts/frameworks/next/wiring.d.ts +33 -0
- package/dist/scripts/frameworks/next/wiring.js +186 -0
- package/dist/scripts/frameworks/nuxt/routes.d.ts +3 -0
- package/dist/scripts/frameworks/nuxt/routes.js +54 -0
- package/dist/scripts/frameworks/nuxt/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/nuxt/template-style.js +27 -0
- package/dist/scripts/frameworks/remix/routes.d.ts +3 -0
- package/dist/scripts/frameworks/remix/routes.js +50 -0
- package/dist/scripts/frameworks/support.d.ts +75 -0
- package/dist/scripts/frameworks/support.js +165 -0
- package/dist/scripts/frameworks/sveltekit/routes.d.ts +3 -0
- package/dist/scripts/frameworks/sveltekit/routes.js +50 -0
- package/dist/scripts/frameworks/sveltekit/template-style.d.ts +9 -0
- package/dist/scripts/frameworks/sveltekit/template-style.js +54 -0
- package/dist/scripts/frameworks/vite/routes.d.ts +15 -0
- package/dist/scripts/frameworks/vite/routes.js +93 -0
- package/dist/scripts/frameworks/vite/wiring.d.ts +10 -0
- package/dist/scripts/frameworks/vite/wiring.js +99 -0
- package/dist/scripts/i18n-namespace.d.ts +27 -0
- package/dist/scripts/i18n-namespace.js +155 -0
- package/dist/scripts/lib/validation-report.d.ts +1 -0
- package/dist/scripts/lib/validation-report.js +3 -0
- package/dist/scripts/render-parity/capture.d.ts +56 -0
- package/dist/scripts/render-parity/capture.js +366 -0
- package/dist/scripts/render-parity/compare.d.ts +59 -0
- package/dist/scripts/render-parity/compare.js +0 -0
- package/dist/scripts/render-parity/normalize-html.d.ts +13 -0
- package/dist/scripts/render-parity/normalize-html.js +223 -0
- package/dist/scripts/render-parity/types.d.ts +41 -0
- package/dist/scripts/render-parity/types.js +1 -0
- package/dist/scripts/scan-wrappable.d.ts +196 -0
- package/dist/scripts/scan-wrappable.js +1232 -0
- package/dist/scripts/validate-route-metadata.js +11 -0
- package/dist/scripts/validate-schemas.js +42 -1
- package/package.json +84 -4
- package/release.json +2 -2
- package/templates/react-router/package.json +1 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Text } from '../primitives/components/Text.js';
|
|
4
|
+
import { useLabels } from '../primitives/labels-hook.js';
|
|
5
|
+
/**
|
|
6
|
+
* Wraps a hardcoded string so it becomes editable in Fimo. The original
|
|
7
|
+
* string stays in code as the fallback:
|
|
8
|
+
*
|
|
9
|
+
* <h1><FimoText id="home.hero.title">Ship your site in hours</FimoText></h1>
|
|
10
|
+
*
|
|
11
|
+
* For attribute strings use the two-argument label function instead:
|
|
12
|
+
*
|
|
13
|
+
* const { t } = useLabels();
|
|
14
|
+
* <input placeholder={String(t('home.newsletter.placeholder', 'Your work email'))} />
|
|
15
|
+
*/
|
|
16
|
+
export function FimoText({ id, children, as, ...props }) {
|
|
17
|
+
const { t } = useLabels();
|
|
18
|
+
const textProps = { value: t(id, children), as, ...props };
|
|
19
|
+
return _jsx(Text, { ...textProps });
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FIMO_SOURCE, FimoMedia, type FimoRichTextContent } from '../primitives/lib/index.js';
|
|
3
|
+
export interface WrappedMediaEntry {
|
|
4
|
+
url: string;
|
|
5
|
+
alt?: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
mime?: string;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
/** data-fimo-source path, e.g. `WrappedMedia.<entryId>.image` */
|
|
11
|
+
source: string;
|
|
12
|
+
}
|
|
13
|
+
export interface WrappedRichTextEntry {
|
|
14
|
+
content: FimoRichTextContent;
|
|
15
|
+
/** data-fimo-source path, e.g. `WrappedRichText.<entryId>.body` */
|
|
16
|
+
source: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WrappedContentValue {
|
|
19
|
+
media: Record<string, WrappedMediaEntry>;
|
|
20
|
+
richText: Record<string, WrappedRichTextEntry>;
|
|
21
|
+
}
|
|
22
|
+
export declare function WrappedContentProvider({ media, richText, children, }: Partial<WrappedContentValue> & {
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
export declare function useWrappedMedia(id: string): FimoMedia | null;
|
|
26
|
+
export declare function useWrappedRichText(id: string): WrappedRichTextEntry | null;
|
|
27
|
+
export { FIMO_SOURCE };
|
|
28
|
+
//# sourceMappingURL=WrappedContentContext.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
4
|
+
import { FIMO_SOURCE, FimoMedia } from '../primitives/lib/index.js';
|
|
5
|
+
const emptyValue = { media: {}, richText: {} };
|
|
6
|
+
const WrappedContentContext = createContext(emptyValue);
|
|
7
|
+
export function WrappedContentProvider({ media, richText, children, }) {
|
|
8
|
+
const value = useMemo(() => ({ media: media ?? {}, richText: richText ?? {} }), [media, richText]);
|
|
9
|
+
return _jsx(WrappedContentContext.Provider, { value: value, children: children });
|
|
10
|
+
}
|
|
11
|
+
export function useWrappedMedia(id) {
|
|
12
|
+
const { media } = useContext(WrappedContentContext);
|
|
13
|
+
const entry = media[id];
|
|
14
|
+
if (!entry) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return new FimoMedia({
|
|
18
|
+
url: entry.url,
|
|
19
|
+
alt: entry.alt ?? '',
|
|
20
|
+
name: entry.name ?? id,
|
|
21
|
+
mime: entry.mime ?? '',
|
|
22
|
+
width: entry.width,
|
|
23
|
+
height: entry.height,
|
|
24
|
+
}, entry.source);
|
|
25
|
+
}
|
|
26
|
+
export function useWrappedRichText(id) {
|
|
27
|
+
const { richText } = useContext(WrappedContentContext);
|
|
28
|
+
return richText[id] ?? null;
|
|
29
|
+
}
|
|
30
|
+
export { FIMO_SOURCE };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { FimoConfig } from '../shared/fimo-config.types.js';
|
|
3
|
+
import { type WrappedMediaEntry, type WrappedRichTextEntry } from './WrappedContentContext.js';
|
|
4
|
+
export interface WrappedLabels {
|
|
5
|
+
dictionaries: Record<string, Record<string, string>>;
|
|
6
|
+
defaultLocale: string;
|
|
7
|
+
locales?: string[];
|
|
8
|
+
updatedAt?: string | null;
|
|
9
|
+
signature: string;
|
|
10
|
+
}
|
|
11
|
+
/** Framework navigation adapter: the ONLY thing an adapter must supply. */
|
|
12
|
+
export interface WrappedNavigation {
|
|
13
|
+
pathname: string | null;
|
|
14
|
+
navigate: (to: string) => void;
|
|
15
|
+
}
|
|
16
|
+
export interface WrappedProvidersProps {
|
|
17
|
+
config: FimoConfig;
|
|
18
|
+
labels?: WrappedLabels;
|
|
19
|
+
media?: Record<string, WrappedMediaEntry>;
|
|
20
|
+
richText?: Record<string, WrappedRichTextEntry>;
|
|
21
|
+
locale?: string;
|
|
22
|
+
navigation: WrappedNavigation;
|
|
23
|
+
children?: React.ReactNode;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Framework-neutral providers for WRAPPED (adopted) apps. Adapters
|
|
27
|
+
* (fimo/next, fimo/remix, ...) supply only the navigation hooks; everything
|
|
28
|
+
* else — label hydration, locale resolution, preview bridge, error boundary —
|
|
29
|
+
* is shared. Content arrives as serializable props from the wrapped server
|
|
30
|
+
* loaders.
|
|
31
|
+
*/
|
|
32
|
+
export declare function WrappedProviders({ config, labels, media, richText, locale, navigation, children }: WrappedProvidersProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=WrappedProviders.d.ts.map
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
import { Suspense, useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import { ErrorBoundary } from 'react-error-boundary';
|
|
6
|
+
import { DataSourcesProvider } from '../content/DataSourcesProvider.js';
|
|
7
|
+
import { AppErrorBoundary, onError } from '../core/AppErrorBoundary.js';
|
|
8
|
+
import { FimoLocaleProvider } from '../i18n/FimoLocaleContext.js';
|
|
9
|
+
import { getFimoTextDirection, resolveFimoLocale, toHtmlLang } from '../i18n/locale.js';
|
|
10
|
+
import { ScreenshotProvider } from '../iframe/ScreenshotProvider.js';
|
|
11
|
+
import { hydrateLabels } from '../primitives/labels-store.js';
|
|
12
|
+
import { getDisplayRoutes } from '../shared/display-routes.js';
|
|
13
|
+
import { WrappedContentProvider } from './WrappedContentContext.js';
|
|
14
|
+
function toLabelState(labels) {
|
|
15
|
+
return {
|
|
16
|
+
dictionaries: labels.dictionaries,
|
|
17
|
+
defaultLocale: labels.defaultLocale,
|
|
18
|
+
signature: labels.signature,
|
|
19
|
+
updatedAt: labels.updatedAt ?? null,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function PreviewBridge({ config, navigation }) {
|
|
23
|
+
const { pathname, navigate } = navigation;
|
|
24
|
+
const localeResolution = useMemo(() => resolveFimoLocale({ pathname: pathname ?? undefined }, config), [pathname, config]);
|
|
25
|
+
const sendRoutes = useCallback(() => {
|
|
26
|
+
window.parent.postMessage({ type: 'nav/routes', payload: { routes: getDisplayRoutes(config) } }, '*');
|
|
27
|
+
}, [config]);
|
|
28
|
+
const sendLocationChange = useCallback(() => {
|
|
29
|
+
window.parent.postMessage({
|
|
30
|
+
type: 'nav/locationChange',
|
|
31
|
+
payload: {
|
|
32
|
+
path: pathname,
|
|
33
|
+
routePath: localeResolution.routePath,
|
|
34
|
+
locale: localeResolution.locale,
|
|
35
|
+
},
|
|
36
|
+
}, '*');
|
|
37
|
+
}, [pathname, localeResolution.locale, localeResolution.routePath]);
|
|
38
|
+
useEffect(sendRoutes, [sendRoutes]);
|
|
39
|
+
useEffect(sendLocationChange, [sendLocationChange]);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const handleMessage = (event) => {
|
|
42
|
+
const type = event.data?.type;
|
|
43
|
+
const payload = event.data?.payload;
|
|
44
|
+
switch (type) {
|
|
45
|
+
case 'nav/navigate':
|
|
46
|
+
if (payload?.to) {
|
|
47
|
+
navigate(payload.to);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
case 'nav/read-routes':
|
|
51
|
+
sendRoutes();
|
|
52
|
+
sendLocationChange();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
window.addEventListener('message', handleMessage);
|
|
57
|
+
return () => {
|
|
58
|
+
window.removeEventListener('message', handleMessage);
|
|
59
|
+
};
|
|
60
|
+
}, [navigate, sendRoutes, sendLocationChange]);
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Framework-neutral providers for WRAPPED (adopted) apps. Adapters
|
|
65
|
+
* (fimo/next, fimo/remix, ...) supply only the navigation hooks; everything
|
|
66
|
+
* else — label hydration, locale resolution, preview bridge, error boundary —
|
|
67
|
+
* is shared. Content arrives as serializable props from the wrapped server
|
|
68
|
+
* loaders.
|
|
69
|
+
*/
|
|
70
|
+
export function WrappedProviders({ config, labels, media, richText, locale, navigation, children }) {
|
|
71
|
+
// Wrapped apps have no QueryClientProvider of their own; ScreenshotProvider
|
|
72
|
+
// (and future data-refresh machinery) need one, so the adapter owns it.
|
|
73
|
+
const [queryClient] = useState(() => new QueryClient());
|
|
74
|
+
useState(() => {
|
|
75
|
+
if (labels) {
|
|
76
|
+
hydrateLabels(toLabelState(labels));
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
});
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (labels) {
|
|
82
|
+
hydrateLabels(toLabelState(labels));
|
|
83
|
+
}
|
|
84
|
+
}, [labels]);
|
|
85
|
+
const pathname = navigation.pathname;
|
|
86
|
+
const localeResolution = useMemo(() => resolveFimoLocale({ pathname: pathname ?? undefined }, config), [pathname, config]);
|
|
87
|
+
const activeLocale = locale ?? localeResolution.locale;
|
|
88
|
+
const activeDir = getFimoTextDirection(activeLocale);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (typeof document === 'undefined') {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
document.documentElement.lang = toHtmlLang(activeLocale);
|
|
94
|
+
document.documentElement.dir = activeDir;
|
|
95
|
+
}, [activeDir, activeLocale]);
|
|
96
|
+
return (_jsx(ErrorBoundary, { FallbackComponent: AppErrorBoundary, onError: onError, children: _jsx(QueryClientProvider, { client: queryClient, children: _jsx(FimoLocaleProvider, { value: {
|
|
97
|
+
...localeResolution,
|
|
98
|
+
locale: activeLocale,
|
|
99
|
+
dir: activeDir,
|
|
100
|
+
}, children: _jsx(WrappedContentProvider, { media: media, richText: richText, children: _jsx(DataSourcesProvider, { children: _jsxs(ScreenshotProvider, { children: [_jsx(PreviewBridge, { config: config, navigation: navigation }), _jsx(Suspense, { children: children })] }) }) }) }) }) }));
|
|
101
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Framework-agnostic DOM contract for wrapped content in NON-React
|
|
2
|
+
// frameworks (Astro templates, Vue SFCs, Svelte components). The edit-mode
|
|
3
|
+
// preview client selects elements by these attributes; rendering the value +
|
|
4
|
+
// attributes is all a template needs:
|
|
5
|
+
//
|
|
6
|
+
// Astro: <h1 {...fimoLabelAttrs('home.title')}>{t('home.title', 'Hello')}</h1>
|
|
7
|
+
// Vue: <h1 v-bind="fimoLabelAttrs('home.title')">{{ t('home.title', 'Hello') }}</h1>
|
|
8
|
+
// Svelte: <h1 {...fimoLabelAttrs('home.title')}>{tv}</h1>
|
|
9
|
+
//
|
|
10
|
+
// Values come from the server loaders (loadFimoT / loadWrappedMedia) — after
|
|
11
|
+
// an edit, the page refetches on reload; live DOM patching is handled by the
|
|
12
|
+
// preview client via these same attributes.
|
|
13
|
+
// NOTE: this must be the literal DOM attribute the preview client queries
|
|
14
|
+
// (`document.querySelectorAll('[data-fimo-source]')`) — NOT the FIMO_SOURCE
|
|
15
|
+
// Symbol from the React primitives: a symbol key vanishes in a template
|
|
16
|
+
// attribute spread and in JSON, silently breaking editability.
|
|
17
|
+
export const FIMO_SOURCE_ATTR = 'data-fimo-source';
|
|
18
|
+
export function fimoLabelAttrs(key, locale = 'en') {
|
|
19
|
+
return { [FIMO_SOURCE_ATTR]: `__labels.${locale}.${key}.value` };
|
|
20
|
+
}
|
|
21
|
+
export function fimoMediaAttrs(source) {
|
|
22
|
+
return { [FIMO_SOURCE_ATTR]: source };
|
|
23
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { FimoNextLabels } from '../next/FimoNextProviders.js';
|
|
2
|
+
import type { FimoConfig } from '../shared/fimo-config.types.js';
|
|
3
|
+
import type { WrappedMediaEntry, WrappedRichTextEntry } from './WrappedContentContext.js';
|
|
4
|
+
export interface FimoServerOptions {
|
|
5
|
+
/** Project root (where fimo-config.json and .env live). Defaults to cwd. */
|
|
6
|
+
rootDir?: string;
|
|
7
|
+
/** Tenant API base URL. Defaults to NEXT_PUBLIC_FIMO_API_URL / VITE_API_URL. */
|
|
8
|
+
apiUrl?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadFimoConfig(rootDir?: string): FimoConfig;
|
|
11
|
+
export declare function loadFimoLabels(options?: FimoServerOptions): Promise<FimoNextLabels>;
|
|
12
|
+
/**
|
|
13
|
+
* Server-component label accessor: `useLabels()` is client-only, and forcing
|
|
14
|
+
* a client wrapper for one aria-label in a server component is not a
|
|
15
|
+
* migration, it is collateral damage. Load once per request scope and read
|
|
16
|
+
* synchronously: `const t = await loadFimoT(); <a aria-label={t('footer.github', 'GitHub')} …`.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadFimoT(options?: FimoServerOptions & {
|
|
19
|
+
locale?: string;
|
|
20
|
+
}): Promise<(key: string, fallback: string) => string>;
|
|
21
|
+
export interface FimoEntry {
|
|
22
|
+
id: string;
|
|
23
|
+
data: Record<string, unknown>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetch published entries of a content type from the public tenant API.
|
|
27
|
+
* Returns [] when the API is unreachable so the wrapped site keeps rendering
|
|
28
|
+
* its fallbacks.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchEntries(typeName: string, options?: FimoServerOptions & {
|
|
31
|
+
/** The public API returns newest-first; rendering wants source order.
|
|
32
|
+
* Default 'oldest' restores insertion order for migrated collections. */
|
|
33
|
+
order?: 'oldest' | 'newest';
|
|
34
|
+
/** Query filter forwarded to the public API (e.g. { slug: { $in: [...] } }). */
|
|
35
|
+
where?: Record<string, unknown>;
|
|
36
|
+
/** Row cap forwarded to the public API. */
|
|
37
|
+
limit?: number;
|
|
38
|
+
}): Promise<FimoEntry[]>;
|
|
39
|
+
/**
|
|
40
|
+
* Load the WrappedMedia entries (key → media) seeded by the wrap migration.
|
|
41
|
+
* Each entry has a `key` (the FimoMediaImage/Video/FileLink id) and an
|
|
42
|
+
* asset-typed `image` field.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadWrappedMedia(options?: FimoServerOptions): Promise<Record<string, WrappedMediaEntry>>;
|
|
45
|
+
/**
|
|
46
|
+
* Load the WrappedRichText entries (key → TipTap JSON) seeded by the wrap
|
|
47
|
+
* migration for formatted text (bold/italic/underline/links).
|
|
48
|
+
*/
|
|
49
|
+
export declare function loadWrappedRichText(options?: FimoServerOptions): Promise<Record<string, WrappedRichTextEntry>>;
|
|
50
|
+
/**
|
|
51
|
+
* Overlay a wrap-migrated collection onto its retained source rows: fetch the
|
|
52
|
+
* Fimo entries matching the source rows' keys, merge field-by-field with the
|
|
53
|
+
* type-matched rule above, and return plain data rows in SOURCE order. The
|
|
54
|
+
* page keeps its original render logic and its original fallback — edits made
|
|
55
|
+
* in Fimo show up, everything else stays byte-identical.
|
|
56
|
+
*/
|
|
57
|
+
export declare function wrapCollection<T extends Record<string, unknown>>(typeName: string, sourceRows: readonly T[], options?: FimoServerOptions & {
|
|
58
|
+
keyField?: string;
|
|
59
|
+
}): Promise<T[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Load a locale's messages from Fimo for an EXISTING i18n runtime (next-intl,
|
|
62
|
+
* i18next). Wrap-mode i18n ingestion seeds the app's message store into Fimo
|
|
63
|
+
* labels (flat dot-keys); this fetches them back and un-flattens them into the
|
|
64
|
+
* nested shape the app's own i18n loader expects — so the app keeps its
|
|
65
|
+
* `useTranslations`/`t()` unchanged, now editable from Fimo Studio.
|
|
66
|
+
*
|
|
67
|
+
* Deep-merge over the shipped messages (pass them as `fallback`) so any key
|
|
68
|
+
* not yet in Fimo falls back to the static file instead of showing a missing
|
|
69
|
+
* key. Wire it in the app's message loader, e.g. next-intl `i18n/request.ts`:
|
|
70
|
+
*
|
|
71
|
+
* const messages = await loadFimoMessages(locale, { fallback: staticMessages });
|
|
72
|
+
* return { locale, messages };
|
|
73
|
+
*/
|
|
74
|
+
export declare function loadFimoMessages(locale: string, options?: FimoServerOptions & {
|
|
75
|
+
fallback?: Record<string, unknown>;
|
|
76
|
+
}): Promise<Record<string, unknown>>;
|
|
77
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
// Server-only loaders for WRAPPED (adopted) apps — framework-neutral.
|
|
2
|
+
// Content resolves at request time from the public tenant API (no Vite
|
|
3
|
+
// virtual modules, no build plugin). Framework adapters re-export these:
|
|
4
|
+
// fimo/next/server today, fimo/astro tomorrow — same loaders, same shapes.
|
|
5
|
+
import { getConfigServer } from '../shared/fimo-config.server.js';
|
|
6
|
+
import { deepMergeMessages, unflattenMessages } from '../shared/i18n-messages.js';
|
|
7
|
+
import { fetchLabelDictionary, fetchWithTimeout, labelSignature, loadLabelBundle, readProjectEnv, resolveTenantApiUrl, } from '../shared/labels-fetch.server.js';
|
|
8
|
+
export function loadFimoConfig(rootDir = process.cwd()) {
|
|
9
|
+
return getConfigServer(rootDir);
|
|
10
|
+
}
|
|
11
|
+
export async function loadFimoLabels(options = {}) {
|
|
12
|
+
const rootDir = options.rootDir ?? process.cwd();
|
|
13
|
+
const bundle = await loadLabelBundle(rootDir);
|
|
14
|
+
return { ...bundle, signature: labelSignature(bundle) };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Server-component label accessor: `useLabels()` is client-only, and forcing
|
|
18
|
+
* a client wrapper for one aria-label in a server component is not a
|
|
19
|
+
* migration, it is collateral damage. Load once per request scope and read
|
|
20
|
+
* synchronously: `const t = await loadFimoT(); <a aria-label={t('footer.github', 'GitHub')} …`.
|
|
21
|
+
*/
|
|
22
|
+
export async function loadFimoT(options = {}) {
|
|
23
|
+
const labels = await loadFimoLabels(options);
|
|
24
|
+
const locale = options.locale ?? labels.defaultLocale;
|
|
25
|
+
const dictionary = labels.dictionaries[locale] ?? labels.dictionaries[labels.defaultLocale] ?? {};
|
|
26
|
+
return (key, fallback) => {
|
|
27
|
+
const value = dictionary[key];
|
|
28
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function resolveApiUrl(options) {
|
|
32
|
+
if (options.apiUrl) {
|
|
33
|
+
return options.apiUrl;
|
|
34
|
+
}
|
|
35
|
+
const env = await readProjectEnv(options.rootDir ?? process.cwd());
|
|
36
|
+
return resolveTenantApiUrl(env);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Fetch published entries of a content type from the public tenant API.
|
|
40
|
+
* Returns [] when the API is unreachable so the wrapped site keeps rendering
|
|
41
|
+
* its fallbacks.
|
|
42
|
+
*/
|
|
43
|
+
export async function fetchEntries(typeName, options = {}) {
|
|
44
|
+
const apiUrl = await resolveApiUrl(options);
|
|
45
|
+
if (!apiUrl) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const url = new URL(`/entries/${typeName}`, apiUrl);
|
|
50
|
+
if (options.where) {
|
|
51
|
+
url.searchParams.set('where', JSON.stringify(options.where));
|
|
52
|
+
}
|
|
53
|
+
if (options.limit) {
|
|
54
|
+
url.searchParams.set('limit', String(options.limit));
|
|
55
|
+
}
|
|
56
|
+
const response = await fetchWithTimeout(url, 4000);
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
const body = (await response.json());
|
|
61
|
+
const rows = Array.isArray(body.data) ? body.data : [];
|
|
62
|
+
const entries = rows
|
|
63
|
+
.filter((row) => Boolean(row) && typeof row === 'object')
|
|
64
|
+
.map((row) => ({
|
|
65
|
+
id: String(row.id ?? row.documentId ?? ''),
|
|
66
|
+
data: (row.data && typeof row.data === 'object' ? row.data : row),
|
|
67
|
+
}))
|
|
68
|
+
.filter((row) => row.id.length > 0);
|
|
69
|
+
return (options.order ?? 'oldest') === 'oldest' ? entries.reverse() : entries;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function readString(value) {
|
|
76
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
77
|
+
}
|
|
78
|
+
function readNumber(value) {
|
|
79
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Load the WrappedMedia entries (key → media) seeded by the wrap migration.
|
|
83
|
+
* Each entry has a `key` (the FimoMediaImage/Video/FileLink id) and an
|
|
84
|
+
* asset-typed `image` field.
|
|
85
|
+
*/
|
|
86
|
+
export async function loadWrappedMedia(options = {}) {
|
|
87
|
+
const entries = await fetchEntries('WrappedMedia', options);
|
|
88
|
+
const map = {};
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
const key = readString(entry.data.key) ?? readString(entry.data.slug);
|
|
91
|
+
const asset = entry.data.image;
|
|
92
|
+
if (!key || !asset || typeof asset !== 'object') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const assetRecord = asset;
|
|
96
|
+
const url = readString(assetRecord.url);
|
|
97
|
+
if (!url) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
map[key] = {
|
|
101
|
+
url,
|
|
102
|
+
alt: readString(assetRecord.alt) ?? readString(entry.data.alt),
|
|
103
|
+
name: readString(assetRecord.name),
|
|
104
|
+
mime: readString(assetRecord.mime) ?? readString(assetRecord.mimeType),
|
|
105
|
+
width: readNumber(assetRecord.width),
|
|
106
|
+
height: readNumber(assetRecord.height),
|
|
107
|
+
source: `WrappedMedia.${entry.id}.image`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
return map;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Load the WrappedRichText entries (key → TipTap JSON) seeded by the wrap
|
|
114
|
+
* migration for formatted text (bold/italic/underline/links).
|
|
115
|
+
*/
|
|
116
|
+
export async function loadWrappedRichText(options = {}) {
|
|
117
|
+
const entries = await fetchEntries('WrappedRichText', options);
|
|
118
|
+
const map = {};
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const key = readString(entry.data.key) ?? readString(entry.data.slug);
|
|
121
|
+
const body = entry.data.body;
|
|
122
|
+
if (!key || !body || typeof body !== 'object') {
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
map[key] = {
|
|
126
|
+
content: body,
|
|
127
|
+
source: `WrappedRichText.${entry.id}.body`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return map;
|
|
131
|
+
}
|
|
132
|
+
/** Key fields tried, in order, to align a source row with its Fimo entry. */
|
|
133
|
+
const COLLECTION_KEY_FIELDS = ['slug', 'id', 'key', 'name', 'title', 'question'];
|
|
134
|
+
/** Fimo entries fetched per collection: covers rendered windows and small
|
|
135
|
+
* collections entirely; rows beyond it keep rendering their source values. */
|
|
136
|
+
const COLLECTION_FETCH_CAP = 100;
|
|
137
|
+
function collectionKeyField(rows) {
|
|
138
|
+
for (const field of COLLECTION_KEY_FIELDS) {
|
|
139
|
+
if (rows.every((row) => typeof row[field] === 'string' && row[field].length > 0)) {
|
|
140
|
+
return field;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
/** A Fimo value replaces its source value only when the SHAPES agree, so a
|
|
146
|
+
* page that rendered strings keeps rendering strings: media envelopes,
|
|
147
|
+
* richtext docs, reference handles, and nulls all fall back to the source
|
|
148
|
+
* value instead of leaking objects into JSX. */
|
|
149
|
+
function preferTypeMatched(fimoValue, sourceValue) {
|
|
150
|
+
if (fimoValue === null || fimoValue === undefined) {
|
|
151
|
+
return sourceValue;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(sourceValue)) {
|
|
154
|
+
if (!Array.isArray(fimoValue)) {
|
|
155
|
+
return sourceValue;
|
|
156
|
+
}
|
|
157
|
+
return fimoValue.every((element) => typeof element === typeof (sourceValue[0] ?? '') && typeof element !== 'object')
|
|
158
|
+
? fimoValue
|
|
159
|
+
: sourceValue;
|
|
160
|
+
}
|
|
161
|
+
return typeof fimoValue === typeof sourceValue && typeof fimoValue !== 'object' ? fimoValue : sourceValue;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Overlay a wrap-migrated collection onto its retained source rows: fetch the
|
|
165
|
+
* Fimo entries matching the source rows' keys, merge field-by-field with the
|
|
166
|
+
* type-matched rule above, and return plain data rows in SOURCE order. The
|
|
167
|
+
* page keeps its original render logic and its original fallback — edits made
|
|
168
|
+
* in Fimo show up, everything else stays byte-identical.
|
|
169
|
+
*/
|
|
170
|
+
export async function wrapCollection(typeName, sourceRows, options = {}) {
|
|
171
|
+
const rows = sourceRows;
|
|
172
|
+
if (rows.length === 0) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const keyField = options.keyField ?? collectionKeyField(rows);
|
|
176
|
+
if (!keyField) {
|
|
177
|
+
return sourceRows;
|
|
178
|
+
}
|
|
179
|
+
const keys = rows.slice(0, COLLECTION_FETCH_CAP).map((row) => row[keyField]);
|
|
180
|
+
const entries = await fetchEntries(typeName, {
|
|
181
|
+
...options,
|
|
182
|
+
where: { [keyField]: { $in: keys } },
|
|
183
|
+
limit: keys.length,
|
|
184
|
+
});
|
|
185
|
+
if (entries.length === 0) {
|
|
186
|
+
return sourceRows;
|
|
187
|
+
}
|
|
188
|
+
const byKey = new Map();
|
|
189
|
+
for (const entry of entries) {
|
|
190
|
+
const key = entry.data[keyField];
|
|
191
|
+
if (typeof key === 'string' && !byKey.has(key)) {
|
|
192
|
+
byKey.set(key, entry.data);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return rows.map((row) => {
|
|
196
|
+
const fimoRow = byKey.get(row[keyField]);
|
|
197
|
+
if (!fimoRow) {
|
|
198
|
+
return row;
|
|
199
|
+
}
|
|
200
|
+
const merged = { ...row };
|
|
201
|
+
for (const field of Object.keys(row)) {
|
|
202
|
+
merged[field] = preferTypeMatched(fimoRow[field], row[field]);
|
|
203
|
+
}
|
|
204
|
+
return merged;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Load a locale's messages from Fimo for an EXISTING i18n runtime (next-intl,
|
|
209
|
+
* i18next). Wrap-mode i18n ingestion seeds the app's message store into Fimo
|
|
210
|
+
* labels (flat dot-keys); this fetches them back and un-flattens them into the
|
|
211
|
+
* nested shape the app's own i18n loader expects — so the app keeps its
|
|
212
|
+
* `useTranslations`/`t()` unchanged, now editable from Fimo Studio.
|
|
213
|
+
*
|
|
214
|
+
* Deep-merge over the shipped messages (pass them as `fallback`) so any key
|
|
215
|
+
* not yet in Fimo falls back to the static file instead of showing a missing
|
|
216
|
+
* key. Wire it in the app's message loader, e.g. next-intl `i18n/request.ts`:
|
|
217
|
+
*
|
|
218
|
+
* const messages = await loadFimoMessages(locale, { fallback: staticMessages });
|
|
219
|
+
* return { locale, messages };
|
|
220
|
+
*/
|
|
221
|
+
export async function loadFimoMessages(locale, options = {}) {
|
|
222
|
+
const fallback = options.fallback ?? {};
|
|
223
|
+
const apiUrl = await resolveApiUrl(options);
|
|
224
|
+
if (!apiUrl) {
|
|
225
|
+
return fallback;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
const { labels } = await fetchLabelDictionary(apiUrl, locale);
|
|
229
|
+
if (Object.keys(labels).length === 0) {
|
|
230
|
+
return fallback;
|
|
231
|
+
}
|
|
232
|
+
return deepMergeMessages(fallback, unflattenMessages(labels));
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
return fallback;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { KeyAllocator, type ApplyWrapResult } from './apply-wrap.js';
|
|
2
|
+
import { type WrappableItem } from './scan-wrappable.js';
|
|
3
|
+
/**
|
|
4
|
+
* The template half of the --apply codemod: wraps plain TEMPLATE text nodes
|
|
5
|
+
* with the framework's t() form and injects the per-file wiring head once.
|
|
6
|
+
* Framework-neutral by contract — everything framework-specific arrives via
|
|
7
|
+
* a TemplateWrapStyle from the framework registry (frameworks/discover.ts).
|
|
8
|
+
*
|
|
9
|
+
* Grown out of the batch-dogfood plan-driven wrapper (392/392 items on a
|
|
10
|
+
* 201-route site); the lessons it encodes:
|
|
11
|
+
* - heads go in the INSTANCE script, never a `<script module>` block;
|
|
12
|
+
* - files that already bind a `t` identifier (i18n libraries, custom
|
|
13
|
+
* helpers) are left whole for the agent — renaming foreign t() calls
|
|
14
|
+
* broke a real site;
|
|
15
|
+
* - label VALUES decode HTML entities while the inline fallback keeps the
|
|
16
|
+
* source form, so rendering never changes;
|
|
17
|
+
* - keys are collision-safe via the shared KeyAllocator.
|
|
18
|
+
*
|
|
19
|
+
* Anything that is not a plain text node (attributes, media, markup the
|
|
20
|
+
* preview matcher cannot anchor) stays in the plan for the agent — same
|
|
21
|
+
* philosophy as the JSX codemod: provably safe or hands off.
|
|
22
|
+
*/
|
|
23
|
+
export interface TemplateWrapStyle {
|
|
24
|
+
/** File extension this style owns, e.g. '.astro'. */
|
|
25
|
+
ext: string;
|
|
26
|
+
/** Wiring lines injected once per file (the t() head). */
|
|
27
|
+
head: string;
|
|
28
|
+
/** Marker proving our wiring (or an equivalent manual one) is present. */
|
|
29
|
+
headMarker: RegExp;
|
|
30
|
+
/** t() delimiters around `"key", "fallback"`. */
|
|
31
|
+
open: string;
|
|
32
|
+
close: string;
|
|
33
|
+
/**
|
|
34
|
+
* Insert the head into a file. Return null when the file has no safe
|
|
35
|
+
* injection point (e.g. an options-API script) — its items stay agent work.
|
|
36
|
+
*/
|
|
37
|
+
injectHead: (code: string, head: string) => string | null;
|
|
38
|
+
/** Override the key scope for route-convention filenames (+page.svelte). */
|
|
39
|
+
fileScope?: (file: string) => string | null;
|
|
40
|
+
/** One-time project wiring once any file was wrapped. Returns files written. */
|
|
41
|
+
ensureWiring?: (rootDir: string) => string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shared injection helper for SFC-style templates: first instance script,
|
|
45
|
+
* never a `<script module>` block (module scope cannot read page data), or a
|
|
46
|
+
* fresh script when the file has none.
|
|
47
|
+
*/
|
|
48
|
+
export declare function injectIntoInstanceScript(code: string, head: string, scriptTag: string): string;
|
|
49
|
+
export declare function applyTemplateWrapToFile(code: string, style: TemplateWrapStyle, items: WrappableItem[], allocator: KeyAllocator): {
|
|
50
|
+
code: string;
|
|
51
|
+
} & Omit<ApplyWrapResult, 'filesChanged'>;
|
|
52
|
+
/**
|
|
53
|
+
* Apply the template wraps across a project — same contract as applyWrap:
|
|
54
|
+
* scans, mutates source files, returns the generated labels for staging and
|
|
55
|
+
* seeding. The style's ensureWiring hook then writes any one-time project
|
|
56
|
+
* wiring (e.g. a root layout labels loader) when files were wrapped.
|
|
57
|
+
*/
|
|
58
|
+
export declare function applyTemplateWrap(rootDir: string, opts: {
|
|
59
|
+
style: TemplateWrapStyle;
|
|
60
|
+
excludePrefixes?: string[];
|
|
61
|
+
}): ApplyWrapResult;
|