fimo 0.11.0-staging.5 → 0.11.0-staging.7
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/agent-templates/content-translator/GOAL.md +2 -2
- package/assets/agent-templates/seo-audit/config.yaml +1 -1
- package/assets/skills/fimo/SKILL.md +1 -0
- package/assets/skills/fimo-cli/SKILL.md +10 -5
- package/assets/skills/fimo-cli/references/agents.md +63 -18
- package/assets/skills/fimo-cli/references/credits.md +1 -1
- package/assets/skills/fimo-cli/references/deploy.md +1 -1
- package/assets/skills/fimo-cli/references/describe.md +1 -1
- package/assets/skills/fimo-cli/references/history.md +1 -1
- package/assets/skills/fimo-cli/references/init.md +51 -16
- package/assets/skills/fimo-cli/references/integrations/firecrawl.md +12 -3
- package/assets/skills/fimo-cli/references/integrations.md +15 -3
- package/assets/skills/fimo-cli/references/invite.md +1 -1
- package/assets/skills/fimo-cli/references/labels.md +1 -1
- package/assets/skills/fimo-cli/references/locales.md +4 -4
- package/assets/skills/fimo-cli/references/mcp.md +85 -0
- package/assets/skills/fimo-cli/references/project-vars-and-secrets.md +1 -1
- package/assets/skills/fimo-cli/references/projects.md +7 -7
- package/assets/skills/fimo-code/references/labels.md +2 -2
- package/assets/skills/fimo-code/references/locales.md +5 -5
- package/assets/skills/fimo-code/references/setup-plain-vite.md +4 -4
- package/assets/skills/fimo-code/references/setup-react-router.md +5 -5
- package/assets/skills/fimo-code/references/ui.md +1 -1
- package/assets/skills/fimo-migration/references/workflow.md +1 -1
- package/assets/skills/fimo-migration/scripts/build-canonical-manifest.mjs +32 -3
- package/assets/starters/fimo-site/manifest.json +64 -0
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/GOAL.md +8 -0
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/capabilities.yaml +2 -0
- package/assets/starters/fimo-site/template/.fimo/agents/content-guide/config.yaml +9 -0
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/GOAL.md +14 -0
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/capabilities.yaml +4 -0
- package/assets/starters/fimo-site/template/.fimo/agents/site-review/config.yaml +9 -0
- package/assets/starters/fimo-site/template/src/forms/contact.json +14 -0
- package/assets/starters/fimo-site/template/src/forms/contact.ts +46 -0
- package/assets/starters/fimo-site/template/src/pages/Contact.tsx +193 -0
- package/assets/starters/fimo-site/template/src/pages/Index.tsx +27 -0
- package/assets/starters/fimo-site/template/src/pages/SiteContent.tsx +191 -0
- package/assets/starters/fimo-site/template/src/schemas/HomePage.json +28 -0
- package/assets/starters/fimo-site/template/src/schemas/HomePage.ts +29 -0
- package/assets/starters/fimo-site/template/src/schemas/HomePage.types.ts +64 -0
- package/dist/build/vite/index.d.ts +3 -3
- package/dist/build/vite/plugins/fimo-config.d.ts +2 -2
- package/dist/build/vite/plugins/fimo-config.js +5 -5
- package/dist/build/vite/plugins/robots.d.ts +1 -1
- package/dist/build/vite/plugins/robots.js +3 -3
- package/dist/build/vite/plugins/server-config.js +1 -1
- package/dist/build/vite/plugins/sitemap.js +2 -2
- package/dist/build/vite/plugins/translations.js +14 -10
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +130 -115
- package/dist/runtime/app/prefetch.d.ts +1 -1
- package/dist/runtime/app/prefetch.js +2 -2
- package/dist/runtime/paths/get-fimo-paths.d.ts +1 -1
- package/dist/runtime/paths/get-fimo-paths.js +2 -2
- package/dist/runtime/paths/types.d.ts +1 -1
- package/dist/runtime/project-layout.d.ts +14 -0
- package/dist/runtime/project-layout.js +57 -0
- package/dist/runtime/seo/resolver.d.ts +1 -1
- package/dist/runtime/seo/resolver.js +1 -1
- package/dist/runtime/shared/fimo-config.server.d.ts +2 -0
- package/dist/runtime/shared/fimo-config.server.js +17 -4
- package/dist/runtime/templates.d.ts +4 -4
- package/dist/runtime/templates.js +11 -6
- package/dist/scripts/validate-route-metadata.js +8 -6
- package/package.json +7 -1
- package/templates/react-router/AGENTS.md +1 -1
- package/templates/react-router/package.json +1 -1
- /package/templates/react-router/{fimo-config.json → .fimo/config.json} +0 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import HomePageContent from '@/schemas/HomePage';
|
|
2
|
+
|
|
3
|
+
import { Contact } from './Contact';
|
|
4
|
+
import { ContentFallback, Hero, SiteHeader, Stories } from './SiteContent';
|
|
5
|
+
|
|
6
|
+
const Index = () => {
|
|
7
|
+
const { data: page, isLoading } = HomePageContent.useGet();
|
|
8
|
+
|
|
9
|
+
return (
|
|
10
|
+
<div className="bg-background text-foreground min-h-screen">
|
|
11
|
+
<SiteHeader />
|
|
12
|
+
<main>
|
|
13
|
+
{page ? (
|
|
14
|
+
<>
|
|
15
|
+
<Hero page={page} />
|
|
16
|
+
<Stories items={page.featured ?? []} />
|
|
17
|
+
</>
|
|
18
|
+
) : (
|
|
19
|
+
<ContentFallback loading={isLoading} />
|
|
20
|
+
)}
|
|
21
|
+
<Contact page={page} loading={isLoading} />
|
|
22
|
+
</main>
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default Index;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { RichText, Text, useLabels } from 'fimo/ui';
|
|
2
|
+
import { ArrowDown, ArrowUpRight } from 'lucide-react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Skeleton } from '@/components/ui/skeleton';
|
|
6
|
+
import { cn } from '@/lib/utils';
|
|
7
|
+
import type { HomePage } from '@/schemas/HomePage';
|
|
8
|
+
|
|
9
|
+
export function SiteHeader() {
|
|
10
|
+
const { t } = useLabels();
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<header className="border-border/70 bg-background/85 sticky top-0 z-30 border-b backdrop-blur">
|
|
14
|
+
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between gap-6 px-6 lg:px-10">
|
|
15
|
+
<Mark />
|
|
16
|
+
<nav className="flex min-w-0 items-center gap-5">
|
|
17
|
+
<a
|
|
18
|
+
href="#stories"
|
|
19
|
+
className="text-muted-foreground hover:text-foreground hidden truncate text-sm transition-colors sm:inline"
|
|
20
|
+
>
|
|
21
|
+
<Text value={t('nav.stories')} />
|
|
22
|
+
</a>
|
|
23
|
+
<Button asChild size="sm" variant="outline" className="min-w-0 shrink">
|
|
24
|
+
<a href="#contact">
|
|
25
|
+
<Text value={t('nav.contact')} className="truncate" />
|
|
26
|
+
</a>
|
|
27
|
+
</Button>
|
|
28
|
+
</nav>
|
|
29
|
+
</div>
|
|
30
|
+
</header>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Hero({ page }: { page: HomePage }) {
|
|
35
|
+
const { t } = useLabels();
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<section className="border-border/70 border-b">
|
|
39
|
+
<div className="mx-auto w-full max-w-6xl px-6 py-20 lg:px-10 lg:py-32">
|
|
40
|
+
<p className="text-muted-foreground flex items-center gap-4 text-[11px] font-semibold tracking-[0.24em] uppercase">
|
|
41
|
+
<span aria-hidden className="bg-primary h-px w-10 shrink-0" />
|
|
42
|
+
<Text value={page.eyebrow} />
|
|
43
|
+
</p>
|
|
44
|
+
|
|
45
|
+
<div className="mt-10 grid gap-12 lg:mt-14 lg:grid-cols-12 lg:gap-16">
|
|
46
|
+
<Text
|
|
47
|
+
value={page.title}
|
|
48
|
+
as="h1"
|
|
49
|
+
className="text-5xl leading-[0.95] font-semibold tracking-[-0.04em] text-balance sm:text-6xl lg:col-span-7 lg:text-7xl"
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
<div className="lg:col-span-5 lg:pt-3">
|
|
53
|
+
<RichText
|
|
54
|
+
value={page.intro}
|
|
55
|
+
className="text-muted-foreground max-w-md text-lg leading-relaxed [&>p+p]:mt-4"
|
|
56
|
+
/>
|
|
57
|
+
|
|
58
|
+
<div className="mt-10 flex flex-wrap items-center gap-x-8 gap-y-4">
|
|
59
|
+
<Button asChild size="lg" className="group">
|
|
60
|
+
<a href={String(page.primaryCtaHref)}>
|
|
61
|
+
<Text value={page.primaryCtaLabel} />
|
|
62
|
+
<ArrowUpRight className="transition-transform group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
|
|
63
|
+
</a>
|
|
64
|
+
</Button>
|
|
65
|
+
<a
|
|
66
|
+
href="#stories"
|
|
67
|
+
className="text-muted-foreground hover:text-foreground group inline-flex items-center gap-2 text-sm transition-colors"
|
|
68
|
+
>
|
|
69
|
+
<Text value={t('hero.scroll')} />
|
|
70
|
+
<ArrowDown className="size-4 transition-transform group-hover:translate-y-0.5" />
|
|
71
|
+
</a>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</section>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function Stories({ items }: { items: HomePage['featured'] }) {
|
|
81
|
+
const { t } = useLabels();
|
|
82
|
+
|
|
83
|
+
if (items.length === 0) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<section id="stories" aria-labelledby="stories-title" className="border-border/70 scroll-mt-16 border-b">
|
|
89
|
+
<div className="mx-auto w-full max-w-6xl px-6 lg:px-10">
|
|
90
|
+
<Text
|
|
91
|
+
value={t('nav.stories')}
|
|
92
|
+
as="h2"
|
|
93
|
+
id="stories-title"
|
|
94
|
+
className="text-muted-foreground pt-16 text-[11px] font-semibold tracking-[0.24em] uppercase lg:pt-24"
|
|
95
|
+
/>
|
|
96
|
+
<ul className="divide-border/70 border-border/70 mt-8 divide-y border-t">
|
|
97
|
+
{items.map((item, index) => (
|
|
98
|
+
<li key={item._key}>
|
|
99
|
+
<a
|
|
100
|
+
href={item.href}
|
|
101
|
+
className="focus-visible:ring-ring/50 group grid gap-5 rounded-md py-10 outline-none focus-visible:ring-[3px] md:grid-cols-12 md:gap-8 md:py-14"
|
|
102
|
+
>
|
|
103
|
+
<div className="flex items-baseline gap-4 md:col-span-4">
|
|
104
|
+
<span className="text-primary font-mono text-xs tabular-nums">
|
|
105
|
+
{String(index + 1).padStart(2, '0')}
|
|
106
|
+
</span>
|
|
107
|
+
<Text value={item.label} className="text-sm font-medium" />
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<div className="flex items-start gap-6 md:col-span-8">
|
|
111
|
+
<div className="min-w-0 transition-transform duration-300 group-hover:translate-x-1">
|
|
112
|
+
<Text
|
|
113
|
+
value={item.title}
|
|
114
|
+
as="h3"
|
|
115
|
+
className="text-2xl font-semibold tracking-[-0.02em] text-balance md:text-3xl"
|
|
116
|
+
/>
|
|
117
|
+
<Text
|
|
118
|
+
value={item.summary}
|
|
119
|
+
as="p"
|
|
120
|
+
className="text-muted-foreground mt-3 max-w-xl leading-relaxed whitespace-pre-line"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
<ArrowUpRight className="text-muted-foreground/40 group-hover:text-foreground ml-auto size-5 shrink-0 transition-all duration-300 group-hover:translate-x-1 group-hover:-translate-y-1" />
|
|
124
|
+
</div>
|
|
125
|
+
</a>
|
|
126
|
+
</li>
|
|
127
|
+
))}
|
|
128
|
+
</ul>
|
|
129
|
+
</div>
|
|
130
|
+
</section>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function ContentFallback({ loading }: { loading: boolean }) {
|
|
135
|
+
return (
|
|
136
|
+
<>
|
|
137
|
+
<section aria-hidden className="border-border/70 border-b">
|
|
138
|
+
<div className="mx-auto w-full max-w-6xl px-6 py-20 lg:px-10 lg:py-32">
|
|
139
|
+
<Bar loading={loading} className="h-3 w-40" />
|
|
140
|
+
<div className="mt-10 grid gap-12 lg:mt-14 lg:grid-cols-12 lg:gap-16">
|
|
141
|
+
<div className="grid gap-4 lg:col-span-7">
|
|
142
|
+
<Bar loading={loading} className="h-14 w-full lg:h-16" />
|
|
143
|
+
<Bar loading={loading} className="h-14 w-5/6 lg:h-16" />
|
|
144
|
+
<Bar loading={loading} className="h-14 w-2/3 lg:h-16" />
|
|
145
|
+
</div>
|
|
146
|
+
<div className="grid gap-3 lg:col-span-5 lg:pt-3">
|
|
147
|
+
<Bar loading={loading} className="h-4 w-full max-w-md" />
|
|
148
|
+
<Bar loading={loading} className="h-4 w-11/12 max-w-md" />
|
|
149
|
+
<Bar loading={loading} className="h-4 w-3/4 max-w-md" />
|
|
150
|
+
<Bar loading={loading} className="mt-7 h-10 w-48" />
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</section>
|
|
155
|
+
|
|
156
|
+
<section aria-hidden className="border-border/70 border-b">
|
|
157
|
+
<div className="mx-auto w-full max-w-6xl px-6 lg:px-10">
|
|
158
|
+
<Bar loading={loading} className="mt-16 h-3 w-28 lg:mt-24" />
|
|
159
|
+
<ul className="divide-border/70 border-border/70 mt-8 divide-y border-t">
|
|
160
|
+
{[0, 1, 2].map((row) => (
|
|
161
|
+
<li key={row} className="grid gap-5 py-10 md:grid-cols-12 md:gap-8 md:py-14">
|
|
162
|
+
<Bar loading={loading} className="h-4 w-32 md:col-span-4" />
|
|
163
|
+
<div className="grid gap-3 md:col-span-8">
|
|
164
|
+
<Bar loading={loading} className="h-8 w-3/4" />
|
|
165
|
+
<Bar loading={loading} className="h-4 w-full max-w-xl" />
|
|
166
|
+
<Bar loading={loading} className="h-4 w-2/3 max-w-xl" />
|
|
167
|
+
</div>
|
|
168
|
+
</li>
|
|
169
|
+
))}
|
|
170
|
+
</ul>
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
</>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Pulses while the request is in flight; settles into a static block once it is not. */
|
|
178
|
+
export function Bar({ loading, className }: { loading: boolean; className: string }) {
|
|
179
|
+
return loading ? <Skeleton className={className} /> : <div className={cn('bg-muted rounded-md', className)} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function Mark() {
|
|
183
|
+
return (
|
|
184
|
+
<span aria-hidden className="grid size-5 shrink-0 grid-cols-2 gap-[3px]">
|
|
185
|
+
<span className="bg-foreground rounded-[2px]" />
|
|
186
|
+
<span className="bg-foreground/25 rounded-[2px]" />
|
|
187
|
+
<span className="bg-foreground/25 rounded-[2px]" />
|
|
188
|
+
<span className="bg-primary rounded-[2px]" />
|
|
189
|
+
</span>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"uid": "HomePage",
|
|
3
|
+
"name": "Home page",
|
|
4
|
+
"isSingleton": true,
|
|
5
|
+
"i18n": { "enabled": false },
|
|
6
|
+
"fields": {
|
|
7
|
+
"eyebrow": { "type": "string", "required": true },
|
|
8
|
+
"title": { "type": "string", "required": true },
|
|
9
|
+
"intro": { "type": "richtext", "required": true },
|
|
10
|
+
"primaryCtaLabel": { "type": "string", "required": true },
|
|
11
|
+
"primaryCtaHref": { "type": "string", "required": true },
|
|
12
|
+
"featured": {
|
|
13
|
+
"type": "blocks",
|
|
14
|
+
"min": 1,
|
|
15
|
+
"max": 3,
|
|
16
|
+
"schema": {
|
|
17
|
+
"fields": {
|
|
18
|
+
"label": { "type": "string", "required": true },
|
|
19
|
+
"title": { "type": "string", "required": true },
|
|
20
|
+
"summary": { "type": "text", "required": true },
|
|
21
|
+
"href": { "type": "string", "required": true }
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"contactHeading": { "type": "string", "required": true },
|
|
26
|
+
"contactIntro": { "type": "text", "required": true }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// AUTO-GENERATED by `fimo` from src/schemas/HomePage.json - DO NOT EDIT.
|
|
2
|
+
// Change content fields in the schema, then run `fimo validate`.
|
|
3
|
+
// Runtime implementation lives in the project-local fimo/ui package.
|
|
4
|
+
import { createSingletonClient } from 'fimo/ui';
|
|
5
|
+
import type { ContentSchema, SingletonContentClient } from 'fimo/ui';
|
|
6
|
+
|
|
7
|
+
import schema from './HomePage.json';
|
|
8
|
+
import type { HomePage, HomePageInput } from './HomePage.types';
|
|
9
|
+
|
|
10
|
+
export type {
|
|
11
|
+
HomePage,
|
|
12
|
+
HomePageFields,
|
|
13
|
+
HomePageFimoMeta,
|
|
14
|
+
HomePageInput,
|
|
15
|
+
HomePagePopulate,
|
|
16
|
+
HomePageQuery,
|
|
17
|
+
HomePageReferences,
|
|
18
|
+
HomePageResult,
|
|
19
|
+
HomePageSort,
|
|
20
|
+
HomePageWhere,
|
|
21
|
+
} from './HomePage.types';
|
|
22
|
+
|
|
23
|
+
const client: SingletonContentClient<HomePage, HomePageInput> = createSingletonClient<HomePage, HomePageInput>({
|
|
24
|
+
schema: schema as unknown as ContentSchema,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const { get, update, useGet, useSuspenseGet, useUpdate, wrapWithSource, meta } = client;
|
|
28
|
+
|
|
29
|
+
export default client;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// AUTO-GENERATED ⚠️ – do not edit by hand
|
|
2
|
+
import {
|
|
3
|
+
type Fields,
|
|
4
|
+
type FimoRichText,
|
|
5
|
+
type FimoRichTextContent,
|
|
6
|
+
type FimoString,
|
|
7
|
+
type Populate,
|
|
8
|
+
type PopulatedResult,
|
|
9
|
+
type ProjectedWithPopulate,
|
|
10
|
+
type Query,
|
|
11
|
+
type Sort,
|
|
12
|
+
type Where,
|
|
13
|
+
type WithReferences,
|
|
14
|
+
} from 'fimo/ui';
|
|
15
|
+
|
|
16
|
+
export interface HomePageFimoMeta {
|
|
17
|
+
contentType: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface HomePage extends WithReferences<HomePageReferences> {
|
|
21
|
+
id: string;
|
|
22
|
+
entryId: string;
|
|
23
|
+
documentId: string;
|
|
24
|
+
slug: FimoString;
|
|
25
|
+
locale?: string;
|
|
26
|
+
createdAt: string;
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
__fimo: HomePageFimoMeta;
|
|
29
|
+
eyebrow: FimoString;
|
|
30
|
+
title: FimoString;
|
|
31
|
+
intro: FimoRichText;
|
|
32
|
+
primaryCtaLabel: FimoString;
|
|
33
|
+
primaryCtaHref: FimoString;
|
|
34
|
+
featured: { _key: string; label: string; title: string; summary: string; href: string }[];
|
|
35
|
+
contactHeading: FimoString;
|
|
36
|
+
contactIntro: FimoString;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface HomePageInput {
|
|
40
|
+
slug?: string;
|
|
41
|
+
eyebrow: string;
|
|
42
|
+
title: string;
|
|
43
|
+
intro: FimoRichTextContent;
|
|
44
|
+
primaryCtaLabel: string;
|
|
45
|
+
primaryCtaHref: string;
|
|
46
|
+
featured: { _key?: string; label: string; title: string; summary: string; href: string }[];
|
|
47
|
+
contactHeading: string;
|
|
48
|
+
contactIntro: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface HomePageReferences {}
|
|
52
|
+
|
|
53
|
+
export type HomePagePopulate = Populate<HomePage>;
|
|
54
|
+
export type HomePageWhere = Where<HomePage>;
|
|
55
|
+
export type HomePageSort = Sort<HomePage>;
|
|
56
|
+
export type HomePageFields = Fields<HomePage>;
|
|
57
|
+
export type HomePageQuery<
|
|
58
|
+
TFields extends HomePageFields | undefined = HomePageFields | undefined,
|
|
59
|
+
TPopulate extends HomePagePopulate | undefined = HomePagePopulate | undefined,
|
|
60
|
+
> = Query<HomePage, TFields, TPopulate>;
|
|
61
|
+
export type HomePageResult<
|
|
62
|
+
TFields extends HomePageFields | undefined = undefined,
|
|
63
|
+
TPopulate extends HomePagePopulate | undefined = undefined,
|
|
64
|
+
> = ProjectedWithPopulate<PopulatedResult<HomePage, TPopulate>, TFields, TPopulate>;
|
|
@@ -2,7 +2,7 @@ import type { PluginOption } from 'vite';
|
|
|
2
2
|
export interface FimoViteOptions {
|
|
3
3
|
/**
|
|
4
4
|
* Emit `sitemap.xml` at build (and serve at `/sitemap.xml` in dev). Reads
|
|
5
|
-
* `seo.url` from
|
|
5
|
+
* `seo.url` from `.fimo/config.json` as the base URL.
|
|
6
6
|
*
|
|
7
7
|
* Opt-in via `fimo({ sitemap: true })`.
|
|
8
8
|
*/
|
|
@@ -10,14 +10,14 @@ export interface FimoViteOptions {
|
|
|
10
10
|
/**
|
|
11
11
|
* Emit `robots.txt` at build (and serve at `/robots.txt` in dev). Defaults
|
|
12
12
|
* to an allow-all robots policy + a `Sitemap:` directive when `seo.url` is
|
|
13
|
-
* set in
|
|
13
|
+
* set in `.fimo/config.json`.
|
|
14
14
|
*
|
|
15
15
|
* Opt-in via `fimo({ robots: true })`.
|
|
16
16
|
*/
|
|
17
17
|
robots?: boolean;
|
|
18
18
|
/**
|
|
19
19
|
* Inject SEO meta tags into `index.html` via `transformIndexHtml`. Resolves
|
|
20
|
-
* per-route SEO from
|
|
20
|
+
* per-route SEO from `.fimo/config.json#seo` and the matched route.
|
|
21
21
|
*
|
|
22
22
|
* Only applies in projects that have an `index.html` (plain Vite). Conflicts
|
|
23
23
|
* with frameworks that have their own SEO mechanism — React Router's `meta`
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
/**
|
|
3
|
-
* Vite plugin that exposes the user's
|
|
3
|
+
* Vite plugin that exposes the user's `.fimo/config.json`:
|
|
4
4
|
* - As a virtual module `virtual:fimo-config` that browser-running package
|
|
5
5
|
* code (fimo/seo, fimo/app) imports to read `seo`, `routes`, etc. without
|
|
6
6
|
* relying on `readFileSync` at runtime.
|
|
7
7
|
*
|
|
8
|
-
* Edits to
|
|
8
|
+
* Edits to the project config invalidate the virtual module so HMR picks up
|
|
9
9
|
* the new values without a full reload.
|
|
10
10
|
*/
|
|
11
11
|
export default function fimoConfigPlugin(): Plugin;
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveFimoConfigPath } from '../../../runtime/shared/fimo-config.server.js';
|
|
3
3
|
const VIRTUAL_ID = 'virtual:fimo-config';
|
|
4
4
|
const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
5
5
|
/**
|
|
6
|
-
* Vite plugin that exposes the user's
|
|
6
|
+
* Vite plugin that exposes the user's `.fimo/config.json`:
|
|
7
7
|
* - As a virtual module `virtual:fimo-config` that browser-running package
|
|
8
8
|
* code (fimo/seo, fimo/app) imports to read `seo`, `routes`, etc. without
|
|
9
9
|
* relying on `readFileSync` at runtime.
|
|
10
10
|
*
|
|
11
|
-
* Edits to
|
|
11
|
+
* Edits to the project config invalidate the virtual module so HMR picks up
|
|
12
12
|
* the new values without a full reload.
|
|
13
13
|
*/
|
|
14
14
|
export default function fimoConfigPlugin() {
|
|
15
|
-
let configPath =
|
|
15
|
+
let configPath = resolveFimoConfigPath(process.cwd());
|
|
16
16
|
return {
|
|
17
17
|
name: 'vite-plugin-fimo-config',
|
|
18
18
|
config(userConfig) {
|
|
19
19
|
const root = typeof userConfig.root === 'string' ? userConfig.root : process.cwd();
|
|
20
|
-
configPath =
|
|
20
|
+
configPath = resolveFimoConfigPath(root);
|
|
21
21
|
},
|
|
22
22
|
resolveId(id) {
|
|
23
23
|
if (id === VIRTUAL_ID) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
2
|
/**
|
|
3
3
|
* Emit a minimal `robots.txt` (allow-all + Sitemap reference if `seo.url` is
|
|
4
|
-
* set in
|
|
4
|
+
* set in `.fimo/config.json`). Serves at `/robots.txt` in dev; emits as a
|
|
5
5
|
* static asset at build time.
|
|
6
6
|
*
|
|
7
7
|
* Opt-in via `fimo({ robots: true })` in your `vite.config.ts`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
-
import {
|
|
2
|
+
import { resolveFimoConfigPath } from '../../../runtime/shared/fimo-config.server.js';
|
|
3
3
|
function readSeoUrl() {
|
|
4
4
|
try {
|
|
5
|
-
const raw = readFileSync(
|
|
5
|
+
const raw = readFileSync(resolveFimoConfigPath(process.cwd()), 'utf-8');
|
|
6
6
|
const config = JSON.parse(raw);
|
|
7
7
|
return config.seo?.url;
|
|
8
8
|
}
|
|
@@ -25,7 +25,7 @@ function buildRobotsTxt(baseUrl) {
|
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Emit a minimal `robots.txt` (allow-all + Sitemap reference if `seo.url` is
|
|
28
|
-
* set in
|
|
28
|
+
* set in `.fimo/config.json`). Serves at `/robots.txt` in dev; emits as a
|
|
29
29
|
* static asset at build time.
|
|
30
30
|
*
|
|
31
31
|
* Opt-in via `fimo({ robots: true })` in your `vite.config.ts`.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
2
|
import { getFimoPaths } from '../../../runtime/paths/get-fimo-paths.js';
|
|
3
|
+
import { resolveFimoConfigPath } from '../../../runtime/shared/fimo-config.server.js';
|
|
4
4
|
function readSeoUrl() {
|
|
5
5
|
try {
|
|
6
|
-
const raw = readFileSync(
|
|
6
|
+
const raw = readFileSync(resolveFimoConfigPath(process.cwd()), 'utf-8');
|
|
7
7
|
const config = JSON.parse(raw);
|
|
8
8
|
return config.seo?.url;
|
|
9
9
|
}
|
|
@@ -4,7 +4,8 @@ import { readFile } from 'node:fs/promises';
|
|
|
4
4
|
import { homedir } from 'node:os';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { getFimoDefaultLocale, getFimoLocales } from '../../../runtime/i18n/locale.js';
|
|
7
|
-
import {
|
|
7
|
+
import { CURRENT_PROJECT_LAYOUT, LEGACY_PROJECT_LAYOUT } from '../../../runtime/project-layout.js';
|
|
8
|
+
import { getConfigServer, resolveFimoConfigPath } from '../../../runtime/shared/fimo-config.server.js';
|
|
8
9
|
const VIRTUAL_ID = 'virtual:translations';
|
|
9
10
|
const RESOLVED_ID = '\0' + VIRTUAL_ID;
|
|
10
11
|
const EVENT_RECONNECT_DELAY_MS = 2000;
|
|
@@ -155,7 +156,7 @@ export default function translationsPlugin() {
|
|
|
155
156
|
},
|
|
156
157
|
async buildStart() {
|
|
157
158
|
bundle = emptyLabelBundle(rootDir);
|
|
158
|
-
this.addWatchFile(
|
|
159
|
+
this.addWatchFile(resolveFimoConfigPath(rootDir));
|
|
159
160
|
this.addWatchFile(join(rootDir, '.env'));
|
|
160
161
|
this.addWatchFile(join(rootDir, '.env.local'));
|
|
161
162
|
if (command === 'build') {
|
|
@@ -165,7 +166,7 @@ export default function translationsPlugin() {
|
|
|
165
166
|
configureServer(server) {
|
|
166
167
|
devServer = server;
|
|
167
168
|
closed = false;
|
|
168
|
-
server.watcher.add([
|
|
169
|
+
server.watcher.add([resolveFimoConfigPath(rootDir), join(rootDir, '.env'), join(rootDir, '.env.local')]);
|
|
169
170
|
void refreshLabelsAndInvalidateIfChanged().catch(() => undefined);
|
|
170
171
|
startFallbackPolling();
|
|
171
172
|
void connectEvents();
|
|
@@ -196,7 +197,7 @@ export default function translationsPlugin() {
|
|
|
196
197
|
].join('\n');
|
|
197
198
|
},
|
|
198
199
|
async handleHotUpdate(ctx) {
|
|
199
|
-
const watched = new Set([
|
|
200
|
+
const watched = new Set([resolveFimoConfigPath(rootDir), join(rootDir, '.env'), join(rootDir, '.env.local')]);
|
|
200
201
|
if (!watched.has(ctx.file)) {
|
|
201
202
|
return;
|
|
202
203
|
}
|
|
@@ -515,13 +516,16 @@ async function readEnvFile(filePath) {
|
|
|
515
516
|
}
|
|
516
517
|
}
|
|
517
518
|
async function readProjectSettings(rootDir) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
519
|
+
for (const relativePath of [CURRENT_PROJECT_LAYOUT.project, LEGACY_PROJECT_LAYOUT.project]) {
|
|
520
|
+
try {
|
|
521
|
+
const raw = await readFile(join(rootDir, relativePath), 'utf8');
|
|
522
|
+
return JSON.parse(raw);
|
|
523
|
+
}
|
|
524
|
+
catch {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
524
527
|
}
|
|
528
|
+
return {};
|
|
525
529
|
}
|
|
526
530
|
function deriveEventsRooms(projectId, tenantApiUrl) {
|
|
527
531
|
const rooms = new Set([projectId]);
|