fimo 0.2.4 → 0.2.5-experimental.1782327181771

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 (62) hide show
  1. package/README.md +2 -2
  2. package/assets/agent-templates/content-translator/GOAL.md +8 -11
  3. package/assets/agent-templates/content-translator/capabilities.yaml +1 -3
  4. package/assets/agent-templates/content-translator/scripts/translate-entries.ts +10 -7
  5. package/assets/content-templates/hooks-template.eta +73 -23
  6. package/assets/skills/fimo/SKILL.md +3 -3
  7. package/assets/skills/fimo/references/forms.md +1 -1
  8. package/assets/skills/fimo/references/setup-plain-vite.md +1 -1
  9. package/assets/skills/fimo/references/setup-react-router.md +1 -1
  10. package/assets/skills/fimo/references/translations.md +42 -14
  11. package/assets/skills/fimo/references/ui.md +4 -4
  12. package/assets/skills/fimo-cli/SKILL.md +3 -3
  13. package/assets/skills/fimo-cli/references/content.md +1 -1
  14. package/assets/skills/fimo-cli/references/forms.md +1 -1
  15. package/assets/skills/fimo-cli/references/translations.md +45 -19
  16. package/dist/build/vite/plugins/fimo-config.d.ts.map +1 -1
  17. package/dist/build/vite/plugins/fimo-config.js +16 -0
  18. package/dist/build/vite/plugins/fimo-config.test.d.ts +2 -0
  19. package/dist/build/vite/plugins/fimo-config.test.d.ts.map +1 -0
  20. package/dist/build/vite/plugins/fimo-config.test.js +46 -0
  21. package/dist/build/vite/plugins/translations.d.ts +7 -6
  22. package/dist/build/vite/plugins/translations.d.ts.map +1 -1
  23. package/dist/build/vite/plugins/translations.js +366 -33
  24. package/dist/build/vite/plugins/translations.test.d.ts +2 -0
  25. package/dist/build/vite/plugins/translations.test.d.ts.map +1 -0
  26. package/dist/build/vite/plugins/translations.test.js +177 -0
  27. package/dist/cli/bundle.json +2 -2
  28. package/dist/cli/index.js +1305 -1078
  29. package/dist/runtime/app/FimoScripts.d.ts.map +1 -1
  30. package/dist/runtime/app/FimoScripts.js +35 -1
  31. package/dist/runtime/app/prefetch.d.ts.map +1 -1
  32. package/dist/runtime/app/prefetch.js +6 -1
  33. package/dist/runtime/paths/get-fimo-paths.d.ts.map +1 -1
  34. package/dist/runtime/paths/get-fimo-paths.js +9 -4
  35. package/dist/runtime/primitives/components/Text.d.ts +1 -1
  36. package/dist/runtime/primitives/components/Text.js +1 -1
  37. package/dist/runtime/primitives/lib/query.d.ts +5 -0
  38. package/dist/runtime/primitives/lib/query.d.ts.map +1 -1
  39. package/dist/runtime/primitives/lib/template.d.ts +1 -1
  40. package/dist/runtime/primitives/lib/template.js +1 -1
  41. package/dist/runtime/primitives/translations.d.ts +9 -3
  42. package/dist/runtime/primitives/translations.d.ts.map +1 -1
  43. package/dist/runtime/primitives/translations.js +32 -5
  44. package/dist/runtime/seo/htmlProps.d.ts +1 -1
  45. package/dist/runtime/seo/htmlProps.js +2 -2
  46. package/dist/runtime/shared/fimo-config.server.d.ts.map +1 -1
  47. package/dist/runtime/shared/fimo-config.server.js +1 -0
  48. package/dist/runtime/shared/fimo-config.types.d.ts +7 -0
  49. package/dist/runtime/shared/fimo-config.types.d.ts.map +1 -1
  50. package/dist/scripts/extract-translations.d.ts +8 -8
  51. package/dist/scripts/extract-translations.js +22 -57
  52. package/dist/scripts/lint-translation-keys.js +24 -5
  53. package/dist/scripts/lint-translation-keys.test.d.ts +1 -0
  54. package/dist/scripts/lint-translation-keys.test.js +16 -0
  55. package/package.json +1 -1
  56. package/release.json +2 -2
  57. package/templates/react-router/fimo-config.json +4 -0
  58. package/templates/react-router/package.json +1 -1
  59. package/assets/agent-templates/content-translator/scripts/write-locale-files.ts +0 -66
  60. package/dist/scripts/inject-translations.d.ts +0 -6
  61. package/dist/scripts/inject-translations.js +0 -168
  62. package/templates/react-router/translations/en.json +0 -1
@@ -0,0 +1,177 @@
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 translationsPlugin from './translations';
6
+ let scratch = null;
7
+ let activePlugin = null;
8
+ const originalFimoApiToken = process.env.FIMO_API_TOKEN;
9
+ const pluginContext = {
10
+ addWatchFile: vi.fn(),
11
+ meta: {},
12
+ error: (error) => {
13
+ throw error;
14
+ },
15
+ info: () => undefined,
16
+ warn: () => undefined,
17
+ debug: () => undefined,
18
+ };
19
+ afterEach(() => {
20
+ if (activePlugin && typeof activePlugin.closeBundle === 'function') {
21
+ activePlugin.closeBundle.call(pluginContext);
22
+ activePlugin = null;
23
+ }
24
+ if (scratch) {
25
+ rmSync(scratch, { recursive: true, force: true });
26
+ scratch = null;
27
+ }
28
+ if (originalFimoApiToken === undefined) {
29
+ delete process.env.FIMO_API_TOKEN;
30
+ }
31
+ else {
32
+ process.env.FIMO_API_TOKEN = originalFimoApiToken;
33
+ }
34
+ vi.unstubAllGlobals();
35
+ vi.clearAllMocks();
36
+ });
37
+ function createProjectRoot() {
38
+ scratch = mkdtempSync(join(tmpdir(), 'fimo-translations-plugin-'));
39
+ writeFileSync(join(scratch, 'fimo-config.json'), JSON.stringify({ i18n: { defaultLocale: 'es', locales: ['es'] } }, null, 2));
40
+ writeFileSync(join(scratch, '.env'), 'VITE_API_URL=https://tenant.example.test\n');
41
+ return scratch;
42
+ }
43
+ function createPlugin(root, command) {
44
+ const plugin = translationsPlugin();
45
+ activePlugin = plugin;
46
+ if (typeof plugin.configResolved === 'function') {
47
+ plugin.configResolved.call(pluginContext, { root, command });
48
+ }
49
+ return plugin;
50
+ }
51
+ async function buildStart(plugin) {
52
+ if (typeof plugin.buildStart !== 'function') {
53
+ throw new Error('Expected buildStart hook');
54
+ }
55
+ await plugin.buildStart.call(pluginContext, {});
56
+ }
57
+ async function loadTranslations(plugin) {
58
+ if (typeof plugin.resolveId !== 'function' || typeof plugin.load !== 'function') {
59
+ throw new Error('Expected resolveId/load hooks');
60
+ }
61
+ const id = await plugin.resolveId.call(pluginContext, 'virtual:translations', undefined, {});
62
+ if (typeof id !== 'string') {
63
+ throw new Error('Expected virtual translations module id');
64
+ }
65
+ const result = await plugin.load.call(pluginContext, id, {});
66
+ if (typeof result !== 'string') {
67
+ throw new Error('Expected virtual translations source');
68
+ }
69
+ return result;
70
+ }
71
+ function createDevServer() {
72
+ return {
73
+ watcher: { add: vi.fn() },
74
+ moduleGraph: {
75
+ getModuleById: vi.fn(() => null),
76
+ idToModuleMap: new Map(),
77
+ },
78
+ ws: { send: vi.fn() },
79
+ };
80
+ }
81
+ function mockLabels(labels) {
82
+ const fetchMock = vi.fn(async () => jsonResponse({ data: { labels, updatedAt: '2026-06-24T00:00:00.000Z' } }));
83
+ vi.stubGlobal('fetch', fetchMock);
84
+ return fetchMock;
85
+ }
86
+ function jsonResponse(body) {
87
+ return {
88
+ ok: true,
89
+ json: async () => body,
90
+ };
91
+ }
92
+ async function waitFor(condition) {
93
+ const deadline = Date.now() + 1000;
94
+ while (Date.now() < deadline) {
95
+ if (condition()) {
96
+ return;
97
+ }
98
+ await new Promise((resolve) => setTimeout(resolve, 10));
99
+ }
100
+ throw new Error('Timed out waiting for condition');
101
+ }
102
+ describe('translationsPlugin', () => {
103
+ it('does not block dev startup on the initial label fetch', async () => {
104
+ const root = createProjectRoot();
105
+ const fetchMock = mockLabels({ headline: 'Hola' });
106
+ const plugin = createPlugin(root, 'serve');
107
+ await buildStart(plugin);
108
+ expect(fetchMock).not.toHaveBeenCalled();
109
+ const source = await loadTranslations(plugin);
110
+ expect(source).toContain('export const translations = {};');
111
+ expect(source).toContain('export const locale = "es";');
112
+ });
113
+ it('invalidates the dev page when background labels arrive', async () => {
114
+ const root = createProjectRoot();
115
+ mockLabels({ headline: 'Hola' });
116
+ const plugin = createPlugin(root, 'serve');
117
+ await buildStart(plugin);
118
+ const server = createDevServer();
119
+ if (typeof plugin.configureServer !== 'function') {
120
+ throw new Error('Expected configureServer hook');
121
+ }
122
+ plugin.configureServer.call(pluginContext, server);
123
+ await waitFor(() => server.ws.send.mock.calls.length > 0);
124
+ expect(server.ws.send).toHaveBeenCalledWith({ type: 'full-reload', path: '*' });
125
+ const source = await loadTranslations(plugin);
126
+ expect(source).toContain('"headline":"Hola"');
127
+ });
128
+ it('discovers the events URL from tenant runtime config when env is absent', async () => {
129
+ const root = createProjectRoot();
130
+ const projectId = '11111111-1111-4111-8111-111111111111';
131
+ writeFileSync(join(root, '.fimo.settings.json'), JSON.stringify({ projectId, apiUrl: 'https://api.example.test' }));
132
+ process.env.FIMO_API_TOKEN = 'test-api-token';
133
+ const socketUrls = [];
134
+ vi.stubGlobal('WebSocket', class {
135
+ onopen = null;
136
+ onmessage = null;
137
+ onclose = null;
138
+ onerror = null;
139
+ constructor(url) {
140
+ socketUrls.push(url);
141
+ }
142
+ close() { }
143
+ });
144
+ vi.stubGlobal('fetch', vi.fn(async (input) => {
145
+ const url = new URL(input.toString());
146
+ if (url.pathname === '/labels') {
147
+ return jsonResponse({ data: { labels: {}, updatedAt: null } });
148
+ }
149
+ if (url.pathname === '/runtime-config') {
150
+ return jsonResponse({ data: { eventsServerUrl: 'https://events.example.test' } });
151
+ }
152
+ if (url.pathname === '/api/auth/one-time-token/generate') {
153
+ return jsonResponse({ data: { token: 'event-token' } });
154
+ }
155
+ return { ok: false, json: async () => ({}) };
156
+ }));
157
+ const plugin = createPlugin(root, 'serve');
158
+ await buildStart(plugin);
159
+ const server = createDevServer();
160
+ if (typeof plugin.configureServer !== 'function') {
161
+ throw new Error('Expected configureServer hook');
162
+ }
163
+ plugin.configureServer.call(pluginContext, server);
164
+ await waitFor(() => socketUrls.length > 0);
165
+ expect(socketUrls[0]).toContain(`wss://events.example.test/parties/events/${projectId}`);
166
+ expect(socketUrls[0]).toContain('authToken=event-token');
167
+ });
168
+ it('still waits for labels during production builds', async () => {
169
+ const root = createProjectRoot();
170
+ const fetchMock = mockLabels({ headline: 'Hola' });
171
+ const plugin = createPlugin(root, 'build');
172
+ await buildStart(plugin);
173
+ expect(fetchMock).toHaveBeenCalled();
174
+ const source = await loadTranslations(plugin);
175
+ expect(source).toContain('"headline":"Hola"');
176
+ });
177
+ });
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-06-24T17:45:46.869Z",
5
- "cliVersion": "0.2.4",
4
+ "bundledAt": "2026-06-24T18:53:10.760Z",
5
+ "cliVersion": "0.2.5-experimental.1782327181771",
6
6
  "external": [
7
7
  "oxc-parser",
8
8
  "fsevents"