fimo 0.6.0 → 0.7.0-experimental.2

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.
Files changed (145) hide show
  1. package/README.md +1 -1
  2. package/assets/content-templates/hooks-template.eta +73 -27
  3. package/assets/content-templates/types-template.eta +4 -1
  4. package/assets/skills/fimo/references/content.md +46 -42
  5. package/assets/skills/fimo-cli/references/content.md +14 -14
  6. package/assets/skills/fimo-cli/references/domains.md +1 -1
  7. package/assets/skills/fimo-studio/references/custom-domains.md +1 -1
  8. package/dist/cli/bundle.json +3 -2
  9. package/dist/cli/index.js +1203 -110450
  10. package/dist/runtime/content/client/createContentClient.d.ts +5 -0
  11. package/dist/runtime/content/client/createContentClient.js +300 -0
  12. package/dist/runtime/content/client/createSingletonClient.d.ts +5 -0
  13. package/dist/runtime/content/client/createSingletonClient.js +108 -0
  14. package/dist/runtime/content/client/internal.d.ts +44 -0
  15. package/dist/runtime/content/client/internal.js +236 -0
  16. package/dist/runtime/content/client/types.d.ts +136 -0
  17. package/dist/runtime/content/client/useQueryLocale.d.ts +2 -0
  18. package/dist/runtime/content/client/useQueryLocale.js +6 -0
  19. package/dist/runtime/content/utils/retrieve-data-sources.d.ts +1 -0
  20. package/dist/runtime/content/utils/retrieve-data-sources.js +27 -4
  21. package/dist/runtime/iframe/runtime/overlay-hmr.js +3 -0
  22. package/dist/runtime/index.d.ts +3 -0
  23. package/dist/runtime/index.js +2 -0
  24. package/dist/runtime/primitives/lib/primitives.d.ts +2 -2
  25. package/dist/runtime/primitives/lib/query.d.ts +5 -3
  26. package/package.json +31 -13
  27. package/release.json +2 -2
  28. package/templates/react-router/package.json +1 -1
  29. package/templates/react-router/src/pages/Index.tsx +1 -1
  30. package/dist/build/vite/index.d.ts.map +0 -1
  31. package/dist/build/vite/plugins/content-sync.d.ts.map +0 -1
  32. package/dist/build/vite/plugins/content-sync.test.d.ts +0 -2
  33. package/dist/build/vite/plugins/content-sync.test.d.ts.map +0 -1
  34. package/dist/build/vite/plugins/content-sync.test.js +0 -64
  35. package/dist/build/vite/plugins/data-id.d.ts.map +0 -1
  36. package/dist/build/vite/plugins/fimo-config.d.ts.map +0 -1
  37. package/dist/build/vite/plugins/fimo-config.test.d.ts +0 -2
  38. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +0 -1
  39. package/dist/build/vite/plugins/fimo-config.test.js +0 -46
  40. package/dist/build/vite/plugins/overlay.d.ts.map +0 -1
  41. package/dist/build/vite/plugins/robots.d.ts.map +0 -1
  42. package/dist/build/vite/plugins/seo.d.ts.map +0 -1
  43. package/dist/build/vite/plugins/server-config.d.ts.map +0 -1
  44. package/dist/build/vite/plugins/sitemap.d.ts.map +0 -1
  45. package/dist/build/vite/plugins/translations.d.ts.map +0 -1
  46. package/dist/build/vite/plugins/translations.test.d.ts +0 -2
  47. package/dist/build/vite/plugins/translations.test.d.ts.map +0 -1
  48. package/dist/build/vite/plugins/translations.test.js +0 -266
  49. package/dist/runtime/app/FimoProviders.d.ts.map +0 -1
  50. package/dist/runtime/app/FimoScripts.d.ts.map +0 -1
  51. package/dist/runtime/app/index.d.ts.map +0 -1
  52. package/dist/runtime/app/prefetch.d.ts.map +0 -1
  53. package/dist/runtime/content/DataSourcesProvider.d.ts.map +0 -1
  54. package/dist/runtime/content/RefreshContentProvider.d.ts.map +0 -1
  55. package/dist/runtime/content/RefreshContentProvider.test.d.ts +0 -2
  56. package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +0 -1
  57. package/dist/runtime/content/RefreshContentProvider.test.js +0 -33
  58. package/dist/runtime/content/parseCollectionResponse.d.ts.map +0 -1
  59. package/dist/runtime/content/parseCollectionResponse.test.d.ts +0 -2
  60. package/dist/runtime/content/parseCollectionResponse.test.d.ts.map +0 -1
  61. package/dist/runtime/content/parseCollectionResponse.test.js +0 -20
  62. package/dist/runtime/content/utils/retrieve-data-sources.d.ts.map +0 -1
  63. package/dist/runtime/core/AppErrorBoundary.d.ts.map +0 -1
  64. package/dist/runtime/core/ViteLifecycles.d.ts.map +0 -1
  65. package/dist/runtime/i18n/FimoLocaleContext.d.ts.map +0 -1
  66. package/dist/runtime/i18n/locale.d.ts.map +0 -1
  67. package/dist/runtime/i18n/locale.test.d.ts +0 -2
  68. package/dist/runtime/i18n/locale.test.d.ts.map +0 -1
  69. package/dist/runtime/i18n/locale.test.js +0 -58
  70. package/dist/runtime/iframe/ConsoleErrorProvider.d.ts.map +0 -1
  71. package/dist/runtime/iframe/ScreenshotProvider.d.ts.map +0 -1
  72. package/dist/runtime/iframe/runtime/overlay-hmr.d.ts.map +0 -1
  73. package/dist/runtime/iframe/runtime/overlay-runtime.d.ts.map +0 -1
  74. package/dist/runtime/index.d.ts.map +0 -1
  75. package/dist/runtime/paths/get-fimo-paths.d.ts.map +0 -1
  76. package/dist/runtime/paths/get-fimo-paths.test.d.ts +0 -2
  77. package/dist/runtime/paths/get-fimo-paths.test.d.ts.map +0 -1
  78. package/dist/runtime/paths/get-fimo-paths.test.js +0 -69
  79. package/dist/runtime/paths/index.d.ts.map +0 -1
  80. package/dist/runtime/paths/types.d.ts.map +0 -1
  81. package/dist/runtime/primitives/components/Boolean.d.ts.map +0 -1
  82. package/dist/runtime/primitives/components/Date.d.ts.map +0 -1
  83. package/dist/runtime/primitives/components/Image.d.ts.map +0 -1
  84. package/dist/runtime/primitives/components/RichText.d.ts.map +0 -1
  85. package/dist/runtime/primitives/components/StaticImage.d.ts.map +0 -1
  86. package/dist/runtime/primitives/components/Text.d.ts.map +0 -1
  87. package/dist/runtime/primitives/components/Video.d.ts.map +0 -1
  88. package/dist/runtime/primitives/components/index.d.ts.map +0 -1
  89. package/dist/runtime/primitives/index.d.ts.map +0 -1
  90. package/dist/runtime/primitives/lib/image-optimization.d.ts.map +0 -1
  91. package/dist/runtime/primitives/lib/index.d.ts.map +0 -1
  92. package/dist/runtime/primitives/lib/primitives.d.ts.map +0 -1
  93. package/dist/runtime/primitives/lib/query.d.ts.map +0 -1
  94. package/dist/runtime/primitives/lib/template.d.ts.map +0 -1
  95. package/dist/runtime/primitives/lib/useFimoLiveValue.d.ts.map +0 -1
  96. package/dist/runtime/primitives/public-surface.test.d.ts +0 -2
  97. package/dist/runtime/primitives/public-surface.test.d.ts.map +0 -1
  98. package/dist/runtime/primitives/public-surface.test.js +0 -45
  99. package/dist/runtime/primitives/translations.d.ts.map +0 -1
  100. package/dist/runtime/react-router/index.d.ts.map +0 -1
  101. package/dist/runtime/react-router/navigation-utils.d.ts.map +0 -1
  102. package/dist/runtime/react-router/navigation.d.ts.map +0 -1
  103. package/dist/runtime/react-router/navigation.test.d.ts +0 -2
  104. package/dist/runtime/react-router/navigation.test.d.ts.map +0 -1
  105. package/dist/runtime/react-router/navigation.test.js +0 -35
  106. package/dist/runtime/react-router/routes.d.ts.map +0 -1
  107. package/dist/runtime/react-router/routes.test.d.ts +0 -2
  108. package/dist/runtime/react-router/routes.test.d.ts.map +0 -1
  109. package/dist/runtime/react-router/routes.test.js +0 -67
  110. package/dist/runtime/routing/AppResetScrollOnNavigate.d.ts.map +0 -1
  111. package/dist/runtime/routing/AppRoutesProvider.d.ts.map +0 -1
  112. package/dist/runtime/seo/buildSeoForPath.d.ts.map +0 -1
  113. package/dist/runtime/seo/htmlProps.d.ts.map +0 -1
  114. package/dist/runtime/seo/index.d.ts.map +0 -1
  115. package/dist/runtime/seo/resolver.d.ts.map +0 -1
  116. package/dist/runtime/shared/fimo-config.server.d.ts.map +0 -1
  117. package/dist/runtime/shared/fimo-config.types.d.ts.map +0 -1
  118. package/dist/runtime/t/OptionalTracking.d.ts.map +0 -1
  119. package/dist/runtime/t/OptionalTracking.test.d.ts +0 -2
  120. package/dist/runtime/t/OptionalTracking.test.d.ts.map +0 -1
  121. package/dist/runtime/t/OptionalTracking.test.js +0 -11
  122. package/dist/runtime/t/Pageview.d.ts.map +0 -1
  123. package/dist/runtime/templates.d.ts.map +0 -1
  124. package/dist/scripts/generated-types.test.js +0 -212
  125. package/dist/scripts/lint-translation-keys.test.d.ts +0 -1
  126. package/dist/scripts/lint-translation-keys.test.js +0 -16
  127. package/dist/scripts/sync-schemas.test.d.ts +0 -1
  128. package/dist/scripts/sync-schemas.test.js +0 -124
  129. package/dist/scripts/validate-route-metadata.test.d.ts +0 -1
  130. package/dist/scripts/validate-route-metadata.test.js +0 -116
  131. package/dist/scripts/validate-schemas.test.d.ts +0 -1
  132. package/dist/scripts/validate-schemas.test.js +0 -263
  133. package/scripts/add-ext.mjs +0 -47
  134. package/scripts/bump-version.mjs +0 -58
  135. package/scripts/bundle-cli.mjs +0 -123
  136. package/scripts/install-cli.mjs +0 -36
  137. package/scripts/lib/channels.mjs +0 -70
  138. package/scripts/lib/dev-release-state.mjs +0 -44
  139. package/scripts/lib/dev-release-state.test.ts +0 -36
  140. package/scripts/lib/postinstall-onboarding.test.ts +0 -82
  141. package/scripts/lib/release-core.mjs +0 -225
  142. package/scripts/lib/release-core.test.ts +0 -147
  143. package/scripts/migration-qa-gate.test.ts +0 -155
  144. package/scripts/publish-npm.mjs +0 -180
  145. /package/dist/{scripts/generated-types.test.d.ts → runtime/content/client/types.js} +0 -0
@@ -1,212 +0,0 @@
1
- import { execFileSync } from 'node:child_process';
2
- import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
3
- import { createRequire } from 'node:module';
4
- import { tmpdir } from 'node:os';
5
- import { dirname, join, resolve } from 'node:path';
6
- import { fileURLToPath } from 'node:url';
7
- import { afterAll, describe, expect, it } from 'vitest';
8
- import { syncSchemas } from './sync-schemas.js';
9
- /**
10
- * The generated-types gate: generate hooks + types from fixture schemas that
11
- * cover every field type and reference shape, add usage code written the way
12
- * an agent writes site code, and run the real TypeScript compiler over the
13
- * output. A template regression fails this test — never a user project.
14
- */
15
- const here = dirname(fileURLToPath(import.meta.url));
16
- const requireFromHere = createRequire(import.meta.url);
17
- const runtimeBarrel = resolve(here, '../runtime/index.ts');
18
- const tscBin = requireFromHere.resolve('typescript/bin/tsc');
19
- const fixtureRoot = mkdtempSync(join(tmpdir(), 'fimo-generated-types-'));
20
- const schemasDir = join(fixtureRoot, 'src', 'schemas');
21
- const SCHEMAS = {
22
- Author: {
23
- uid: 'Author',
24
- name: 'Author',
25
- i18n: { enabled: true },
26
- fields: {
27
- name: { type: 'string', required: true },
28
- bio: { type: 'richtext' },
29
- avatar: { type: 'media' },
30
- active: { type: 'boolean' },
31
- bornAt: { type: 'date' },
32
- meta: { type: 'json' },
33
- rating: { type: 'number' },
34
- },
35
- },
36
- Video: {
37
- uid: 'Video',
38
- name: 'Video',
39
- i18n: { enabled: false },
40
- fields: {
41
- title: { type: 'string', required: true },
42
- clips: { type: 'media', multiple: true },
43
- },
44
- },
45
- Post: {
46
- uid: 'Post',
47
- name: 'Post',
48
- i18n: { enabled: true },
49
- fields: {
50
- title: { type: 'string', required: true },
51
- author: { type: 'reference', target: 'Author' },
52
- contributors: { type: 'reference', target: 'Author', many: true },
53
- related: { type: 'reference', target: 'Post', many: true },
54
- },
55
- },
56
- Page: {
57
- uid: 'Page',
58
- name: 'Page',
59
- i18n: { enabled: true },
60
- fields: {
61
- title: { type: 'string' },
62
- featured: { type: 'reference', targets: ['Post', 'Video'], many: true },
63
- hero: { type: 'reference', targets: ['Author', 'Video'] },
64
- },
65
- },
66
- SiteSettings: {
67
- uid: 'SiteSettings',
68
- name: 'SiteSettings',
69
- isSingleton: true,
70
- i18n: { enabled: false },
71
- fields: {
72
- siteName: { type: 'string', required: true },
73
- },
74
- },
75
- };
76
- const USAGE = `// Agent-style usage of the generated types — compile-time contract.
77
- import type { PageResult, PageWhere } from "./Page";
78
- import Post from "./Post";
79
- import type { PostResult } from "./Post";
80
-
81
- export function renderFeatured(page: PageResult<undefined, readonly ["featured", "hero"]>): string[] {
82
- const out: string[] = [];
83
- for (const item of page.featured) {
84
- switch (item.$type) {
85
- case "Post":
86
- out.push(\`post \${String(item.title)} by \${item.author?.documentId ?? "nobody"}\`);
87
- break;
88
- case "Video":
89
- out.push(\`video \${String(item.title)} with \${item.clips.length} clip(s)\`);
90
- break;
91
- default: {
92
- const exhaustive: never = item;
93
- return exhaustive;
94
- }
95
- }
96
- }
97
- const hero = page.hero;
98
- if (hero && hero.$type === "Author") {
99
- out.push(\`hero \${String(hero.name)}\`);
100
- }
101
- return out;
102
- }
103
-
104
- export function renderPost(post: PostResult<undefined, readonly ["author", "contributors"]>): string {
105
- const contributors = post.contributors.map((author) => String(author.name)).join(", ");
106
- return \`\${String(post.title)} — \${post.author ? String(post.author.name) : "anonymous"} (\${contributors})\`;
107
- }
108
-
109
- export function whereContract(): PageWhere {
110
- return {
111
- title: "Home",
112
- // @ts-expect-error filtering on polymorphic reference fields is not supported
113
- featured: "some-document-id",
114
- };
115
- }
116
-
117
- export function paginatedPostContract(): void {
118
- const legacy = Post.useGet({ limit: 3 });
119
- const page = Post.useList({
120
- fields: ["title", "author"] as const,
121
- populate: ["author"] as const,
122
- limit: 12,
123
- offset: 24,
124
- });
125
- const infinite = Post.useInfiniteList({
126
- fields: ["title", "author"] as const,
127
- populate: ["author"] as const,
128
- limit: 12,
129
- });
130
-
131
- const legacyPosts: PostResult[] = legacy.data ?? [];
132
- const pagePosts: PostResult<readonly ["title", "author"], readonly ["author"]>[] = page.data?.items ?? [];
133
- const total: number = page.data?.pageInfo.total ?? 0;
134
- const nextOffset: number | null = page.data?.pageInfo.nextOffset ?? null;
135
- const infinitePosts: PostResult<readonly ["title", "author"], readonly ["author"]>[] = infinite.items;
136
- const firstAuthorName: string = infinitePosts[0]?.author ? String(infinitePosts[0].author.name) : "";
137
- const hasMore: boolean = infinite.hasMore;
138
- const isLoadingMore: boolean = infinite.isLoadingMore;
139
-
140
- void infinite.loadMore();
141
- // @ts-expect-error infinite lists own offset progression
142
- Post.useInfiniteList({ offset: 12 });
143
-
144
- void [legacyPosts, pagePosts, total, nextOffset, infinitePosts, firstAuthorName, hasMore, isLoadingMore];
145
- }
146
-
147
- export async function paginatedPostFetchContract(): Promise<void> {
148
- const page = await Post.list({ limit: 12, offset: 24 });
149
- const legacyPosts: PostResult[] = await Post.get({ limit: 3 });
150
- const pagePosts: PostResult[] = page.items;
151
- const hasMore: boolean = page.pageInfo.hasMore;
152
-
153
- void [legacyPosts, pagePosts, hasMore];
154
- }
155
- `;
156
- const SHIMS = `declare module "virtual:fimo-config" {
157
- const config: Record<string, unknown>;
158
- export default config;
159
- }
160
- declare module "virtual:translations" {
161
- export const dictionaries: Record<string, Record<string, string>>;
162
- export const defaultLocale: string;
163
- export const signature: string;
164
- export const updatedAt: string;
165
- }
166
- declare module "*.css" {}
167
- `;
168
- afterAll(() => {
169
- rmSync(fixtureRoot, { recursive: true, force: true });
170
- });
171
- describe('generated types gate', () => {
172
- it('generates hooks + types for every field shape and the output typechecks with agent-style usage', () => {
173
- mkdirSync(schemasDir, { recursive: true });
174
- // Real react-query types: the fixture resolves them like a real project.
175
- const reactQueryDir = dirname(requireFromHere.resolve('@tanstack/react-query/package.json'));
176
- mkdirSync(join(fixtureRoot, 'node_modules', '@tanstack'), { recursive: true });
177
- symlinkSync(reactQueryDir, join(fixtureRoot, 'node_modules', '@tanstack', 'react-query'));
178
- for (const [uid, schema] of Object.entries(SCHEMAS)) {
179
- writeFileSync(join(schemasDir, `${uid}.json`), JSON.stringify(schema, null, 2));
180
- }
181
- const result = syncSchemas({ cwd: fixtureRoot, verbose: false });
182
- expect(result.created).toBeGreaterThanOrEqual(Object.keys(SCHEMAS).length * 2);
183
- writeFileSync(join(schemasDir, 'usage.ts'), USAGE);
184
- writeFileSync(join(fixtureRoot, 'shims.d.ts'), SHIMS);
185
- writeFileSync(join(fixtureRoot, 'tsconfig.json'), JSON.stringify({
186
- compilerOptions: {
187
- strict: true,
188
- noEmit: true,
189
- skipLibCheck: true,
190
- target: 'ES2022',
191
- module: 'ESNext',
192
- moduleResolution: 'bundler',
193
- jsx: 'react-jsx',
194
- allowImportingTsExtensions: true,
195
- baseUrl: '.',
196
- paths: {
197
- 'fimo/ui': [runtimeBarrel],
198
- },
199
- },
200
- include: ['src/**/*.ts', 'shims.d.ts'],
201
- }, null, 2));
202
- let output = '';
203
- try {
204
- output = execFileSync(process.execPath, [tscBin, '-p', fixtureRoot], { encoding: 'utf8' });
205
- }
206
- catch (error) {
207
- const failure = error;
208
- throw new Error(`generated types failed to typecheck:\n${failure.stdout ?? ''}${failure.stderr ?? ''}`);
209
- }
210
- expect(output.trim()).toBe('');
211
- }, 120_000);
212
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,16 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { lintTranslationKeys } from './lint-translation-keys';
3
- describe('lintTranslationKeys', () => {
4
- it('allows literal DB-owned label keys', () => {
5
- const errors = lintTranslationKeys('src/pages/Index.tsx', "const title = t('hero.title');");
6
- expect(errors).toEqual([]);
7
- });
8
- it('rejects inline fallback values', () => {
9
- const errors = lintTranslationKeys('src/pages/Index.tsx', "const title = t('hero.title', 'Welcome');");
10
- expect(errors).toEqual([expect.objectContaining({ rule: 'labels/no-inline-default' })]);
11
- });
12
- it('rejects dynamic keys', () => {
13
- const errors = lintTranslationKeys('src/pages/Index.tsx', 'const title = t(heroKey);');
14
- expect(errors).toEqual([expect.objectContaining({ rule: 'translation-keys' })]);
15
- });
16
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,124 +0,0 @@
1
- import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
2
- import { tmpdir } from 'node:os';
3
- import { join } from 'node:path';
4
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
5
- import { syncSchemas } from './sync-schemas.js';
6
- let scratch;
7
- beforeEach(() => {
8
- scratch = mkdtempSync(join(tmpdir(), 'fimo-sync-schemas-'));
9
- mkdirSync(join(scratch, 'src/schemas'), { recursive: true });
10
- });
11
- afterEach(() => {
12
- rmSync(scratch, { recursive: true, force: true });
13
- });
14
- function writeSchema(name, schema) {
15
- writeFileSync(join(scratch, 'src/schemas', `${name}.json`), JSON.stringify(schema, null, 2));
16
- }
17
- describe('syncSchemas', () => {
18
- it('generates raw references plus typed populated helpers', () => {
19
- writeSchema('Author', {
20
- uid: 'Author',
21
- name: 'Authors',
22
- fields: {
23
- name: { type: 'string' },
24
- },
25
- });
26
- writeSchema('Post', {
27
- uid: 'Post',
28
- name: 'Posts',
29
- fields: {
30
- title: { type: 'string' },
31
- author: { type: 'reference', target: 'Author' },
32
- contributors: { type: 'reference', target: 'Author', many: true },
33
- relatedPosts: { type: 'reference', target: 'Post', many: true },
34
- },
35
- });
36
- expect(syncSchemas({ cwd: scratch, verbose: false })).toEqual({ created: 4, updated: 0, deleted: 0 });
37
- const post = readFileSync(join(scratch, 'src/schemas/Post.ts'), 'utf-8');
38
- const postTypes = readFileSync(join(scratch, 'src/schemas/Post.types.ts'), 'utf-8');
39
- expect(post).toContain('import { wrapWithSource as wrapAuthorWithSource } from "./Author";');
40
- expect(post).toContain('import type { Post, PostInput, PostPopulate, PostResult, PostFields, PostQuery } from "./Post.types";');
41
- expect(post).toContain('from "./Post.types";');
42
- expect(post).not.toContain('import type { Author } from "./Author";');
43
- expect(post).not.toContain('export interface Post extends WithReferences<PostReferences>');
44
- expect(post).not.toContain('type FimoReference');
45
- expect(post).toContain('Promise<PostResult<undefined, TPopulate>>');
46
- expect(post).toContain('TPopulate extends PostPopulate | undefined');
47
- expect(post).toContain('wrapAuthorWithSource(value)');
48
- expect(post).toContain('wrapWithSource(value)');
49
- expect(post).toContain('export async function create(payload: PostInput): Promise<Post>');
50
- expect(post).toContain('return wrapWithSource(data.data);');
51
- expect(postTypes).toContain('import type { Author } from "./Author.types";');
52
- expect(postTypes).toContain('export interface Post extends WithReferences<PostReferences>');
53
- expect(postTypes).toContain('type FimoReference');
54
- expect(postTypes).toContain('author: FimoReference | null;');
55
- expect(postTypes).toContain('contributors: FimoReference[];');
56
- expect(postTypes).toContain('relatedPosts: FimoReference[];');
57
- expect(postTypes).toContain('export interface PostReferences');
58
- expect(postTypes).toContain('export interface PostInput');
59
- expect(postTypes).toContain('author?: FimoReference | null;');
60
- expect(postTypes).toContain('author: Author | null;');
61
- expect(postTypes).toContain('contributors: Author[];');
62
- expect(postTypes).toContain('relatedPosts: Post[];');
63
- expect(postTypes).toContain('export type PostResult<');
64
- expect(postTypes).not.toContain('PostPopulated');
65
- });
66
- it('generates locale-free shared types and locale-sensitive populated target keys', () => {
67
- writeSchema('Homepage', {
68
- uid: 'Homepage',
69
- name: 'Homepages',
70
- fields: { title: { type: 'string' } },
71
- });
72
- writeSchema('Category', {
73
- uid: 'Category',
74
- name: 'Categories',
75
- i18n: { enabled: false },
76
- fields: { name: { type: 'string' } },
77
- });
78
- writeSchema('Settings', {
79
- uid: 'Settings',
80
- name: 'Settings',
81
- i18n: { enabled: false },
82
- fields: {
83
- homepage: { type: 'reference', target: 'Homepage' },
84
- category: { type: 'reference', target: 'Category' },
85
- },
86
- });
87
- syncSchemas({ cwd: scratch, verbose: false });
88
- const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
89
- const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
90
- const homepageTypes = readFileSync(join(scratch, 'src/schemas/Homepage.types.ts'), 'utf-8');
91
- expect(settingsTypes).not.toContain('locale: string;');
92
- expect(settingsTypes).not.toContain('locale?: string;');
93
- expect(homepageTypes).toContain('locale: string;');
94
- expect(settings).toContain('const LOCALIZED_CONTENT = false;');
95
- expect(settings).toContain('const LOCALIZED_REFERENCE_FIELDS = new Set<string>(["homepage"]);');
96
- expect(settings).not.toContain(' locale: entry.locale,');
97
- expect(settings).toContain('readLocale(params, LOCALIZED_CONTENT, useQueryLocale(params.locale))');
98
- expect(settings.match(/readLocale\(params, LOCALIZED_CONTENT, useQueryLocale\(params\.locale\)\)/g)?.length).toBe(4);
99
- expect(settings.match(/readLocale\(options, false, useQueryLocale\(options\.locale\)\)/g)?.length).toBe(2);
100
- });
101
- it('generates singleton document APIs without collection mutations', () => {
102
- writeSchema('Settings', {
103
- uid: 'Settings',
104
- name: 'Settings',
105
- isSingleton: true,
106
- i18n: { enabled: false },
107
- fields: { siteName: { type: 'string', required: true } },
108
- });
109
- syncSchemas({ cwd: scratch, verbose: false });
110
- const settings = readFileSync(join(scratch, 'src/schemas/Settings.ts'), 'utf-8');
111
- const settingsTypes = readFileSync(join(scratch, 'src/schemas/Settings.types.ts'), 'utf-8');
112
- expect(settings).toContain('fetch(`${base}${withReadOptions(`/documents/Settings`, options)}`');
113
- expect(settings).toContain('export async function update(payload: Partial<SettingsInput>)');
114
- expect(settings).toContain('export function useSuspenseGet');
115
- expect(settings).not.toContain('export async function create(');
116
- expect(settings).not.toContain('export async function remove(');
117
- expect(settings).not.toContain('export function useCreate(');
118
- expect(settings).not.toContain('export function useList(');
119
- expect(settings).not.toContain('export function useInfiniteList(');
120
- expect(settings).not.toContain('useInfiniteQuery');
121
- expect(settingsTypes).toContain('siteName: string;');
122
- expect(settingsTypes).not.toContain('documentId?: string;');
123
- });
124
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,116 +0,0 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { mkdtempSync, rmSync } from 'node:fs';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { afterEach, describe, expect, it } from 'vitest';
6
- import { validateRouteMetadata } from './validate-route-metadata';
7
- describe('validateRouteMetadata i18n checks', () => {
8
- let scratch = null;
9
- afterEach(() => {
10
- if (scratch) {
11
- rmSync(scratch, { recursive: true, force: true });
12
- scratch = null;
13
- }
14
- });
15
- it('accepts fimoRoutes() with canonical routes and multi-locale path prefixes', () => {
16
- const cwd = writeProject({
17
- config: {
18
- i18n: {
19
- defaultLocale: 'en',
20
- locales: ['en', 'es'],
21
- routing: { strategy: 'path-prefix' },
22
- },
23
- routes: {
24
- home: { path: '/', label: 'Home' },
25
- pricing: { path: '/pricing', label: 'Pricing' },
26
- },
27
- },
28
- routesSource: `import type { RouteConfig } from '@react-router/dev/routes';
29
- import { index, route } from '@react-router/dev/routes';
30
- import { fimoRoutes } from 'fimo/react-router/routes';
31
-
32
- const routes = [
33
- index('./pages/Index.tsx', { id: 'home' }),
34
- route('pricing', './pages/Pricing.tsx', { id: 'pricing' }),
35
- ] satisfies RouteConfig;
36
-
37
- export default fimoRoutes(routes) satisfies RouteConfig;
38
- `,
39
- files: ['src/pages/Index.tsx', 'src/pages/Pricing.tsx'],
40
- });
41
- expect(validateRouteMetadata({ cwd }).errors).toEqual([]);
42
- });
43
- it('rejects invalid locale config', () => {
44
- const cwd = writeProject({
45
- config: {
46
- i18n: {
47
- defaultLocale: 'en--US',
48
- locales: ['es'],
49
- routing: { strategy: 'path-prefix' },
50
- },
51
- routes: {
52
- home: { path: '/', label: 'Home' },
53
- },
54
- },
55
- routesSource: `import { index } from '@react-router/dev/routes';
56
- export default [index('./pages/Index.tsx')];
57
- `,
58
- files: ['src/pages/Index.tsx'],
59
- });
60
- const errors = validateRouteMetadata({ cwd }).errors.join('\n');
61
- expect(errors).toContain('Invalid i18n.defaultLocale "en--US"');
62
- expect(errors).toContain('i18n.locales must include i18n.defaultLocale');
63
- });
64
- it('rejects canonical routes that claim locale prefixes', () => {
65
- const cwd = writeProject({
66
- config: {
67
- i18n: {
68
- defaultLocale: 'en',
69
- locales: ['en', 'es'],
70
- routing: { strategy: 'path-prefix' },
71
- },
72
- routes: {
73
- spanish: { path: '/es', label: 'Spanish' },
74
- },
75
- },
76
- routesSource: `import { route } from '@react-router/dev/routes';
77
- export default [route('es', './pages/Spanish.tsx')];
78
- `,
79
- files: ['src/pages/Spanish.tsx'],
80
- });
81
- const errors = validateRouteMetadata({ cwd }).errors.join('\n');
82
- expect(errors).toContain('uses reserved locale prefix "es"');
83
- });
84
- it('warns as an error when top-level dynamic routes can capture locale URLs without fimoRoutes()', () => {
85
- const cwd = writeProject({
86
- config: {
87
- i18n: {
88
- defaultLocale: 'en',
89
- locales: ['en', 'es'],
90
- routing: { strategy: 'path-prefix' },
91
- },
92
- routes: {
93
- page: { path: '/:slug', label: 'Page', params: { slug: { schema: 'Page', queryField: 'slug' } } },
94
- },
95
- },
96
- routesSource: `import { route } from '@react-router/dev/routes';
97
- export default [route(':slug', './pages/Page.tsx')];
98
- `,
99
- files: ['src/pages/Page.tsx'],
100
- });
101
- const errors = validateRouteMetadata({ cwd }).errors.join('\n');
102
- expect(errors).toContain('top-level dynamic route');
103
- expect(errors).toContain('fimoRoutes(routes)');
104
- });
105
- function writeProject(args) {
106
- scratch = mkdtempSync(join(tmpdir(), 'fimo-route-metadata-'));
107
- mkdirSync(join(scratch, 'src'), { recursive: true });
108
- writeFileSync(join(scratch, 'fimo-config.json'), JSON.stringify(args.config, null, 2));
109
- writeFileSync(join(scratch, 'src', 'routes.ts'), args.routesSource);
110
- for (const file of args.files) {
111
- mkdirSync(join(scratch, file, '..'), { recursive: true });
112
- writeFileSync(join(scratch, file), 'export default function Page() { return null; }\n');
113
- }
114
- return scratch;
115
- }
116
- });
@@ -1 +0,0 @@
1
- export {};