expo-desktop 0.1.19 → 0.1.23

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,14 +1,15 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { env } from "node:process";
4
+ import process from "node:process";
5
5
  import readline from "node:readline";
6
6
  import { stripVTControlCharacters } from "node:util";
7
7
  /**
8
8
  * Clack {@link Task} that runs a subprocess; piped stdout/stderr lines are sent
9
- * through the task `message` callback (not `log.message`). Lines are also kept in
10
- * an interleaved buffer; on failure they are written under {@link debugLogDir} or
11
- * {@link SpawnOptions.cwd} or the current working directory.
9
+ * through the task `message` callback (not `log.message`). Lines are also kept
10
+ * in an interleaved buffer; on failure they are written under
11
+ * {@link debugLogDir} or {@link SpawnOptions.cwd} or the current working
12
+ * directory.
12
13
  */
13
14
  export function promisifiedSpawnTask({ title, command, args, options = {}, debugLogDir, }) {
14
15
  return {
@@ -16,7 +17,16 @@ export function promisifiedSpawnTask({ title, command, args, options = {}, debug
16
17
  task: (message) => runPromisifiedSpawn({
17
18
  command,
18
19
  args,
19
- options,
20
+ options: {
21
+ // On Windows, Volta-managed package managers are spawned via .cmd
22
+ // shims which require shell interpretation. Although I've been having
23
+ // luck with `shell: false` with Volta-managed package managers on
24
+ // macOS, I'd rather just go with one consistent approach across all
25
+ // platforms, and `shell: true` will tend to reduce surprises.
26
+ // https://github.com/shirakaba/expo-desktop/issues/4
27
+ shell: true,
28
+ ...options,
29
+ },
20
30
  logLine: message,
21
31
  ...(debugLogDir !== undefined ? { debugLogDir } : {}),
22
32
  }),
@@ -129,7 +139,7 @@ function envWithForcedColorIfPiped(options) {
129
139
  const stdoutMode = Array.isArray(stdio) ? stdio.at(1) : stdio;
130
140
  const stderrMode = Array.isArray(stdio) ? stdio.at(2) : stdio;
131
141
  const capturesOutput = stdoutMode !== "inherit" || stderrMode !== "inherit";
132
- const base = { ...env, ...options?.env };
142
+ const base = { ...process.env, ...options?.env };
133
143
  if (!capturesOutput || base.NO_COLOR !== undefined) {
134
144
  return base;
135
145
  }
@@ -87,11 +87,20 @@ export async function createExpoDesktopApp({ localDev, name, packageManager, tem
87
87
  title("Installing Cocoapods for the macOS app…", { spacing: 1 });
88
88
  await podInstall({ projectPath, type: "macos" });
89
89
  }
90
+ // react-native config seems to return `windows: null` on non-Windows
91
+ // platforms (while returning a populated object for Windows). If you force it
92
+ // to non-null for development on macOS, macOS runs the autolinking command
93
+ // successfully but writes out erroneous output, so
94
+ // there's no point.
95
+ if (platform === "win32") {
96
+ title("Autolinking the Windows app…", { spacing: 1 });
97
+ await autolinkWindows({ projectPath });
98
+ }
90
99
  title("Adding Expo support to the Metro config…", { spacing: 1 });
91
100
  await improveMetroConfig({ projectPath });
101
+ await addWindowsExpoPolyfill({ projectPath });
92
102
  title("Adding Expo support to the Babel config…", { spacing: 1 });
93
103
  await writeBabelConfig({ projectPath });
94
- // TODO: Set up Windows app.cpp entrypoint
95
104
  }
96
105
  async function createExpoApp({ localDev, name, packageManager, versions, }) {
97
106
  // `create-expo-app` aggravatingly reconfigures your workspace to use
@@ -249,19 +258,24 @@ async function updatePackageJson({ localDev, name, projectPath, task, versions,
249
258
  }
250
259
  packageJson.scripts.macos = "rnc-cli run-macos";
251
260
  packageJson.scripts.windows = "rnc-cli run-windows";
261
+ packageJson.scripts["autolink-windows"] = "react-native autolink-windows";
252
262
  }
253
263
  else {
254
264
  if (!packageJson.dependencies) {
255
265
  packageJson.dependencies = {};
256
266
  }
257
- // expo-desktop-prebuild-config itself depends on
258
- // expo-desktop-config-plugins.
259
- if (localDev) {
260
- packageJson.dependencies["expo-desktop-prebuild-config"] =
261
- "file:../../expo-desktop-prebuild-config";
262
- }
263
- else {
264
- packageJson.dependencies["expo-desktop-prebuild-config"] = "^1.0.0";
267
+ const monorepoDeps = {
268
+ // expo-desktop-prebuild-config itself depends on
269
+ // expo-desktop-config-plugins.
270
+ ["expo-desktop-prebuild-config"]: "^1.0.0",
271
+ ["expo-desktop-modules-core"]: `^${versions.expoMajor}.0.0`,
272
+ ["expo-desktop-stubs"]: `^${versions.expoMajor}.0.0`,
273
+ };
274
+ for (const [key, value] of Object.entries(monorepoDeps)) {
275
+ // TODO: Try replacing this `localDev` logic with `linkWorkspacePackages`:
276
+ // - https://pnpm.io/workspaces#linkworkspacepackages
277
+ // - https://pnpm.io/workspaces#workspace-protocol-workspace
278
+ packageJson.dependencies[key] = localDev ? `file:../../${key}` : value;
265
279
  }
266
280
  packageJson.dependencies["react-native-macos"] = versions.macos;
267
281
  packageJson.dependencies["react-native-windows"] = versions.windows;
@@ -428,6 +442,27 @@ async function podInstall({ projectPath, type }) {
428
442
  }
429
443
  console.log(`\n${green("◆")} Installed Cocoapods for the ${type === "ios" ? "iOS" : "macOS"} app.\n`);
430
444
  }
445
+ async function autolinkWindows({ projectPath }) {
446
+ const command = "node";
447
+ const args = ["--run", "autolink-windows"];
448
+ const printedCommand = `${command} ${args.join(" ")}`;
449
+ console.log(`${cyan("◆")} Running: ${yellow(printedCommand)}\n`);
450
+ try {
451
+ await tasks([
452
+ promisifiedSpawnTask({
453
+ title: "react-native autolink-windows",
454
+ command,
455
+ args,
456
+ options: { cwd: projectPath, stdio: "inherit" },
457
+ }),
458
+ ]);
459
+ }
460
+ catch (error) {
461
+ log.error(`Error running ${yellow(printedCommand)}${error instanceof Error ? `: ${error.message}` : "."}`);
462
+ process.exit(1);
463
+ }
464
+ console.log(`\n${green("◆")} Autolinked the Windows app.\n`);
465
+ }
431
466
  async function improveMetroConfig({ projectPath }) {
432
467
  const metroConfigPath = path.resolve(projectPath, "metro.config.js");
433
468
  console.log(`${cyan("◆")} Overwriting metro.config.js…\n`);
@@ -437,6 +472,17 @@ const { getDefaultConfig } = require("@expo/metro-config");
437
472
  const { makeMetroConfig } = require("@rnx-kit/metro-config");
438
473
 
439
474
  const config = makeMetroConfig(getDefaultConfig(__dirname));
475
+
476
+ const getPolyfills = config.serializer.getPolyfills;
477
+ const windowsExpoPolyfill = require.resolve("./expo-polyfill.windows.js");
478
+ config.serializer.getPolyfills = (platform) => {
479
+ const polyfills = getPolyfills(platform);
480
+ if (platform === "windows") {
481
+ polyfills.push(windowsExpoPolyfill);
482
+ }
483
+ return polyfills;
484
+ };
485
+
440
486
  module.exports = config;
441
487
  `.trim() + "\n", "utf-8");
442
488
  }
@@ -446,6 +492,69 @@ module.exports = config;
446
492
  }
447
493
  console.log(`\n${green("◆")} Overwrote metro.config.js.\n`);
448
494
  }
495
+ /**
496
+ */
497
+ async function addWindowsExpoPolyfill({ projectPath }) {
498
+ await fs.writeFile(path.resolve(projectPath, "expo-polyfill.windows.js"), `
499
+ try {
500
+ // Until we can configure TurboModules for eager initialisation (which is
501
+ // waiting on https://github.com/microsoft/react-native-windows/pull/16093), we
502
+ // need to trigger the lazy-init of our TurboModule by accessing it for the
503
+ // first time, which causes the TurboModuleManager to call its REACT_INIT
504
+ // method.
505
+ //
506
+ // We can't do any imports inside getPolyfills(), but can use the global proxy:
507
+ globalThis.nativeModuleProxy.ExpoMainRuntimeInstaller;
508
+
509
+ // TODO: Implement Expo's NativeModulesProxy and make all of this
510
+ // lazy-initialised (and actually populate \`exportedMethods\`, etc.).
511
+
512
+ // Below are temporary stubs to suppress these warnings:
513
+ // WARN The "EXNativeModulesProxy" native module is not exported through NativeModules; verify that expo-modules-core's native code is linked properly
514
+ // WARN No native ExponentConstants module found, are you sure the expo-constants's module is linked properly?
515
+ // WARN No native ExponentConstants module found, are you sure the expo-constants's module is linked properly?
516
+
517
+ // Funnily enough, although expo-modules-core prefers to access
518
+ // global.expo?.modules?.NativeModulesProxy over the deprecated
519
+ // NativeModules?.NativeUnimoduleProxy, we still need the latter to exist in
520
+ // order for it to check the former. And it's only
521
+ globalThis.nativeModuleProxy.NativeUnimoduleProxy;
522
+ const { ExpoAsset } = globalThis.nativeModuleProxy;
523
+
524
+ const ExponentConstants = {};
525
+
526
+ globalThis.expo.modules = {
527
+ ExpoAsset,
528
+ ExponentConstants,
529
+ // - JS: apps/demo/node_modules/expo-modules-core/src/NativeModulesProxy.native.ts
530
+ // - iOS:
531
+ // - NativeUnimoduleProxy: apps/demo/node_modules/expo-modules-core/ios/Legacy/NativeModulesProxy/NativeModulesProxyModule.swift
532
+ // - NativeModulesProxy: apps/demo/node_modules/expo-modules-core/ios/Legacy/NativeModulesProxy/EXNativeModulesProxy.mm
533
+ // - Android:
534
+ // - NativeUnimoduleProxy: apps/demo/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
535
+ // - NativeModulesProxy: apps/demo/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/defaultmodules/NativeModulesProxyModule.kt
536
+ NativeModulesProxy: {
537
+ exportedMethods: {
538
+ ExpoAsset: [],
539
+ ExponentConstants: [],
540
+ },
541
+ modulesConstants: {
542
+ ExpoAsset: [],
543
+ ExponentConstants: [],
544
+ },
545
+ },
546
+ };
547
+
548
+ // Now expo-modules-core can populate its fileprivate
549
+ // \`const NativeModulesProxy: Record<string, ProxyNativeModule = {}\` from our
550
+ // global.expo?.modules?.NativeModulesProxy. This avoids the warning about
551
+ // EXNativeModulesProxy being missing.
552
+ } catch (error) {
553
+ console.error("Polyfill failed", error);
554
+ throw error;
555
+ }
556
+ `.trim() + "\n", "utf-8");
557
+ }
449
558
  async function updatePodfile({ projectPath }) {
450
559
  const appJsonPath = path.resolve(projectPath, "macos/Podfile");
451
560
  let contents;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-desktop",
3
- "version": "0.1.19",
3
+ "version": "0.1.23",
4
4
  "description": "Best-effort desktop support for Expo",
5
5
  "keywords": [
6
6
  "android",
@@ -37,12 +37,12 @@
37
37
  "@clack/prompts": "^1.2.0",
38
38
  "arktype": "^2.2.0",
39
39
  "citty": "^0.2.2",
40
+ "expo-desktop-config-plugins": "^1.1.22",
41
+ "expo-desktop-prebuild-config": "^1.0.11",
40
42
  "glob": "^10.5.0",
41
43
  "kleur": "^4.1.5",
42
44
  "mustache": "^4.2.0",
43
- "toml": "^4.1.1",
44
- "expo-desktop-config-plugins": "1.1.19",
45
- "expo-desktop-prebuild-config": "1.0.8"
45
+ "toml": "^4.1.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@expo/config": "^12.0.13",