fimo 0.2.5-experimental.1782385179125 → 0.2.5-staging.14
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/references/locales.md +1 -1
- package/assets/skills/fimo-cli/SKILL.md +1 -0
- package/assets/skills/fimo-cli/references/locales.md +1 -1
- package/assets/skills/fimo-cli/references/schedules.md +46 -0
- package/dist/build/vite/index.d.ts.map +1 -1
- package/dist/build/vite/index.js +1 -0
- package/dist/build/vite/plugins/translations.d.ts.map +1 -1
- package/dist/build/vite/plugins/translations.js +47 -2
- package/dist/build/vite/plugins/translations.test.js +74 -5
- package/dist/cli/bundle.json +2 -2
- package/dist/cli/index.js +319 -122
- package/dist/runtime/app/FimoScripts.d.ts.map +1 -1
- package/dist/runtime/app/FimoScripts.js +1 -35
- package/dist/runtime/content/RefreshContentProvider.d.ts +16 -0
- package/dist/runtime/content/RefreshContentProvider.d.ts.map +1 -1
- package/dist/runtime/content/RefreshContentProvider.js +55 -6
- package/dist/runtime/content/RefreshContentProvider.test.d.ts +2 -0
- package/dist/runtime/content/RefreshContentProvider.test.d.ts.map +1 -0
- package/dist/runtime/content/RefreshContentProvider.test.js +33 -0
- package/dist/runtime/primitives/translations.d.ts +0 -6
- package/dist/runtime/primitives/translations.d.ts.map +1 -1
- package/dist/runtime/primitives/translations.js +72 -31
- package/dist/runtime/templates.d.ts +13 -0
- package/dist/runtime/templates.d.ts.map +1 -1
- package/dist/runtime/templates.js +51 -0
- package/package.json +1 -1
- package/templates/react-router/package.json +1 -1
|
@@ -14,7 +14,7 @@ Fimo provides locale utilities that follow the Fimo standard, but they are helpe
|
|
|
14
14
|
"routing": {
|
|
15
15
|
"strategy": "path-prefix", // only supported strategy: /es/about for Spanish; default locale can stay /about
|
|
16
16
|
"prefixDefaultLocale": false, // when false, omit /en for the default locale
|
|
17
|
-
}
|
|
17
|
+
},
|
|
18
18
|
},
|
|
19
19
|
}
|
|
20
20
|
```
|
|
@@ -69,6 +69,7 @@ Load the matching reference when the user's request fires its trigger. Recipes t
|
|
|
69
69
|
- "What's in this project / show the project structure / describe the project / what schemas does it have / how many routes / project tree / show me what's here" → `references/describe.md`
|
|
70
70
|
- "Deploy / publish / go live / push my changes" → `references/deploy.md`
|
|
71
71
|
- "Get the preview URL / open the preview / what's the URL for this env / refresh the preview link / I just want to see the site / the preview URL stopped working" → `references/preview.md`
|
|
72
|
+
- "Schedule a command / cron / nightly publish / recurring checks / `fimo schedules`" → `references/schedules.md`
|
|
72
73
|
- "Set env vars / configure API keys / add project secrets / local `.env.local` / `fimo vars` / `fimo secrets`" → `references/project-vars-and-secrets.md`
|
|
73
74
|
- "Enable provider tools / integrations / API key connection for a provider / agent integration access" → `references/integrations.md`
|
|
74
75
|
- "Firecrawl / scrape a public website / competitor research / agent web research" → `references/integrations.md` **+ load `references/integrations/firecrawl.md`**
|
|
@@ -14,7 +14,7 @@ Edit `fimo-config.json`. Keep one locale unless the user asks for multilingual o
|
|
|
14
14
|
"routing": {
|
|
15
15
|
"strategy": "path-prefix", // only supported strategy: /es/about for Spanish; default locale can stay /about
|
|
16
16
|
"prefixDefaultLocale": false, // when false, omit /en for the default locale
|
|
17
|
-
}
|
|
17
|
+
},
|
|
18
18
|
},
|
|
19
19
|
}
|
|
20
20
|
```
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Scheduled Commands
|
|
2
|
+
|
|
3
|
+
`fimo schedules` manages recurring commands for a project env. A schedule is scoped to the env of the current checkout by default, or to `--env <name>` when passed.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
fimo schedules create <name> --cron "0 2 * * *" --do "fimo publish"
|
|
9
|
+
fimo schedules create smoke --cron "*/15 * * * *" --do "pnpm test"
|
|
10
|
+
fimo schedules list
|
|
11
|
+
fimo schedules run <name>
|
|
12
|
+
fimo schedules delete <name>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Runtime Model
|
|
16
|
+
|
|
17
|
+
Scheduled commands run in the project's env runtime, not on the user's machine. On each run, Fimo prepares the env, refreshes the project files and dependencies, authenticates as the schedule owner, then executes the stored command from the project root.
|
|
18
|
+
|
|
19
|
+
`--do` can be any CLI command that makes sense for the project, such as `fimo publish`, `fimo validate`, `pnpm test`, or a project script.
|
|
20
|
+
|
|
21
|
+
Cron expressions use standard five-field syntax. Schedules must not run more often than every five minutes.
|
|
22
|
+
|
|
23
|
+
## Useful Patterns
|
|
24
|
+
|
|
25
|
+
Nightly publish:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
fimo schedules create nightly --cron "0 2 * * *" --do "fimo publish"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Recurring smoke test:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
fimo schedules create smoke --cron "*/15 * * * *" --do "pnpm test"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Manual run without waiting for cron:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
fimo schedules run smoke
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- `list`, `run`, and `delete` operate on the current checkout env unless `--env` is passed.
|
|
46
|
+
- A failed command marks the schedule `failed`, stores the error/log excerpt, and `fimo schedules run` exits non-zero.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/build/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/build/vite/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,YAAY,EAAE,MAAM,MAAM,CAAC;AAyEjD,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;OAMG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;;;;;;;;OAUG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAGD,wBAAgB,IAAI,CAAC,IAAI,GAAE,eAAoB,GAAG,YAAY,EAAE,CAsB/D"}
|
package/dist/build/vite/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../../src/build/vite/plugins/translations.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAQlD,UAAU,WAAW;IACnB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../../src/build/vite/plugins/translations.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,MAAM,EAAiB,MAAM,MAAM,CAAC;AAQlD,UAAU,WAAW;IACnB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAwCD,MAAM,CAAC,OAAO,UAAU,kBAAkB,IAAI,MAAM,CAmOnD;AAoED,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwC3E"}
|
|
@@ -34,7 +34,21 @@ export default function translationsPlugin() {
|
|
|
34
34
|
for (const mod of findVirtualTranslationModules(devServer)) {
|
|
35
35
|
devServer.moduleGraph.invalidateModule(mod);
|
|
36
36
|
}
|
|
37
|
-
devServer.ws.send({
|
|
37
|
+
devServer.ws.send({
|
|
38
|
+
type: 'custom',
|
|
39
|
+
event: 'fimo:labels-updated',
|
|
40
|
+
data: labelUpdatePayload(bundle),
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
const emitContentUpdate = (payload) => {
|
|
44
|
+
if (!devServer) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
devServer.ws.send({
|
|
48
|
+
type: 'custom',
|
|
49
|
+
event: 'fimo:content-updated',
|
|
50
|
+
data: payload,
|
|
51
|
+
});
|
|
38
52
|
};
|
|
39
53
|
const scheduleRefresh = () => {
|
|
40
54
|
if (refreshTimer) {
|
|
@@ -161,6 +175,10 @@ export default function translationsPlugin() {
|
|
|
161
175
|
}
|
|
162
176
|
try {
|
|
163
177
|
const message = JSON.parse(raw);
|
|
178
|
+
if (message.type === 'content:changed') {
|
|
179
|
+
emitContentUpdate(parseContentChangePayload(message.payload));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
164
182
|
if (message.type !== 'labels:changed') {
|
|
165
183
|
return;
|
|
166
184
|
}
|
|
@@ -190,6 +208,27 @@ export default function translationsPlugin() {
|
|
|
190
208
|
return sockets;
|
|
191
209
|
}
|
|
192
210
|
}
|
|
211
|
+
function parseContentChangePayload(payload) {
|
|
212
|
+
if (!payload || typeof payload !== 'object') {
|
|
213
|
+
return { changes: [] };
|
|
214
|
+
}
|
|
215
|
+
const source = payload;
|
|
216
|
+
const changes = Array.isArray(source.changes)
|
|
217
|
+
? source.changes
|
|
218
|
+
.filter((change) => Boolean(change) && typeof change === 'object')
|
|
219
|
+
.map((change) => ({
|
|
220
|
+
action: typeof change.action === 'string' ? change.action : undefined,
|
|
221
|
+
contentType: typeof change.contentType === 'string' ? change.contentType : undefined,
|
|
222
|
+
documentId: typeof change.documentId === 'string' ? change.documentId : undefined,
|
|
223
|
+
id: typeof change.id === 'string' ? change.id : undefined,
|
|
224
|
+
locale: typeof change.locale === 'string' ? change.locale : undefined,
|
|
225
|
+
}))
|
|
226
|
+
: [];
|
|
227
|
+
return {
|
|
228
|
+
changes,
|
|
229
|
+
updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : undefined,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
193
232
|
function serializeBundle(bundle) {
|
|
194
233
|
return JSON.stringify({
|
|
195
234
|
dictionaries: bundle.dictionaries,
|
|
@@ -221,6 +260,12 @@ function findVirtualTranslationModules(server) {
|
|
|
221
260
|
function labelSignature(bundle) {
|
|
222
261
|
return Buffer.from(serializeBundle(bundle)).toString('base64');
|
|
223
262
|
}
|
|
263
|
+
function labelUpdatePayload(bundle) {
|
|
264
|
+
return {
|
|
265
|
+
...bundle,
|
|
266
|
+
signature: labelSignature(bundle),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
224
269
|
export async function loadLabelBundle(rootDir) {
|
|
225
270
|
const config = getConfigServer(rootDir);
|
|
226
271
|
const defaultLocale = getFimoDefaultLocale(config);
|
|
@@ -379,7 +424,7 @@ async function generateOneTimeToken(apiUrl) {
|
|
|
379
424
|
throw new Error('Not signed in to Fimo.');
|
|
380
425
|
}
|
|
381
426
|
const response = await fetch(new URL('/api/auth/one-time-token/generate', apiUrl), {
|
|
382
|
-
method: '
|
|
427
|
+
method: 'GET',
|
|
383
428
|
headers: {
|
|
384
429
|
Authorization: `Bearer ${bearer}`,
|
|
385
430
|
},
|
|
@@ -110,7 +110,7 @@ describe('translationsPlugin', () => {
|
|
|
110
110
|
expect(source).toContain('export const translations = {};');
|
|
111
111
|
expect(source).toContain('export const defaultLocale = "es";');
|
|
112
112
|
});
|
|
113
|
-
it('
|
|
113
|
+
it('pushes updated labels over custom HMR without a full page reload', async () => {
|
|
114
114
|
const root = createProjectRoot();
|
|
115
115
|
mockLabels({ headline: 'Hola' });
|
|
116
116
|
const plugin = createPlugin(root, 'serve');
|
|
@@ -121,7 +121,18 @@ describe('translationsPlugin', () => {
|
|
|
121
121
|
}
|
|
122
122
|
plugin.configureServer.call(pluginContext, server);
|
|
123
123
|
await waitFor(() => server.ws.send.mock.calls.length > 0);
|
|
124
|
-
expect(server.ws.send).toHaveBeenCalledWith({
|
|
124
|
+
expect(server.ws.send).toHaveBeenCalledWith({
|
|
125
|
+
type: 'custom',
|
|
126
|
+
event: 'fimo:labels-updated',
|
|
127
|
+
data: expect.objectContaining({
|
|
128
|
+
defaultLocale: 'es',
|
|
129
|
+
dictionaries: { es: { headline: 'Hola' } },
|
|
130
|
+
locales: ['es'],
|
|
131
|
+
signature: expect.any(String),
|
|
132
|
+
updatedAt: '2026-06-24T00:00:00.000Z',
|
|
133
|
+
}),
|
|
134
|
+
});
|
|
135
|
+
expect(server.ws.send).not.toHaveBeenCalledWith({ type: 'full-reload', path: '*' });
|
|
125
136
|
const source = await loadTranslations(plugin);
|
|
126
137
|
expect(source).toContain('"headline":"Hola"');
|
|
127
138
|
});
|
|
@@ -141,7 +152,7 @@ describe('translationsPlugin', () => {
|
|
|
141
152
|
}
|
|
142
153
|
close() { }
|
|
143
154
|
});
|
|
144
|
-
|
|
155
|
+
const fetchMock = vi.fn(async (input, init) => {
|
|
145
156
|
const url = new URL(input.toString());
|
|
146
157
|
if (url.pathname === '/labels') {
|
|
147
158
|
return jsonResponse({ data: { labels: {}, updatedAt: null } });
|
|
@@ -150,10 +161,12 @@ describe('translationsPlugin', () => {
|
|
|
150
161
|
return jsonResponse({ data: { eventsServerUrl: 'https://events.example.test' } });
|
|
151
162
|
}
|
|
152
163
|
if (url.pathname === '/api/auth/one-time-token/generate') {
|
|
153
|
-
|
|
164
|
+
expect(init?.method).toBe('GET');
|
|
165
|
+
return jsonResponse({ token: 'event-token' });
|
|
154
166
|
}
|
|
155
167
|
return { ok: false, json: async () => ({}) };
|
|
156
|
-
})
|
|
168
|
+
});
|
|
169
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
157
170
|
const plugin = createPlugin(root, 'serve');
|
|
158
171
|
await buildStart(plugin);
|
|
159
172
|
const server = createDevServer();
|
|
@@ -165,6 +178,62 @@ describe('translationsPlugin', () => {
|
|
|
165
178
|
expect(socketUrls[0]).toContain(`wss://events.example.test/parties/events/${projectId}`);
|
|
166
179
|
expect(socketUrls[0]).toContain('authToken=event-token');
|
|
167
180
|
});
|
|
181
|
+
it('forwards content changes over custom HMR without a full page reload', async () => {
|
|
182
|
+
const root = createProjectRoot();
|
|
183
|
+
const projectId = '11111111-1111-4111-8111-111111111111';
|
|
184
|
+
writeFileSync(join(root, '.fimo.settings.json'), JSON.stringify({ projectId, apiUrl: 'https://api.example.test' }));
|
|
185
|
+
process.env.FIMO_API_TOKEN = 'test-api-token';
|
|
186
|
+
const sockets = [];
|
|
187
|
+
vi.stubGlobal('WebSocket', class {
|
|
188
|
+
onopen = null;
|
|
189
|
+
onmessage = null;
|
|
190
|
+
onclose = null;
|
|
191
|
+
onerror = null;
|
|
192
|
+
constructor(_url) {
|
|
193
|
+
sockets.push(this);
|
|
194
|
+
}
|
|
195
|
+
close() { }
|
|
196
|
+
});
|
|
197
|
+
vi.stubGlobal('fetch', vi.fn(async (input) => {
|
|
198
|
+
const url = new URL(input.toString());
|
|
199
|
+
if (url.pathname === '/labels') {
|
|
200
|
+
return jsonResponse({ data: { labels: {}, updatedAt: null } });
|
|
201
|
+
}
|
|
202
|
+
if (url.pathname === '/runtime-config') {
|
|
203
|
+
return jsonResponse({ data: { eventsServerUrl: 'https://events.example.test' } });
|
|
204
|
+
}
|
|
205
|
+
if (url.pathname === '/api/auth/one-time-token/generate') {
|
|
206
|
+
return jsonResponse({ data: { token: 'event-token' } });
|
|
207
|
+
}
|
|
208
|
+
return { ok: false, json: async () => ({}) };
|
|
209
|
+
}));
|
|
210
|
+
const plugin = createPlugin(root, 'serve');
|
|
211
|
+
await buildStart(plugin);
|
|
212
|
+
const server = createDevServer();
|
|
213
|
+
if (typeof plugin.configureServer !== 'function') {
|
|
214
|
+
throw new Error('Expected configureServer hook');
|
|
215
|
+
}
|
|
216
|
+
plugin.configureServer.call(pluginContext, server);
|
|
217
|
+
await waitFor(() => sockets.length > 0);
|
|
218
|
+
sockets[0].onmessage?.({
|
|
219
|
+
data: JSON.stringify({
|
|
220
|
+
type: 'content:changed',
|
|
221
|
+
payload: {
|
|
222
|
+
changes: [{ action: 'update', contentType: 'LiveNote', id: 'entry-1', locale: 'en' }],
|
|
223
|
+
updatedAt: '2026-06-24T00:00:00.000Z',
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
});
|
|
227
|
+
expect(server.ws.send).toHaveBeenCalledWith({
|
|
228
|
+
type: 'custom',
|
|
229
|
+
event: 'fimo:content-updated',
|
|
230
|
+
data: {
|
|
231
|
+
changes: [{ action: 'update', contentType: 'LiveNote', id: 'entry-1', locale: 'en' }],
|
|
232
|
+
updatedAt: '2026-06-24T00:00:00.000Z',
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
expect(server.ws.send).not.toHaveBeenCalledWith({ type: 'full-reload', path: '*' });
|
|
236
|
+
});
|
|
168
237
|
it('still waits for labels during production builds', async () => {
|
|
169
238
|
const root = createProjectRoot();
|
|
170
239
|
const fetchMock = mockLabels({ headline: 'Hola' });
|
package/dist/cli/bundle.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"bundled": true,
|
|
3
3
|
"bundler": "esbuild",
|
|
4
|
-
"bundledAt": "2026-06-
|
|
5
|
-
"cliVersion": "0.2.5-
|
|
4
|
+
"bundledAt": "2026-06-26T17:21:55.057Z",
|
|
5
|
+
"cliVersion": "0.2.5-staging.14",
|
|
6
6
|
"external": [
|
|
7
7
|
"oxc-parser",
|
|
8
8
|
"fsevents"
|