fimo 0.15.0-staging.2 → 0.15.0-staging.3
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/README.md +1 -0
- package/assets/content-templates/forms/framework.eta +90 -0
- package/assets/content-templates/forms/react.eta +42 -21
- package/assets/skills/fimo-cli/references/forms.md +2 -2
- package/assets/skills/fimo-cli/references/init.md +10 -8
- package/assets/skills/fimo-cli/references/projects.md +1 -1
- package/assets/skills/fimo-code/references/forms.md +24 -10
- package/assets/skills/fimo-code/references/frameworks/astro.md +9 -0
- package/assets/skills/fimo-code/references/frameworks/next.md +25 -0
- package/assets/skills/fimo-code/references/frameworks/sveltekit.md +33 -5
- package/assets/skills/fimo-code/references/labels.md +1 -1
- package/dist/build/sveltekit/config.d.ts +2 -1
- package/dist/build/sveltekit/config.js +15 -5
- package/dist/build/sveltekit/files/edge.js +68 -0
- package/dist/build/sveltekit/files/serverless.js +42 -0
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +12 -12
- package/dist/runtime/astro/server.d.ts +2 -0
- package/dist/runtime/astro/server.js +2 -0
- package/dist/runtime/forms/client.d.ts +13 -0
- package/dist/runtime/forms/client.js +83 -0
- package/dist/runtime/forms/index.d.ts +2 -0
- package/dist/runtime/forms/index.js +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/next/client.d.ts +2 -0
- package/dist/runtime/next/client.js +2 -0
- package/dist/runtime/next/server.d.ts +2 -0
- package/dist/runtime/next/server.js +2 -0
- package/dist/runtime/react-router/index.d.ts +1 -0
- package/dist/runtime/react-router/index.js +1 -0
- package/dist/runtime/sveltekit/forms.d.ts +15 -0
- package/dist/runtime/sveltekit/forms.js +15 -0
- package/dist/runtime/sveltekit/index.d.ts +2 -0
- package/dist/runtime/sveltekit/index.js +1 -0
- package/dist/runtime/sveltekit/labels.d.ts +7 -3
- package/dist/runtime/sveltekit/labels.js +1 -1
- package/dist/runtime/sveltekit/runtime.d.ts +2 -1
- package/dist/runtime/sveltekit/runtime.js +8 -2
- package/dist/runtime/templates.d.ts +1 -1
- package/dist/runtime/templates.js +1 -1
- package/dist/scripts/sync-forms.d.ts +1 -0
- package/dist/scripts/sync-forms.js +78 -26
- package/package.json +9 -4
- package/templates/astro/.fimo/config.json +21 -0
- package/templates/astro/AGENTS.md +37 -0
- package/templates/astro/CLAUDE.md +5 -0
- package/templates/astro/_gitignore +15 -0
- package/templates/astro/astro.config.mjs +7 -0
- package/templates/astro/package.json +22 -0
- package/templates/astro/pnpm-workspace.yaml +7 -0
- package/templates/astro/src/components/ProjectOverview.astro +42 -0
- package/templates/astro/src/env.d.ts +1 -0
- package/templates/astro/src/layouts/Base.astro +81 -0
- package/templates/astro/src/pages/index.astro +10 -0
- package/templates/astro/src/pages/projects/[slug].astro +26 -0
- package/templates/astro/src/pages/projects/index.astro +10 -0
- package/templates/astro/src/schemas/Project.json +9 -0
- package/templates/astro/src/schemas/Project.ts +29 -0
- package/templates/astro/src/schemas/Project.types.ts +70 -0
- package/templates/astro/src/schemas/SiteSettings.json +10 -0
- package/templates/astro/src/schemas/SiteSettings.ts +31 -0
- package/templates/astro/src/schemas/SiteSettings.types.ts +69 -0
- package/templates/astro/tsconfig.json +5 -0
- package/templates/react-router/package.json +1 -1
- package/templates/sveltekit/src/routes/+layout.server.ts +1 -2
|
@@ -7,6 +7,8 @@ export declare function createSingletonClient<T extends {
|
|
|
7
7
|
id: string;
|
|
8
8
|
}, TInput extends object>(config: ContentClientConfig): SingletonContentCore<T, TInput>;
|
|
9
9
|
export declare function getLabels(options?: GetLabelsOptions): Promise<import("./server.js").Labels>;
|
|
10
|
+
export declare const formClient: import("./server.js").FormClient;
|
|
11
|
+
export { type FormClient, type FormSubmissionResult } from '../forms/index.js';
|
|
10
12
|
export { fimo } from '../primitives/lib/template.js';
|
|
11
13
|
export type { CollectionContentCore as CollectionContentClient, SingletonContentCore as SingletonContentClient, } from '../content/client/core-types.js';
|
|
12
14
|
export type { GetLabelsOptions } from '../labels/fetch.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServerContentClient, createServerSingletonClient } from '../content/client/server-content-client.js';
|
|
2
|
+
import { createFormClient } from '../forms/index.js';
|
|
2
3
|
import { fetchLabels } from '../labels/fetch.js';
|
|
3
4
|
import { astroServerContentRuntime } from './server-runtime.js';
|
|
4
5
|
export function createContentClient(config) {
|
|
@@ -10,5 +11,6 @@ export function createSingletonClient(config) {
|
|
|
10
11
|
export function getLabels(options) {
|
|
11
12
|
return fetchLabels(astroServerContentRuntime, options);
|
|
12
13
|
}
|
|
14
|
+
export const formClient = createFormClient(astroServerContentRuntime);
|
|
13
15
|
export { fimo } from '../primitives/lib/template.js';
|
|
14
16
|
export * from '../content/index.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ContentRuntime } from '../content/client/runtime.js';
|
|
2
|
+
export interface FormSubmissionResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
status: number;
|
|
5
|
+
errors?: Record<string, string>;
|
|
6
|
+
message?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface FormClient {
|
|
9
|
+
submit<TData extends object>(formId: string, data: TData): Promise<FormSubmissionResult>;
|
|
10
|
+
}
|
|
11
|
+
export declare function submitForm<TData extends object>(formId: string, data: TData, runtime: ContentRuntime): Promise<FormSubmissionResult>;
|
|
12
|
+
export declare function createFormClient(runtime: ContentRuntime): FormClient;
|
|
13
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
function isUploadValue(value) {
|
|
2
|
+
return typeof Blob !== 'undefined' && value instanceof Blob;
|
|
3
|
+
}
|
|
4
|
+
function formHeaders(runtime) {
|
|
5
|
+
return runtime.headers({ 'Content-Type': 'application/json' });
|
|
6
|
+
}
|
|
7
|
+
async function uploadFile(file, runtime) {
|
|
8
|
+
const presignResponse = await runtime.fetch(`${runtime.apiBase()}/forms/uploads/presign`, {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: formHeaders(runtime),
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
filename: file.name ?? 'upload',
|
|
13
|
+
contentType: file.type ?? 'application/octet-stream',
|
|
14
|
+
size: file.size ?? 0,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
if (!presignResponse.ok) {
|
|
18
|
+
throw new Error(`Failed to get upload URL (${presignResponse.status})`);
|
|
19
|
+
}
|
|
20
|
+
const presignBody = (await presignResponse.json());
|
|
21
|
+
const uploadUrl = presignBody.data?.uploadUrl;
|
|
22
|
+
const fileUrl = presignBody.data?.fileUrl;
|
|
23
|
+
if (typeof uploadUrl !== 'string' || typeof fileUrl !== 'string') {
|
|
24
|
+
throw new Error('Upload service returned an invalid upload response.');
|
|
25
|
+
}
|
|
26
|
+
const uploadResponse = await globalThis.fetch(uploadUrl, {
|
|
27
|
+
method: 'PUT',
|
|
28
|
+
body: file,
|
|
29
|
+
headers: { 'Content-Type': file.type ?? 'application/octet-stream' },
|
|
30
|
+
credentials: 'omit',
|
|
31
|
+
});
|
|
32
|
+
if (!uploadResponse.ok) {
|
|
33
|
+
throw new Error(`Failed to upload file (${uploadResponse.status})`);
|
|
34
|
+
}
|
|
35
|
+
return fileUrl;
|
|
36
|
+
}
|
|
37
|
+
async function preparePayload(data, runtime) {
|
|
38
|
+
const payload = { ...data };
|
|
39
|
+
await Promise.all(Object.entries(payload).map(async ([key, value]) => {
|
|
40
|
+
if (isUploadValue(value)) {
|
|
41
|
+
payload[key] = await uploadFile(value, runtime);
|
|
42
|
+
}
|
|
43
|
+
}));
|
|
44
|
+
return payload;
|
|
45
|
+
}
|
|
46
|
+
export async function submitForm(formId, data, runtime) {
|
|
47
|
+
if (!formId || formId === 'undefined') {
|
|
48
|
+
throw new Error(`Form "${formId || 'unknown'}" has not been pushed. Run fimo forms push first.`);
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
const response = await runtime.fetch(`${runtime.apiBase()}/forms/${encodeURIComponent(formId)}/submit`, {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: formHeaders(runtime),
|
|
54
|
+
body: JSON.stringify(await preparePayload(data, runtime)),
|
|
55
|
+
});
|
|
56
|
+
let body = {};
|
|
57
|
+
try {
|
|
58
|
+
body = (await response.json());
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// A non-JSON upstream response still gets a useful status below.
|
|
62
|
+
}
|
|
63
|
+
if (response.ok) {
|
|
64
|
+
return { success: true, status: response.status, message: body.message };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
success: false,
|
|
68
|
+
status: response.status,
|
|
69
|
+
errors: body.errors,
|
|
70
|
+
message: body.message,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { success: false, status: 0 };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export function createFormClient(runtime) {
|
|
78
|
+
return {
|
|
79
|
+
submit(formId, data) {
|
|
80
|
+
return submitForm(formId, data, runtime);
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFormClient, submitForm } from './client.js';
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { parseCollectionResponse } from './content/parseCollectionResponse.js';
|
|
|
10
10
|
export type { CollectionResponse } from './content/parseCollectionResponse.js';
|
|
11
11
|
export { createContentClient } from './content/client/createContentClient.js';
|
|
12
12
|
export { createSingletonClient } from './content/client/createSingletonClient.js';
|
|
13
|
+
export { createFormClient, type FormClient, type FormSubmissionResult } from './forms/index.js';
|
|
14
|
+
export declare const formClient: import("./index.js").FormClient;
|
|
13
15
|
export type { CollectionContentClient, CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentSchema, ContentSchemaField, InfiniteListResult, SingletonContentClient, } from './content/client/types.js';
|
|
14
16
|
export { AppErrorBoundary } from './core/AppErrorBoundary.js';
|
|
15
17
|
export { ViteLifecycles } from './core/ViteLifecycles.js';
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { viteContentRuntime } from './content/client/vite-runtime.js';
|
|
2
|
+
import { createFormClient } from './forms/index.js';
|
|
1
3
|
// Routing
|
|
2
4
|
export { AppRoutesProvider } from './routing/AppRoutesProvider.js';
|
|
3
5
|
export { useLocale } from './i18n/FimoLocaleContext.js';
|
|
@@ -8,6 +10,8 @@ export { RefreshContentProvider } from './content/RefreshContentProvider.js';
|
|
|
8
10
|
export { parseCollectionResponse } from './content/parseCollectionResponse.js';
|
|
9
11
|
export { createContentClient } from './content/client/createContentClient.js';
|
|
10
12
|
export { createSingletonClient } from './content/client/createSingletonClient.js';
|
|
13
|
+
export { createFormClient } from './forms/index.js';
|
|
14
|
+
export const formClient = createFormClient(viteContentRuntime);
|
|
11
15
|
// Core
|
|
12
16
|
export { AppErrorBoundary } from './core/AppErrorBoundary.js';
|
|
13
17
|
export { ViteLifecycles } from './core/ViteLifecycles.js';
|
|
@@ -6,6 +6,8 @@ export declare function createSingletonClient<T extends {
|
|
|
6
6
|
id: string;
|
|
7
7
|
}, TInput extends object>(config: ContentClientConfig): SingletonContentClient<T, TInput>;
|
|
8
8
|
export type { CollectionContentClient, InfiniteListResult, SingletonContentClient } from '../content/client/types.js';
|
|
9
|
+
export { type FormClient, type FormSubmissionResult } from '../forms/index.js';
|
|
10
|
+
export declare const formClient: import("./client.js").FormClient;
|
|
9
11
|
export { createContentCore, createSingletonCore } from '../content/index.js';
|
|
10
12
|
export type { CollectionContentCore, CollectionPage, CollectionPageInfo, ContentClientConfig, ContentClientHandle, ContentModuleMeta, ContentModuleRef, ContentReadOptions, ContentResult, ContentRuntime, ContentSchema, ContentSchemaField, SingletonContentCore, } from '../content/index.js';
|
|
11
13
|
export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../content/index.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { createReactContentClient } from '../content/client/react-content-client.js';
|
|
3
3
|
import { createReactSingletonClient } from '../content/client/react-singleton-client.js';
|
|
4
|
+
import { createFormClient } from '../forms/index.js';
|
|
4
5
|
import { nextClientContentRuntime } from './client-runtime.js';
|
|
5
6
|
import { useNextContentLocale } from './provider.js';
|
|
6
7
|
export function createContentClient(config) {
|
|
@@ -9,6 +10,7 @@ export function createContentClient(config) {
|
|
|
9
10
|
export function createSingletonClient(config) {
|
|
10
11
|
return createReactSingletonClient(config, nextClientContentRuntime, useNextContentLocale);
|
|
11
12
|
}
|
|
13
|
+
export const formClient = createFormClient(nextClientContentRuntime);
|
|
12
14
|
export { createContentCore, createSingletonCore } from '../content/index.js';
|
|
13
15
|
export { FIMO_PARTS, FIMO_SOURCE, FimoBoolean, FimoDate, FimoMedia, FimoRichText, FimoString, getFimoParts, getFimoSource, hasFimoSource, isFimoDate, isFimoString, } from '../content/index.js';
|
|
14
16
|
export { Boolean } from '../react/primitives/editable/Boolean.js';
|
|
@@ -5,6 +5,7 @@ export { Boolean, Date, Image, Json, RichText, Video } from './primitives/server
|
|
|
5
5
|
export { createFimo } from './facade.js';
|
|
6
6
|
export type { CreateFimoOptions, NextContentRegistry } from './facade.js';
|
|
7
7
|
export { getLabels } from './labels.js';
|
|
8
|
+
export { type FormClient, type FormSubmissionResult } from '../forms/index.js';
|
|
8
9
|
export type { GetLabelsOptions } from '../labels/fetch.js';
|
|
9
10
|
export type { Labels, LabelsSnapshot } from '../labels/core.js';
|
|
10
11
|
export type { FimoRichTextComponents, FimoRichTextMarkProps, FimoRichTextNodeProps, } from '../react/primitives/contracts.js';
|
|
@@ -14,6 +15,7 @@ export declare function createContentClient<T extends {
|
|
|
14
15
|
export declare function createSingletonClient<T extends {
|
|
15
16
|
id: string;
|
|
16
17
|
}, TInput extends object>(config: ContentClientConfig): SingletonContentCore<T, TInput>;
|
|
18
|
+
export declare const formClient: import("./client.js").FormClient;
|
|
17
19
|
export type { CollectionContentCore as CollectionContentClient, SingletonContentCore as SingletonContentClient, } from '../content/client/core-types.js';
|
|
18
20
|
export * from '../content/index.js';
|
|
19
21
|
//# sourceMappingURL=server.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServerContentClient, createServerSingletonClient } from '../content/client/server-content-client.js';
|
|
2
|
+
import { createFormClient } from '../forms/index.js';
|
|
2
3
|
import { nextServerContentRuntime } from './server-runtime.js';
|
|
3
4
|
export { StaticImage } from '../react/primitives/renderers/StaticImage.js';
|
|
4
5
|
export { Text } from './primitives/Text.js';
|
|
@@ -11,4 +12,5 @@ export function createContentClient(config) {
|
|
|
11
12
|
export function createSingletonClient(config) {
|
|
12
13
|
return createServerSingletonClient(config, nextServerContentRuntime);
|
|
13
14
|
}
|
|
15
|
+
export const formClient = createFormClient(nextServerContentRuntime);
|
|
14
16
|
export * from '../content/index.js';
|
|
@@ -3,6 +3,7 @@ export { FimoScripts } from '../app/FimoScripts.js';
|
|
|
3
3
|
export { useLocale } from '../i18n/FimoLocaleContext.js';
|
|
4
4
|
export { localizeFimoPath, resolveFimoLocale } from '../i18n/locale.js';
|
|
5
5
|
export { FimoLink, useFimoHref, useFimoNavigate } from './navigation.js';
|
|
6
|
+
export { formClient } from '../index.js';
|
|
6
7
|
export { localizeFimoTo } from './navigation-utils.js';
|
|
7
8
|
export type { DisplayRoute, FimoProvidersProps, ParamInfo } from '../app/FimoProviders.js';
|
|
8
9
|
export type { FimoLocaleContextValue } from '../i18n/FimoLocaleContext.js';
|
|
@@ -12,6 +12,7 @@ export { FimoScripts } from '../app/FimoScripts.js';
|
|
|
12
12
|
export { useLocale } from '../i18n/FimoLocaleContext.js';
|
|
13
13
|
export { localizeFimoPath, resolveFimoLocale } from '../i18n/locale.js';
|
|
14
14
|
export { FimoLink, useFimoHref, useFimoNavigate } from './navigation.js';
|
|
15
|
+
export { formClient } from '../index.js';
|
|
15
16
|
export { localizeFimoTo } from './navigation-utils.js';
|
|
16
17
|
export { loadFimoMetaData } from '../seo/index.js';
|
|
17
18
|
export * from '../seo/buildSeoForPath.js';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type FormClient } from '../forms/index.js';
|
|
2
|
+
import type { SvelteKitEvent } from './content.js';
|
|
3
|
+
export interface CreateFimoFormClientOptions {
|
|
4
|
+
event?: SvelteKitEvent;
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
defaultLocale?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Create a form client for a SvelteKit request. Pass `event` from a server
|
|
10
|
+
* action or load function when the request-aware fetch should be preserved.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createFimoFormClient({ event, apiUrl, defaultLocale }?: CreateFimoFormClientOptions): FormClient;
|
|
13
|
+
/** Browser-friendly default for simple client-side submissions. */
|
|
14
|
+
export declare const formClient: FormClient;
|
|
15
|
+
//# sourceMappingURL=forms.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createFormClient } from '../forms/index.js';
|
|
2
|
+
import { createSvelteKitContentRuntime } from './runtime.js';
|
|
3
|
+
/**
|
|
4
|
+
* Create a form client for a SvelteKit request. Pass `event` from a server
|
|
5
|
+
* action or load function when the request-aware fetch should be preserved.
|
|
6
|
+
*/
|
|
7
|
+
export function createFimoFormClient({ event, apiUrl, defaultLocale } = {}) {
|
|
8
|
+
return createFormClient(createSvelteKitContentRuntime({
|
|
9
|
+
apiUrl,
|
|
10
|
+
defaultLocale,
|
|
11
|
+
fetch: event?.fetch ?? globalThis.fetch,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
/** Browser-friendly default for simple client-side submissions. */
|
|
15
|
+
export const formClient = createFimoFormClient();
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { createContentClient, createFimo, createSingletonClient, type CreateFimoOptions, type SvelteKitContentClientConfig, type SvelteKitContentModule, type SvelteKitContentRegistry, type SvelteKitEvent, } from './content.js';
|
|
2
2
|
export { useLabels } from './context.js';
|
|
3
3
|
export { getLabels, type GetLabelsOptions } from './labels.js';
|
|
4
|
+
export { createFimoFormClient, formClient } from './forms.js';
|
|
5
|
+
export { type FormClient, type FormSubmissionResult } from '../forms/index.js';
|
|
4
6
|
export type { Labels, LabelsSnapshot } from '../labels/core.js';
|
|
5
7
|
export type { CollectionContentCore as CollectionContentClient, SingletonContentCore as SingletonContentClient, } from '../content/client/core-types.js';
|
|
6
8
|
export * from '../content/index.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { createContentClient, createFimo, createSingletonClient, } from './content.js';
|
|
2
2
|
export { useLabels } from './context.js';
|
|
3
3
|
export { getLabels } from './labels.js';
|
|
4
|
+
export { createFimoFormClient, formClient } from './forms.js';
|
|
4
5
|
export * from '../content/index.js';
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { LabelsSnapshot } from '../labels/core.js';
|
|
2
2
|
import type { SvelteKitEvent } from './content.js';
|
|
3
3
|
export interface GetLabelsOptions {
|
|
4
|
-
|
|
4
|
+
/** Optional escape hatch for a custom content API endpoint. */
|
|
5
|
+
apiUrl?: string;
|
|
5
6
|
event: SvelteKitEvent;
|
|
6
|
-
/**
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* The application resolves the active locale; Fimo never infers it from the
|
|
9
|
+
* URL. Omit it to let the Fimo API answer with the project default.
|
|
10
|
+
*/
|
|
11
|
+
locale?: string;
|
|
8
12
|
}
|
|
9
13
|
/**
|
|
10
14
|
* Read the label dictionary for one locale through the request `fetch`.
|
|
@@ -7,7 +7,7 @@ import { createSvelteKitContentRuntime } from './runtime.js';
|
|
|
7
7
|
* pass it to `FimoProvider` in a layout to expose `Label` and `useLabels()`.
|
|
8
8
|
*/
|
|
9
9
|
export async function getLabels({ apiUrl, event, locale }) {
|
|
10
|
-
const runtime = createSvelteKitContentRuntime({ apiUrl,
|
|
10
|
+
const runtime = createSvelteKitContentRuntime({ apiUrl, fetch: event.fetch });
|
|
11
11
|
const labels = await fetchLabels(runtime, { locale });
|
|
12
12
|
return labels.snapshot;
|
|
13
13
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ContentRuntime } from '../content/client/runtime.js';
|
|
2
2
|
export interface SvelteKitContentRuntimeOptions {
|
|
3
|
-
|
|
3
|
+
/** Optional escape hatch for tests or deliberately custom API endpoints. */
|
|
4
|
+
apiUrl?: string;
|
|
4
5
|
defaultLocale?: string;
|
|
5
6
|
fetch: typeof globalThis.fetch;
|
|
6
7
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { requireContentApiUrl } from '../content/client/runtime.js';
|
|
2
|
+
function resolveApiUrl(apiUrl) {
|
|
3
|
+
return apiUrl ?? (typeof process === 'undefined' ? undefined : process.env.PUBLIC_FIMO_API_URL);
|
|
4
|
+
}
|
|
2
5
|
export function createSvelteKitContentRuntime(options) {
|
|
6
|
+
const apiUrl = resolveApiUrl(options.apiUrl);
|
|
3
7
|
return {
|
|
4
8
|
apiBase() {
|
|
5
|
-
return requireContentApiUrl(
|
|
9
|
+
return requireContentApiUrl(apiUrl, 'PUBLIC_FIMO_API_URL');
|
|
6
10
|
},
|
|
7
11
|
defaultLocale() {
|
|
8
|
-
|
|
12
|
+
// `undefined` leaves `locale` off the request so the Fimo API answers with
|
|
13
|
+
// the project default; hardcoding `en` here would silently override it.
|
|
14
|
+
return options.defaultLocale?.trim() || undefined;
|
|
9
15
|
},
|
|
10
16
|
headers(extra) {
|
|
11
17
|
return { ...extra };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const TEMPLATE_FRAMEWORKS: readonly ['react-router', 'nextjs', 'sveltekit'];
|
|
1
|
+
export declare const TEMPLATE_FRAMEWORKS: readonly ['react-router', 'astro', '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', 'sveltekit'];
|
|
30
|
+
export const TEMPLATE_FRAMEWORKS = ['react-router', 'astro', 'nextjs', 'sveltekit'];
|
|
31
31
|
export const DEFAULT_TEMPLATE = 'react-router';
|
|
32
32
|
/**
|
|
33
33
|
* Absolute path to the requested framework's template directory.
|
|
@@ -30,12 +30,54 @@ function generateIndexContent(forms) {
|
|
|
30
30
|
for (const form of forms) {
|
|
31
31
|
const pascal = pascalCase(form.name);
|
|
32
32
|
const varName = pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
33
|
-
lines.push(`export { ${pascal}Schema, submit${pascal}, FORM_ID as ${varName}FormId } from "./${form.name}";`);
|
|
33
|
+
lines.push(`export { ${pascal}Schema, parse${pascal}FormData, submit${pascal}, FORM_ID as ${varName}FormId } from "./${form.name}";`);
|
|
34
34
|
lines.push(`export type { ${pascal}Data } from "./${form.name}";`);
|
|
35
35
|
}
|
|
36
36
|
lines.push('');
|
|
37
37
|
return lines.join('\n');
|
|
38
38
|
}
|
|
39
|
+
function frameworkFormsTarget(framework) {
|
|
40
|
+
if (framework === 'nextjs')
|
|
41
|
+
return 'next';
|
|
42
|
+
if (framework === 'astro')
|
|
43
|
+
return 'astro';
|
|
44
|
+
if (framework === 'sveltekit' || framework === 'sveltekit-1')
|
|
45
|
+
return 'sveltekit';
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function hasPackageExport(cwd, subpath) {
|
|
49
|
+
const packagePath = join(cwd, 'node_modules', 'fimo', 'package.json');
|
|
50
|
+
if (!existsSync(packagePath))
|
|
51
|
+
return false;
|
|
52
|
+
try {
|
|
53
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
54
|
+
return Boolean(packageJson.exports && Object.prototype.hasOwnProperty.call(packageJson.exports, subpath));
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function assertFrameworkRuntime(cwd, target) {
|
|
61
|
+
const requiredExports = target === 'next' ? ['./forms', './next', './next/client'] : ['./forms', `./${target}`];
|
|
62
|
+
const missing = requiredExports.filter((subpath) => !hasPackageExport(cwd, subpath));
|
|
63
|
+
if (missing.length === 0)
|
|
64
|
+
return;
|
|
65
|
+
throw new Error(`Cannot generate ${target} form clients because the project-local fimo package is missing ${missing.join(', ')}. ` +
|
|
66
|
+
'Install the current fimo package, then run `fimo validate` again. No generated files were changed.');
|
|
67
|
+
}
|
|
68
|
+
function generatedFormFiles(target) {
|
|
69
|
+
if (target === 'next') {
|
|
70
|
+
return [
|
|
71
|
+
{ suffix: '.ts', runtimeImport: 'fimo/next' },
|
|
72
|
+
{ suffix: '.client.ts', runtimeImport: 'fimo/next/client', clientDirective: "'use client';" },
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
if (target === 'astro')
|
|
76
|
+
return [{ suffix: '.ts', runtimeImport: 'fimo/astro' }];
|
|
77
|
+
if (target === 'sveltekit')
|
|
78
|
+
return [{ suffix: '.ts', runtimeImport: 'fimo/sveltekit' }];
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
39
81
|
export function syncForms(options = {}) {
|
|
40
82
|
const cwd = options.cwd ?? process.cwd();
|
|
41
83
|
const log = (msg) => {
|
|
@@ -44,10 +86,16 @@ export function syncForms(options = {}) {
|
|
|
44
86
|
}
|
|
45
87
|
};
|
|
46
88
|
const formsDir = resolve(cwd, FORMS_REL);
|
|
47
|
-
const templatePath = assetPath('content-templates', 'forms', 'react.eta');
|
|
48
|
-
const templateSource = readFileSync(templatePath, 'utf-8');
|
|
49
|
-
const eta = new Eta({ views: undefined, useWith: true, varName: 'it', autoEscape: false });
|
|
50
89
|
const formFiles = getFormFiles(formsDir);
|
|
90
|
+
const target = frameworkFormsTarget(options.framework);
|
|
91
|
+
if (target && formFiles.length > 0)
|
|
92
|
+
assertFrameworkRuntime(cwd, target);
|
|
93
|
+
const legacyTemplateSource = readFileSync(assetPath('content-templates', 'forms', 'react.eta'), 'utf-8');
|
|
94
|
+
const frameworkTemplateSource = target
|
|
95
|
+
? readFileSync(assetPath('content-templates', 'forms', 'framework.eta'), 'utf-8')
|
|
96
|
+
: null;
|
|
97
|
+
const outputFiles = generatedFormFiles(target);
|
|
98
|
+
const eta = new Eta({ views: undefined, useWith: true, varName: 'it', autoEscape: false });
|
|
51
99
|
const expectedTsFiles = new Set();
|
|
52
100
|
const allForms = [];
|
|
53
101
|
let created = 0;
|
|
@@ -66,31 +114,35 @@ export function syncForms(options = {}) {
|
|
|
66
114
|
.replace(/\.json$/, '');
|
|
67
115
|
}
|
|
68
116
|
allForms.push(form);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
117
|
+
const formModel = { ...form, pascal: pascalCase(form.name) };
|
|
118
|
+
const files = target ? outputFiles : [{ suffix: '.ts', runtimeImport: '', clientDirective: '' }];
|
|
119
|
+
for (const output of files) {
|
|
120
|
+
const tsPath = join(formsDir, `${form.name}${output.suffix}`);
|
|
121
|
+
expectedTsFiles.add(tsPath);
|
|
122
|
+
try {
|
|
123
|
+
const content = eta.renderString(target ? frameworkTemplateSource : legacyTemplateSource, {
|
|
124
|
+
form: { ...formModel, ...output },
|
|
125
|
+
});
|
|
126
|
+
const dir = dirname(tsPath);
|
|
127
|
+
if (!existsSync(dir)) {
|
|
128
|
+
mkdirSync(dir, { recursive: true });
|
|
129
|
+
}
|
|
130
|
+
if (!existsSync(tsPath)) {
|
|
131
|
+
writeFileSync(tsPath, content, 'utf-8');
|
|
132
|
+
created++;
|
|
133
|
+
log(`Created: ${tsPath}`);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (readFileSync(tsPath, 'utf-8') !== content) {
|
|
137
|
+
writeFileSync(tsPath, content, 'utf-8');
|
|
138
|
+
updated++;
|
|
139
|
+
log(`Updated: ${tsPath}`);
|
|
140
|
+
}
|
|
84
141
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
updated++;
|
|
88
|
-
log(`Updated: ${tsPath}`);
|
|
142
|
+
catch (error) {
|
|
143
|
+
console.error(`Failed to generate ${tsPath}:`, error);
|
|
89
144
|
}
|
|
90
145
|
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
console.error(`Failed to generate ${tsPath}:`, error);
|
|
93
|
-
}
|
|
94
146
|
}
|
|
95
147
|
if (allForms.length > 0) {
|
|
96
148
|
const indexPath = join(formsDir, 'index.ts');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "0.15.0-staging.
|
|
3
|
+
"version": "0.15.0-staging.3",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fimo": "dist/cli/index.js"
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./dist/runtime/content/index.d.ts",
|
|
27
27
|
"import": "./dist/runtime/content/index.js"
|
|
28
28
|
},
|
|
29
|
+
"./forms": {
|
|
30
|
+
"types": "./dist/runtime/forms/index.d.ts",
|
|
31
|
+
"import": "./dist/runtime/forms/index.js"
|
|
32
|
+
},
|
|
29
33
|
"./astro": {
|
|
30
34
|
"types": "./dist/runtime/astro/server.d.ts",
|
|
31
35
|
"import": "./dist/runtime/astro/server.js"
|
|
@@ -110,7 +114,7 @@
|
|
|
110
114
|
},
|
|
111
115
|
"scripts": {
|
|
112
116
|
"build": "pnpm run clean && tsc -b tsconfig.json && chmod +x dist/cli/index.js",
|
|
113
|
-
"build:bundle": "node scripts/bundle-cli.mjs",
|
|
117
|
+
"build:bundle": "node scripts/bundle-cli.mjs && node scripts/bundle-sveltekit.mjs",
|
|
114
118
|
"check:types": "tsc -b tsconfig.json && tsc -p tsconfig.test.json",
|
|
115
119
|
"start": "tsx src/cli/index.ts",
|
|
116
120
|
"install-cli": "pnpm build && node scripts/install-cli.mjs",
|
|
@@ -132,8 +136,9 @@
|
|
|
132
136
|
"dependencies": {
|
|
133
137
|
"@astrojs/vercel": "11.0.4",
|
|
134
138
|
"@rollup/pluginutils": "^5.1.0",
|
|
135
|
-
"@
|
|
139
|
+
"@vercel/nft": "^1.10.2",
|
|
136
140
|
"change-case": "^5.4.4",
|
|
141
|
+
"esbuild": "^0.28.1",
|
|
137
142
|
"eta": "^3.5.0",
|
|
138
143
|
"oxc-parser": "^0.118.0",
|
|
139
144
|
"react-error-boundary": "^6.0.0",
|
|
@@ -149,6 +154,7 @@
|
|
|
149
154
|
"@fimo/mcp": "workspace:*",
|
|
150
155
|
"@fimo/tsconfig": "workspace:*",
|
|
151
156
|
"@modelcontextprotocol/sdk": "1.27.1",
|
|
157
|
+
"@sveltejs/adapter-vercel": "6.3.4",
|
|
152
158
|
"@tanstack/react-query": "^5.80.6",
|
|
153
159
|
"@types/node": "^22.15.29",
|
|
154
160
|
"@types/react": "^19.2.14",
|
|
@@ -157,7 +163,6 @@
|
|
|
157
163
|
"better-auth": "1.6.25",
|
|
158
164
|
"cheerio": "^1.0.0",
|
|
159
165
|
"commander": "^13.1.0",
|
|
160
|
-
"esbuild": "^0.25.10",
|
|
161
166
|
"open": "^10.1.2",
|
|
162
167
|
"react": "^19.2.4",
|
|
163
168
|
"react-dom": "^19.2.4",
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"runtime": { "framework": "astro" },
|
|
3
|
+
"i18n": {
|
|
4
|
+
"defaultLocale": "en",
|
|
5
|
+
"locales": ["en"],
|
|
6
|
+
"routing": { "strategy": "path-prefix" }
|
|
7
|
+
},
|
|
8
|
+
"routes": {
|
|
9
|
+
"home": { "label": "Home", "path": "/" },
|
|
10
|
+
"projects": { "label": "Projects", "path": "/projects" },
|
|
11
|
+
"project": {
|
|
12
|
+
"label": "Project",
|
|
13
|
+
"path": "/projects/:slug",
|
|
14
|
+
"params": {
|
|
15
|
+
"slug": { "schema": "Project", "queryField": "slug", "displayField": "title" }
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"version": "4",
|
|
20
|
+
"source": "cli"
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Fimo project
|
|
2
|
+
|
|
3
|
+
This Astro application is connected to Fimo for content, editing, environments, and hosting.
|
|
4
|
+
|
|
5
|
+
## Load the Fimo skills before doing Fimo work
|
|
6
|
+
|
|
7
|
+
Before writing schema-driven code, running CLI/deploy workflows, or migrating content, load only the
|
|
8
|
+
skill relevant to the task. During onboarding, preserve the exact CLI invocation prefix from the
|
|
9
|
+
initiating prompt for every operational command; otherwise default to `npx fimo@latest`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm exec fimo skills get fimo-code
|
|
13
|
+
pnpm exec fimo skills get fimo-code --ref frameworks/astro
|
|
14
|
+
npx fimo@latest skills get fimo-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Use `npm exec fimo`, `yarn fimo`, or `bunx --no-install fimo` instead of `pnpm exec fimo` when that
|
|
18
|
+
matches the project lockfile. Add `--ref <doc>` for one reference at a time. Do not load `fimo-cli` for
|
|
19
|
+
a code-only task, or use `npx fimo@latest` for project-local code generation and validation.
|
|
20
|
+
|
|
21
|
+
## Development loop
|
|
22
|
+
|
|
23
|
+
1. Start the application with the project package manager (`pnpm dev`, `npm run dev`, `yarn dev`, or `bun dev`).
|
|
24
|
+
2. Build and verify against the local server.
|
|
25
|
+
3. When a unit of work is done, **offer** to deploy: `fimo deploy` for a hosted preview / shareable link, or `fimo deploy --publish` to go live. Deploy on the developer's request or confirmation. The one exception: new-project onboarding creates a single first hosted preview automatically unless the developer said not to deploy. Never publish without an explicit request.
|
|
26
|
+
|
|
27
|
+
## Project rules
|
|
28
|
+
|
|
29
|
+
- **Deploy is on-demand**, not a save step. Batch related edits into one deploy.
|
|
30
|
+
- The application owns routes, locale resolution, links, HTML language/direction, and SEO. Fimo connects to those decisions.
|
|
31
|
+
- Use generated schema clients, `getLabels({ locale })`, and native components from `fimo/astro/components` for editable values.
|
|
32
|
+
- Do not add React or a second deployment platform.
|
|
33
|
+
- Never edit `.fimo/project.json`; it links this directory to the remote project.
|
|
34
|
+
- Keep secrets in the Fimo dashboard, not committed environment files.
|
|
35
|
+
- Pass flags to interactive Fimo commands when scripting them.
|
|
36
|
+
|
|
37
|
+
The scaffolded pages and content models are disposable. Replace them when building the requested site.
|