expo-desktop 0.1.34 → 0.1.36

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.
@@ -26,6 +26,7 @@ export const AppJson = type({
26
26
  export const PackageJson = type({
27
27
  "name?": "string",
28
28
  "scripts?": "Record<string, string>",
29
+ "expo?": "Record<string, unknown.any>",
29
30
  "dependencies?": "Record<string, string>",
30
31
  "devDependencies?": "Record<string, string>",
31
32
  "peerDependencies?": "Record<string, string>",
@@ -1,34 +1,18 @@
1
1
  import { createRequire } from "node:module";
2
- import * as path from "node:path";
3
2
  import { withInternal } from "./with-internal.js";
4
3
  const require = createRequire(import.meta.url);
5
4
  const { getPrebuildConfigAsync } = require("expo-desktop-prebuild-config");
6
5
  const { compileModsAsync } = require("expo-desktop-config-plugins");
6
+ // These are subdependencies of expo-desktop-prebuild-config.
7
+ const { getConfig } = require("@expo/config");
8
+ const { withPlugins } = require("@expo/config-plugins");
7
9
  /**
8
10
  * Applies config plugins.
9
11
  * @see https://github.com/microsoft/react-native-test-app/blob/trunk/packages/app/scripts/config-plugins/apply.mjs
10
12
  */
11
13
  export async function applyConfigPlugins(options) {
12
14
  const { projectRoot } = options;
13
- // To avoid making expo-desktop depend on Expo SDK 54 when we might be running
14
- // on an Expo 55 project, we import Expo deps from the project itself.
15
- let expoConfigModule;
16
- try {
17
- expoConfigModule = require(path.dirname(require.resolve("@expo/config/package.json", { paths: [projectRoot] })));
18
- }
19
- catch (cause) {
20
- throw new Error(`Error importing "@expo/config" relative to projectRoot "${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".`, { cause });
21
- }
22
- const { getConfig } = expoConfigModule;
23
- let expoConfigPluginsModule;
24
- try {
25
- expoConfigPluginsModule = require(path.dirname(require.resolve("@expo/config-plugins/package.json", { paths: [projectRoot] })));
26
- }
27
- catch (cause) {
28
- throw new Error(`Error importing "@expo/config-plugins" relative to projectRoot "${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".`, { cause });
29
- }
30
- const { withPlugins } = expoConfigPluginsModule;
31
- // (1) Filter out platforms that aren't in the app.json.
15
+ // Filter out platforms that aren't in the app.json.
32
16
  // https://github.com/expo/expo/blob/8dd645080f52927e2a8bf406167da7241a1d46d8/packages/%40expo/cli/src/prebuild/prebuildAsync.ts#L74
33
17
  let { exp: expoConfig } = getConfig(projectRoot);
34
18
  const { platforms, plugins } = expoConfig;
@@ -0,0 +1,43 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ /** True when the project root has its own `.git` (from create-expo-app's init). */
5
+ export async function hasProjectGitRepositoryAsync(projectPath) {
6
+ try {
7
+ await fs.lstat(path.join(projectPath, ".git"));
8
+ return true;
9
+ }
10
+ catch (error) {
11
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
12
+ return false;
13
+ }
14
+ throw error;
15
+ }
16
+ }
17
+ /** `git diff --cached --quiet` exits 1 when there are staged changes. */
18
+ export async function hasGitStagedChangesAsync(projectPath) {
19
+ try {
20
+ await runGitAsync(projectPath, ["diff", "--cached", "--quiet"]);
21
+ return false;
22
+ }
23
+ catch {
24
+ return true;
25
+ }
26
+ }
27
+ function runGitAsync(projectPath, args) {
28
+ return new Promise((resolve, reject) => {
29
+ const cp = spawn(`git ${args.join(" ")}`, {
30
+ cwd: projectPath,
31
+ shell: true,
32
+ stdio: "ignore",
33
+ });
34
+ cp.on("error", reject);
35
+ cp.on("close", (code) => {
36
+ if (code === 0) {
37
+ resolve();
38
+ return;
39
+ }
40
+ reject(new Error(`git ${args.join(" ")} exited with code ${code ?? "null"}`));
41
+ });
42
+ });
43
+ }
@@ -11,6 +11,7 @@ import { applyConfigPlugins } from "../common/apply-config-plugins.js";
11
11
  import { makePrettySummary } from "../common/arktype.js";
12
12
  import { promisifiedSpawnTask, SPAWN_DEBUG_LOG_GLOB } from "../common/child-process.js";
13
13
  import { title } from "../common/clack.js";
14
+ import { hasGitStagedChangesAsync, hasProjectGitRepositoryAsync } from "../common/git.js";
14
15
  import { packageManagerExec } from "../common/npm.js";
15
16
  import { preserveFile } from "../common/preserve-file.js";
16
17
  import { applySelectedTemplatesAsync } from "../common/template.js";
@@ -103,6 +104,10 @@ export async function createExpoDesktopApp({ localDev, name, packageManager, tem
103
104
  await improveMetroConfig({ projectPath });
104
105
  title("Adding Expo support to the Babel config…", { spacing: 1 });
105
106
  await writeBabelConfig({ projectPath });
107
+ title("Improving App.tsx…", { spacing: 1 });
108
+ await improveAppTsx({ projectPath });
109
+ title("Committing changes…", { spacing: 1 });
110
+ await commitChanges({ projectPath });
106
111
  logProjectReady({ cdPath: name.filesafeName, packageManager });
107
112
  }
108
113
  async function getBundleEntryFileCandidates({ projectPath }) {
@@ -363,6 +368,24 @@ async function updatePackageJson({ localDev, name, projectPath, task, versions,
363
368
  packageJson.overrides["react-native"] = packageJson.dependencies["react-native"];
364
369
  }
365
370
  }
371
+ if (!packageJson.expo) {
372
+ packageJson.expo = {};
373
+ }
374
+ if (!packageJson.expo.install) {
375
+ packageJson.expo.install = {};
376
+ }
377
+ if (!packageJson.expo.install.exclude) {
378
+ packageJson.expo.install.exclude = [];
379
+ }
380
+ // Stop `expo start` erroneously warning about our overrides.
381
+ // - https://github.com/shirakaba/expo-desktop/issues/15
382
+ // - https://docs.expo.dev/versions/latest/config/package-json/#installexclude
383
+ if (!packageJson.expo.install.exclude.includes("react")) {
384
+ packageJson.expo.install.exclude.push("react");
385
+ }
386
+ if (!packageJson.expo.install.exclude.includes("react-native")) {
387
+ packageJson.expo.install.exclude.push("react-native");
388
+ }
366
389
  }
367
390
  try {
368
391
  await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), "utf-8");
@@ -622,6 +645,93 @@ module.exports = function (api) {
622
645
  }
623
646
  console.log(`\n${green("◆")} Wrote babel.config.js.\n`);
624
647
  }
648
+ /**
649
+ * Improve the App.tsx so that it's not white-on-white on macOS in dark mode.
650
+ * Here, we simply ensure that it's black-on-white in both light and dark modes.
651
+ *
652
+ * @see https://github.com/shirakaba/expo-desktop/pull/14#issuecomment-4614018796
653
+ * @see https://github.com/expo/expo/blob/sdk-54/templates/expo-template-blank-typescript/App.tsx
654
+ */
655
+ async function improveAppTsx({ projectPath }) {
656
+ const metroConfigPath = path.resolve(projectPath, "App.tsx");
657
+ console.log(`${cyan("◆")} Overwriting App.tsx…\n`);
658
+ try {
659
+ await fs.writeFile(metroConfigPath, `
660
+ import { StatusBar } from 'expo-status-bar';
661
+ import { StyleSheet, Text, View } from 'react-native';
662
+
663
+ export default function App() {
664
+ return (
665
+ <View style={styles.container}>
666
+ <Text style={styles.text}>Open up App.tsx to start working on your app!</Text>
667
+ <StatusBar style="auto" />
668
+ </View>
669
+ );
670
+ }
671
+
672
+ const styles = StyleSheet.create({
673
+ container: {
674
+ flex: 1,
675
+ backgroundColor: '#fff',
676
+ alignItems: 'center',
677
+ justifyContent: 'center',
678
+ },
679
+ text: {
680
+ color: '#000',
681
+ },
682
+ });
683
+ `.trim() + "\n", "utf-8");
684
+ }
685
+ catch (error) {
686
+ log.error(`Error improving ${yellow("App.tsx")} file${error instanceof Error ? `: ${error.message}` : "."}`);
687
+ process.exit(1);
688
+ }
689
+ console.log(`\n${green("◆")} Overwrote App.tsx.\n`);
690
+ }
691
+ /**
692
+ * Stage and commit all changes.
693
+ *
694
+ * @see https://github.com/expo/expo/blob/main/packages/create-expo/src/utils/git.ts
695
+ */
696
+ async function commitChanges({ projectPath }) {
697
+ if (!(await hasProjectGitRepositoryAsync(projectPath))) {
698
+ console.warn(`\n${yellow("⚠")} Skipping git commit: create-expo-app did not initialize a Git repository in this project (this happens when creating inside an existing repo, e.g. in a monorepo).\n`);
699
+ return;
700
+ }
701
+ try {
702
+ await tasks([
703
+ promisifiedSpawnTask({
704
+ title: "git add",
705
+ command: "git",
706
+ args: ["add", "-A"],
707
+ options: { cwd: projectPath, stdio: "ignore" },
708
+ }),
709
+ ]);
710
+ }
711
+ catch (error) {
712
+ log.error(`Error running ${yellow("git add -A")}${error instanceof Error ? `: ${error.message}` : "."}`);
713
+ process.exit(1);
714
+ }
715
+ if (!(await hasGitStagedChangesAsync(projectPath))) {
716
+ console.log(`\n${green("◆")} Nothing to commit.\n`);
717
+ return;
718
+ }
719
+ try {
720
+ await tasks([
721
+ promisifiedSpawnTask({
722
+ title: "git commit",
723
+ command: "git",
724
+ args: ["commit", "-m", "Set up with expo-desktop"],
725
+ options: { cwd: projectPath, stdio: "ignore" },
726
+ }),
727
+ ]);
728
+ }
729
+ catch (error) {
730
+ log.error(`Error running ${yellow("git commit")}${error instanceof Error ? `: ${error.message}` : "."}`);
731
+ process.exit(1);
732
+ }
733
+ console.log(`\n${green("◆")} Committed changes.\n`);
734
+ }
625
735
  /**
626
736
  * For now, this is just a convenience script to allow me to run the config
627
737
  * plugins again after creating the app, to help with development. But it is
@@ -637,6 +747,10 @@ const require = createRequire(import.meta.url);
637
747
  const { getPrebuildConfigAsync } = require("expo-desktop-prebuild-config");
638
748
  const { compileModsAsync } = require("expo-desktop-config-plugins");
639
749
 
750
+ // These are subdependencies of expo-desktop-prebuild-config.
751
+ const { getConfig } = require("@expo/config");
752
+ const { withPlugins } = require("@expo/config-plugins");
753
+
640
754
  const projectRoot = import.meta.dirname;
641
755
 
642
756
  const info = {
@@ -679,33 +793,7 @@ const withInternal = (config, internals) => {
679
793
  async function applyConfigPlugins(options) {
680
794
  const { projectRoot } = options;
681
795
 
682
- // To avoid making expo-desktop depend on Expo SDK 54 when we might be running
683
- // on an Expo 55 project, we import Expo deps from the project itself.
684
- /** @type {typeof import("@expo/config")} */
685
- let expoConfigModule;
686
- try {
687
- expoConfigModule = require("@expo/config");
688
- } catch (cause) {
689
- throw new Error(
690
- \`Error importing "@expo/config" relative to projectRoot "\${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".\`,
691
- { cause },
692
- );
693
- }
694
- const { getConfig } = expoConfigModule;
695
-
696
- /** @type {typeof import("@expo/config-plugins")} */
697
- let expoConfigPluginsModule;
698
- try {
699
- expoConfigPluginsModule = require("@expo/config-plugins");
700
- } catch (cause) {
701
- throw new Error(
702
- \`Error importing "@expo/config-plugins" relative to projectRoot "\${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".\`,
703
- { cause },
704
- );
705
- }
706
- const { withPlugins } = expoConfigPluginsModule;
707
-
708
- // (1) Filter out platforms that aren't in the app.json.
796
+ // Filter out platforms that aren't in the app.json.
709
797
  // https://github.com/expo/expo/blob/8dd645080f52927e2a8bf406167da7241a1d46d8/packages/%40expo/cli/src/prebuild/prebuildAsync.ts#L74
710
798
  let { exp: expoConfig } = getConfig(projectRoot);
711
799
  const { platforms, plugins } = expoConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-desktop",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "Best-effort desktop support for Expo",
5
5
  "keywords": [
6
6
  "android",
@@ -37,8 +37,8 @@
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.31",
41
- "expo-desktop-prebuild-config": "^1.0.17",
40
+ "expo-desktop-config-plugins": "^1.1.33",
41
+ "expo-desktop-prebuild-config": "^1.0.19",
42
42
  "glob": "^10.5.0",
43
43
  "kleur": "^4.1.5",
44
44
  "mustache": "^4.2.0",
@@ -46,18 +46,14 @@
46
46
  "toml": "^4.1.1"
47
47
  },
48
48
  "devDependencies": {
49
- "@expo/config": "^12.0.13",
50
- "@expo/config-plugins": "^54.0.4",
49
+ "@expo/config": "^56.0.9",
50
+ "@expo/config-plugins": "^56.0.8",
51
51
  "@tsconfig/node24": "^24.0.4",
52
52
  "@types/mustache": "^4.2.6",
53
53
  "@types/node": "^24.12.2",
54
54
  "@typescript/native-preview": "^7.0.0-dev.20260425.1",
55
55
  "vitest": "^3.2.4"
56
56
  },
57
- "peerDependencies": {
58
- "@expo/config": ">=12.0.0",
59
- "@expo/config-plugins": ">=54.0.0"
60
- },
61
57
  "lint-staged": {
62
58
  "scripts/update-schema.ts": [
63
59
  "node --run schema",