expo-desktop 0.1.18 → 0.1.20
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/build/cli.js
CHANGED
|
@@ -23,6 +23,11 @@ const main = defineCommand({
|
|
|
23
23
|
description: `The ${kleur.bold("display name")} for the app ${grey("(Examples: 'My App 123', '俺のアプリ')")}`,
|
|
24
24
|
valueHint: "name",
|
|
25
25
|
},
|
|
26
|
+
"local-dev": {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "An undocumented switch for use during development to skip the questionnaire.",
|
|
29
|
+
hidden: true,
|
|
30
|
+
},
|
|
26
31
|
rdns: {
|
|
27
32
|
type: "string",
|
|
28
33
|
description: `The ${kleur.bold("reverse DNS")} for the app ${grey("(Example: 'com.example.my-app-123')")}`,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* Read the state of a file on first call of the generator, then run the
|
|
4
|
+
* generator again to restore it back to that value that was read.
|
|
5
|
+
*/
|
|
6
|
+
export async function* preserveFile({ filePath, enable, }) {
|
|
7
|
+
if (!enable) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const fileBefore = await fs.readFile(filePath, "utf-8");
|
|
11
|
+
yield;
|
|
12
|
+
await fs.writeFile(filePath, fileBefore, "utf-8");
|
|
13
|
+
}
|
|
@@ -3,20 +3,21 @@ import { default as kleur } from "kleur";
|
|
|
3
3
|
import { green, grey } from "kleur/colors";
|
|
4
4
|
import { platform } from "node:process";
|
|
5
5
|
import { title } from "../common/clack.js";
|
|
6
|
-
import { createExpoDesktopApp
|
|
6
|
+
import { createExpoDesktopApp } from "./create-expo-desktop-app.js";
|
|
7
7
|
import { previewFileTree } from "./preview-file-tree.js";
|
|
8
8
|
import { promptForVersion } from "./prompt-for-version.js";
|
|
9
9
|
export async function newExpoDesktopProject(args) {
|
|
10
|
-
// A
|
|
11
|
-
const
|
|
12
|
-
if (
|
|
10
|
+
// A switch for skipping the questions
|
|
11
|
+
const localDev = args["local-dev"];
|
|
12
|
+
if (localDev) {
|
|
13
13
|
await createExpoDesktopApp({
|
|
14
|
+
localDev,
|
|
14
15
|
name: {
|
|
15
16
|
displayName: "Your App Display Name",
|
|
16
17
|
filesafeName: "YourApp456",
|
|
17
18
|
rdns: "uk.co.birchlabs.your-app-456",
|
|
18
19
|
},
|
|
19
|
-
packageManager: "
|
|
20
|
+
packageManager: "pnpm",
|
|
20
21
|
templates: {
|
|
21
22
|
template: args.template,
|
|
22
23
|
"template-ios": args["template-ios"],
|
|
@@ -57,6 +58,7 @@ export async function newExpoDesktopProject(args) {
|
|
|
57
58
|
process.exit(0);
|
|
58
59
|
}
|
|
59
60
|
await createExpoDesktopApp({
|
|
61
|
+
localDev,
|
|
60
62
|
name,
|
|
61
63
|
packageManager,
|
|
62
64
|
versions,
|
|
@@ -11,18 +11,10 @@ import { makePrettySummary } from "../common/arktype.js";
|
|
|
11
11
|
import { promisifiedSpawnTask, SPAWN_DEBUG_LOG_GLOB } from "../common/child-process.js";
|
|
12
12
|
import { title } from "../common/clack.js";
|
|
13
13
|
import { packageManagerExec } from "../common/npm.js";
|
|
14
|
+
import { preserveFile } from "../common/preserve-file.js";
|
|
14
15
|
import { applySelectedTemplatesAsync } from "../common/template.js";
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
18
|
-
* - Skips the questionnaire at the start.
|
|
19
|
-
* - Installs the local copy of expo-desktop-config-plugins rather than pinning
|
|
20
|
-
* to a published release.
|
|
21
|
-
* - Adds the apply-config-plugins.mjs script.
|
|
22
|
-
*/
|
|
23
|
-
export const localDev = false;
|
|
24
|
-
export async function createExpoDesktopApp({ name, packageManager, templates, versions, }) {
|
|
25
|
-
const { projectPath } = await createExpoApp({ name, packageManager, versions });
|
|
16
|
+
export async function createExpoDesktopApp({ localDev, name, packageManager, templates, versions, }) {
|
|
17
|
+
const { projectPath } = await createExpoApp({ localDev, name, packageManager, versions });
|
|
26
18
|
await appendRootGitignoreSpawnDebugLogs(projectPath);
|
|
27
19
|
const templateSelection = {
|
|
28
20
|
// https://github.com/expo/expo/blob/sdk-54/templates/expo-template-blank-typescript
|
|
@@ -54,13 +46,14 @@ export async function createExpoDesktopApp({ name, packageManager, templates, ve
|
|
|
54
46
|
await updateAppJson({ name, projectPath });
|
|
55
47
|
title("Altering package.json…", { spacing: 1 });
|
|
56
48
|
const { name: packageJsonName } = await updatePackageJson({
|
|
49
|
+
localDev,
|
|
57
50
|
name,
|
|
58
51
|
projectPath,
|
|
59
52
|
versions,
|
|
60
53
|
task: { type: "create" },
|
|
61
54
|
});
|
|
62
55
|
title("Installing dependencies…", { spacing: 1 });
|
|
63
|
-
await npmInstall({ cwd: projectPath, packageManager });
|
|
56
|
+
await npmInstall({ asNewWorkspace: true, cwd: projectPath, packageManager });
|
|
64
57
|
await updatePackageJson({
|
|
65
58
|
name,
|
|
66
59
|
projectPath,
|
|
@@ -94,13 +87,37 @@ export async function createExpoDesktopApp({ name, packageManager, templates, ve
|
|
|
94
87
|
title("Installing Cocoapods for the macOS app…", { spacing: 1 });
|
|
95
88
|
await podInstall({ projectPath, type: "macos" });
|
|
96
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
|
+
}
|
|
97
99
|
title("Adding Expo support to the Metro config…", { spacing: 1 });
|
|
98
100
|
await improveMetroConfig({ projectPath });
|
|
101
|
+
await addWindowsExpoPolyfill({ projectPath });
|
|
99
102
|
title("Adding Expo support to the Babel config…", { spacing: 1 });
|
|
100
103
|
await writeBabelConfig({ projectPath });
|
|
101
|
-
// TODO: Set up Windows app.cpp entrypoint
|
|
102
104
|
}
|
|
103
|
-
async function createExpoApp({ name, packageManager, versions, }) {
|
|
105
|
+
async function createExpoApp({ localDev, name, packageManager, versions, }) {
|
|
106
|
+
// `create-expo-app` aggravatingly reconfigures your workspace to use
|
|
107
|
+
// `nodeLinker: hoisted`, which sucks when creating sample projects inside
|
|
108
|
+
// this monorepo during local dev (even with `--no-install`). So we fight
|
|
109
|
+
// back.
|
|
110
|
+
//
|
|
111
|
+
// For non-local dev, it sounds like we can use `nodeLinker: isolated` as of
|
|
112
|
+
// Expo SDK 54, so I'm tempted to enforce that in created templates, too. But
|
|
113
|
+
// one thing at a time.
|
|
114
|
+
// - https://docs.expo.dev/more/create-expo/#pnpm
|
|
115
|
+
// - https://github.com/expo/expo/blob/222b3b12610d69784bab6c5a188a46ea388f866a/packages/create-expo/src/resolvePackageManager.ts#L109
|
|
116
|
+
const gen = preserveFile({
|
|
117
|
+
enable: localDev,
|
|
118
|
+
filePath: localDev ? path.resolve(import.meta.dirname, "../../../../pnpm-workspace.yaml") : "",
|
|
119
|
+
});
|
|
120
|
+
await gen.next();
|
|
104
121
|
// `npm create` drops flags meant for create-expo-app unless you add `--`; use
|
|
105
122
|
// `npx --yes` instead to forward args correctly and skip prompts.
|
|
106
123
|
const command = packageManager === "npm" ? "npx" : packageManager;
|
|
@@ -133,6 +150,9 @@ async function createExpoApp({ name, packageManager, versions, }) {
|
|
|
133
150
|
log.error(`Error running ${yellow("create expo-app")}${error instanceof Error ? `: ${error.message}` : "."}`);
|
|
134
151
|
process.exit(1);
|
|
135
152
|
}
|
|
153
|
+
finally {
|
|
154
|
+
await gen.next();
|
|
155
|
+
}
|
|
136
156
|
return { projectPath };
|
|
137
157
|
}
|
|
138
158
|
async function appendRootGitignoreSpawnDebugLogs(projectPath) {
|
|
@@ -209,7 +229,7 @@ async function updateAppJson({ name, projectPath, }) {
|
|
|
209
229
|
}
|
|
210
230
|
console.log(`${green("◆")} Altered app.json.\n`);
|
|
211
231
|
}
|
|
212
|
-
async function updatePackageJson({ name, projectPath, task, versions, }) {
|
|
232
|
+
async function updatePackageJson({ localDev, name, projectPath, task, versions, }) {
|
|
213
233
|
const packageJsonPath = path.resolve(projectPath, "package.json");
|
|
214
234
|
let packageJson;
|
|
215
235
|
try {
|
|
@@ -238,19 +258,21 @@ async function updatePackageJson({ name, projectPath, task, versions, }) {
|
|
|
238
258
|
}
|
|
239
259
|
packageJson.scripts.macos = "rnc-cli run-macos";
|
|
240
260
|
packageJson.scripts.windows = "rnc-cli run-windows";
|
|
261
|
+
packageJson.scripts["autolink-windows"] = "react-native autolink-windows";
|
|
241
262
|
}
|
|
242
263
|
else {
|
|
243
264
|
if (!packageJson.dependencies) {
|
|
244
265
|
packageJson.dependencies = {};
|
|
245
266
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
packageJson.dependencies[key] = localDev ? `file:../../${key}` : value;
|
|
254
276
|
}
|
|
255
277
|
packageJson.dependencies["react-native-macos"] = versions.macos;
|
|
256
278
|
packageJson.dependencies["react-native-windows"] = versions.windows;
|
|
@@ -281,10 +303,41 @@ async function updatePackageJson({ name, projectPath, task, versions, }) {
|
|
|
281
303
|
console.log(`${green("◆")} Altered package.json.\n`);
|
|
282
304
|
return { name: nameBefore };
|
|
283
305
|
}
|
|
284
|
-
async function npmInstall({ cwd, packageManager, }) {
|
|
306
|
+
async function npmInstall({ asNewWorkspace, cwd, packageManager, }) {
|
|
285
307
|
const command = packageManager;
|
|
286
308
|
const args = ["install"];
|
|
287
309
|
console.log(`${cyan("◆")} Running: ${yellow(`${command} ${args.join(" ")}`)}\n`);
|
|
310
|
+
// Unlike npm and bun, pnpm climbs up to install dependencies in the closest
|
|
311
|
+
// ancestor directory if there is one. This is particularly inconvenient
|
|
312
|
+
// during local dev when we're creating samples inside the monorepo.
|
|
313
|
+
if (asNewWorkspace && packageManager === "pnpm") {
|
|
314
|
+
// (1) Ensure a file name pnpm-workspace.yaml exists.
|
|
315
|
+
//
|
|
316
|
+
// (2) Also ensure that it uses nodeLinker: hoisted, as otherwise
|
|
317
|
+
// `:path => "#{config[:reactNativePath]}-macos"` predicts that there will
|
|
318
|
+
// be a react-native-macos directory right beside the react-native
|
|
319
|
+
// directory by just optimistically appending "-macos" on the end, like so:
|
|
320
|
+
// "../node_modules/.pnpm/react-native@0.81.5_@babel+core@7.29.0_@react-native-community+cli@20.1.3_typescript@5._941d99d35895d1a3626e14fd9f3b3666/node_modules/react-native" + "-macos"
|
|
321
|
+
//
|
|
322
|
+
// As this is not true with pnpm's default `nodeLinker: isolated`, we
|
|
323
|
+
// need to stick to `nodeLinker: hoisted` until we can rewrite the
|
|
324
|
+
// Podfile script to resolve it properly.
|
|
325
|
+
//
|
|
326
|
+
// This is consistent with what the Expo team do for pnpm and yarn:
|
|
327
|
+
// - https://docs.expo.dev/more/create-expo/#pnpm
|
|
328
|
+
// - https://github.com/expo/expo/blob/222b3b12610d69784bab6c5a188a46ea388f866a/packages/create-expo/src/resolvePackageManager.ts#L109
|
|
329
|
+
try {
|
|
330
|
+
await fs.writeFile(path.resolve(cwd, "pnpm-workspace.yaml"), "nodeLinker: hoisted\n", {
|
|
331
|
+
flag: "wx",
|
|
332
|
+
encoding: "utf-8",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
if (!(error instanceof Error) || !("code" in error) || error.code !== "EEXIST") {
|
|
337
|
+
throw error;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
288
341
|
try {
|
|
289
342
|
await tasks([
|
|
290
343
|
promisifiedSpawnTask({
|
|
@@ -386,6 +439,27 @@ async function podInstall({ projectPath, type }) {
|
|
|
386
439
|
}
|
|
387
440
|
console.log(`\n${green("◆")} Installed Cocoapods for the ${type === "ios" ? "iOS" : "macOS"} app.\n`);
|
|
388
441
|
}
|
|
442
|
+
async function autolinkWindows({ projectPath }) {
|
|
443
|
+
const command = "node";
|
|
444
|
+
const args = ["--run", "autolink-windows"];
|
|
445
|
+
const printedCommand = `${command} ${args.join(" ")}`;
|
|
446
|
+
console.log(`${cyan("◆")} Running: ${yellow(printedCommand)}\n`);
|
|
447
|
+
try {
|
|
448
|
+
await tasks([
|
|
449
|
+
promisifiedSpawnTask({
|
|
450
|
+
title: "react-native autolink-windows",
|
|
451
|
+
command,
|
|
452
|
+
args,
|
|
453
|
+
options: { cwd: projectPath, stdio: "inherit" },
|
|
454
|
+
}),
|
|
455
|
+
]);
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
log.error(`Error running ${yellow(printedCommand)}${error instanceof Error ? `: ${error.message}` : "."}`);
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
console.log(`\n${green("◆")} Autolinked the Windows app.\n`);
|
|
462
|
+
}
|
|
389
463
|
async function improveMetroConfig({ projectPath }) {
|
|
390
464
|
const metroConfigPath = path.resolve(projectPath, "metro.config.js");
|
|
391
465
|
console.log(`${cyan("◆")} Overwriting metro.config.js…\n`);
|
|
@@ -395,6 +469,17 @@ const { getDefaultConfig } = require("@expo/metro-config");
|
|
|
395
469
|
const { makeMetroConfig } = require("@rnx-kit/metro-config");
|
|
396
470
|
|
|
397
471
|
const config = makeMetroConfig(getDefaultConfig(__dirname));
|
|
472
|
+
|
|
473
|
+
const getPolyfills = config.serializer.getPolyfills;
|
|
474
|
+
const windowsExpoPolyfill = require.resolve("./expo-polyfill.windows.js");
|
|
475
|
+
config.serializer.getPolyfills = (platform) => {
|
|
476
|
+
const polyfills = getPolyfills(platform);
|
|
477
|
+
if (platform === "windows") {
|
|
478
|
+
polyfills.push(windowsExpoPolyfill);
|
|
479
|
+
}
|
|
480
|
+
return polyfills;
|
|
481
|
+
};
|
|
482
|
+
|
|
398
483
|
module.exports = config;
|
|
399
484
|
`.trim() + "\n", "utf-8");
|
|
400
485
|
}
|
|
@@ -404,6 +489,69 @@ module.exports = config;
|
|
|
404
489
|
}
|
|
405
490
|
console.log(`\n${green("◆")} Overwrote metro.config.js.\n`);
|
|
406
491
|
}
|
|
492
|
+
/**
|
|
493
|
+
*/
|
|
494
|
+
async function addWindowsExpoPolyfill({ projectPath }) {
|
|
495
|
+
await fs.writeFile(path.resolve(projectPath, "expo-polyfill.windows.js"), `
|
|
496
|
+
try {
|
|
497
|
+
// Until we can configure TurboModules for eager initialisation (which is
|
|
498
|
+
// waiting on https://github.com/microsoft/react-native-windows/pull/16093), we
|
|
499
|
+
// need to trigger the lazy-init of our TurboModule by accessing it for the
|
|
500
|
+
// first time, which causes the TurboModuleManager to call its REACT_INIT
|
|
501
|
+
// method.
|
|
502
|
+
//
|
|
503
|
+
// We can't do any imports inside getPolyfills(), but can use the global proxy:
|
|
504
|
+
globalThis.nativeModuleProxy.ExpoMainRuntimeInstaller;
|
|
505
|
+
|
|
506
|
+
// TODO: Implement Expo's NativeModulesProxy and make all of this
|
|
507
|
+
// lazy-initialised (and actually populate \`exportedMethods\`, etc.).
|
|
508
|
+
|
|
509
|
+
// Below are temporary stubs to suppress these warnings:
|
|
510
|
+
// WARN The "EXNativeModulesProxy" native module is not exported through NativeModules; verify that expo-modules-core's native code is linked properly
|
|
511
|
+
// WARN No native ExponentConstants module found, are you sure the expo-constants's module is linked properly?
|
|
512
|
+
// WARN No native ExponentConstants module found, are you sure the expo-constants's module is linked properly?
|
|
513
|
+
|
|
514
|
+
// Funnily enough, although expo-modules-core prefers to access
|
|
515
|
+
// global.expo?.modules?.NativeModulesProxy over the deprecated
|
|
516
|
+
// NativeModules?.NativeUnimoduleProxy, we still need the latter to exist in
|
|
517
|
+
// order for it to check the former. And it's only
|
|
518
|
+
globalThis.nativeModuleProxy.NativeUnimoduleProxy;
|
|
519
|
+
const { ExpoAsset } = globalThis.nativeModuleProxy;
|
|
520
|
+
|
|
521
|
+
const ExponentConstants = {};
|
|
522
|
+
|
|
523
|
+
globalThis.expo.modules = {
|
|
524
|
+
ExpoAsset,
|
|
525
|
+
ExponentConstants,
|
|
526
|
+
// - JS: apps/demo/node_modules/expo-modules-core/src/NativeModulesProxy.native.ts
|
|
527
|
+
// - iOS:
|
|
528
|
+
// - NativeUnimoduleProxy: apps/demo/node_modules/expo-modules-core/ios/Legacy/NativeModulesProxy/NativeModulesProxyModule.swift
|
|
529
|
+
// - NativeModulesProxy: apps/demo/node_modules/expo-modules-core/ios/Legacy/NativeModulesProxy/EXNativeModulesProxy.mm
|
|
530
|
+
// - Android:
|
|
531
|
+
// - NativeUnimoduleProxy: apps/demo/node_modules/expo-modules-core/android/src/main/java/expo/modules/adapters/react/NativeModulesProxy.java
|
|
532
|
+
// - NativeModulesProxy: apps/demo/node_modules/expo-modules-core/android/src/main/java/expo/modules/kotlin/defaultmodules/NativeModulesProxyModule.kt
|
|
533
|
+
NativeModulesProxy: {
|
|
534
|
+
exportedMethods: {
|
|
535
|
+
ExpoAsset: [],
|
|
536
|
+
ExponentConstants: [],
|
|
537
|
+
},
|
|
538
|
+
modulesConstants: {
|
|
539
|
+
ExpoAsset: [],
|
|
540
|
+
ExponentConstants: [],
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
// Now expo-modules-core can populate its fileprivate
|
|
546
|
+
// \`const NativeModulesProxy: Record<string, ProxyNativeModule = {}\` from our
|
|
547
|
+
// global.expo?.modules?.NativeModulesProxy. This avoids the warning about
|
|
548
|
+
// EXNativeModulesProxy being missing.
|
|
549
|
+
} catch (error) {
|
|
550
|
+
console.error("Polyfill failed", error);
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
553
|
+
`.trim() + "\n", "utf-8");
|
|
554
|
+
}
|
|
407
555
|
async function updatePodfile({ projectPath }) {
|
|
408
556
|
const appJsonPath = path.resolve(projectPath, "macos/Podfile");
|
|
409
557
|
let contents;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-desktop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"description": "Best-effort desktop support for Expo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"kleur": "^4.1.5",
|
|
42
42
|
"mustache": "^4.2.0",
|
|
43
43
|
"toml": "^4.1.1",
|
|
44
|
-
"expo-desktop-config-plugins": "1.1.
|
|
45
|
-
"expo-desktop-prebuild-config": "1.0.
|
|
44
|
+
"expo-desktop-config-plugins": "1.1.20",
|
|
45
|
+
"expo-desktop-prebuild-config": "1.0.9"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@expo/config": "^12.0.13",
|