fimo 2.6.0-staging.4 → 2.7.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/skills/fimo-cli/references/analytics.md +16 -2
- package/assets/skills/fimo-cli/references/locales.md +2 -2
- package/assets/skills/fimo-cli/references/pages.md +14 -2
- package/assets/skills/fimo-code/SKILL.md +8 -0
- package/assets/skills/fimo-code/references/analytics.md +104 -0
- package/assets/skills/fimo-code/references/frameworks/astro.md +2 -0
- package/assets/skills/fimo-studio/SKILL.md +1 -0
- package/assets/skills/fimo-studio/references/analytics.md +28 -0
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +8 -8
- package/dist/runtime/paths/get-fimo-paths.d.ts +3 -1
- package/dist/runtime/paths/get-fimo-paths.js +59 -5
- package/dist/runtime/routes/index.d.ts +19 -3
- package/dist/runtime/routes/index.js +27 -2
- package/dist/runtime/templates.d.ts +1 -1
- package/dist/runtime/templates.js +1 -0
- package/dist/scripts/validate-page-registry.js +14 -3
- package/package.json +8 -8
- package/templates/astro/package.json +2 -2
- package/templates/nextjs/package.json +3 -3
- package/templates/react-router/package.json +4 -4
- package/templates/sveltekit/package.json +2 -2
- package/templates/vite/package.json +4 -4
|
@@ -6,7 +6,9 @@ import type { FimoPath, GetFimoPathsOptions } from './types.js';
|
|
|
6
6
|
*
|
|
7
7
|
* - Static paths in config: emitted as-is.
|
|
8
8
|
* - Dynamic paths (with `:slug`-style entry params): expanded by fetching
|
|
9
|
-
* schema
|
|
9
|
+
* every schema entry from `VITE_API_URL`, page by page. One path per entry.
|
|
10
|
+
* - A route with an external param is never emitted: the application owns
|
|
11
|
+
* those values and lists those URLs itself.
|
|
10
12
|
* - Locales come from the route contract, not from the project locale list: a
|
|
11
13
|
* route is emitted once per locale only when its path declares a `:locale`
|
|
12
14
|
* segment. The default locale is always unprefixed. A route without a
|
|
@@ -11,6 +11,9 @@ const isValidationBuild = !!process.env.FIMO_VALIDATION_BUILD;
|
|
|
11
11
|
const MAX_FETCH_ATTEMPTS = 3;
|
|
12
12
|
const RETRYABLE_FETCH_STATUSES = new Set([502, 503, 504]);
|
|
13
13
|
const RETRY_BASE_DELAY_MS = 250;
|
|
14
|
+
// The content API pages with `limit` (capped at 100) and `offset`; its
|
|
15
|
+
// `meta.hasMore` says whether another page follows.
|
|
16
|
+
const ENTRIES_PAGE_SIZE = 100;
|
|
14
17
|
function readEnvValue(rootDir, key) {
|
|
15
18
|
if (process.env[key]) {
|
|
16
19
|
return process.env[key];
|
|
@@ -42,9 +45,6 @@ function resolveApiBaseUrl(rootDir) {
|
|
|
42
45
|
async function fetchEntries(baseUrl, schema, rootDir, locale) {
|
|
43
46
|
const url = new URL(`${baseUrl.replace(/\/$/, '')}/entries/${schema}`);
|
|
44
47
|
url.searchParams.set('locale', locale);
|
|
45
|
-
if (isValidationBuild) {
|
|
46
|
-
url.searchParams.set('limit', '1');
|
|
47
|
-
}
|
|
48
48
|
const headers = {};
|
|
49
49
|
// `FIMO_ENV` is a customer-facing variable name and keeps its spelling; the
|
|
50
50
|
// header this build emits is canonical. A
|
|
@@ -54,11 +54,47 @@ async function fetchEntries(baseUrl, schema, rootDir, locale) {
|
|
|
54
54
|
if (branch) {
|
|
55
55
|
headers['X-Fimo-Branch'] = branch;
|
|
56
56
|
}
|
|
57
|
+
// A validation build only proves the route expands, so one entry is enough.
|
|
58
|
+
if (isValidationBuild) {
|
|
59
|
+
url.searchParams.set('limit', '1');
|
|
60
|
+
return (await fetchEntriesPage(url, headers, schema)).data;
|
|
61
|
+
}
|
|
62
|
+
// Every entry becomes a URL, so walk the whole collection page by page.
|
|
63
|
+
const entries = [];
|
|
64
|
+
let offset = 0;
|
|
65
|
+
for (;;) {
|
|
66
|
+
const pageUrl = new URL(url);
|
|
67
|
+
pageUrl.searchParams.set('limit', String(ENTRIES_PAGE_SIZE));
|
|
68
|
+
pageUrl.searchParams.set('offset', String(offset));
|
|
69
|
+
const page = await fetchEntriesPage(pageUrl, headers, schema);
|
|
70
|
+
entries.push(...page.data);
|
|
71
|
+
if (!page.meta) {
|
|
72
|
+
// Without pagination metadata a short page is the whole collection; a
|
|
73
|
+
// full one may hide more entries, and a truncated list is a silent bug.
|
|
74
|
+
if (page.data.length >= ENTRIES_PAGE_SIZE) {
|
|
75
|
+
throw new Error(`Entries response for ${schema} returned ${page.data.length} rows without pagination metadata, so every path cannot be enumerated.`);
|
|
76
|
+
}
|
|
77
|
+
return entries;
|
|
78
|
+
}
|
|
79
|
+
if (!page.meta.hasMore) {
|
|
80
|
+
return entries;
|
|
81
|
+
}
|
|
82
|
+
const nextOffset = page.meta.offset + page.meta.limit;
|
|
83
|
+
if (nextOffset <= offset) {
|
|
84
|
+
throw new Error(`Entries pagination for ${schema} did not advance past offset ${offset}.`);
|
|
85
|
+
}
|
|
86
|
+
offset = nextOffset;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function fetchEntriesPage(url, headers, schema) {
|
|
57
90
|
for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt += 1) {
|
|
58
91
|
const res = await fetch(url, { headers });
|
|
59
92
|
if (res.ok) {
|
|
60
93
|
const payload = (await res.json());
|
|
61
|
-
return
|
|
94
|
+
return {
|
|
95
|
+
data: Array.isArray(payload?.data) ? payload.data : [],
|
|
96
|
+
meta: readPageMeta(payload?.meta),
|
|
97
|
+
};
|
|
62
98
|
}
|
|
63
99
|
if (!RETRYABLE_FETCH_STATUSES.has(res.status) || attempt === MAX_FETCH_ATTEMPTS) {
|
|
64
100
|
const attemptDetail = attempt > 1 ? ` after ${attempt} attempts` : '';
|
|
@@ -68,6 +104,16 @@ async function fetchEntries(baseUrl, schema, rootDir, locale) {
|
|
|
68
104
|
}
|
|
69
105
|
throw new Error(`Failed to fetch ${schema}`);
|
|
70
106
|
}
|
|
107
|
+
function readPageMeta(meta) {
|
|
108
|
+
if (!meta || typeof meta !== 'object') {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
const { limit, offset, hasMore } = meta;
|
|
112
|
+
if (typeof limit !== 'number' || typeof offset !== 'number' || typeof hasMore !== 'boolean') {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
return { limit, offset, hasMore };
|
|
116
|
+
}
|
|
71
117
|
// Glob-ish matcher for route keys: '*' matches one segment, '**' matches any.
|
|
72
118
|
function compilePattern(pattern) {
|
|
73
119
|
const escaped = pattern
|
|
@@ -87,7 +133,9 @@ function matchesAny(routeKey, patterns) {
|
|
|
87
133
|
*
|
|
88
134
|
* - Static paths in config: emitted as-is.
|
|
89
135
|
* - Dynamic paths (with `:slug`-style entry params): expanded by fetching
|
|
90
|
-
* schema
|
|
136
|
+
* every schema entry from `VITE_API_URL`, page by page. One path per entry.
|
|
137
|
+
* - A route with an external param is never emitted: the application owns
|
|
138
|
+
* those values and lists those URLs itself.
|
|
91
139
|
* - Locales come from the route contract, not from the project locale list: a
|
|
92
140
|
* route is emitted once per locale only when its path declares a `:locale`
|
|
93
141
|
* segment. The default locale is always unprefixed. A route without a
|
|
@@ -125,6 +173,12 @@ export async function getFimoPaths(opts = {}) {
|
|
|
125
173
|
if (contract.unresolvedParams.length > 0) {
|
|
126
174
|
return false;
|
|
127
175
|
}
|
|
176
|
+
// Values the application owns cannot be enumerated here. The app lists
|
|
177
|
+
// those URLs itself — React Router `prerender`, Next `generateStaticParams`
|
|
178
|
+
// — and concatenates them with this result.
|
|
179
|
+
if (contract.externalParams.length > 0) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
128
182
|
if (opts.include && opts.include.length > 0 && !matchesAny(contract.id, opts.include)) {
|
|
129
183
|
return false;
|
|
130
184
|
}
|
|
@@ -11,7 +11,12 @@ export interface FimoRouteSeoConfig {
|
|
|
11
11
|
siteName?: string;
|
|
12
12
|
}
|
|
13
13
|
export interface FimoRouteParamConfig<Query = unknown> {
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Omitted means `'entry'`; the reserved name `locale` infers `'locale'`.
|
|
16
|
+
* `'external'` marks a placeholder whose values the application owns: it is
|
|
17
|
+
* matched from URLs but never looked up in the CMS and never enumerated.
|
|
18
|
+
*/
|
|
19
|
+
type?: 'entry' | 'locale' | 'external';
|
|
15
20
|
schema?: string;
|
|
16
21
|
queryField?: string;
|
|
17
22
|
displayField?: string;
|
|
@@ -67,7 +72,16 @@ export interface FimoLocaleParam {
|
|
|
67
72
|
*/
|
|
68
73
|
optional: boolean;
|
|
69
74
|
}
|
|
70
|
-
|
|
75
|
+
/**
|
|
76
|
+
* A placeholder whose values the application owns: a synced catalogue, a
|
|
77
|
+
* browser-only id, a computed segment. Never read from the CMS, never
|
|
78
|
+
* enumerated by Fimo.
|
|
79
|
+
*/
|
|
80
|
+
export interface FimoExternalParam {
|
|
81
|
+
type: 'external';
|
|
82
|
+
name: string;
|
|
83
|
+
}
|
|
84
|
+
export type FimoRouteParam = FimoEntryParam | FimoLocaleParam | FimoExternalParam;
|
|
71
85
|
export interface FimoRouteIssue {
|
|
72
86
|
/** The parameter the issue is about, when it is about one. */
|
|
73
87
|
param?: string;
|
|
@@ -85,6 +99,8 @@ export interface FimoRouteContract<RouteConfig extends FimoRouteConfig = FimoRou
|
|
|
85
99
|
canonicalPath: string;
|
|
86
100
|
params: FimoRouteParam[];
|
|
87
101
|
entryParams: FimoEntryParam[];
|
|
102
|
+
/** Params the application owns. Non-empty means Fimo cannot enumerate this route's URLs. */
|
|
103
|
+
externalParams: FimoExternalParam[];
|
|
88
104
|
localeParam: FimoLocaleParam | null;
|
|
89
105
|
/** Placeholders in the path that no usable descriptor resolves. */
|
|
90
106
|
unresolvedParams: string[];
|
|
@@ -120,7 +136,7 @@ export declare function hasLocaleSegment(path: string): boolean;
|
|
|
120
136
|
* Normalize one declared route into the contract every consumer reads.
|
|
121
137
|
*
|
|
122
138
|
* Parameter types resolve in this order:
|
|
123
|
-
* 1. an explicit `type
|
|
139
|
+
* 1. an explicit `type` (`entry`, `locale`, or `external`);
|
|
124
140
|
* 2. the reserved name `locale`, which infers the locale type;
|
|
125
141
|
* 3. otherwise the entry type, which is what every route written before
|
|
126
142
|
* locale-aware routing existed relies on.
|
|
@@ -85,6 +85,26 @@ function buildLocaleParam(name, descriptor, index, issues) {
|
|
|
85
85
|
}
|
|
86
86
|
return { type: "locale", name, optional: descriptor?.optional !== false };
|
|
87
87
|
}
|
|
88
|
+
function buildExternalParam(name, descriptor, issues) {
|
|
89
|
+
if (name === FIMO_LOCALE_PARAM) {
|
|
90
|
+
issues.push({
|
|
91
|
+
param: name,
|
|
92
|
+
message: `Param "${name}" is reserved for the locale segment and cannot use \`"type": "external"\`.`,
|
|
93
|
+
fix: `Drop \`"type": "external"\` from \`params.${name}\`, or rename the segment.`
|
|
94
|
+
});
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
for (const field of ["schema", "queryField", "displayField", "query", "optional"]) {
|
|
98
|
+
if (descriptor[field] !== void 0) {
|
|
99
|
+
issues.push({
|
|
100
|
+
param: name,
|
|
101
|
+
message: `External param "${name}" must not declare "${field}" \u2014 its values are owned by the application, never read from the CMS.`,
|
|
102
|
+
fix: `Remove "${field}" from \`params.${name}\`.`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { type: "external", name };
|
|
107
|
+
}
|
|
88
108
|
function buildEntryParam(name, descriptor, issues) {
|
|
89
109
|
if (name === FIMO_LOCALE_PARAM) {
|
|
90
110
|
issues.push({
|
|
@@ -153,7 +173,8 @@ function resolveRouteContract(id, route) {
|
|
|
153
173
|
}
|
|
154
174
|
seen.add(name);
|
|
155
175
|
const descriptor = declared[name];
|
|
156
|
-
const
|
|
176
|
+
const type = resolveParamType(name, descriptor);
|
|
177
|
+
const resolved = type === "locale" ? buildLocaleParam(name, descriptor, index, issues) : type === "external" && descriptor ? buildExternalParam(name, descriptor, issues) : buildEntryParam(name, descriptor, issues);
|
|
157
178
|
if (resolved) {
|
|
158
179
|
params.push(resolved);
|
|
159
180
|
} else {
|
|
@@ -178,6 +199,7 @@ function resolveRouteContract(id, route) {
|
|
|
178
199
|
canonicalPath,
|
|
179
200
|
params,
|
|
180
201
|
entryParams: params.filter((param) => param.type === "entry"),
|
|
202
|
+
externalParams: params.filter((param) => param.type === "external"),
|
|
181
203
|
localeParam,
|
|
182
204
|
unresolvedParams,
|
|
183
205
|
issues
|
|
@@ -217,6 +239,7 @@ function matchContract(urlSegments, contract, vocabulary) {
|
|
|
217
239
|
}
|
|
218
240
|
const params = {};
|
|
219
241
|
const entryParams = {};
|
|
242
|
+
const entryNames = new Set(contract.entryParams.map((param) => param.name));
|
|
220
243
|
let localeFromPath = null;
|
|
221
244
|
let score = 0;
|
|
222
245
|
let offset = 0;
|
|
@@ -244,7 +267,9 @@ function matchContract(urlSegments, contract, vocabulary) {
|
|
|
244
267
|
const rawValue = catchAll ? urlSegments.slice(index + offset).join("/") : value;
|
|
245
268
|
const decoded = safeDecode(rawValue);
|
|
246
269
|
params[name] = decoded;
|
|
247
|
-
|
|
270
|
+
if (entryNames.has(name)) {
|
|
271
|
+
entryParams[name] = decoded;
|
|
272
|
+
}
|
|
248
273
|
if (catchAll) {
|
|
249
274
|
break;
|
|
250
275
|
}
|
|
@@ -76,7 +76,7 @@ export declare function rewriteFimoValidateCommand(pkg: PkgWithScripts, pin?: st
|
|
|
76
76
|
* only the ones it uses; every pin is derived from the `fimo` pin so the
|
|
77
77
|
* platform keeps a single version selector (FIMO-1635).
|
|
78
78
|
*/
|
|
79
|
-
export declare const FIMO_PACKAGES: readonly ['fimo', 'fimo-sveltekit', 'fimo-astro', 'fimo-vite', 'fimo-react', 'fimo-react-router', 'fimo-next'];
|
|
79
|
+
export declare const FIMO_PACKAGES: readonly ['fimo-analytics', 'fimo', 'fimo-sveltekit', 'fimo-astro', 'fimo-vite', 'fimo-react', 'fimo-react-router', 'fimo-next'];
|
|
80
80
|
export type FimoPackage = (typeof FIMO_PACKAGES)[number];
|
|
81
81
|
/**
|
|
82
82
|
* Pin for a companion package given the `fimo` pin. Semver and dist-tags carry
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { pageSourceParamExpressions, parsePageParamExpression, } from '@fimo/api-contracts';
|
|
3
|
+
import { isEntryParamBinding, pageSourceParamExpressions, parsePageParamExpression, } from '@fimo/api-contracts';
|
|
4
|
+
/** Only an entry binding names content; the rest declare where values come from. */
|
|
5
|
+
function isEntryBinding(binding) {
|
|
6
|
+
return isEntryParamBinding(binding);
|
|
7
|
+
}
|
|
4
8
|
function isRecord(value) {
|
|
5
9
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
10
|
}
|
|
@@ -74,7 +78,14 @@ function validateQuery(query, pointer, schema, params, schemas) {
|
|
|
74
78
|
for (const expression of pageSourceParamExpressions(query)) {
|
|
75
79
|
const parsed = parsePageParamExpression(expression);
|
|
76
80
|
const binding = parsed ? params[parsed.param] : undefined;
|
|
77
|
-
if (!parsed || !binding
|
|
81
|
+
if (!parsed || !binding) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (!isEntryBinding(binding)) {
|
|
85
|
+
errors.push(`${pointer}: ${expression} reads param "${parsed.param}", which is not a Collection entry param.`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (typeof binding.schema !== 'string') {
|
|
78
89
|
continue;
|
|
79
90
|
}
|
|
80
91
|
const boundSchema = schemas.get(binding.schema);
|
|
@@ -99,7 +110,7 @@ export function validatePageRegistrySchemaReferences(cwd, config, displayPath) {
|
|
|
99
110
|
const params = isRecord(rawRoute.params) ? rawRoute.params : {};
|
|
100
111
|
for (const [param, binding] of Object.entries(params)) {
|
|
101
112
|
const pointer = `${displayPath}#routes.${routeId}.params.${param}`;
|
|
102
|
-
if (typeof binding.schema !== 'string') {
|
|
113
|
+
if (!isEntryBinding(binding) || typeof binding.schema !== 'string') {
|
|
103
114
|
continue;
|
|
104
115
|
}
|
|
105
116
|
const schema = schemas.get(binding.schema);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0-experimental.1",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"astro-component"
|
|
@@ -194,13 +194,13 @@
|
|
|
194
194
|
"devDependencies": {
|
|
195
195
|
"@clack/core": "^1.2.0",
|
|
196
196
|
"@clack/prompts": "^1.2.0",
|
|
197
|
-
"@fimo/agents-bundle": "2.
|
|
198
|
-
"@fimo/api-contracts": "2.
|
|
199
|
-
"@fimo/cli-kit": "2.
|
|
200
|
-
"@fimo/framework-detect": "2.
|
|
201
|
-
"@fimo/routes": "2.
|
|
202
|
-
"@fimo/sdk": "2.
|
|
203
|
-
"@fimo/tsconfig": "2.
|
|
197
|
+
"@fimo/agents-bundle": "2.7.0-experimental.1",
|
|
198
|
+
"@fimo/api-contracts": "2.7.0-experimental.1",
|
|
199
|
+
"@fimo/cli-kit": "2.7.0-experimental.1",
|
|
200
|
+
"@fimo/framework-detect": "2.7.0-experimental.1",
|
|
201
|
+
"@fimo/routes": "2.7.0-experimental.1",
|
|
202
|
+
"@fimo/sdk": "2.7.0-experimental.1",
|
|
203
|
+
"@fimo/tsconfig": "2.7.0-experimental.1",
|
|
204
204
|
"@types/node": "22.18.6",
|
|
205
205
|
"agent-install": "^0.0.5",
|
|
206
206
|
"astro": "^7.1.6",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"check": "astro sync && astro check"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"fimo": "2.
|
|
13
|
-
"fimo-astro": "2.
|
|
12
|
+
"fimo": "2.7.0-experimental.1",
|
|
13
|
+
"fimo-astro": "2.7.0-experimental.1"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@astrojs/check": "^0.9.10",
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@tanstack/react-query": "^5.80.6",
|
|
15
|
-
"fimo": "2.
|
|
16
|
-
"fimo-next": "2.
|
|
17
|
-
"fimo-react": "2.
|
|
15
|
+
"fimo": "2.7.0-experimental.1",
|
|
16
|
+
"fimo-next": "2.7.0-experimental.1",
|
|
17
|
+
"fimo-react": "2.7.0-experimental.1",
|
|
18
18
|
"next": "^16.3.3",
|
|
19
19
|
"react": "^19.2.7",
|
|
20
20
|
"react-dom": "^19.2.7"
|
|
@@ -21,10 +21,10 @@
|
|
|
21
21
|
"cn": "0.2.4",
|
|
22
22
|
"date-fns": "^4.1.0",
|
|
23
23
|
"embla-carousel-react": "^8.6.0",
|
|
24
|
-
"fimo": "2.
|
|
25
|
-
"fimo-react": "2.
|
|
26
|
-
"fimo-react-router": "2.
|
|
27
|
-
"fimo-vite": "2.
|
|
24
|
+
"fimo": "2.7.0-experimental.1",
|
|
25
|
+
"fimo-react": "2.7.0-experimental.1",
|
|
26
|
+
"fimo-react-router": "2.7.0-experimental.1",
|
|
27
|
+
"fimo-vite": "2.7.0-experimental.1",
|
|
28
28
|
"input-otp": "^1.4.2",
|
|
29
29
|
"isbot": "^5",
|
|
30
30
|
"lucide-react": "^0.577.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"prepare": "svelte-kit sync"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"fimo": "2.
|
|
15
|
-
"fimo-sveltekit": "2.
|
|
14
|
+
"fimo": "2.7.0-experimental.1",
|
|
15
|
+
"fimo-sveltekit": "2.7.0-experimental.1"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@sveltejs/kit": "^2.70.2",
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@tanstack/react-query": "^5.80.6",
|
|
14
|
-
"fimo": "2.
|
|
15
|
-
"fimo-react": "2.
|
|
16
|
-
"fimo-react-router": "2.
|
|
17
|
-
"fimo-vite": "2.
|
|
14
|
+
"fimo": "2.7.0-experimental.1",
|
|
15
|
+
"fimo-react": "2.7.0-experimental.1",
|
|
16
|
+
"fimo-react-router": "2.7.0-experimental.1",
|
|
17
|
+
"fimo-vite": "2.7.0-experimental.1",
|
|
18
18
|
"react": "^19.2.7",
|
|
19
19
|
"react-dom": "^19.2.7",
|
|
20
20
|
"react-router": "^8.3.0"
|