fimo 0.5.0 → 0.6.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/SKILL.md +1 -1
- package/assets/skills/fimo/references/locales.md +8 -0
- package/assets/skills/fimo/references/setup-react-router.md +1 -1
- package/assets/skills/fimo/references/ui.md +1 -1
- package/assets/skills/fimo-cli/references/content.md +3 -0
- package/assets/skills/fimo-cli/references/locales.md +6 -0
- package/dist/cli/bundle.json +1 -1
- package/dist/cli/index.js +11 -20
- package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -1
- package/dist/runtime/paths/get-fimo-paths.js +15 -5
- package/dist/runtime/paths/get-fimo-paths.test.d.ts +2 -0
- package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +1 -0
- package/dist/runtime/paths/get-fimo-paths.test.js +69 -0
- package/dist/runtime/seo/buildSeoForPath.d.ts +1 -3
- package/dist/runtime/seo/buildSeoForPath.d.ts.map +1 -1
- package/dist/runtime/seo/buildSeoForPath.js +4 -1
- package/dist/scripts/resolve-routes.js +1 -1
- package/package.json +2 -2
- package/release.json +2 -2
- package/templates/react-router/package.json +10 -7
|
@@ -43,7 +43,7 @@ Workflow / CLI-side concerns (pushing schemas, generating images, managing label
|
|
|
43
43
|
## Decision Tree (specific tasks)
|
|
44
44
|
|
|
45
45
|
- **Setting up Fimo in a new project** — pick the framework:
|
|
46
|
-
- React Router
|
|
46
|
+
- React Router framework mode (new projects use React Router 8) → `references/setup-react-router.md`
|
|
47
47
|
- Plain Vite + React (or upgrading a legacy v3 project) → `references/setup-plain-vite.md`
|
|
48
48
|
- Writing schema JSON / picking field types / Tiptap richtext → `references/content.md`
|
|
49
49
|
- Writing form JSON / generated form client / form code → `references/forms.md`
|
|
@@ -67,6 +67,14 @@ This helper exists so the Fimo UI route selector, preview navigation, labels, an
|
|
|
67
67
|
|
|
68
68
|
Copyable examples live in `assets/react-router-locales/routes.ts` and `assets/react-router-locales/navigation.tsx`.
|
|
69
69
|
|
|
70
|
+
## Existing Custom Locale Systems
|
|
71
|
+
|
|
72
|
+
Before changing a multilingual app, inspect its locale provider, route table, language switcher, redirects, and current public URLs. Preserve that behavior while migrating.
|
|
73
|
+
|
|
74
|
+
For React Router projects, prefer converging on one locale contract owned by `fimo-config.json`, `fimoRoutes()`, and `FimoLink`. Move existing providers and language-switcher UI into the framework root instead of replacing them or adding a second locale context. Keep route metadata canonical and reuse page modules across locales.
|
|
75
|
+
|
|
76
|
+
If adopting the standard helper would change established public URLs, do not silently rewrite them. Keep the custom router temporarily, pass its resolved locale to `<FimoProviders locale={locale}>`, and ask the user to choose the canonical URLs and redirects before normalizing the route table.
|
|
77
|
+
|
|
70
78
|
## Links
|
|
71
79
|
|
|
72
80
|
Use `FimoLink` for internal links that should preserve or switch locale:
|
|
@@ -42,7 +42,7 @@ The template uses **Vite + React 19 + TypeScript + Tailwind v4 + shadcn/ui + rea
|
|
|
42
42
|
|
|
43
43
|
### Routing
|
|
44
44
|
|
|
45
|
-
- This template uses React Router
|
|
45
|
+
- This template uses React Router 8 framework mode (file-based route config in `src/routes.ts`). Use `<FimoLink>` from `fimo/react-router` for locale-aware internal links, especially in projects with more than one configured locale. Use React Router `<Link>` / `<NavLink>` only when you intentionally do not want Fimo's locale behavior. Never raw `<a href="/...">` for internal navigation because it causes full page reloads.
|
|
46
46
|
- Reserve `<a>` for external links, with `target="_blank" rel="noreferrer"`.
|
|
47
47
|
|
|
48
48
|
### Loading states
|
|
@@ -20,6 +20,9 @@ Steps 1, 3, and 5 reference the project skill (`fimo`) for shape and React API.
|
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
22
|
fimo schemas push [TypeName] # push JSON to API (create or update). Omit TypeName to push all.
|
|
23
|
+
# Pushing all sends every schema as ONE atomic batch, so types that
|
|
24
|
+
# reference each other (even cycles) can be added in a single push.
|
|
25
|
+
# Files not present are never deleted — use `schemas delete` for that.
|
|
23
26
|
fimo schemas list # list all registered schemas
|
|
24
27
|
fimo schemas get <TypeName> # get a single schema
|
|
25
28
|
fimo schemas delete <TypeName> # delete a schema
|
|
@@ -67,6 +67,12 @@ import { fimoRoutes } from 'fimo/react-router/routes';
|
|
|
67
67
|
|
|
68
68
|
For custom frameworks, the project may implement its own URL scheme, but it still needs to pass the active locale into Fimo and keep canonical route metadata if it wants Fimo Studio route selection to work.
|
|
69
69
|
|
|
70
|
+
## Migrate An Existing Locale System
|
|
71
|
+
|
|
72
|
+
Inspect the existing locale provider, route table, switcher, redirects, and public URLs before editing configuration. Preserve working URL behavior during the migration.
|
|
73
|
+
|
|
74
|
+
For React Router projects, prefer one contract based on `fimo-config.json`, `fimoRoutes()`, and `FimoLink`; migrate existing wrappers into the framework root instead of layering a second locale context. If that would change established URLs, keep the custom router temporarily, pass its active locale to `FimoProviders`, and ask the user to choose canonical URLs and redirects before rewriting routes.
|
|
75
|
+
|
|
70
76
|
## Labels And Content
|
|
71
77
|
|
|
72
78
|
Labels and content are separate translated surfaces:
|
package/dist/cli/bundle.json
CHANGED
package/dist/cli/index.js
CHANGED
|
@@ -20618,6 +20618,13 @@ var init_api = __esm({
|
|
|
20618
20618
|
});
|
|
20619
20619
|
return body.data;
|
|
20620
20620
|
}
|
|
20621
|
+
async upsertSchemas(projectId, contentTypes) {
|
|
20622
|
+
const body = await this.request(this.tenantPath(projectId, "/schema/batch"), {
|
|
20623
|
+
method: "POST",
|
|
20624
|
+
body: JSON.stringify({ contentTypes })
|
|
20625
|
+
});
|
|
20626
|
+
return body.data;
|
|
20627
|
+
}
|
|
20621
20628
|
async deleteSchema(projectId, typeName) {
|
|
20622
20629
|
const body = await this.request(this.tenantPath(projectId, `/schema/types/${typeName}`), {
|
|
20623
20630
|
method: "DELETE"
|
|
@@ -109785,7 +109792,6 @@ init_dist();
|
|
|
109785
109792
|
init_sync_schemas();
|
|
109786
109793
|
import fs42 from "node:fs";
|
|
109787
109794
|
import path46 from "node:path";
|
|
109788
|
-
init_api();
|
|
109789
109795
|
init_project_context();
|
|
109790
109796
|
init_project();
|
|
109791
109797
|
function toApiShape(schema) {
|
|
@@ -109802,10 +109808,6 @@ function renderPrettyJson8(data2) {
|
|
|
109802
109808
|
return JSON.stringify(data2, null, 2);
|
|
109803
109809
|
}
|
|
109804
109810
|
__name(renderPrettyJson8, "renderPrettyJson");
|
|
109805
|
-
function isAlreadyExistsError(error45) {
|
|
109806
|
-
return error45 instanceof ApiRequestError && error45.status === 400 && /already exists/i.test(error45.detail);
|
|
109807
|
-
}
|
|
109808
|
-
__name(isAlreadyExistsError, "isAlreadyExistsError");
|
|
109809
109811
|
async function schemaListCommand(options, cmd) {
|
|
109810
109812
|
const { ctx, api } = await bootstrapProjectCommand(options, cmd);
|
|
109811
109813
|
await requireOperation("cms.schema.list", { projectId: ctx.projectId, env: ctx.env });
|
|
@@ -109849,21 +109851,10 @@ async function schemaPushCommand(typeName, options, cmd) {
|
|
|
109849
109851
|
return JSON.parse(fs42.readFileSync(filePath, "utf-8"));
|
|
109850
109852
|
});
|
|
109851
109853
|
syncSchemas({ cwd, verbose: false });
|
|
109852
|
-
const results =
|
|
109853
|
-
|
|
109854
|
-
|
|
109855
|
-
|
|
109856
|
-
const result = await api.createSchema(ctx.projectId, contentType);
|
|
109857
|
-
results.push(result);
|
|
109858
|
-
} catch (error45) {
|
|
109859
|
-
if (isAlreadyExistsError(error45)) {
|
|
109860
|
-
const result = await api.updateSchema(ctx.projectId, contentType.name, contentType);
|
|
109861
|
-
results.push(result);
|
|
109862
|
-
} else {
|
|
109863
|
-
throw error45;
|
|
109864
|
-
}
|
|
109865
|
-
}
|
|
109866
|
-
}
|
|
109854
|
+
const results = await api.upsertSchemas(
|
|
109855
|
+
ctx.projectId,
|
|
109856
|
+
schemas3.map((schema) => toApiShape(schema))
|
|
109857
|
+
);
|
|
109867
109858
|
const payload = results.length === 1 ? results[0] : results;
|
|
109868
109859
|
ui.result({ command: "schemas push", data: payload, render: /* @__PURE__ */ __name(() => renderPrettyJson8(payload), "render") });
|
|
109869
109860
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"get-fimo-paths.d.ts","sourceRoot":"","sources":["../../../src/runtime/paths/get-fimo-paths.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"get-fimo-paths.d.ts","sourceRoot":"","sources":["../../../src/runtime/paths/get-fimo-paths.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAkFhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,YAAY,CAAC,IAAI,GAAE,mBAAwB,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAoGtF"}
|
|
@@ -7,6 +7,9 @@ import { getFimoLocales, localizeFimoPath } from '../i18n/locale.js';
|
|
|
7
7
|
import { resolveSeoForPath } from '../seo/resolver.js';
|
|
8
8
|
import { getConfigServer } from '../shared/fimo-config.server.js';
|
|
9
9
|
const isValidationBuild = !!process.env.FIMO_VALIDATION_BUILD;
|
|
10
|
+
const MAX_FETCH_ATTEMPTS = 3;
|
|
11
|
+
const RETRYABLE_FETCH_STATUSES = new Set([502, 503, 504]);
|
|
12
|
+
const RETRY_BASE_DELAY_MS = 250;
|
|
10
13
|
function readEnvValue(rootDir, key) {
|
|
11
14
|
if (process.env[key]) {
|
|
12
15
|
return process.env[key];
|
|
@@ -42,12 +45,19 @@ async function fetchEntries(baseUrl, schema, rootDir, locale) {
|
|
|
42
45
|
if (branch) {
|
|
43
46
|
headers['X-Fimo-Branch'] = branch;
|
|
44
47
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
for (let attempt = 1; attempt <= MAX_FETCH_ATTEMPTS; attempt += 1) {
|
|
49
|
+
const res = await fetch(url, { headers });
|
|
50
|
+
if (res.ok) {
|
|
51
|
+
const payload = (await res.json());
|
|
52
|
+
return Array.isArray(payload?.data) ? payload.data : [];
|
|
53
|
+
}
|
|
54
|
+
if (!RETRYABLE_FETCH_STATUSES.has(res.status) || attempt === MAX_FETCH_ATTEMPTS) {
|
|
55
|
+
const attemptDetail = attempt > 1 ? ` after ${attempt} attempts` : '';
|
|
56
|
+
throw new Error(`Failed to fetch ${schema}: ${res.status}${attemptDetail}`);
|
|
57
|
+
}
|
|
58
|
+
await new Promise((resolveDelay) => setTimeout(resolveDelay, RETRY_BASE_DELAY_MS * attempt));
|
|
48
59
|
}
|
|
49
|
-
|
|
50
|
-
return Array.isArray(payload?.data) ? payload.data : [];
|
|
60
|
+
throw new Error(`Failed to fetch ${schema}`);
|
|
51
61
|
}
|
|
52
62
|
// Glob-ish matcher for route keys: '*' matches one segment, '**' matches any.
|
|
53
63
|
function compilePattern(pattern) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-fimo-paths.test.d.ts","sourceRoot":"","sources":["../../../src/runtime/paths/get-fimo-paths.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { getFimoPaths } from './get-fimo-paths.js';
|
|
6
|
+
const roots = [];
|
|
7
|
+
function createDynamicProject() {
|
|
8
|
+
const root = mkdtempSync(join(tmpdir(), 'fimo-paths-'));
|
|
9
|
+
roots.push(root);
|
|
10
|
+
writeFileSync(join(root, '.env'), 'VITE_API_URL=https://tenant.example.test\n');
|
|
11
|
+
writeFileSync(join(root, 'fimo-config.json'), JSON.stringify({
|
|
12
|
+
routes: {
|
|
13
|
+
'blog/post': {
|
|
14
|
+
path: '/blog/:slug',
|
|
15
|
+
params: {
|
|
16
|
+
slug: {
|
|
17
|
+
schema: 'BlogPost',
|
|
18
|
+
queryField: 'slug',
|
|
19
|
+
displayField: 'title',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
return root;
|
|
26
|
+
}
|
|
27
|
+
function response(status, data = []) {
|
|
28
|
+
return new Response(JSON.stringify({ data }), {
|
|
29
|
+
status,
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
vi.useRealTimers();
|
|
35
|
+
vi.unstubAllGlobals();
|
|
36
|
+
for (const root of roots.splice(0)) {
|
|
37
|
+
rmSync(root, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
describe('getFimoPaths content fetches', () => {
|
|
41
|
+
it.each([502, 503, 504])('recovers from a transient %s response', async (status) => {
|
|
42
|
+
vi.useFakeTimers();
|
|
43
|
+
const fetchMock = vi
|
|
44
|
+
.fn()
|
|
45
|
+
.mockResolvedValueOnce(response(status))
|
|
46
|
+
.mockResolvedValueOnce(response(200, [{ slug: 'hello-world' }]));
|
|
47
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
48
|
+
const resultPromise = getFimoPaths({ cwd: createDynamicProject() });
|
|
49
|
+
await vi.runAllTimersAsync();
|
|
50
|
+
await expect(resultPromise).resolves.toContainEqual(expect.objectContaining({ routeKey: 'blog/post', path: '/blog/hello-world' }));
|
|
51
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
52
|
+
});
|
|
53
|
+
it('fails clearly after exhausting the transient retry budget', async () => {
|
|
54
|
+
vi.useFakeTimers();
|
|
55
|
+
const fetchMock = vi.fn().mockResolvedValue(response(503));
|
|
56
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
57
|
+
const resultPromise = getFimoPaths({ cwd: createDynamicProject() });
|
|
58
|
+
const rejection = expect(resultPromise).rejects.toThrow('Failed to fetch BlogPost: 503 after 3 attempts');
|
|
59
|
+
await vi.runAllTimersAsync();
|
|
60
|
+
await rejection;
|
|
61
|
+
expect(fetchMock).toHaveBeenCalledTimes(3);
|
|
62
|
+
});
|
|
63
|
+
it('does not retry an author response', async () => {
|
|
64
|
+
const fetchMock = vi.fn().mockResolvedValue(response(400));
|
|
65
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
66
|
+
await expect(getFimoPaths({ cwd: createDynamicProject() })).rejects.toThrow('Failed to fetch BlogPost: 400');
|
|
67
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildSeoForPath.d.ts","sourceRoot":"","sources":["../../../src/runtime/seo/buildSeoForPath.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAK1E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAExF;AA+BD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAMrE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE
|
|
1
|
+
{"version":3,"file":"buildSeoForPath.d.ts","sourceRoot":"","sources":["../../../src/runtime/seo/buildSeoForPath.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAc,OAAO,EAAE,MAAM,gCAAgC,CAAC;AAK1E,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,CAExF;AA+BD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAMrE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAAE,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,OAAO,EAAE,SAAS,OAAO,EAAE,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAclG"}
|
|
@@ -42,7 +42,10 @@ export function buildMetaForPath(pathname, routeData) {
|
|
|
42
42
|
}
|
|
43
43
|
export function buildFimoMeta(args) {
|
|
44
44
|
const pathname = args.location.pathname;
|
|
45
|
-
const
|
|
45
|
+
const firstMatch = args.matches[0];
|
|
46
|
+
const rootData = firstMatch && typeof firstMatch === 'object' && 'data' in firstMatch
|
|
47
|
+
? firstMatch.data
|
|
48
|
+
: undefined;
|
|
46
49
|
const routeData = rootData && typeof rootData === 'object' ? rootData.fimoMetaData : undefined;
|
|
47
50
|
const seo = getSeoForPath(pathname, routeData);
|
|
48
51
|
if (!seo) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Given a list of changed files, resolves which preview routes are affected
|
|
5
5
|
* by walking the reverse import graph from changed files up to page entry points
|
|
6
|
-
* referenced in src/routes.ts (
|
|
6
|
+
* referenced in src/routes.ts (React Router framework-mode config).
|
|
7
7
|
*
|
|
8
8
|
* Output (JSON):
|
|
9
9
|
* {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fimo",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0-experimental.1",
|
|
4
4
|
"description": "Fimo CLI - create, deploy, and manage Fimo projects",
|
|
5
5
|
"bin": {
|
|
6
6
|
"fimo": "dist/cli/index.js"
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
"@tanstack/react-query": "^5.80.6",
|
|
100
100
|
"react": "^19.0.0",
|
|
101
101
|
"react-dom": "^19.0.0",
|
|
102
|
-
"react-router": "^7.0.0",
|
|
102
|
+
"react-router": "^7.0.0 || ^8.0.0",
|
|
103
103
|
"vite": "^7.0.0 || ^8.0.0"
|
|
104
104
|
},
|
|
105
105
|
"engines": {
|
package/release.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "react-router dev --port 5173",
|
|
8
|
-
"build": "react-router build && fimo export-static",
|
|
8
|
+
"build": "tsc --noEmit && react-router build && fimo export-static",
|
|
9
9
|
"lint": "oxlint --deny-warnings",
|
|
10
10
|
"typecheck": "tsc --noEmit",
|
|
11
11
|
"preview": "vite preview --host 0.0.0.0 --port 4173"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"@base-ui/react": "^1.3.0",
|
|
15
15
|
"@hookform/resolvers": "^5.2.2",
|
|
16
|
-
"@react-router/node": "^
|
|
16
|
+
"@react-router/node": "^8.2.0",
|
|
17
17
|
"@tailwindcss/typography": "^0.5.19",
|
|
18
18
|
"@tanstack/react-query": "^5.80.6",
|
|
19
19
|
"class-variance-authority": "^0.7.1",
|
|
@@ -21,20 +21,20 @@
|
|
|
21
21
|
"cmdk": "^1.1.1",
|
|
22
22
|
"date-fns": "^4.1.0",
|
|
23
23
|
"embla-carousel-react": "^8.6.0",
|
|
24
|
-
"fimo": "0.
|
|
24
|
+
"fimo": "0.6.0-experimental.1",
|
|
25
25
|
"input-otp": "^1.4.2",
|
|
26
26
|
"isbot": "^5",
|
|
27
27
|
"lucide-react": "^0.577.0",
|
|
28
28
|
"motion": "^12.36.0",
|
|
29
29
|
"next-themes": "^0.4.6",
|
|
30
30
|
"radix-ui": "^1.4.3",
|
|
31
|
-
"react": "^19.2.
|
|
31
|
+
"react": "^19.2.7",
|
|
32
32
|
"react-day-picker": "^9.14.0",
|
|
33
|
-
"react-dom": "^19.2.
|
|
33
|
+
"react-dom": "^19.2.7",
|
|
34
34
|
"react-error-boundary": "^6.0.0",
|
|
35
35
|
"react-hook-form": "^7.72.1",
|
|
36
36
|
"react-resizable-panels": "^4.9.0",
|
|
37
|
-
"react-router": "^
|
|
37
|
+
"react-router": "^8.2.0",
|
|
38
38
|
"recharts": "^3.8.0",
|
|
39
39
|
"sonner": "^2.0.7",
|
|
40
40
|
"tailwind-merge": "^3.2.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"zod": "^4.3.6"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@react-router/dev": "^
|
|
45
|
+
"@react-router/dev": "^8.2.0",
|
|
46
46
|
"@tailwindcss/vite": "^4.3.0",
|
|
47
47
|
"@types/node": "^24.12.0",
|
|
48
48
|
"@types/react": "^19.2.14",
|
|
@@ -52,5 +52,8 @@
|
|
|
52
52
|
"tw-animate-css": "^1.4.0",
|
|
53
53
|
"typescript": "5.9.3",
|
|
54
54
|
"vite": "^8.0.13"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22.22.0"
|
|
55
58
|
}
|
|
56
59
|
}
|