rollipop 1.0.0-alpha.33 → 1.0.0-beta.0

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/constants.js CHANGED
@@ -20,7 +20,7 @@ var constants_exports = /* @__PURE__ */ __exportAll({
20
20
  ROLLIPOP_VIRTUAL_ENTRY_ID: () => ROLLIPOP_VIRTUAL_ENTRY_ID,
21
21
  ROLLIPOP_VIRTUAL_PREFIX: () => ROLLIPOP_VIRTUAL_PREFIX
22
22
  });
23
- const ROLLIPOP_VERSION = "1.0.0-alpha.33";
23
+ const ROLLIPOP_VERSION = "1.0.0-beta.0";
24
24
  const ROLLIPOP_VIRTUAL_PREFIX = "\0rollipop/";
25
25
  const ROLLIPOP_VIRTUAL_BOOTSTRAP_ID = `${ROLLIPOP_VIRTUAL_PREFIX}bootstrap`;
26
26
  const ROLLIPOP_VIRTUAL_ENTRY_ID = `${ROLLIPOP_VIRTUAL_PREFIX}entry`;
@@ -756,7 +756,6 @@ var __rolldown_runtime__ = (function() {
756
756
  }
757
757
  this.lastSeq = o;
758
758
  var i = this.computeHmrUpdate(t);
759
- if (i.type === "noop") return;
760
759
  if (i.type === "full-reload") {
761
760
  this.reload(i.reason);
762
761
  return;
@@ -766,7 +765,7 @@ var __rolldown_runtime__ = (function() {
766
765
  type: "hmr:payload-delivered",
767
766
  filename: n
768
767
  }));
769
- this.applyUpdate(i);
768
+ if (i.type === "boundaries") this.applyUpdate(i);
770
769
  }
771
770
  },
772
771
  {
@@ -17,6 +17,10 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
17
17
  " rollipop skills get <name>",
18
18
  "",
19
19
  "Rollipop exposes build events, diagnostics, and client logs through Devframe MCP.",
20
+ "MCP requires the optional @devframes/agentic peer dependency:",
21
+ "",
22
+ " yarn add -D @devframes/agentic@1.0.0",
23
+ "",
20
24
  "Keep the dev server running while your agent uses the MCP tools.",
21
25
  "",
22
26
  "Start the dev server:",
@@ -27,8 +31,7 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
27
31
  "",
28
32
  ` MCP server: ${baseUrl}/__rollipop/__mcp`,
29
33
  "",
30
- "If you start Rollipop with custom --host, --port, or --https options,",
31
- "adjust these URLs to match the running dev server.",
34
+ "For a custom loopback --host or --port, adjust both the MCP URL and Origin.",
32
35
  "",
33
36
  "MCP setup:",
34
37
  "",
@@ -44,13 +47,12 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
44
47
  " }",
45
48
  " }",
46
49
  "",
47
- " Devframe accepts loopback origins only; keep the MCP URL and Origin on loopback.",
50
+ " Devframe requires a loopback Origin and a connection from the same machine.",
48
51
  "",
49
52
  " Tool discovery:",
50
53
  " MCP clients discover supported tools by calling tools/list.",
51
54
  " The response includes tool names, descriptions, and input schemas.",
52
- " For Streamable HTTP, initialize first, then call tools/list with",
53
- " the returned mcp-session-id header.",
55
+ " The endpoint is stateless; no mcp-session-id header is returned or required.",
54
56
  "",
55
57
  "External debugging:",
56
58
  "",
@@ -65,9 +67,11 @@ function getAgentGuide(baseUrl = defaultBaseUrl) {
65
67
  "Agent workflow:",
66
68
  "",
67
69
  " 1. Run rollipop start and keep it running while using MCP tools.",
68
- " 2. Use get_build_events before and after edits; bundler-scoped events include bundlerId.",
70
+ " 2. Start get_build_events, then make edits while it is listening.",
71
+ " Only new events are collected; bundler-scoped events include bundlerId.",
69
72
  " 3. Wait for bundle_build_done or bundle_build_failed for bundle builds.",
70
- " For HMR edits, watch for watch_change, hmr_failed, and HMR client messages.",
73
+ " For HMR edits, watch for watch_change and hmr_failed.",
74
+ " Use build_logs and build_errors when events occurred before listening.",
71
75
  " 4. Use get_bundler_status when you need the latest state for a known bundlerId.",
72
76
  " 5. Use get_console_logs, clear_console_logs, and clear_build_* for focused diagnostics.",
73
77
  " 6. Use bundleFilePath from bundle_build_done when bundle-level debugging is needed.",
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.0.0-alpha.33";
2
+ var version = "1.0.0-beta.0";
3
3
  //#endregion
4
4
  export { version };
@@ -44,19 +44,22 @@ export declare class BundlerDevEngine {
44
44
  getBundle(): Promise<BundleStore>;
45
45
  getSourceMap(): Promise<string | undefined>;
46
46
  triggerFullBuild(): Promise<void>;
47
+ close(): Promise<void>;
47
48
  }
48
49
  export declare class BundlerPool {
49
50
  private readonly config;
50
51
  private readonly resolvedServerOptions;
51
52
  private readonly eventBus;
52
- private static readonly instances;
53
+ private readonly instances;
54
+ private closeTask;
53
55
  readonly hotUpdateStore: HotUpdateStore;
54
56
  constructor(config: ResolvedConfig, resolvedServerOptions: Required<Pick<ServerOptions, 'host' | 'port'>> & Pick<ServerOptions, 'buildOptions'>, eventBus: EventBus);
55
- get(bundleName: string, buildOptions: Pick<BuildOptions, 'platform' | 'dev'>): BundlerDevEngine;
57
+ get(bundleName: string, buildOptions: Pick<BuildOptions, 'platform' | 'dev' | 'minify'>): BundlerDevEngine;
56
58
  /**
57
59
  * Look up a cached bundler by the id carried as `bundlerId` in events such as `bundle_build_done`. Returns `undefined` when no instance with that id has been created yet.
58
60
  */
59
61
  getInstanceById(id: string): BundlerDevEngine | undefined;
60
62
  getInstances(): BundlerDevEngine[];
63
+ close(): Promise<void>;
61
64
  }
62
65
  //#endregion
@@ -218,12 +218,19 @@ var BundlerDevEngine = class {
218
218
  await this.ensureInitialized;
219
219
  this.devEngine.triggerFullBuild();
220
220
  }
221
+ async close() {
222
+ await this.initializeTask.catch(() => {});
223
+ const devEngine = this._devEngine;
224
+ this._devEngine = null;
225
+ await devEngine?.close();
226
+ }
221
227
  };
222
- var BundlerPool = class BundlerPool {
228
+ var BundlerPool = class {
223
229
  config;
224
230
  resolvedServerOptions;
225
231
  eventBus;
226
- static instances = /* @__PURE__ */ new Map();
232
+ instances = /* @__PURE__ */ new Map();
233
+ closeTask;
227
234
  hotUpdateStore;
228
235
  constructor(config, resolvedServerOptions, eventBus) {
229
236
  this.config = config;
@@ -233,13 +240,14 @@ var BundlerPool = class BundlerPool {
233
240
  this.hotUpdateStore.clear();
234
241
  }
235
242
  get(bundleName, buildOptions) {
243
+ invariant(this.closeTask == null, "Bundler pool is closed");
236
244
  const baseBundleName = getBaseBundleName(bundleName);
237
245
  const resolvedBuildOptions = resolveBuildOptions(this.config, {
238
246
  ...this.resolvedServerOptions.buildOptions,
239
247
  ...buildOptions
240
248
  });
241
249
  const key = `${baseBundleName}-${Bundler.createId(this.config, resolvedBuildOptions)}`;
242
- const instance = BundlerPool.instances.get(key);
250
+ const instance = this.instances.get(key);
243
251
  if (instance) return instance;
244
252
  else {
245
253
  logger.debug("Preparing new bundler instance", {
@@ -248,7 +256,7 @@ var BundlerPool = class BundlerPool {
248
256
  });
249
257
  const instance = new BundlerDevEngine(baseBundleName, { server: this.resolvedServerOptions }, this.config, resolvedBuildOptions, this.eventBus, this.hotUpdateStore);
250
258
  logger.debug("Setting new bundler instance", { key });
251
- BundlerPool.instances.set(key, instance);
259
+ this.instances.set(key, instance);
252
260
  return instance;
253
261
  }
254
262
  }
@@ -256,10 +264,19 @@ var BundlerPool = class BundlerPool {
256
264
  * Look up a cached bundler by the id carried as `bundlerId` in events such as `bundle_build_done`. Returns `undefined` when no instance with that id has been created yet.
257
265
  */
258
266
  getInstanceById(id) {
259
- for (const instance of BundlerPool.instances.values()) if (instance.id === id && instance.projectRoot === this.config.root) return instance;
267
+ for (const instance of this.instances.values()) if (instance.id === id && instance.projectRoot === this.config.root) return instance;
260
268
  }
261
269
  getInstances() {
262
- return Array.from(BundlerPool.instances.values());
270
+ return Array.from(this.instances.values());
271
+ }
272
+ close() {
273
+ this.closeTask ??= (async () => {
274
+ const instances = this.getInstances();
275
+ this.instances.clear();
276
+ const errors = (await Promise.allSettled(instances.map((instance) => instance.close()))).flatMap((result) => result.status === "rejected" ? [result.reason] : []);
277
+ if (errors.length > 0) throw new AggregateError(errors, "Failed to close bundler engines");
278
+ })();
279
+ return this.closeTask;
263
280
  }
264
281
  };
265
282
  //#endregion
@@ -147,6 +147,7 @@ async function createDevServer(config, options) {
147
147
  server.close(() => resolve());
148
148
  })));
149
149
  });
150
+ fastify.addHook("onClose", () => bundlerPool.close());
150
151
  await invokePostConfigureServer();
151
152
  eventBus.emit({
152
153
  type: "server_ready",
@@ -20,7 +20,7 @@ var RollipopDevframeController = class {
20
20
  this.definition = defineDevframe({
21
21
  id: "rollipop",
22
22
  name: "Rollipop",
23
- version: "1.0.0-alpha.33",
23
+ version: "1.0.0-beta.0",
24
24
  packageName: "rollipop",
25
25
  homepage: "https://github.com/rollipop-dev/rollipop",
26
26
  description: "React Native development dashboard powered by Rollipop.",
@@ -83,7 +83,8 @@ var RollipopDevframeController = class {
83
83
  if (pathname == null || query.platform == null || query.dev == null) throw new Error("Bundle URL must include pathname, platform, and dev query parameters");
84
84
  const bundle = await this.context.bundlerPool.get(getBaseBundleName(pathname), {
85
85
  platform: String(query.platform),
86
- dev: query.dev === "true"
86
+ dev: query.dev === "true",
87
+ ...query.minify == null ? {} : { minify: query.minify === "true" }
87
88
  }).getBundle();
88
89
  return symbolicate(bundle, [{
89
90
  file: bundleUrl,
@@ -16,7 +16,7 @@ async function setupDevframe(context, server, inspectorDeviceServer) {
16
16
  const controller = new RollipopDevframeController(context, createAgentToolContext(context));
17
17
  const devtools = initHub({
18
18
  name: DEVFRAME_NAME,
19
- version: "1.0.0-alpha.33",
19
+ version: "1.0.0-beta.0",
20
20
  base: ROLLIPOP_DEVFRAME_BASE,
21
21
  cwd: context.config.root,
22
22
  ui: createUi({
@@ -55,7 +55,8 @@ function getBundleStoresByFrameUrl(stack, context) {
55
55
  const bundleName = getBaseBundleName(pathname);
56
56
  const bundler = context.bundlerPool.get(bundleName, {
57
57
  platform,
58
- dev
58
+ dev,
59
+ ...query.minify == null ? {} : { minify: query.minify === "true" }
59
60
  });
60
61
  bundleStores.set(file, bundler.getBundle());
61
62
  }
@@ -4,6 +4,16 @@ import { WebSocketServer } from "./server.js";
4
4
  import { invariant } from "es-toolkit";
5
5
  //#region src/server/wss/hmr-server.ts
6
6
  const MAX_PENDING_PAYLOADS_PER_CLIENT = 8;
7
+ const RESERVED_WEBSOCKET_EVENTS = /* @__PURE__ */ new Set([
8
+ "close",
9
+ "connection",
10
+ "error",
11
+ "headers",
12
+ "listening",
13
+ "wsClientError",
14
+ "newListener",
15
+ "removeListener"
16
+ ]);
7
17
  var HMRServer = class extends WebSocketServer {
8
18
  bundlerPool;
9
19
  eventBus;
@@ -19,9 +29,9 @@ var HMRServer = class extends WebSocketServer {
19
29
  }
20
30
  parseClientMessage(data) {
21
31
  const parsedData = JSON.parse(this.rawDataToString(data));
22
- const clientMessage = "type" in parsedData ? parsedData : null;
23
- invariant(clientMessage, "Invalid HMR client message");
24
- return clientMessage;
32
+ invariant(typeof parsedData?.type === "string", "Invalid HMR client message");
33
+ invariant(!RESERVED_WEBSOCKET_EVENTS.has(parsedData.type), `Reserved HMR event name: ${parsedData.type}`);
34
+ return parsedData;
25
35
  }
26
36
  async handleConnected(client, runtimeClientId, platform, bundleEntry) {
27
37
  const connectionToken = Symbol();
@@ -28,7 +28,7 @@ var FileStorage = class FileStorage {
28
28
  this.dataFilePath = path.join(FileStorage.getPath(basePath, { prepare: true }), DATA_FILENAME);
29
29
  if (fs.existsSync(this.dataFilePath)) {
30
30
  const loadedData = JSON.parse(fs.readFileSync(this.dataFilePath, "utf-8"));
31
- if ("version" in loadedData && loadedData.version === "1.0.0-alpha.33") this.data = loadedData;
31
+ if ("version" in loadedData && loadedData.version === "1.0.0-beta.0") this.data = loadedData;
32
32
  else {
33
33
  this.data = createDefaultData();
34
34
  this.flush();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rollipop",
3
- "version": "1.0.0-alpha.33",
3
+ "version": "1.0.0-beta.0",
4
4
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/rollipop-dev/rollipop/issues"
@@ -67,8 +67,8 @@
67
67
  "@react-native-community/cli-server-api": "20.2.0",
68
68
  "@react-native-community/cli-types": "20.2.0",
69
69
  "@react-native/dev-middleware": "0.87.1",
70
- "@rollipop/dashboard": "1.0.0-alpha.33",
71
- "@rollipop/rolldown": "1.0.32",
70
+ "@rollipop/dashboard": "1.0.0-beta.0",
71
+ "@rollipop/rolldown": "1.0.34",
72
72
  "@swc/core": "1.16.13",
73
73
  "@swc/helpers": "0.5.23",
74
74
  "@types/ws": "8.18.1",
@@ -117,7 +117,7 @@
117
117
  }
118
118
  },
119
119
  "engines": {
120
- "node": ">=22.12.0"
120
+ "node": "^22.18.0 || ^24.11.0 || >=26.0.0"
121
121
  },
122
122
  "installConfig": {
123
123
  "hoistingLimits": "none"
@@ -18,7 +18,7 @@ Use https://rollipop.dev/docs/features/env.md when changing env file loading, pr
18
18
 
19
19
  ## Experimental Features
20
20
 
21
- Use https://rollipop.dev/docs/features/experimental.md before enabling experimental options. Keep the change explicit and mention stability/version risk. For worklets specifically, read https://rollipop.dev/docs/features/reanimated-worklets.md.
21
+ Use https://rollipop.dev/docs/experimental/overview.md before enabling experimental options. Keep the change explicit and mention stability/version risk. For worklets specifically, read https://rollipop.dev/docs/experimental/reanimated-worklets.md.
22
22
 
23
23
  ## Resolve Changes
24
24
 
@@ -27,5 +27,5 @@ When changing module resolution behavior, verify package metadata (`exports`, `r
27
27
  ## Watchpoints
28
28
 
29
29
  - Translate intent from existing Metro config instead of copying Metro fields mechanically.
30
- - Overriding arrays can accidentally remove React Native defaults; check docs before replacing prelude or resolve extension lists.
30
+ - Configured `prelude`, `polyfills`, `plugins`, `resolve.sourceExtensions`, and `resolve.assetExtensions` arrays are appended to the defaults with duplicates removed. Supplying an empty array does not remove the defaults.
31
31
  - If config is produced dynamically, inspect both the source config and the resolved behavior through dev-server diagnostics when available.
@@ -10,7 +10,7 @@ Use this skill when Rollipop is installed or running and the task needs diagnost
10
10
 
11
11
  ## MCP Server
12
12
 
13
- Devframe MCP exposes structured Rollipop diagnostics whenever the dev server is running. Use it for build events, logs, cache actions, app logs, device status, reloads, build info, and symbolication. Read https://rollipop.dev/docs/features/mcp.md.
13
+ Devframe MCP exposes structured Rollipop diagnostics while the dev server is running when the optional `@devframes/agentic` peer dependency is installed. Use it for build events, logs, cache actions, app logs, device status, reloads, build info, and symbolication. Read https://rollipop.dev/docs/features/mcp.md for installation and setup.
14
14
 
15
15
  ## React Native DevTools
16
16
 
@@ -18,7 +18,7 @@ Use Rollipop's dev server for React Native HMR, source maps, multi-platform deve
18
18
 
19
19
  ## MCP Server
20
20
 
21
- The Devframe MCP endpoint is available whenever the dev server is running. Read https://rollipop.dev/docs/features/mcp.md.
21
+ The Devframe MCP endpoint is available while the dev server is running when the optional `@devframes/agentic` peer dependency is installed. Read https://rollipop.dev/docs/features/mcp.md for installation and setup.
22
22
 
23
23
  ## Interactive Tools
24
24
 
@@ -26,6 +26,6 @@ React Native DevTools and custom terminal commands belong to the dev-server work
26
26
 
27
27
  ## Watchpoints
28
28
 
29
- - Keep server options consistent with device/app URLs: host, port, HTTPS, key, and cert must match the running target.
29
+ - Keep the host and port consistent with device/app URLs. HTTPS is not supported; `--https` fails even though the CLI accepts it alongside `--key` and `--cert`.
30
30
  - Interactive mode is useful for humans; disable it for automation when needed.
31
31
  - If dev-server startup or HMR fails, switch to `troubleshooting.md`; use `debugging.md` when diagnostics tooling is needed.
@@ -522,10 +522,14 @@ describe('HMR runtime', () => {
522
522
  expect(reload).not.toHaveBeenCalled();
523
523
  });
524
524
 
525
- it('does not evaluate or acknowledge changes for modules that were not executed', async () => {
526
- const evaluate = vi.fn();
525
+ it('updates unexecuted modules without executing them until their first import', async () => {
526
+ // oxlint-disable-next-line no-eval
527
+ const evaluate = vi.fn((source: string) => (0, eval)(source));
527
528
  globalThis.globalEvalWithSourceUrl = evaluate;
528
- const { socket } = await setupRuntime();
529
+ const { runtime, socket } = await setupRuntime();
530
+ runtime.registerFactory('dep.js', 'esm', (id) => {
531
+ runtime.registerModule(id, { exports: { value: 'before' } });
532
+ });
529
533
 
530
534
  socket.emit('message', {
531
535
  data: JSON.stringify({
@@ -539,8 +543,13 @@ describe('HMR runtime', () => {
539
543
  });
540
544
 
541
545
  await new Promise((resolve) => setTimeout(resolve, 0));
542
- expect(evaluate).not.toHaveBeenCalled();
543
- expect(socket.sent).toEqual([]);
546
+ expect(runtime.isExecuted('dep.js')).toBe(false);
547
+ expect(runtime.initModule('dep.js')).toEqual({ value: 'after' });
548
+ expect(runtime.getImporters('dep.js')).toEqual(['entry.js']);
549
+ expect(evaluate).toHaveBeenCalledOnce();
550
+ expect(socket.sent.map((message) => JSON.parse(message))).toEqual([
551
+ { type: 'hmr:payload-delivered', filename: 'hmr_patch_1.js' },
552
+ ]);
544
553
  expect(reload).not.toHaveBeenCalled();
545
554
  });
546
555
 
@@ -299,9 +299,6 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
299
299
  this.lastSeq = seq;
300
300
 
301
301
  const update = this.computeHmrUpdate(changedIds);
302
- if (update.type === 'noop') {
303
- return;
304
- }
305
302
  if (update.type === 'full-reload') {
306
303
  this.reload(update.reason);
307
304
  return;
@@ -311,7 +308,9 @@ class ReactNativeDevRuntime extends BaseDevRuntime {
311
308
  this.socketHolder.send(
312
309
  JSON.stringify({ type: 'hmr:payload-delivered', filename } satisfies HMRClientMessage),
313
310
  );
314
- this.applyUpdate(update);
311
+ if (update.type === 'boundaries') {
312
+ this.applyUpdate(update);
313
+ }
315
314
  }
316
315
 
317
316
  private applyInvalidate(moduleId: string, firstInvalidatedBy: string) {