rollipop 1.0.0-alpha.31 → 1.0.0-alpha.33

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.
@@ -1,13 +1,40 @@
1
+ import * as swc from "@swc/core";
1
2
  import transform from "fast-flow-transform";
2
3
  //#region src/common/transformer.ts
3
4
  async function stripFlowTypes(id, code) {
4
- return await transform({
5
- filename: id,
6
- source: code,
7
- sourcemap: true,
8
- dialect: "flow",
9
- format: "pretty"
10
- });
5
+ try {
6
+ return await transform({
7
+ filename: id,
8
+ source: code,
9
+ sourcemap: true,
10
+ dialect: "flow",
11
+ format: "pretty"
12
+ });
13
+ } catch {
14
+ const result = swc.transformSync(code, {
15
+ filename: id,
16
+ configFile: false,
17
+ swcrc: false,
18
+ sourceMaps: true,
19
+ inputSourceMap: false,
20
+ jsc: {
21
+ target: "esnext",
22
+ parser: {
23
+ syntax: "flow",
24
+ all: true,
25
+ jsx: true,
26
+ enums: true,
27
+ components: true,
28
+ patternMatching: true
29
+ },
30
+ minify: { format: { comments: false } }
31
+ }
32
+ });
33
+ return {
34
+ code: result.code,
35
+ map: result.map ? JSON.parse(result.map) : void 0
36
+ };
37
+ }
11
38
  }
12
39
  //#endregion
13
40
  export { stripFlowTypes };
@@ -1,8 +1,8 @@
1
1
  import { stripFlowTypes } from "../common/transformer.js";
2
- import { DEFAULT_ANALYZE_FILE, DEFAULT_ANALYZE_REPORT_FILE, DEFAULT_ASSET_EXTENSIONS, DEFAULT_ASSET_REGISTRY_PATH, DEFAULT_ENV_FILE, DEFAULT_ENV_PREFIX, DEFAULT_HMR_CLIENT_PATH, DEFAULT_RESOLVER_ALIAS_FIELDS, DEFAULT_RESOLVER_CONDITION_NAMES, DEFAULT_RESOLVER_MAIN_FIELDS, DEFAULT_RUNTIME_TARGET, DEFAULT_SOURCE_EXTENSIONS } from "../constants.js";
2
+ import { DEFAULT_ANALYZE_FILE, DEFAULT_ANALYZE_REPORT_FILE, DEFAULT_ASSET_EXTENSIONS, DEFAULT_ENV_FILE, DEFAULT_ENV_PREFIX, DEFAULT_HMR_CLIENT_PATH, DEFAULT_RESOLVER_ALIAS_FIELDS, DEFAULT_RESOLVER_CONDITION_NAMES, DEFAULT_RESOLVER_MAIN_FIELDS, DEFAULT_RUNTIME_TARGET, DEFAULT_SOURCE_EXTENSIONS } from "../constants.js";
3
3
  import { ClientLogReporter } from "../events/builtin-reporters.js";
4
- import { getInitializeCorePath, getPolyfillScriptPaths } from "../internal/react-native.js";
5
4
  import { resolvePackagePath } from "../utils/node-resolve.js";
5
+ import { resolveAssetRegistryPath, resolveInitializeCorePath, resolvePolyfillScriptPaths } from "../internal/react-native.js";
6
6
  import fs from "node:fs";
7
7
  //#region src/config/defaults.ts
8
8
  async function getDefaultConfig(projectRoot, mode) {
@@ -26,8 +26,8 @@ async function getDefaultConfig(projectRoot, mode) {
26
26
  symlinks: true
27
27
  },
28
28
  transform: { swc: { native: { externalHelpers: true } } },
29
- prelude: [getInitializeCorePath(projectRoot)],
30
- polyfills: await Promise.all(getPolyfillScriptPaths(reactNativePath).map(async (path) => {
29
+ prelude: [resolveInitializeCorePath(projectRoot)],
30
+ polyfills: await Promise.all(resolvePolyfillScriptPaths(projectRoot, reactNativePath).map(async (path) => {
31
31
  const code = fs.readFileSync(path, "utf-8");
32
32
  return {
33
33
  type: "iife",
@@ -38,7 +38,7 @@ async function getDefaultConfig(projectRoot, mode) {
38
38
  treeshake: true,
39
39
  reactNative: {
40
40
  reactNativePath,
41
- assetRegistryPath: DEFAULT_ASSET_REGISTRY_PATH,
41
+ assetRegistryPath: resolveAssetRegistryPath(projectRoot),
42
42
  hmrClientPath: DEFAULT_HMR_CLIENT_PATH
43
43
  },
44
44
  dev: {
@@ -5,11 +5,11 @@ import "../core/plugins/index.js";
5
5
  import { InteractiveCommand } from "../node/commands/start/setup-interactive-mode.js";
6
6
  import "../node/cli-utils.js";
7
7
  import { Plugin } from "../core/plugins/types.js";
8
+ import * as swc from "@swc/core";
8
9
  import { DevWatchOptions, RollipopReactNativeFlowConfig, RollipopReactNativeSwcConfig, RollipopReactNativeWorkletsConfig } from "@rollipop/rolldown/experimental";
9
10
  import * as rolldown from "@rollipop/rolldown";
10
11
  import { TopLevelFilterExpression } from "@rollipop/rolldown/filter";
11
12
  import * as babel from "@babel/core";
12
- import * as swc from "@swc/core";
13
13
  //#region src/config/types.d.ts
14
14
  type RolldownExperimentalOptions = NonNullable<rolldown.InputOptions['experimental']>;
15
15
  type RolldownTransformOptions = NonNullable<rolldown.InputOptions['transform']>;
@@ -267,7 +267,8 @@ export interface ReactNativeConfig {
267
267
  /**
268
268
  * Path to asset registry file.
269
269
  *
270
- * Defaults to: `react-native/Libraries/Image/AssetRegistry.js`
270
+ * Defaults to: `react-native/asset-registry`,
271
+ * with the legacy `react-native/Libraries/Image/AssetRegistry.js` path as a fallback.
271
272
  */
272
273
  assetRegistryPath?: string | ((root: string) => MaybePromise<string>);
273
274
  /**
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.33";
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
  }
@@ -5,6 +5,7 @@ import * as rolldown from "@rollipop/rolldown";
5
5
  export interface ReactNativePluginOptions {
6
6
  context: BundlerContext;
7
7
  projectRoot: string;
8
+ reactNativePath: string;
8
9
  platform: string;
9
10
  preferNativePlatform: boolean;
10
11
  buildType: BuildType;
@@ -1,10 +1,32 @@
1
1
  import { copyAssetsToDestination, generateAssetRegistryCode, resolveScaledAssets } from "../assets.js";
2
2
  import { setFlag } from "./utils/transform-utils.js";
3
+ import path from "node:path";
3
4
  import { rollipopReactNativePlugin } from "@rollipop/rolldown/experimental";
4
5
  import { id, include } from "@rollipop/rolldown/filter";
5
6
  //#region src/core/plugins/react-native-plugin.ts
6
7
  function reactNativePlugin(options) {
7
- const { projectRoot, platform, preferNativePlatform, buildType, context, assetsDir, assetExtensions, assetRegistryPath, builtinPluginConfig } = options;
8
+ const { projectRoot, reactNativePath, platform, preferNativePlatform, buildType, context, assetsDir, assetExtensions, assetRegistryPath, builtinPluginConfig } = options;
9
+ const privateModulePlugin = {
10
+ name: "rollipop:react-native-private-module",
11
+ resolveId: {
12
+ order: "post",
13
+ filter: [include(id(new RegExp(`^react-native/src/private/`)))],
14
+ async handler(source, importer, extraOptions) {
15
+ const resolved = await this.resolve(source, importer, {
16
+ ...extraOptions,
17
+ skipSelf: true
18
+ });
19
+ if (resolved != null) return resolved;
20
+ const candidate = path.resolve(reactNativePath, source.slice(13));
21
+ const relativeCandidate = path.relative(reactNativePath, candidate);
22
+ if (relativeCandidate.startsWith("..") || path.isAbsolute(relativeCandidate)) return null;
23
+ return this.resolve(candidate, importer, {
24
+ ...extraOptions,
25
+ skipSelf: true
26
+ });
27
+ }
28
+ }
29
+ };
8
30
  const assets = [];
9
31
  const assetPlugin = {
10
32
  name: "rollipop:react-native-asset",
@@ -42,7 +64,11 @@ function reactNativePlugin(options) {
42
64
  }
43
65
  }
44
66
  };
45
- return [rollipopReactNativePlugin(builtinPluginConfig), assetPlugin];
67
+ return [
68
+ privateModulePlugin,
69
+ rollipopReactNativePlugin(builtinPluginConfig),
70
+ assetPlugin
71
+ ];
46
72
  }
47
73
  //#endregion
48
74
  export { reactNativePlugin as reactNative };
@@ -1,9 +1,9 @@
1
1
  import { getFlag } from "./utils/transform-utils.js";
2
2
  import { ROLLDOWN_RUNTIME_EXCLUDE_FILTER, withRuntimeExclude } from "./shared/filters.js";
3
3
  import { mergeSwcOptions } from "../../utils/swc.js";
4
+ import * as swc from "@swc/core";
4
5
  import { partition } from "es-toolkit";
5
6
  import { id, include } from "@rollipop/rolldown/filter";
6
- import * as swc from "@swc/core";
7
7
  //#region src/core/plugins/swc-plugin.ts
8
8
  function swcPlugin({ context, transformConfig }) {
9
9
  const { rules = [] } = transformConfig ?? {};
@@ -2,8 +2,8 @@ import { ROLLIPOP_VIRTUAL_ENTRY_ID } from "../constants.js";
2
2
  import { isDebugEnabled } from "../common/env.js";
3
3
  import { CompatStatusReporter, ProgressBarStatusReporter } from "../events/builtin-reporters.js";
4
4
  import { asLiteral, iife, nodeEnvironment } from "../common/code.js";
5
- import { getGlobalVariables } from "../internal/react-native.js";
6
5
  import { resolveFrom, resolvePackageJson } from "../utils/node-resolve.js";
6
+ import { getGlobalVariables } from "../internal/react-native.js";
7
7
  import { printPluginLog } from "./plugins/context.js";
8
8
  import { applyRolldownOptionsConfig } from "../config/compose-override.js";
9
9
  import { createVirtualModuleId, escapeVirtualModuleId } from "../utils/id.js";
@@ -200,6 +200,7 @@ async function resolveReactNativePluginOptions(config, context, buildOptions) {
200
200
  return {
201
201
  context,
202
202
  projectRoot: config.root,
203
+ reactNativePath: config.reactNative.reactNativePath,
203
204
  platform: buildOptions.platform,
204
205
  preferNativePlatform: config.resolve.preferNativePlatform,
205
206
  buildType: context.buildType,
@@ -261,6 +261,14 @@ var __rolldown_runtime__ = (function() {
261
261
  } });
262
262
  if (t) _set_prototype_of(e, t);
263
263
  }
264
+ function _is_native_reflect_construct() {
265
+ try {
266
+ var e = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
267
+ } catch (e) {}
268
+ return (_is_native_reflect_construct = function() {
269
+ return !!e;
270
+ })();
271
+ }
264
272
  function _iterable_to_array(e) {
265
273
  if (typeof Symbol !== "undefined" && e[Symbol.iterator] != null || e["@@iterator"] != null) return Array.from(e);
266
274
  }
@@ -289,10 +297,10 @@ var __rolldown_runtime__ = (function() {
289
297
  return n;
290
298
  }
291
299
  function _non_iterable_rest() {
292
- throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
300
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
293
301
  }
294
302
  function _non_iterable_spread() {
295
- throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
303
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
296
304
  }
297
305
  function _possible_constructor_return(e, t) {
298
306
  if (t && (_type_of(t) === "object" || typeof t === "function")) return t;
@@ -311,26 +319,6 @@ var __rolldown_runtime__ = (function() {
311
319
  function _to_consumable_array(e) {
312
320
  return _array_without_holes(e) || _iterable_to_array(e) || _unsupported_iterable_to_array(e) || _non_iterable_spread();
313
321
  }
314
- function _type_of(e) {
315
- "@swc/helpers - typeof";
316
- return e && typeof Symbol !== "undefined" && e.constructor === Symbol ? "symbol" : typeof e;
317
- }
318
- function _unsupported_iterable_to_array(e, t) {
319
- if (!e) return;
320
- if (typeof e === "string") return _array_like_to_array(e, t);
321
- var r = Object.prototype.toString.call(e).slice(8, -1);
322
- if (r === "Object" && e.constructor) r = e.constructor.name;
323
- if (r === "Map" || r === "Set") return Array.from(r);
324
- if (r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return _array_like_to_array(e, t);
325
- }
326
- function _is_native_reflect_construct() {
327
- try {
328
- var e = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {}));
329
- } catch (e) {}
330
- return (_is_native_reflect_construct = function() {
331
- return !!e;
332
- })();
333
- }
334
322
  function _ts_generator(e, t) {
335
323
  var r, n, o, a = {
336
324
  label: 0,
@@ -411,6 +399,18 @@ var __rolldown_runtime__ = (function() {
411
399
  };
412
400
  }
413
401
  }
402
+ function _type_of(e) {
403
+ "@swc/helpers - typeof";
404
+ return e && typeof Symbol !== "undefined" && e.constructor === Symbol ? "symbol" : typeof e;
405
+ }
406
+ function _unsupported_iterable_to_array(e, t) {
407
+ if (!e) return;
408
+ if (typeof e === "string") return _array_like_to_array(e, t);
409
+ var r = Object.prototype.toString.call(e).slice(8, -1);
410
+ if (r === "Object" && e.constructor) r = e.constructor.name;
411
+ if (r === "Map" || r === "Set") return Array.from(r);
412
+ if (r === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)) return _array_like_to_array(e, t);
413
+ }
414
414
  var _globalThis;
415
415
  var ___rollipop_runtime__;
416
416
  var BaseDevRuntime = DevRuntime;
@@ -616,14 +616,14 @@ var __rolldown_runtime__ = (function() {
616
616
  ]);
617
617
  return e;
618
618
  }();
619
- var ReactNativeDevRuntime = /*#__PURE__*/ function(BaseDevRuntime1) {
619
+ var ReactNativeDevRuntime = /*#__PURE__*/ function(BaseDevRuntime) {
620
620
  "use strict";
621
- _inherits(ReactNativeDevRuntime1, BaseDevRuntime1);
622
- function ReactNativeDevRuntime1() {
623
- _class_call_check(this, ReactNativeDevRuntime1);
621
+ _inherits(ReactNativeDevRuntime, BaseDevRuntime);
622
+ function ReactNativeDevRuntime() {
623
+ _class_call_check(this, ReactNativeDevRuntime);
624
624
  var e;
625
625
  var t = new SocketHolder();
626
- e = _call_super(this, ReactNativeDevRuntime1, [createClientId()]), e.moduleHotContexts = /* @__PURE__ */ new Map(), e.moduleHotContextsToBeUpdated = /* @__PURE__ */ new Map(), e.moduleHotData = /* @__PURE__ */ new Map(), e.applyQueue = Promise.resolve(), e.updatesEnabled = Promise.resolve(), e.reloadRequested = false, e.lastSeq = 0;
626
+ e = _call_super(this, ReactNativeDevRuntime, [createClientId()]), e.moduleHotContexts = /* @__PURE__ */ new Map(), e.moduleHotContextsToBeUpdated = /* @__PURE__ */ new Map(), e.moduleHotData = /* @__PURE__ */ new Map(), e.applyQueue = Promise.resolve(), e.updatesEnabled = Promise.resolve(), e.reloadRequested = false, e.lastSeq = 0;
627
627
  e.socketHolder = t;
628
628
  e.hooks = {
629
629
  createModuleHotContext: function t(t) {
@@ -635,7 +635,7 @@ var __rolldown_runtime__ = (function() {
635
635
  };
636
636
  return e;
637
637
  }
638
- _create_class(ReactNativeDevRuntime1, [
638
+ _create_class(ReactNativeDevRuntime, [
639
639
  {
640
640
  key: "createHotContext",
641
641
  value: function e(e) {
@@ -1025,7 +1025,7 @@ var __rolldown_runtime__ = (function() {
1025
1025
  }
1026
1026
  }
1027
1027
  ]);
1028
- return ReactNativeDevRuntime1;
1028
+ return ReactNativeDevRuntime;
1029
1029
  }(BaseDevRuntime);
1030
1030
  function createClientId() {
1031
1031
  return "".concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2));
@@ -1,14 +1,45 @@
1
- import { __require } from "../_virtual/_rolldown/runtime.js";
2
1
  import { asLiteral } from "../common/code.js";
2
+ import { resolvePackagePath } from "../utils/node-resolve.js";
3
+ import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
  import { isNotNil } from "es-toolkit";
5
6
  //#region src/internal/react-native.ts
6
- function getInitializeCorePath(basePath) {
7
- return __require.resolve("react-native/Libraries/Core/InitializeCore", { paths: [basePath] });
7
+ const ASSET_REGISTRY_PATH = "react-native/asset-registry";
8
+ const LEGACY_ASSET_REGISTRY_PATH = "react-native/Libraries/Image/AssetRegistry.js";
9
+ const require = createRequire(import.meta.url);
10
+ function resolveInitializeCorePath(basePath) {
11
+ return resolveOptional("react-native/setup-env", [basePath]) ?? require.resolve("react-native/Libraries/Core/InitializeCore", { paths: [basePath] });
8
12
  }
9
- function getPolyfillScriptPaths(reactNativePath) {
10
- const scriptPath = path.join(reactNativePath, "rn-get-polyfills");
11
- return __require(scriptPath)();
13
+ function resolveAssetRegistryPath(basePath) {
14
+ return resolveOptional(ASSET_REGISTRY_PATH, [basePath]) ? ASSET_REGISTRY_PATH : LEGACY_ASSET_REGISTRY_PATH;
15
+ }
16
+ function resolvePolyfillScriptPaths(basePath, reactNativePath) {
17
+ const polyfillPath = resolveOptional("@react-native/js-polyfills", [reactNativePath, basePath]);
18
+ if (polyfillPath) return loadPolyfillScriptPaths(polyfillPath);
19
+ let metroConfigPath;
20
+ try {
21
+ metroConfigPath = resolvePackagePath(basePath, "@react-native/metro-config");
22
+ } catch {}
23
+ if (metroConfigPath) {
24
+ const requireFromMetroConfig = createRequire(path.join(metroConfigPath, "package.json"));
25
+ const metroPolyfillPath = resolveOptionalWith(() => requireFromMetroConfig.resolve("@react-native/js-polyfills"));
26
+ if (metroPolyfillPath) return loadPolyfillScriptPaths(metroPolyfillPath, requireFromMetroConfig);
27
+ }
28
+ return loadPolyfillScriptPaths(require.resolve(path.join(reactNativePath, "rn-get-polyfills")));
29
+ }
30
+ function loadPolyfillScriptPaths(modulePath, load = require) {
31
+ return load(modulePath)();
32
+ }
33
+ function resolveOptional(moduleId, paths) {
34
+ return resolveOptionalWith(() => require.resolve(moduleId, { paths }));
35
+ }
36
+ function resolveOptionalWith(resolve) {
37
+ try {
38
+ return resolve();
39
+ } catch (error) {
40
+ if (error instanceof Error && "code" in error && (error.code === "MODULE_NOT_FOUND" || error.code === "ERR_PACKAGE_PATH_NOT_EXPORTED")) return;
41
+ throw error;
42
+ }
12
43
  }
13
44
  function getGlobalVariables(dev) {
14
45
  return [
@@ -20,4 +51,4 @@ function getGlobalVariables(dev) {
20
51
  ].filter(isNotNil);
21
52
  }
22
53
  //#endregion
23
- export { getGlobalVariables, getInitializeCorePath, getPolyfillScriptPaths };
54
+ export { getGlobalVariables, resolveAssetRegistryPath, resolveInitializeCorePath, resolvePolyfillScriptPaths };
@@ -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.33";
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";
@@ -17,6 +17,7 @@ import { DevServerState } from "./state/store.js";
17
17
  import { getWebSocketUpgradeHandler } from "./wss/server.js";
18
18
  import { HMRServer } from "./wss/hmr-server.js";
19
19
  import url from "node:url";
20
+ import { format } from "node:util";
20
21
  import { createDevServerMiddleware } from "@react-native-community/cli-server-api";
21
22
  import { createDevMiddleware } from "@react-native/dev-middleware";
22
23
  import Fastify, { LogController } from "fastify";
@@ -61,10 +62,14 @@ async function createDevServer(config, options) {
61
62
  logger: {
62
63
  info(...args) {
63
64
  if (args[0].includes("JavaScript logs have moved")) return;
64
- logger.info(...args);
65
+ logger.info(format(...args));
65
66
  },
66
- warn: logger.warn.bind(logger),
67
- error: logger.error.bind(logger)
67
+ warn(...args) {
68
+ logger.warn(format(...args));
69
+ },
70
+ error(...args) {
71
+ logger.error(format(...args));
72
+ }
68
73
  },
69
74
  unstable_experiments: {
70
75
  enableNetworkInspector: true,
@@ -121,7 +126,7 @@ async function createDevServer(config, options) {
121
126
  middlewares: { use: fastify.use.bind(fastify) }
122
127
  };
123
128
  const { invokePostConfigureServer } = await invokeConfigureServer(devServer, config.plugins ?? []);
124
- const devframeMiddleware = await setupDevframe(context, devServer);
129
+ const devframeMiddleware = await setupDevframe(context, devServer, websocketEndpoints["/inspector/device"]);
125
130
  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
131
  fastify.setErrorHandler(errorHandler);
127
132
  const upgradeHandler = getWebSocketUpgradeHandler({
@@ -4,27 +4,23 @@ import { parseUrl } from "../../utils/url.js";
4
4
  import { symbolicate } from "../symbolicate.js";
5
5
  import { toDevframeEvent } from "./events.js";
6
6
  import { registerAgentTools } from "./agent/tools/index.js";
7
- import { getBuildSummary, getBundlers, getConfigInfo, getDevice, getFeatureFlags, getProjectInfo, getSnapshot } from "./data.js";
7
+ import { getConfigInfo, getDevice, getFeatureFlags, getSnapshot } from "./data.js";
8
8
  import { defineDevframe, defineRpcFunction } from "devframe";
9
9
  //#region src/server/devframe/index.ts
10
10
  const ROLLIPOP_DEVFRAME_SCOPE = "rollipop";
11
- const DASHBOARD_SHARED_STATE_KEY = "dashboard";
11
+ const DASHBOARD_EVENTS_STATE_KEY = "events";
12
12
  var RollipopDevframeController = class {
13
13
  context;
14
14
  sequence = 0;
15
- lastEvent = null;
16
- updateSharedState;
15
+ updateEventState;
17
16
  unsubscribeEventBus;
18
- refreshQueue;
19
17
  definition;
20
18
  constructor(context, agentToolContext) {
21
19
  this.context = context;
22
- const initialState = createInitialDashboardState(context);
23
- this.refreshQueue = Promise.resolve(initialState);
24
20
  this.definition = defineDevframe({
25
21
  id: "rollipop",
26
22
  name: "Rollipop",
27
- version: "1.0.0-alpha.31",
23
+ version: "1.0.0-alpha.33",
28
24
  packageName: "rollipop",
29
25
  homepage: "https://github.com/rollipop-dev/rollipop",
30
26
  description: "React Native development dashboard powered by Rollipop.",
@@ -34,13 +30,10 @@ var RollipopDevframeController = class {
34
30
  },
35
31
  setup: async (devframeContext) => {
36
32
  const scope = devframeContext.scope(ROLLIPOP_DEVFRAME_SCOPE);
37
- const sharedState = await scope.rpc.sharedState(DASHBOARD_SHARED_STATE_KEY, { initialValue: initialState });
38
- this.updateSharedState = (state) => {
33
+ const sharedState = await scope.rpc.sharedState(DASHBOARD_EVENTS_STATE_KEY, { initialValue: { lastEvent: null } });
34
+ this.updateEventState = (event) => {
39
35
  sharedState.mutate((current) => {
40
- current.snapshot = state.snapshot;
41
- current.builds = state.builds;
42
- current.featureFlags = state.featureFlags;
43
- current.lastEvent = state.lastEvent;
36
+ current.lastEvent = event;
44
37
  });
45
38
  };
46
39
  registerDashboardRpcFunctions(scope.rpc, this);
@@ -48,26 +41,19 @@ var RollipopDevframeController = class {
48
41
  this.unsubscribeEventBus = context.eventBus.subscribe((event) => {
49
42
  const data = toDevframeEvent(event);
50
43
  if (data == null) return;
51
- this.lastEvent = {
52
- sequence: ++this.sequence,
53
- data
54
- };
55
- this.refresh();
44
+ this.notify(data);
56
45
  });
57
46
  }
58
47
  });
59
48
  }
60
- async refresh() {
61
- const lastEvent = this.lastEvent;
62
- this.refreshQueue = this.refreshQueue.catch(() => createInitialDashboardState(this.context)).then(async () => {
63
- const state = await this.readDashboardState(lastEvent);
64
- this.updateSharedState?.(state);
65
- return state;
49
+ notify(data) {
50
+ this.updateEventState?.({
51
+ sequence: ++this.sequence,
52
+ data
66
53
  });
67
- return this.refreshQueue;
68
54
  }
69
- async getSnapshot() {
70
- return (await this.refresh()).snapshot;
55
+ getSnapshot() {
56
+ return getSnapshot(this.context);
71
57
  }
72
58
  getBuilds() {
73
59
  return this.context.state.getBuilds();
@@ -78,9 +64,8 @@ var RollipopDevframeController = class {
78
64
  if (this.context.bundlerPool.getInstanceById(bundlerId) == null) throw new Error(`Build logs not found: ${bundlerId}`);
79
65
  return [];
80
66
  }
81
- async deleteBuildLogs(bundlerId) {
67
+ deleteBuildLogs(bundlerId) {
82
68
  if (!this.context.state.clearBuildLogs(bundlerId) && this.context.bundlerPool.getInstanceById(bundlerId) == null) throw new Error(`Build logs not found: ${bundlerId}`);
83
- await this.refresh();
84
69
  }
85
70
  getConfig() {
86
71
  return getConfigInfo(this.context);
@@ -111,33 +96,21 @@ var RollipopDevframeController = class {
111
96
  if (bundler == null) throw new Error(`Bundler not found: ${bundlerId}`);
112
97
  setTimeout(() => this.context.message.broadcast("reload"), 0);
113
98
  await bundler.triggerFullBuild();
114
- await this.refresh();
115
99
  }
116
- async reload() {
100
+ reload() {
117
101
  this.context.message.broadcast("reload");
118
- await this.refresh();
119
102
  }
120
103
  async resetCache() {
121
104
  await resetCache();
122
105
  this.context.eventBus.emit({ type: "cache_reset" });
123
- await this.refresh();
124
106
  }
125
- async resetBundlerState() {
107
+ resetBundlerState() {
126
108
  this.context.state.resetBufferedState();
127
- await this.refresh();
128
109
  }
129
110
  dispose() {
130
111
  this.unsubscribeEventBus?.();
131
112
  this.unsubscribeEventBus = void 0;
132
113
  }
133
- async readDashboardState(lastEvent) {
134
- return {
135
- snapshot: await getSnapshot(this.context),
136
- builds: this.context.state.getBuilds(),
137
- featureFlags: getFeatureFlags(this.context),
138
- lastEvent
139
- };
140
- }
141
114
  };
142
115
  function registerDashboardRpcFunctions(rpc, controller) {
143
116
  rpc.register(defineRpcFunction({
@@ -201,18 +174,5 @@ function registerDashboardRpcFunctions(rpc, controller) {
201
174
  handler: () => controller.resetBundlerState()
202
175
  }));
203
176
  }
204
- function createInitialDashboardState(context) {
205
- return {
206
- snapshot: {
207
- project: getProjectInfo(context),
208
- bundlers: getBundlers(context),
209
- devices: [],
210
- buildSummary: getBuildSummary(context)
211
- },
212
- builds: context.state.getBuilds(),
213
- featureFlags: getFeatureFlags(context),
214
- lastEvent: null
215
- };
216
- }
217
177
  //#endregion
218
- export { DASHBOARD_SHARED_STATE_KEY, ROLLIPOP_DEVFRAME_SCOPE, RollipopDevframeController };
178
+ export { DASHBOARD_EVENTS_STATE_KEY, ROLLIPOP_DEVFRAME_SCOPE, RollipopDevframeController };
@@ -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.33",
20
20
  base: ROLLIPOP_DEVFRAME_BASE,
21
21
  cwd: context.config.root,
22
22
  ui: createUi({
@@ -44,14 +44,22 @@ async function setupDevframe(context, server) {
44
44
  }
45
45
  });
46
46
  await devtools.ready;
47
+ const notifyDevicesChanged = () => {
48
+ controller.notify({ type: "devices_changed" });
49
+ };
50
+ const onDeviceConnection = (socket) => {
51
+ socket.once("close", notifyDevicesChanged);
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");
50
57
  logger.info(`MCP server listening at ${mcpUrl}`);
51
58
  }
52
- controller.refresh();
53
59
  });
54
60
  server.instance.addHook("preClose", async () => {
61
+ inspectorDeviceServer?.off("connection", onDeviceConnection);
62
+ for (const socket of inspectorDeviceServer?.clients ?? []) socket.off("close", notifyDevicesChanged);
55
63
  controller.dispose();
56
64
  await devtools.close();
57
65
  });
@@ -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.33") 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.33",
4
4
  "homepage": "https://github.com/rollipop-dev/rollipop#readme",
5
5
  "bugs": {
6
6
  "url": "https://github.com/rollipop-dev/rollipop/issues"
@@ -67,9 +67,9 @@
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.33",
71
71
  "@rollipop/rolldown": "1.0.32",
72
- "@swc/core": "1.15.43",
72
+ "@swc/core": "1.16.13",
73
73
  "@swc/helpers": "0.5.23",
74
74
  "@types/ws": "8.18.1",
75
75
  "arktype": "2.2.3",