@whop/react-native 0.1.15 → 0.2.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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "../chunk-KIXXMEFR.mjs";
5
5
 
6
6
  // src/cli/index.ts
7
- import { existsSync as existsSync2 } from "fs";
7
+ import { existsSync as existsSync3 } from "fs";
8
8
  import path7 from "path";
9
9
  import { parseArgs } from "util";
10
10
  import { findUp as findUp2 } from "find-up";
@@ -298,79 +298,498 @@ var CustomReporter = class {
298
298
  }
299
299
  };
300
300
 
301
+ // src/cli/mobile-rspack.ts
302
+ import { existsSync as existsSync2, readdirSync as readdirSync2 } from "fs";
303
+ import { mkdir as mkdir2, rename as rename2, unlink, writeFile as writeFile2 } from "fs/promises";
304
+ import path4 from "path";
305
+ var flowLoaderPath = path4.join(__dirname, "rspack-flow-loader.js");
306
+ var reanimatedLoaderPath = path4.join(__dirname, "rspack-reanimated-loader.js");
307
+ async function loadRspack(root) {
308
+ try {
309
+ const rspackPath = __require.resolve("@rspack/core", { paths: [root] });
310
+ return __require(rspackPath);
311
+ } catch {
312
+ return __require("@rspack/core");
313
+ }
314
+ }
315
+ async function loadRepack(root) {
316
+ try {
317
+ const repackPath = __require.resolve("@callstack/repack", { paths: [root] });
318
+ return __require(repackPath);
319
+ } catch {
320
+ return __require("@callstack/repack");
321
+ }
322
+ }
323
+ function isFlowPreTransformed(root) {
324
+ const markerFile = path4.join(root, "node_modules/.flow-pretransformed");
325
+ return existsSync2(markerFile);
326
+ }
327
+ function getReactNativeInitModules(root) {
328
+ const reactNativePath = path4.dirname(
329
+ __require.resolve("react-native/package.json", { paths: [root] })
330
+ );
331
+ const getPolyfills = __require(path4.join(reactNativePath, "rn-get-polyfills.js"));
332
+ const polyfills = getPolyfills();
333
+ const initializeCore = path4.join(reactNativePath, "Libraries/Core/InitializeCore.js");
334
+ return [...polyfills, initializeCore];
335
+ }
336
+ async function createHermesPolyfills(root, platform) {
337
+ const polyfillDir = path4.join(root, "build", "entrypoints", platform);
338
+ await mkdir2(polyfillDir, { recursive: true });
339
+ const polyfillPath = path4.join(polyfillDir, "hermes-polyfills.js");
340
+ const polyfillCode = `
341
+ // Polyfills for Web APIs not available in Hermes
342
+ // This runs after React Native's InitializeCore, so 'global' is available
343
+
344
+ (function(g) {
345
+ // TextDecoder/TextEncoder (needed by jose/JWT libraries)
346
+ if (typeof g.TextDecoder === 'undefined') {
347
+ g.TextDecoder = function TextDecoder(encoding) {
348
+ if (encoding && encoding !== 'utf-8' && encoding !== 'utf8') {
349
+ throw new Error('TextDecoder polyfill only supports UTF-8');
350
+ }
351
+ };
352
+ g.TextDecoder.prototype.decode = function(input) {
353
+ if (!input) return '';
354
+ var bytes = new Uint8Array(input.buffer || input);
355
+ var result = '';
356
+ for (var i = 0; i < bytes.length; i++) {
357
+ result += String.fromCharCode(bytes[i]);
358
+ }
359
+ try {
360
+ return decodeURIComponent(escape(result));
361
+ } catch (e) {
362
+ return result;
363
+ }
364
+ };
365
+ }
366
+
367
+ if (typeof g.TextEncoder === 'undefined') {
368
+ g.TextEncoder = function TextEncoder() {};
369
+ g.TextEncoder.prototype.encode = function(str) {
370
+ var utf8 = unescape(encodeURIComponent(str || ''));
371
+ var result = new Uint8Array(utf8.length);
372
+ for (var i = 0; i < utf8.length; i++) {
373
+ result[i] = utf8.charCodeAt(i);
374
+ }
375
+ return result;
376
+ };
377
+ }
378
+
379
+ // URL polyfill (minimal, for libraries that need it before RN's kicks in)
380
+ if (typeof g.URL === 'undefined') {
381
+ g.URL = function URL(url, base) {
382
+ if (base && typeof base === 'string' && !/^[a-z][a-z0-9+.-]*:/i.test(url)) {
383
+ url = base.replace(/\\/[^\\/]*$/, '/') + url;
384
+ }
385
+ this.href = url || '';
386
+ var match = this.href.match(/^([a-z][a-z0-9+.-]*:)?\\/\\/([^\\/:]*)(:[0-9]+)?(\\/[^?#]*)?(\\?[^#]*)?(#.*)?$/i);
387
+ if (match) {
388
+ this.protocol = match[1] || '';
389
+ this.hostname = match[2] || '';
390
+ this.port = (match[3] || '').slice(1);
391
+ this.pathname = match[4] || '/';
392
+ this.search = match[5] || '';
393
+ this.hash = match[6] || '';
394
+ this.host = this.hostname + (this.port ? ':' + this.port : '');
395
+ this.origin = this.protocol + '//' + this.host;
396
+ } else {
397
+ this.protocol = ''; this.hostname = ''; this.port = '';
398
+ this.pathname = this.href; this.search = ''; this.hash = '';
399
+ this.host = ''; this.origin = '';
400
+ }
401
+ };
402
+ g.URL.prototype.toString = function() { return this.href; };
403
+ }
404
+
405
+ // URLSearchParams polyfill (minimal)
406
+ if (typeof g.URLSearchParams === 'undefined') {
407
+ g.URLSearchParams = function URLSearchParams(init) {
408
+ this._params = [];
409
+ if (typeof init === 'string') {
410
+ init = init.replace(/^\\?/, '');
411
+ var pairs = init.split('&');
412
+ for (var i = 0; i < pairs.length; i++) {
413
+ var pair = pairs[i].split('=');
414
+ if (pair[0]) {
415
+ this._params.push([decodeURIComponent(pair[0]), decodeURIComponent(pair[1] || '')]);
416
+ }
417
+ }
418
+ }
419
+ };
420
+ g.URLSearchParams.prototype.get = function(name) {
421
+ for (var i = 0; i < this._params.length; i++) {
422
+ if (this._params[i][0] === name) return this._params[i][1];
423
+ }
424
+ return null;
425
+ };
426
+ g.URLSearchParams.prototype.toString = function() {
427
+ return this._params.map(function(p) {
428
+ return encodeURIComponent(p[0]) + '=' + encodeURIComponent(p[1]);
429
+ }).join('&');
430
+ };
431
+ }
432
+ })(typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);
433
+ `;
434
+ await writeFile2(polyfillPath, polyfillCode);
435
+ return polyfillPath;
436
+ }
437
+ async function bundleWithRspack(root, platform) {
438
+ const rspackModule = await loadRspack(root);
439
+ const { rspack } = rspackModule;
440
+ const Repack = await loadRepack(root);
441
+ const entryFile = await makeEntrypoint2(root, platform);
442
+ const rnInitModules = getReactNativeInitModules(root);
443
+ const hermesPolyfills = await createHermesPolyfills(root, platform);
444
+ const outputDir = path4.join(root, "build", "output", platform);
445
+ await mkdir2(outputDir, { recursive: true });
446
+ const outputFile = path4.join(outputDir, "main_js_bundle.js");
447
+ const flowPreTransformed = isFlowPreTransformed(root);
448
+ if (flowPreTransformed) {
449
+ console.log(` \u2139\uFE0E [${platform}] using pre-transformed Flow files (fast path)`);
450
+ }
451
+ const moduleRules = [
452
+ // Allow extensionless imports in ESM modules (e.g., @rn-primitives)
453
+ {
454
+ test: /\.m?js$/,
455
+ resolve: {
456
+ fullySpecified: false
457
+ }
458
+ }
459
+ ];
460
+ if (flowPreTransformed) {
461
+ moduleRules.push({
462
+ test: /\.[cm]?[jt]sx?$/,
463
+ use: {
464
+ loader: "builtin:swc-loader",
465
+ options: {
466
+ jsc: {
467
+ parser: {
468
+ syntax: "typescript",
469
+ tsx: true
470
+ },
471
+ transform: {
472
+ react: {
473
+ runtime: "automatic"
474
+ }
475
+ }
476
+ }
477
+ }
478
+ },
479
+ type: "javascript/auto"
480
+ });
481
+ } else {
482
+ moduleRules.push(
483
+ // Strip Flow types from react-native core files FIRST
484
+ {
485
+ test: /\.jsx?$/,
486
+ include: [
487
+ /node_modules[/\\]react-native[/\\]/,
488
+ /node_modules[/\\]@react-native[/\\]/
489
+ ],
490
+ use: [
491
+ // Then process with SWC
492
+ {
493
+ loader: "builtin:swc-loader",
494
+ options: {
495
+ jsc: {
496
+ parser: {
497
+ syntax: "ecmascript",
498
+ jsx: true
499
+ },
500
+ transform: {
501
+ react: {
502
+ runtime: "automatic"
503
+ }
504
+ }
505
+ }
506
+ }
507
+ },
508
+ // First strip Flow types with Babel
509
+ {
510
+ loader: flowLoaderPath
511
+ }
512
+ ],
513
+ type: "javascript/auto"
514
+ },
515
+ // App code and other node_modules: JS/TS/JSX/TSX via SWC
516
+ {
517
+ test: /\.[cm]?[jt]sx?$/,
518
+ exclude: [
519
+ /node_modules[/\\]react-native[/\\]/,
520
+ /node_modules[/\\]@react-native[/\\]/
521
+ ],
522
+ use: {
523
+ loader: "builtin:swc-loader",
524
+ options: {
525
+ jsc: {
526
+ parser: {
527
+ syntax: "typescript",
528
+ tsx: true
529
+ },
530
+ transform: {
531
+ react: {
532
+ runtime: "automatic"
533
+ }
534
+ }
535
+ }
536
+ }
537
+ },
538
+ type: "javascript/auto"
539
+ }
540
+ );
541
+ }
542
+ moduleRules.unshift({
543
+ test: /\.[jt]sx?$/,
544
+ include: [
545
+ /node_modules[/\\]react-native-reanimated[/\\]/,
546
+ // Also include app code that might have worklets
547
+ path4.join(root, "src")
548
+ ],
549
+ use: {
550
+ loader: reanimatedLoaderPath
551
+ },
552
+ enforce: "pre"
553
+ // Run before other loaders
554
+ });
555
+ moduleRules.push({
556
+ test: /\.(png|jpg|jpeg|gif|webp|svg)$/,
557
+ type: "asset/inline"
558
+ });
559
+ const cacheDir = path4.join(root, "node_modules/.cache/rspack");
560
+ const hasCacheDir = existsSync2(cacheDir);
561
+ if (hasCacheDir) {
562
+ console.log(` \u2139\uFE0E [${platform}] using pre-warmed Rspack cache`);
563
+ }
564
+ const config2 = {
565
+ mode: "production",
566
+ context: root,
567
+ // Disable default target - we're targeting React Native's Hermes runtime
568
+ target: false,
569
+ // Entry order: RN init -> Hermes polyfills -> app code
570
+ entry: [...rnInitModules, hermesPolyfills, entryFile],
571
+ output: {
572
+ path: outputDir,
573
+ filename: "main_js_bundle.js",
574
+ publicPath: "/",
575
+ clean: false,
576
+ // Use 'self' as global object (initialized by our banner)
577
+ globalObject: "self",
578
+ // Disable async chunk loading - RN needs single bundle
579
+ chunkLoading: false,
580
+ chunkFormat: false
581
+ },
582
+ // Enable caching (required for experiments.cache)
583
+ cache: true,
584
+ // Persistent filesystem cache (Rspack 1.3+ format)
585
+ experiments: {
586
+ cache: {
587
+ type: "persistent",
588
+ version: "rspack-rn-v2",
589
+ storage: {
590
+ type: "filesystem",
591
+ directory: cacheDir
592
+ }
593
+ }
594
+ },
595
+ resolve: {
596
+ // Get Re.Pack's resolve options for React Native
597
+ ...Repack.getResolveOptions({ platform }),
598
+ // Ensure we can resolve from the app's node_modules
599
+ modules: [
600
+ path4.join(root, "node_modules"),
601
+ "node_modules"
602
+ ],
603
+ // Use react-native field first in package.json
604
+ mainFields: ["react-native", "browser", "module", "main"],
605
+ // Re-enable exports field resolution (Re.Pack disables it)
606
+ conditionNames: ["react-native", "import", "require", "default"],
607
+ exportsFields: ["exports"],
608
+ // Platform-specific extensions - order matters! Most specific first
609
+ // Re.Pack's extensions have literal [platform] which doesn't work
610
+ extensions: [
611
+ `.${platform}.ts`,
612
+ `.${platform}.tsx`,
613
+ `.${platform}.js`,
614
+ `.${platform}.jsx`,
615
+ ".native.ts",
616
+ ".native.tsx",
617
+ ".native.js",
618
+ ".native.jsx",
619
+ ".ts",
620
+ ".tsx",
621
+ ".js",
622
+ ".jsx",
623
+ ".json"
624
+ ],
625
+ // Alias react-native-reanimated to lib/module to avoid TypeScript source
626
+ alias: {
627
+ ...Repack.getResolveOptions({ platform }).alias,
628
+ "react-native-reanimated": path4.join(root, "node_modules/react-native-reanimated/lib/module")
629
+ }
630
+ },
631
+ resolveLoader: {
632
+ // Allow resolving loaders from the CLI package's node_modules
633
+ modules: [
634
+ path4.join(__dirname, ".."),
635
+ // dist/ folder
636
+ path4.dirname(__dirname),
637
+ // package root
638
+ "node_modules"
639
+ ]
640
+ },
641
+ module: {
642
+ rules: moduleRules
643
+ },
644
+ plugins: [
645
+ // NOTE: We intentionally do NOT use RepackTargetPlugin because it sets
646
+ // chunkLoading: 'jsonp' which creates separate chunk files that can't be
647
+ // loaded in React Native. Instead, we manually set up what we need.
648
+ // Initialize 'self' global (normally done by RepackTargetPlugin)
649
+ new rspack.BannerPlugin({
650
+ raw: true,
651
+ entryOnly: true,
652
+ banner: `var self = self || this || new Function("return this")() || {};`
653
+ }),
654
+ // Define globals for React Native
655
+ new rspack.DefinePlugin({
656
+ __DEV__: JSON.stringify(false),
657
+ "process.env.NODE_ENV": JSON.stringify("production")
658
+ })
659
+ ],
660
+ optimization: {
661
+ minimize: false,
662
+ // Keep readable for debugging
663
+ // Disable code splitting - React Native needs a single bundle
664
+ splitChunks: false,
665
+ runtimeChunk: false
666
+ },
667
+ devtool: false,
668
+ stats: "errors-warnings"
669
+ };
670
+ await new Promise((resolve, reject) => {
671
+ rspack(config2, (err, stats) => {
672
+ if (err) {
673
+ reject(err);
674
+ return;
675
+ }
676
+ if (stats?.hasErrors()) {
677
+ const info = stats.toJson();
678
+ console.error(info.errors);
679
+ reject(new Error("Rspack build failed"));
680
+ return;
681
+ }
682
+ resolve();
683
+ });
684
+ });
685
+ const finalFile = path4.join(outputDir, "main_js_bundle.hbc");
686
+ if (!existsSync2(outputFile)) {
687
+ throw new Error(
688
+ `[${platform}] Rspack completed but bundle file was not created at ${outputFile}. Check the build output for warnings or errors.`
689
+ );
690
+ }
691
+ await rename2(outputFile, finalFile);
692
+ const outputFiles = readdirSync2(outputDir);
693
+ console.log(` \u2139\uFE0E [${platform}] files created:`, outputFiles.join(", "));
694
+ for (const file of outputFiles) {
695
+ if (file !== "main_js_bundle.hbc" && file !== "assets") {
696
+ await unlink(path4.join(outputDir, file));
697
+ }
698
+ }
699
+ console.log(` \u2714\uFE0E [${platform}] bundle created (rspack)`);
700
+ }
701
+ async function makeEntrypoint2(root, platform) {
702
+ const entrypoint = path4.join(
703
+ root,
704
+ "build",
705
+ "entrypoints",
706
+ platform,
707
+ "index.js"
708
+ );
709
+ const files = await getSupportedAppViewTypes(root);
710
+ const pascalCase = (str) => str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
711
+ const imports = files.map(
712
+ (file) => `import { ${pascalCase(file)} } from "../../../src/views/${file}";`
713
+ );
714
+ const registry = files.map(
715
+ (file) => `AppRegistry.registerComponent("${pascalCase(file)}", () => ${pascalCase(
716
+ file
717
+ )});`
718
+ );
719
+ const entrypointContent = `import { AppRegistry } from "react-native";
720
+ ${imports.join("\n")}
721
+
722
+ ${registry.join("\n")}
723
+ `;
724
+ const entrypointDir = path4.dirname(entrypoint);
725
+ await mkdir2(entrypointDir, { recursive: true });
726
+ await writeFile2(entrypoint, entrypointContent, "utf-8");
727
+ console.log(` \u2714\uFE0E [${platform}] entrypoint created`);
728
+ return entrypoint;
729
+ }
730
+
301
731
  // src/cli/web.ts
302
- import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
732
+ import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
303
733
  import path6 from "path";
304
734
  import { build } from "esbuild";
305
735
 
306
736
  // src/cli/reanimated-bable.ts
307
- import * as fs2 from "fs/promises";
308
- import * as path4 from "path";
309
737
  import * as babel from "@babel/core";
310
- var JS_RE = /\.(m|c)?(t|j)sx?$/;
738
+ var WORKLET_DIRECTIVE_RX = /['"]worklet['"]/;
311
739
  function reanimatedBabelPlugin() {
312
- const shouldTransform = (p) => {
313
- if (!JS_RE.test(p)) return false;
314
- if (p.includes(`${path4.sep}react-native-reanimated${path4.sep}`))
315
- return true;
316
- if (p.includes(`${path4.sep}node_modules${path4.sep}`)) return false;
317
- return p.includes(`${path4.sep}src${path4.sep}`);
318
- };
319
740
  return {
320
741
  name: "reanimated-babel",
321
742
  setup(b) {
322
- b.onLoad({ filter: JS_RE }, async (args) => {
323
- if (!shouldTransform(args.path)) {
324
- return null;
743
+ b.onLoad({ filter: /\.[jt]sx?$/ }, async (args) => {
744
+ const fs3 = await import("fs/promises");
745
+ const source = await fs3.readFile(args.path, "utf8");
746
+ if (!WORKLET_DIRECTIVE_RX.test(source)) {
747
+ return void 0;
325
748
  }
326
- const code = await fs2.readFile(args.path, "utf8");
327
- const result = await babel.transformAsync(code, {
749
+ const isTS = args.path.endsWith(".ts") || args.path.endsWith(".tsx");
750
+ const hasJSX = args.path.endsWith("x") || args.path.endsWith(".js");
751
+ const syntaxPlugins = [];
752
+ if (isTS) {
753
+ syntaxPlugins.push(["@babel/plugin-syntax-typescript", { isTSX: args.path.endsWith("x") }]);
754
+ } else if (hasJSX) {
755
+ syntaxPlugins.push("@babel/plugin-syntax-jsx");
756
+ }
757
+ const result = await babel.transformAsync(source, {
328
758
  filename: args.path,
329
- sourceMaps: false,
330
759
  babelrc: false,
331
760
  configFile: false,
332
- // ORDER MATTERS: Reanimated plugin MUST BE LAST
333
761
  plugins: [
334
- // Needed by Reanimated on web per docs
335
- "@babel/plugin-transform-export-namespace-from",
336
- // Handle Flow types present in some RN libs
337
- [
338
- "@babel/plugin-transform-flow-strip-types",
339
- { allowDeclareFields: true }
340
- ],
341
- // MUST be last
342
- [
343
- "react-native-reanimated/plugin",
344
- { relativeSourceLocation: true }
345
- ]
762
+ ...syntaxPlugins,
763
+ // Transform worklets
764
+ "react-native-reanimated/plugin"
346
765
  ],
347
766
  presets: [],
348
767
  // esbuild handles TS/JSX syntax; no preset-env/preset-react
349
768
  caller: { name: "esbuild" },
350
- // Let Babel parse TS/JSX/Flow; keep it broad
351
- parserOpts: { plugins: ["jsx", "typescript"] },
352
- generatorOpts: { decoratorsBeforeExport: true }
769
+ sourceMaps: "inline"
353
770
  });
771
+ if (!result?.code) {
772
+ return void 0;
773
+ }
774
+ const ext = args.path.split(".").pop() ?? "ts";
775
+ const loaderMap = {
776
+ tsx: "tsx",
777
+ jsx: "jsx",
778
+ ts: "ts",
779
+ js: "jsx"
780
+ // .js files in RN often contain JSX
781
+ };
354
782
  return {
355
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
356
783
  contents: result.code,
357
- // biome-ignore lint/suspicious/noExplicitAny: <explanation>
358
- loader: pickLoader(args.path)
784
+ loader: loaderMap[ext] ?? "ts"
359
785
  };
360
786
  });
361
787
  }
362
788
  };
363
789
  }
364
- function pickLoader(file) {
365
- const ext = path4.extname(file).toLowerCase();
366
- if (ext === ".tsx") return "tsx";
367
- if (ext === ".ts") return "ts";
368
- if (ext === ".jsx") return "jsx";
369
- return "jsx";
370
- }
371
790
 
372
791
  // src/cli/strip-flow.ts
373
- import * as fs3 from "fs/promises";
792
+ import * as fs2 from "fs/promises";
374
793
  import * as path5 from "path";
375
794
  import * as babel2 from "@babel/core";
376
795
  function stripFlowWithBabel() {
@@ -380,10 +799,11 @@ function stripFlowWithBabel() {
380
799
  // biome-ignore lint/suspicious/noExplicitAny: <explanation>
381
800
  setup(b) {
382
801
  b.onLoad({ filter }, async (args) => {
383
- if (!args.path.includes(`${path5.sep}react-native${path5.sep}`)) {
802
+ const isReactNative = args.path.includes(`${path5.sep}react-native${path5.sep}`) || args.path.includes(`${path5.sep}@react-native${path5.sep}`);
803
+ if (!isReactNative) {
384
804
  return null;
385
805
  }
386
- const code = await fs3.readFile(args.path, "utf8");
806
+ const code = await fs2.readFile(args.path, "utf8");
387
807
  const out = await babel2.transformAsync(code, {
388
808
  filename: args.path,
389
809
  babelrc: false,
@@ -437,7 +857,7 @@ function toPascalCase(str) {
437
857
  async function makeWebEntrypoint(root) {
438
858
  const files = await getSupportedAppViewTypes(root);
439
859
  const packageJsonPath = path6.join(root, "package.json");
440
- const packageJson = JSON.parse(await readFile3(packageJsonPath, "utf-8"));
860
+ const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf-8"));
441
861
  const hasReactNativeReanimated = packageJson.dependencies?.["react-native-reanimated"];
442
862
  const imports = files.map(
443
863
  (file) => `import { ${toPascalCase(file)} } from "../../../src/views/${file}";`
@@ -471,14 +891,14 @@ const root = document.getElementById("root") || (() => {
471
891
  AppRegistry.runApplication(viewType, { rootTag: root });
472
892
  `;
473
893
  const entryFile = path6.join(root, "build", "entrypoints", "web", "index.tsx");
474
- await mkdir2(path6.dirname(entryFile), { recursive: true });
475
- await writeFile2(entryFile, entry, "utf-8");
894
+ await mkdir3(path6.dirname(entryFile), { recursive: true });
895
+ await writeFile3(entryFile, entry, "utf-8");
476
896
  return entryFile;
477
897
  }
478
898
  async function bundleWeb(root) {
479
899
  const entry = await makeWebEntrypoint(root);
480
900
  const outDir = path6.join(root, "build", "output", "web");
481
- await mkdir2(outDir, { recursive: true });
901
+ await mkdir3(outDir, { recursive: true });
482
902
  await build({
483
903
  entryPoints: [entry],
484
904
  outfile: path6.join(outDir, "main.js"),
@@ -564,7 +984,7 @@ async function bundleWeb(root) {
564
984
  <script type="module" src="./main.js"></script>
565
985
  </body>
566
986
  </html>`;
567
- await writeFile2(path6.join(outDir, "index.html"), html, "utf-8");
987
+ await writeFile3(path6.join(outDir, "index.html"), html, "utf-8");
568
988
  console.log(" \u2714\uFE0E [web] bundle created at build/output/web/main.js");
569
989
  }
570
990
  async function buildAndPublish2(root, {
@@ -586,11 +1006,11 @@ async function createWebBuild(root) {
586
1006
  const fullDirectory = path6.join(root, "build", "output", "web");
587
1007
  const mainJsFile = path6.join(fullDirectory, "main.js");
588
1008
  try {
589
- await readFile3(mainJsFile);
1009
+ await readFile2(mainJsFile);
590
1010
  } catch {
591
1011
  throw new Error(`main.js not found in ${fullDirectory}`);
592
1012
  }
593
- const buf = await readFile3(mainJsFile);
1013
+ const buf = await readFile2(mainJsFile);
594
1014
  const checksum = await getChecksum(buf);
595
1015
  console.log(` \u2714\uFE0E [web] build checksummed: ${checksum}`);
596
1016
  const fileName = `rnweb_${checksum}.js`;
@@ -648,6 +1068,9 @@ async function main() {
648
1068
  },
649
1069
  web: {
650
1070
  type: "boolean"
1071
+ },
1072
+ metro: {
1073
+ type: "boolean"
651
1074
  }
652
1075
  },
653
1076
  strict: true,
@@ -676,10 +1099,13 @@ async function main() {
676
1099
  } else {
677
1100
  console.error(
678
1101
  `Usage:
679
- whop-react-native ship [--ios] [--android] [--web] # runs build and then publishes it as a dev build to whop.
680
- whop-react-native build [--ios] [--android] [--web] # builds your app into a distributable bundle in the build/ directory.
681
- whop-react-native upload [--ios] [--android] [--web] # uploads the existing build directory to whop.
682
- whop-react-native clean # cleans the build directory.`
1102
+ whop-react-native ship [--ios] [--android] [--web] [--metro] # runs build and then publishes it as a dev build to whop.
1103
+ whop-react-native build [--ios] [--android] [--web] [--metro] # builds your app into a distributable bundle in the build/ directory.
1104
+ whop-react-native upload [--ios] [--android] [--web] # uploads the existing build directory to whop.
1105
+ whop-react-native clean # cleans the build directory.
1106
+
1107
+ Options:
1108
+ --metro Use Metro instead of Rspack for mobile builds (slower, for compatibility)`
683
1109
  );
684
1110
  process.exit(1);
685
1111
  }
@@ -692,29 +1118,53 @@ async function main() {
692
1118
  }
693
1119
  }
694
1120
  const didProvidePlatform = args.values.ios || args.values.android || args.values.web;
1121
+ const useMetro = args.values.metro || envBool("WRN_USE_METRO");
695
1122
  const opts = { shouldBuild, shouldUpload };
696
1123
  const promises = [];
1124
+ const bundler = useMetro ? "metro" : "rspack";
1125
+ if (shouldBuild) {
1126
+ console.log(` \u2139\uFE0E using ${bundler} for mobile builds`);
1127
+ }
697
1128
  if (args.values.ios || !didProvidePlatform) {
698
- promises.push(buildAndPublish(root, "ios", opts));
1129
+ if (useMetro) {
1130
+ promises.push(buildAndPublish(root, "ios", opts));
1131
+ } else {
1132
+ promises.push(buildAndPublishWithRspack(root, "ios", opts));
1133
+ }
699
1134
  }
700
1135
  if (args.values.android || !didProvidePlatform) {
701
- promises.push(buildAndPublish(root, "android", opts));
1136
+ if (useMetro) {
1137
+ promises.push(buildAndPublish(root, "android", opts));
1138
+ } else {
1139
+ promises.push(buildAndPublishWithRspack(root, "android", opts));
1140
+ }
702
1141
  }
703
1142
  if (args.values.web || !didProvidePlatform) {
704
1143
  promises.push(buildAndPublish2(root, opts));
705
1144
  }
706
1145
  await Promise.all(promises);
707
1146
  }
1147
+ async function buildAndPublishWithRspack(root, platform, {
1148
+ shouldBuild = true,
1149
+ shouldUpload = true
1150
+ }) {
1151
+ if (shouldBuild) {
1152
+ await bundleWithRspack(root, platform);
1153
+ }
1154
+ if (shouldUpload) {
1155
+ await createMobileBuild(root, platform);
1156
+ }
1157
+ }
708
1158
  async function cleanBuildDirectory(root) {
709
1159
  const buildDirectory = path7.join(root, "build");
710
- if (existsSync2(buildDirectory)) {
1160
+ if (existsSync3(buildDirectory)) {
711
1161
  await rimraf(buildDirectory);
712
1162
  }
713
1163
  console.log(" \u2714\uFE0E cleaned build directory");
714
1164
  }
715
1165
  async function cleanEntrypointsDirectory(root) {
716
1166
  const buildDirectory = path7.join(root, "build", "entrypoints");
717
- if (existsSync2(buildDirectory)) {
1167
+ if (existsSync3(buildDirectory)) {
718
1168
  await rimraf(buildDirectory);
719
1169
  }
720
1170
  console.log(" \u2714\uFE0E cleaned build entrypoints directory");