@foldkit/vite-plugin 0.11.1 → 0.11.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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +79 -25
- package/package.json +4 -4
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EACV,MAAM,EAIP,MAAM,MAAM,CAAA;AAKb,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;CACzB,CAAC,CAAA;AA+kBF;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAAI,UAAS,oBAAyB,KAAG,KAAK,CAAC,MAAM,CA6DxE,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Array, Console, Data, Effect, Exit, Fiber, HashMap, HashSet, Match as M, Option, Queue, Ref, Schema as S, Stream, pipe, } from 'effect';
|
|
1
|
+
import { Array, Console, Data, Duration, Effect, Exit, Fiber, HashMap, HashSet, Match as M, Option, Predicate, Queue, Ref, Schema as S, Schedule, Stream, pipe, } from 'effect';
|
|
2
2
|
import { EventFrame, RequestFrame, ResponseFrame, ResponseRuntimes, } from 'foldkit/devtools-protocol';
|
|
3
3
|
import { PreserveModelMessage, RequestModelMessage, RestoreModelMessage, } from 'foldkit/hmr-protocol';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
@@ -81,7 +81,7 @@ const resolveInstalledFoldkitPackages = (root) => {
|
|
|
81
81
|
}
|
|
82
82
|
catch (error) {
|
|
83
83
|
return !(error instanceof Error &&
|
|
84
|
-
'code'
|
|
84
|
+
Predicate.hasProperty(error, 'code') &&
|
|
85
85
|
error.code === 'MODULE_NOT_FOUND');
|
|
86
86
|
}
|
|
87
87
|
});
|
|
@@ -239,20 +239,18 @@ const registerViteWsHandlers = (server, state, enqueue) => Effect.sync(() => {
|
|
|
239
239
|
server.ws.on('foldkit:devTools:response', (data) => enqueue(Event.BrowserResponseFrameReceived({ data })));
|
|
240
240
|
});
|
|
241
241
|
// MCP RELAY
|
|
242
|
-
|
|
242
|
+
// NOTE: Restarting a dev server briefly leaves two of them alive. Vite builds
|
|
243
|
+
// the replacement, which binds its relay, before closing the server it
|
|
244
|
+
// replaces, which still owns the port. The bind loses that race and has to
|
|
245
|
+
// wait for the outgoing server to release the port, so it retries for four
|
|
246
|
+
// seconds before reporting the port as taken.
|
|
247
|
+
const RELAY_BIND_RETRY_DELAY = Duration.millis(100);
|
|
248
|
+
const RELAY_BIND_RETRY_COUNT = 40;
|
|
249
|
+
class RelayBindFailed extends Data.TaggedError('RelayBindFailed') {
|
|
250
|
+
}
|
|
251
|
+
const isPortInUse = (error) => Predicate.hasProperty(error, 'code') && error.code === 'EADDRINUSE';
|
|
252
|
+
const bindMcpRelay = (port, enqueue) => Effect.callback(resume => {
|
|
243
253
|
const wss = new WebSocketServer({ port });
|
|
244
|
-
wss.on('error', error => {
|
|
245
|
-
if ('code' in error && error.code === 'EADDRINUSE') {
|
|
246
|
-
console.error(`\n[foldkit:devTools] Port ${port} is already in use, so the DevTools MCP relay could not start.\n` +
|
|
247
|
-
`[foldkit:devTools] This usually means another Foldkit project is already running and bound to this port.\n` +
|
|
248
|
-
`[foldkit:devTools] Until the port is freed, agents will not be able to connect to this app via the Foldkit DevTools MCP server.\n` +
|
|
249
|
-
`[foldkit:devTools] Stop the other project, or set a different \`devToolsMcpPort\` in this project's vite config.\n` +
|
|
250
|
-
`[foldkit:devTools] If you change \`devToolsMcpPort\`, also set \`FOLDKIT_DEVTOOLS_MCP_PORT\` to the same value for your MCP server.\n`);
|
|
251
|
-
}
|
|
252
|
-
else {
|
|
253
|
-
console.error(`[foldkit:devTools] MCP relay failed to start on port ${port}; continuing without the relay`, error);
|
|
254
|
-
}
|
|
255
|
-
});
|
|
256
254
|
wss.on('connection', client => {
|
|
257
255
|
enqueue(Event.McpClientConnected({ client }));
|
|
258
256
|
client.on('message', raw => enqueue(Event.McpRequestReceived({ client, raw: raw.toString() })));
|
|
@@ -261,14 +259,45 @@ const startMcpRelay = (port, enqueue) => Effect.acquireRelease(Effect.sync(() =>
|
|
|
261
259
|
console.error('[foldkit:devTools] MCP client error', error);
|
|
262
260
|
});
|
|
263
261
|
});
|
|
264
|
-
|
|
262
|
+
const onListening = () => {
|
|
263
|
+
wss.off('error', onBindFailed);
|
|
264
|
+
wss.on('error', error => {
|
|
265
|
+
console.error('[foldkit:devTools] MCP relay error', error);
|
|
266
|
+
});
|
|
265
267
|
console.log(`[foldkit:devTools] MCP relay listening on ws://localhost:${port}`);
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
resume(Effect.succeed(wss));
|
|
269
|
+
};
|
|
270
|
+
const onBindFailed = (cause) => {
|
|
271
|
+
wss.off('listening', onListening);
|
|
272
|
+
wss.close();
|
|
273
|
+
resume(Effect.fail(new RelayBindFailed({ cause })));
|
|
274
|
+
};
|
|
275
|
+
wss.once('listening', onListening);
|
|
276
|
+
wss.once('error', onBindFailed);
|
|
277
|
+
});
|
|
278
|
+
const reportRelayBindFailed = (port, cause) => {
|
|
279
|
+
if (isPortInUse(cause)) {
|
|
280
|
+
return Console.error(`\n[foldkit:devTools] Port ${port} is already in use, so the DevTools MCP relay could not start.\n` +
|
|
281
|
+
`[foldkit:devTools] This usually means another Foldkit project is already running and bound to this port.\n` +
|
|
282
|
+
`[foldkit:devTools] Until the port is freed, agents will not be able to connect to this app via the Foldkit DevTools MCP server.\n` +
|
|
283
|
+
`[foldkit:devTools] Stop the other project, or set a different \`devToolsMcpPort\` in this project's vite config.\n` +
|
|
284
|
+
`[foldkit:devTools] If you change \`devToolsMcpPort\`, also set \`FOLDKIT_DEVTOOLS_MCP_PORT\` to the same value for your MCP server.\n`);
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
return Console.error(`[foldkit:devTools] MCP relay failed to start on port ${port}; continuing without the relay`, cause);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
const startMcpRelay = (port, enqueue) => Effect.acquireRelease(bindMcpRelay(port, enqueue), wss => Effect.gen(function* () {
|
|
291
|
+
for (const client of wss.clients) {
|
|
292
|
+
client.terminate();
|
|
293
|
+
}
|
|
269
294
|
wss.close();
|
|
270
|
-
|
|
271
|
-
}))
|
|
295
|
+
yield* Console.log('[foldkit:devTools] MCP relay stopped');
|
|
296
|
+
})).pipe(Effect.retry({
|
|
297
|
+
while: ({ cause }) => isPortInUse(cause),
|
|
298
|
+
times: RELAY_BIND_RETRY_COUNT,
|
|
299
|
+
schedule: Schedule.spaced(RELAY_BIND_RETRY_DELAY),
|
|
300
|
+
}), Effect.catchTag('RelayBindFailed', ({ cause }) => reportRelayBindFailed(port, cause)));
|
|
272
301
|
// PROGRAM
|
|
273
302
|
const main = (server, events, options) => Effect.gen(function* () {
|
|
274
303
|
const state = yield* makeState;
|
|
@@ -276,8 +305,13 @@ const main = (server, events, options) => Effect.gen(function* () {
|
|
|
276
305
|
Queue.offerUnsafe(events, event);
|
|
277
306
|
};
|
|
278
307
|
yield* registerViteWsHandlers(server, state, enqueue);
|
|
308
|
+
// NOTE: Forked rather than awaited because binding the relay can retry for
|
|
309
|
+
// seconds. The HMR bridge is independent of the relay, and the runtime
|
|
310
|
+
// gives up on its boot-time model request in well under a second, so
|
|
311
|
+
// sequencing the dispatch loop behind the bind would cost model
|
|
312
|
+
// preservation whenever the port is contended.
|
|
279
313
|
if (options.devToolsMcpPort !== undefined) {
|
|
280
|
-
yield* startMcpRelay(options.devToolsMcpPort, enqueue);
|
|
314
|
+
yield* Effect.forkScoped(startMcpRelay(options.devToolsMcpPort, enqueue));
|
|
281
315
|
}
|
|
282
316
|
yield* Stream.fromQueue(events).pipe(Stream.runForEach(event => dispatchEvent(server, state, event)));
|
|
283
317
|
});
|
|
@@ -290,6 +324,21 @@ const main = (server, events, options) => Effect.gen(function* () {
|
|
|
290
324
|
*/
|
|
291
325
|
export const foldkit = (options = {}) => {
|
|
292
326
|
const events = Effect.runSync(Queue.unbounded());
|
|
327
|
+
// NOTE: One plugin instance can serve more than one dev server, and on
|
|
328
|
+
// restart Vite builds the replacement, running `configureServer` again,
|
|
329
|
+
// before closing the server being replaced. Keying by resolved config keeps
|
|
330
|
+
// each server's shutdown pointed at its own fiber.
|
|
331
|
+
const mainFibers = new WeakMap();
|
|
332
|
+
const stopMain = (config) => Effect.suspend(() => {
|
|
333
|
+
const fiber = mainFibers.get(config);
|
|
334
|
+
mainFibers.delete(config);
|
|
335
|
+
if (fiber === undefined) {
|
|
336
|
+
return Effect.void;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
return Fiber.interrupt(fiber);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
293
342
|
const hmrPlugin = {
|
|
294
343
|
name: 'foldkit-hmr',
|
|
295
344
|
apply: 'serve',
|
|
@@ -303,9 +352,14 @@ export const foldkit = (options = {}) => {
|
|
|
303
352
|
}),
|
|
304
353
|
configureServer: server => {
|
|
305
354
|
const fiber = Effect.runFork(Effect.scoped(main(server, events, options)));
|
|
306
|
-
server.
|
|
307
|
-
|
|
308
|
-
|
|
355
|
+
mainFibers.set(server.config, fiber);
|
|
356
|
+
},
|
|
357
|
+
// NOTE: Vite awaits `closeBundle` when the dev server closes, once per
|
|
358
|
+
// environment plugin container. Hanging shutdown off `server.httpServer`
|
|
359
|
+
// instead would never run in middleware mode, which is how Vitest and
|
|
360
|
+
// other embedders run Vite, and the relay would outlive the server.
|
|
361
|
+
closeBundle() {
|
|
362
|
+
return Effect.runPromise(stopMain(this.environment.getTopLevelConfig()));
|
|
309
363
|
},
|
|
310
364
|
handleHotUpdate: ({ server, modules, }) => {
|
|
311
365
|
if (modules.length === 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foldkit/vite-plugin",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.2",
|
|
4
4
|
"description": "Vite plugin for Foldkit hot module reloading with state preservation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"effect": "4.0.0-beta.
|
|
19
|
+
"effect": "4.0.0-beta.102",
|
|
20
20
|
"foldkit": "^0",
|
|
21
21
|
"vite": "^7.0.0 || ^8.0.0"
|
|
22
22
|
},
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/node": "^25.9.3",
|
|
29
29
|
"@types/ws": "^8.18.1",
|
|
30
|
-
"effect": "4.0.0-beta.
|
|
30
|
+
"effect": "4.0.0-beta.102",
|
|
31
31
|
"happy-dom": "^20.10.4",
|
|
32
32
|
"rimraf": "^6.1.3",
|
|
33
33
|
"typescript": "^6.0.3",
|
|
34
34
|
"vite": "^8.0.16",
|
|
35
35
|
"vitest": "^4.1.9",
|
|
36
|
-
"foldkit": "0.
|
|
36
|
+
"foldkit": "0.133.0"
|
|
37
37
|
},
|
|
38
38
|
"keywords": [
|
|
39
39
|
"vite",
|