rollipop 1.0.0-alpha.31 → 1.0.0-alpha.32

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.31";
23
+ const ROLLIPOP_VERSION = "1.0.0-alpha.32";
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`;
@@ -3,6 +3,7 @@ import { EventBus } from "../events/event-bus.js";
3
3
  import { FileStorage } from "../storage/file-storage.js";
4
4
  import { resolveBuildOptions } from "../utils/build-options.js";
5
5
  import { createId } from "../utils/id.js";
6
+ import { replaceSourceMappingUrl } from "../utils/source-map.js";
6
7
  import { resolveRolldownOptions } from "./rolldown.js";
7
8
  import fs from "node:fs";
8
9
  import path from "node:path";
@@ -81,6 +82,18 @@ var Bundler = class Bundler {
81
82
  const sourcemapFile = path.join(outputDir, chunk.sourcemapFileName);
82
83
  if (!fs.existsSync(sourcemapDir)) fs.mkdirSync(sourcemapDir, { recursive: true });
83
84
  fs.renameSync(sourcemapFile, resolvedBuildOptions.sourcemapOutfile);
85
+ const sourcemapFileName = path.relative(outputDir, resolvedBuildOptions.sourcemapOutfile).split(path.sep).join("/");
86
+ let code = chunk.code;
87
+ if (output.sourcemap !== "hidden") {
88
+ const sourceMapUrl = sourcemapFileName.split("/").map(encodeURIComponent).join("/");
89
+ code = replaceSourceMappingUrl(code, output.sourcemapBaseUrl ? new URL(sourceMapUrl, output.sourcemapBaseUrl).href : sourceMapUrl);
90
+ fs.writeFileSync(resolvedBuildOptions.outfile, code);
91
+ }
92
+ return {
93
+ ...chunk,
94
+ code,
95
+ sourcemapFileName
96
+ };
84
97
  }
85
98
  return chunk;
86
99
  }
package/dist/core/env.js CHANGED
@@ -10,6 +10,7 @@ function loadEnv(options) {
10
10
  invariant(envPrefix.length > 0, "`envPrefix` is required");
11
11
  invariant(envFile.length > 0, "`envFile` is required");
12
12
  const env = {};
13
+ const parsed = {};
13
14
  const envFilesToLoad = [
14
15
  envFile,
15
16
  `${envFile}.local`,
@@ -20,15 +21,18 @@ function loadEnv(options) {
20
21
  const envPath = path.resolve(envDir, file);
21
22
  if (!fs.existsSync(envPath)) continue;
22
23
  logger.trace(`Loading environment variables from ${envPath}`);
23
- const parsed = dotenv.parse(fs.readFileSync(envPath, "utf-8"));
24
- const expanded = dotenvExpand.expand({
25
- parsed,
26
- processEnv: {}
27
- });
28
- if (expanded.parsed) Object.entries(expanded.parsed).forEach(([key, value]) => {
29
- if (key.startsWith(envPrefix)) env[key] = key in process.env ? process.env[key] : value;
30
- });
24
+ Object.assign(parsed, dotenv.parse(fs.readFileSync(envPath, "utf-8")));
31
25
  }
26
+ const processEnv = {};
27
+ for (const [key, value] of Object.entries(process.env)) if (value !== void 0) {
28
+ processEnv[key] = value;
29
+ if (key in parsed) parsed[key] = value;
30
+ }
31
+ const expanded = dotenvExpand.expand({
32
+ parsed,
33
+ processEnv
34
+ });
35
+ for (const [key, value] of Object.entries(expanded.parsed ?? {})) if (key.startsWith(envPrefix)) env[key] = process.env[key] ?? value;
32
36
  logger.trace("Loaded environment variables:", env);
33
37
  return env;
34
38
  }
@@ -25,12 +25,12 @@ const command = {
25
25
  },
26
26
  {
27
27
  name: "--dev [boolean]",
28
- description: "If false, warnings are disabled and the bundle is minified",
28
+ description: "Build in development mode. Defaults to whether config.mode is development",
29
29
  parse: parseBoolean
30
30
  },
31
31
  {
32
32
  name: "--minify [boolean]",
33
- description: "Allows overriding whether bundle is minified. This defaults to false if dev is true, and true if dev is false. Disabling minification can be useful for speeding up production builds for testing purposes.",
33
+ description: "Override output.minify from the configuration. Defaults to false when not configured, regardless of --dev.",
34
34
  parse: parseBoolean
35
35
  },
36
36
  {
package/dist/package.js CHANGED
@@ -1,4 +1,4 @@
1
1
  //#region package.json
2
- var version = "1.0.0-alpha.31";
2
+ var version = "1.0.0-alpha.32";
3
3
  //#endregion
4
4
  export { version };
@@ -1,7 +1,7 @@
1
1
  import { resolveBuildOptions } from "../utils/build-options.js";
2
+ import { replaceSourceMappingUrl } from "../utils/source-map.js";
2
3
  import { bindReporter } from "../utils/config.js";
3
4
  import { getBaseUrl } from "../utils/server.js";
4
- import { replaceSourceMappingUrl } from "../utils/source-map.js";
5
5
  import { Bundler } from "../core/bundler.js";
6
6
  import { logger } from "./logger.js";
7
7
  import { getBaseBundleName } from "../utils/bundle.js";
@@ -121,7 +121,7 @@ async function createDevServer(config, options) {
121
121
  middlewares: { use: fastify.use.bind(fastify) }
122
122
  };
123
123
  const { invokePostConfigureServer } = await invokeConfigureServer(devServer, config.plugins ?? []);
124
- const devframeMiddleware = await setupDevframe(context, devServer);
124
+ const devframeMiddleware = await setupDevframe(context, devServer, websocketEndpoints["/inspector/device"]);
125
125
  fastify.use(requestLogger).use(devframeMiddleware).use(communityMiddleware).use(devMiddleware).register(plugin, { context }).register(plugin$3, { hotUpdateStore: bundlerPool.hotUpdateStore }).register(plugin$4, { context }).register(plugin$2, { context }).register(plugin$1, { context });
126
126
  fastify.setErrorHandler(errorHandler);
127
127
  const upgradeHandler = getWebSocketUpgradeHandler({
@@ -24,7 +24,7 @@ var RollipopDevframeController = class {
24
24
  this.definition = defineDevframe({
25
25
  id: "rollipop",
26
26
  name: "Rollipop",
27
- version: "1.0.0-alpha.31",
27
+ version: "1.0.0-alpha.32",
28
28
  packageName: "rollipop",
29
29
  homepage: "https://github.com/rollipop-dev/rollipop",
30
30
  description: "React Native development dashboard powered by Rollipop.",
@@ -11,12 +11,12 @@ const DEVFRAME_NAME = "Rollipop";
11
11
  const ROLLIPOP_DEVFRAME_BASE = "/__rollipop/";
12
12
  const ROLLIPOP_DEVFRAME_SSE_ROUTE = "__sse";
13
13
  const ROLLIPOP_DEVFRAME_MCP_PATH = "__mcp";
14
- async function setupDevframe(context, server) {
14
+ async function setupDevframe(context, server, inspectorDeviceServer) {
15
15
  const mcpEnabled = isAgenticInstalled();
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.31",
19
+ version: "1.0.0-alpha.32",
20
20
  base: ROLLIPOP_DEVFRAME_BASE,
21
21
  cwd: context.config.root,
22
22
  ui: createUi({
@@ -44,6 +44,13 @@ async function setupDevframe(context, server) {
44
44
  }
45
45
  });
46
46
  await devtools.ready;
47
+ const refreshDevices = () => {
48
+ controller.refresh();
49
+ };
50
+ const onDeviceConnection = (socket) => {
51
+ socket.once("close", refreshDevices);
52
+ };
53
+ inspectorDeviceServer?.on("connection", onDeviceConnection);
47
54
  server.instance.addHook("onListen", () => {
48
55
  if (mcpEnabled) {
49
56
  const mcpUrl = getServerDisplayUrl(server.instance.listeningOrigin, "/__rollipop/__mcp");
@@ -52,6 +59,8 @@ async function setupDevframe(context, server) {
52
59
  controller.refresh();
53
60
  });
54
61
  server.instance.addHook("preClose", async () => {
62
+ inspectorDeviceServer?.off("connection", onDeviceConnection);
63
+ for (const socket of inspectorDeviceServer?.clients ?? []) socket.off("close", refreshDevices);
55
64
  controller.dispose();
56
65
  await devtools.close();
57
66
  });
@@ -16,20 +16,34 @@ const queryParamSchema = asConst({
16
16
  });
17
17
  const plugin = fp((fastify, options) => {
18
18
  const { context } = options;
19
- function resolveAsset(asset) {
20
- return path.resolve(context.config.root, asset);
21
- }
19
+ const projectRoot = path.resolve(context.config.root);
20
+ const workspaceRoot = findWorkspaceRoot(projectRoot);
21
+ const assetExtensions = new Set(context.config.resolve.assetExtensions);
22
22
  fastify.get(`/${DEV_SERVER_ASSET_PATH}/*`, {
23
23
  schema: { querystring: queryParamSchema },
24
24
  async handler(request, reply) {
25
25
  const { params, query } = request;
26
- const assetPath = resolveAsset(params["*"]);
26
+ const assetPath = path.resolve(projectRoot, params["*"]);
27
+ if (!assetExtensions.has(path.extname(assetPath).slice(1))) {
28
+ await reply.status(403).send();
29
+ return;
30
+ }
27
31
  try {
32
+ const realWorkspaceRoot = await fs.promises.realpath(workspaceRoot);
33
+ if (!isWithin(realWorkspaceRoot, await fs.promises.realpath(path.dirname(assetPath)))) {
34
+ await reply.status(403).send();
35
+ return;
36
+ }
28
37
  const resolvedAssetPath = resolveAssetPath(assetPath, {
29
38
  platform: query.platform,
30
39
  preferNativePlatform: context.config.resolve.preferNativePlatform
31
40
  });
32
- const [assetData, { size }] = await Promise.all([fs.promises.readFile(resolvedAssetPath), fs.promises.stat(resolvedAssetPath)]);
41
+ const realAssetPath = await fs.promises.realpath(resolvedAssetPath);
42
+ if (!isWithin(realWorkspaceRoot, realAssetPath)) {
43
+ await reply.status(403).send();
44
+ return;
45
+ }
46
+ const [assetData, { size }] = await Promise.all([fs.promises.readFile(realAssetPath), fs.promises.stat(realAssetPath)]);
33
47
  await reply.header("Content-Type", mime.getType(resolvedAssetPath) ?? "").header("Content-Length", size).send(assetData);
34
48
  } catch (error) {
35
49
  fastify.log.error(error, "Failed to serve asset");
@@ -38,5 +52,19 @@ const plugin = fp((fastify, options) => {
38
52
  }
39
53
  });
40
54
  }, { name: "serve-assets" });
55
+ function isWithin(root, file) {
56
+ const relative = path.relative(root, file);
57
+ return relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative);
58
+ }
59
+ function findWorkspaceRoot(projectRoot) {
60
+ let directory = projectRoot;
61
+ while (true) {
62
+ const packageJson = path.join(directory, "package.json");
63
+ if (fs.existsSync(path.join(directory, "pnpm-workspace.yaml")) || fs.existsSync(packageJson) && JSON.parse(fs.readFileSync(packageJson, "utf-8")).workspaces) return directory;
64
+ const parent = path.dirname(directory);
65
+ if (parent === directory) return projectRoot;
66
+ directory = parent;
67
+ }
68
+ }
41
69
  //#endregion
42
70
  export { plugin as serveAssets };
@@ -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.31") this.data = loadedData;
31
+ if ("version" in loadedData && loadedData.version === "1.0.0-alpha.32") 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.31",
3
+ "version": "1.0.0-alpha.32",
4
4
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/rollipop-dev/rollipop/issues"
@@ -67,7 +67,7 @@
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.31",
70
+ "@rollipop/dashboard": "1.0.0-alpha.32",
71
71
  "@rollipop/rolldown": "1.0.32",
72
72
  "@swc/core": "1.15.43",
73
73
  "@swc/helpers": "0.5.23",