fimo 0.2.5-experimental.1782387685584 → 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.
@@ -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`**
@@ -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;AAwEjD,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"}
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"}
@@ -34,6 +34,7 @@ function bundleFimoIntoSsrPlugin() {
34
34
  config: () => ({
35
35
  ssr: { noExternal: ['fimo'] },
36
36
  optimizeDeps: {
37
+ include: ['fimo/ui'],
37
38
  esbuildOptions: {
38
39
  plugins: [
39
40
  {
@@ -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;AA2BD,MAAM,CAAC,OAAO,UAAU,kBAAkB,IAAI,MAAM,CA+MnD;AA4CD,wBAAsB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwC3E"}
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"}
@@ -40,6 +40,16 @@ export default function translationsPlugin() {
40
40
  data: labelUpdatePayload(bundle),
41
41
  });
42
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
+ });
52
+ };
43
53
  const scheduleRefresh = () => {
44
54
  if (refreshTimer) {
45
55
  clearTimeout(refreshTimer);
@@ -165,6 +175,10 @@ export default function translationsPlugin() {
165
175
  }
166
176
  try {
167
177
  const message = JSON.parse(raw);
178
+ if (message.type === 'content:changed') {
179
+ emitContentUpdate(parseContentChangePayload(message.payload));
180
+ return;
181
+ }
168
182
  if (message.type !== 'labels:changed') {
169
183
  return;
170
184
  }
@@ -194,6 +208,27 @@ export default function translationsPlugin() {
194
208
  return sockets;
195
209
  }
196
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
+ }
197
232
  function serializeBundle(bundle) {
198
233
  return JSON.stringify({
199
234
  dictionaries: bundle.dictionaries,
@@ -389,7 +424,7 @@ async function generateOneTimeToken(apiUrl) {
389
424
  throw new Error('Not signed in to Fimo.');
390
425
  }
391
426
  const response = await fetch(new URL('/api/auth/one-time-token/generate', apiUrl), {
392
- method: 'POST',
427
+ method: 'GET',
393
428
  headers: {
394
429
  Authorization: `Bearer ${bearer}`,
395
430
  },
@@ -152,7 +152,7 @@ describe('translationsPlugin', () => {
152
152
  }
153
153
  close() { }
154
154
  });
155
- vi.stubGlobal('fetch', vi.fn(async (input) => {
155
+ const fetchMock = vi.fn(async (input, init) => {
156
156
  const url = new URL(input.toString());
157
157
  if (url.pathname === '/labels') {
158
158
  return jsonResponse({ data: { labels: {}, updatedAt: null } });
@@ -161,10 +161,12 @@ describe('translationsPlugin', () => {
161
161
  return jsonResponse({ data: { eventsServerUrl: 'https://events.example.test' } });
162
162
  }
163
163
  if (url.pathname === '/api/auth/one-time-token/generate') {
164
- return jsonResponse({ data: { token: 'event-token' } });
164
+ expect(init?.method).toBe('GET');
165
+ return jsonResponse({ token: 'event-token' });
165
166
  }
166
167
  return { ok: false, json: async () => ({}) };
167
- }));
168
+ });
169
+ vi.stubGlobal('fetch', fetchMock);
168
170
  const plugin = createPlugin(root, 'serve');
169
171
  await buildStart(plugin);
170
172
  const server = createDevServer();
@@ -176,6 +178,62 @@ describe('translationsPlugin', () => {
176
178
  expect(socketUrls[0]).toContain(`wss://events.example.test/parties/events/${projectId}`);
177
179
  expect(socketUrls[0]).toContain('authToken=event-token');
178
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
+ });
179
237
  it('still waits for labels during production builds', async () => {
180
238
  const root = createProjectRoot();
181
239
  const fetchMock = mockLabels({ headline: 'Hola' });
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "bundled": true,
3
3
  "bundler": "esbuild",
4
- "bundledAt": "2026-06-25T11:41:35.375Z",
5
- "cliVersion": "0.2.5-experimental.1782387685584",
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"