expo-desktop 0.1.37 → 1.0.0-beta.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.
- package/build/cli.js +42 -46
- package/build/common/expo/cmd.js +2 -0
- package/build/common/expo/cocoapods.js +159 -0
- package/build/common/expo/create-async-utils.js +108 -0
- package/build/common/expo/create-file-transform.js +70 -0
- package/build/common/expo/dir.js +89 -0
- package/build/common/expo/env.js +30 -0
- package/build/common/expo/error.js +112 -0
- package/build/common/expo/generate-agent-files.js +55 -0
- package/build/common/expo/git-cli.js +55 -0
- package/build/common/expo/git.js +73 -0
- package/build/common/expo/github.js +72 -0
- package/build/common/expo/interactive.js +5 -0
- package/build/common/expo/is-module-symlinked.js +42 -0
- package/build/common/expo/legacy-templates.js +34 -0
- package/build/common/expo/log.js +48 -0
- package/build/common/expo/node-modules.js +14 -0
- package/build/common/expo/npm.js +218 -0
- package/build/common/expo/ora.js +84 -0
- package/build/common/expo/prompts-cli.js +103 -0
- package/build/common/expo/resolve-package-manager.js +128 -0
- package/build/common/expo/tar.js +119 -0
- package/build/common/expo/template.js +681 -0
- package/build/common/expo/url.js +56 -0
- package/build/common/node-env.js +38 -0
- package/build/common/npm.js +5 -20
- package/build/common/read-app-name-from-config.js +20 -0
- package/build/config/expo/config-async.js +96 -0
- package/build/create-app/command.js +25 -137
- package/build/create-app/create-expo-desktop-app.js +57 -840
- package/build/create-app/resolve-project-root.js +74 -0
- package/build/create-app/template-versions.js +72 -0
- package/build/prebuild/command.js +201 -80
- package/build/prebuild/create-temp-path.js +22 -0
- package/build/prebuild/ensure-config-async.js +237 -0
- package/build/prebuild/expo/clear-native-folder.js +154 -0
- package/build/prebuild/expo/configure-project-async.js +74 -0
- package/build/prebuild/expo/copy-template-files.js +57 -0
- package/build/prebuild/expo/inline-modules.js +63 -0
- package/build/prebuild/expo/merge-gitignore-paths.js +155 -0
- package/build/prebuild/expo/resolve-local-template.js +120 -0
- package/build/prebuild/expo/resolve-options.js +98 -0
- package/build/prebuild/expo/resolve-template.js +144 -0
- package/build/prebuild/expo/update-package-json.js +150 -0
- package/build/prebuild/expo/validate-template-platforms.js +16 -0
- package/build/prebuild/modify-config-async.js +29 -0
- package/build/prebuild/update-from-template-async.js +112 -0
- package/build/prebuild/update-package-json-async.js +166 -0
- package/build/prebuild/validate-application-id.js +31 -0
- package/build/prebuild/validate-url.js +17 -0
- package/package.json +22 -4
- package/build/common/apply-config-plugins.js +0 -32
- package/build/common/apply-windows-cpp-app-template.js +0 -367
- package/build/common/child-process.js +0 -152
- package/build/common/clack.js +0 -9
- package/build/common/git.js +0 -43
- package/build/common/preserve-file.js +0 -13
- package/build/common/shescape.js +0 -34
- package/build/common/template.js +0 -308
- package/build/create-app/preview-file-tree.js +0 -48
- package/build/prebuild/resolve-options.js +0 -18
package/build/cli.js
CHANGED
|
@@ -13,21 +13,34 @@ const main = defineCommand({
|
|
|
13
13
|
"create-app": defineCommand({
|
|
14
14
|
meta: { name: "create-app", description: "Create a new Expo Desktop project" },
|
|
15
15
|
args: {
|
|
16
|
-
"
|
|
17
|
-
type: "
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
"project-root": {
|
|
17
|
+
type: "positional",
|
|
18
|
+
required: false,
|
|
19
|
+
description: `The ${kleur.bold("project root")} for the app in alphanumeric format ${grey("(Example: 'MyApp123')")}`,
|
|
20
20
|
},
|
|
21
21
|
"display-name": {
|
|
22
22
|
type: "string",
|
|
23
23
|
description: `The ${kleur.bold("display name")} for the app ${grey("(Examples: 'My App 123', '俺のアプリ')")}`,
|
|
24
24
|
valueHint: "name",
|
|
25
25
|
},
|
|
26
|
+
yes: {
|
|
27
|
+
type: "boolean",
|
|
28
|
+
description: "Use the default options for creating a project",
|
|
29
|
+
alias: "y",
|
|
30
|
+
},
|
|
26
31
|
"local-dev": {
|
|
27
32
|
type: "boolean",
|
|
28
33
|
description: "An undocumented switch for use during development to skip the questionnaire.",
|
|
29
34
|
hidden: true,
|
|
30
35
|
},
|
|
36
|
+
"no-agents-md": {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Skip generating AGENTS.md, CLAUDE.md, and .claude/settings.json",
|
|
39
|
+
},
|
|
40
|
+
"no-install": {
|
|
41
|
+
type: "boolean",
|
|
42
|
+
description: "Skip installing npm packages or CocoaPods",
|
|
43
|
+
},
|
|
31
44
|
rdns: {
|
|
32
45
|
type: "string",
|
|
33
46
|
description: `The ${kleur.bold("reverse DNS")} for the app ${grey("(Example: 'com.example.my-app-123')")}`,
|
|
@@ -43,28 +56,10 @@ const main = defineCommand({
|
|
|
43
56
|
description: "Base template source (tarball, npm spec, or GitHub owner/repo#ref:subpath)",
|
|
44
57
|
valueHint: "template",
|
|
45
58
|
},
|
|
46
|
-
"template-ios": {
|
|
47
|
-
type: "string",
|
|
48
|
-
description: "iOS-specific template source",
|
|
49
|
-
valueHint: "template",
|
|
50
|
-
},
|
|
51
|
-
"template-android": {
|
|
52
|
-
type: "string",
|
|
53
|
-
description: "Android-specific template source",
|
|
54
|
-
valueHint: "template",
|
|
55
|
-
},
|
|
56
|
-
"template-macos": {
|
|
57
|
-
type: "string",
|
|
58
|
-
description: "macOS-specific template source",
|
|
59
|
-
valueHint: "template",
|
|
60
|
-
},
|
|
61
|
-
"template-windows": {
|
|
62
|
-
type: "string",
|
|
63
|
-
description: "Windows-specific template source",
|
|
64
|
-
valueHint: "template",
|
|
65
|
-
},
|
|
66
59
|
},
|
|
67
60
|
async run({ args }) {
|
|
61
|
+
parseNoArg(args, "no-agents-md");
|
|
62
|
+
parseNoArg(args, "no-install");
|
|
68
63
|
(await import("./create-app/command.js")).newExpoDesktopProject(args);
|
|
69
64
|
},
|
|
70
65
|
}),
|
|
@@ -100,37 +95,38 @@ const main = defineCommand({
|
|
|
100
95
|
description: "Project template to clone from. File path pointing to a local tar file, npm package or a github repo",
|
|
101
96
|
valueHint: "template",
|
|
102
97
|
},
|
|
103
|
-
"template-ios": {
|
|
104
|
-
type: "string",
|
|
105
|
-
description: "iOS-specific template source",
|
|
106
|
-
valueHint: "template",
|
|
107
|
-
},
|
|
108
|
-
"template-android": {
|
|
109
|
-
type: "string",
|
|
110
|
-
description: "Android-specific template source",
|
|
111
|
-
valueHint: "template",
|
|
112
|
-
},
|
|
113
|
-
"template-macos": {
|
|
114
|
-
type: "string",
|
|
115
|
-
description: "macOS-specific template source",
|
|
116
|
-
valueHint: "template",
|
|
117
|
-
},
|
|
118
|
-
"template-windows": {
|
|
119
|
-
type: "string",
|
|
120
|
-
description: "Windows-specific template source",
|
|
121
|
-
valueHint: "template",
|
|
122
|
-
},
|
|
123
98
|
platform: {
|
|
124
99
|
type: "string",
|
|
125
|
-
description: `Platforms to sync: macos, windows, desktop ${dim("(Default: desktop)")}`,
|
|
126
|
-
valueHint: "
|
|
100
|
+
description: `Platforms to sync: ios, android, mobile, macos, windows, desktop, or all ${dim("(Default: desktop)")}`,
|
|
101
|
+
valueHint: "ios|android|mobile|macos|windows|desktop|all",
|
|
127
102
|
alias: "p",
|
|
128
103
|
},
|
|
104
|
+
"skip-dependency-update": {
|
|
105
|
+
type: "boolean",
|
|
106
|
+
valueHint: "dependencies",
|
|
107
|
+
description: "Preserves versions of listed packages in package.json (comma separated list)",
|
|
108
|
+
},
|
|
129
109
|
},
|
|
130
110
|
async run({ args }) {
|
|
111
|
+
parseNoArg(args, "no-install");
|
|
131
112
|
(await import("./prebuild/command.js")).prebuild(args);
|
|
132
113
|
},
|
|
133
114
|
}),
|
|
134
115
|
},
|
|
135
116
|
});
|
|
136
117
|
await runMain(main);
|
|
118
|
+
function parseNoArg(args, noKey, defaultValue = true) {
|
|
119
|
+
const [, yesKey] = noKey.split("no-");
|
|
120
|
+
// Assuming the noArg has no default value:
|
|
121
|
+
//
|
|
122
|
+
// When `--no-install` is passed, it parses as:
|
|
123
|
+
// { install: false, "no-install": undefined }
|
|
124
|
+
//
|
|
125
|
+
// When `--no-install` is omitted, it parses as:
|
|
126
|
+
// { install: undefined, "no-install": undefined }
|
|
127
|
+
if (args[yesKey] === undefined) {
|
|
128
|
+
args[yesKey] = defaultValue;
|
|
129
|
+
}
|
|
130
|
+
args[noKey] = !args[yesKey];
|
|
131
|
+
return noKey;
|
|
132
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getPackageJson } from "@expo/config";
|
|
2
|
+
import * as PackageManager from "@expo/package-manager";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { hashForDependencyMap } from "../../prebuild/expo/update-package-json.js";
|
|
8
|
+
import { ensureDirectoryAsync } from "./dir.js";
|
|
9
|
+
import { env } from "./env.js";
|
|
10
|
+
import { AbortCommandError } from "./error.js";
|
|
11
|
+
import * as Log from "./log.js";
|
|
12
|
+
import { logNewSection } from "./ora.js";
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const JsonFile = require("@expo/json-file").default;
|
|
15
|
+
const PROJECT_PREBUILD_SETTINGS = ".expo/prebuild";
|
|
16
|
+
const CACHED_PACKAGE_JSON = "cached-packages.json";
|
|
17
|
+
function getTempPrebuildFolder(projectRoot) {
|
|
18
|
+
return path.join(projectRoot, PROJECT_PREBUILD_SETTINGS);
|
|
19
|
+
}
|
|
20
|
+
function hasNewDependenciesSinceLastBuild(projectRoot, packageChecksums) {
|
|
21
|
+
// TODO: Maybe comparing lock files would be better...
|
|
22
|
+
const templateDirectory = getTempPrebuildFolder(projectRoot);
|
|
23
|
+
const tempPkgJsonPath = path.join(templateDirectory, CACHED_PACKAGE_JSON);
|
|
24
|
+
if (!fs.existsSync(tempPkgJsonPath)) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
const { dependencies, devDependencies } = JsonFile.read(tempPkgJsonPath);
|
|
28
|
+
// Only change the dependencies if the normalized hash changes, this helps to reduce meaningless changes.
|
|
29
|
+
const hasNewDependencies = packageChecksums.dependencies !== dependencies;
|
|
30
|
+
const hasNewDevDependencies = packageChecksums.devDependencies !== devDependencies;
|
|
31
|
+
return hasNewDependencies || hasNewDevDependencies;
|
|
32
|
+
}
|
|
33
|
+
function createPackageChecksums(pkg) {
|
|
34
|
+
return {
|
|
35
|
+
dependencies: hashForDependencyMap(pkg.dependencies || {}),
|
|
36
|
+
devDependencies: hashForDependencyMap(pkg.devDependencies || {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** @returns `true` if the package.json dependency hash does not match the cached hash from the last run. */
|
|
40
|
+
export async function hasPackageJsonDependencyListChangedAsync(projectRoot) {
|
|
41
|
+
const pkg = getPackageJson(projectRoot);
|
|
42
|
+
const packages = createPackageChecksums(pkg);
|
|
43
|
+
const hasNewDependencies = hasNewDependenciesSinceLastBuild(projectRoot, packages);
|
|
44
|
+
// Cache package.json
|
|
45
|
+
await ensureDirectoryAsync(getTempPrebuildFolder(projectRoot));
|
|
46
|
+
const templateDirectory = path.join(getTempPrebuildFolder(projectRoot), CACHED_PACKAGE_JSON);
|
|
47
|
+
await JsonFile.writeAsync(templateDirectory, packages);
|
|
48
|
+
return hasNewDependencies;
|
|
49
|
+
}
|
|
50
|
+
export async function installCocoaPodsAsync(projectRoot, platform) {
|
|
51
|
+
let step = logNewSection(`Installing CocoaPods for ${platform}…`);
|
|
52
|
+
if (process.platform !== "darwin") {
|
|
53
|
+
step.succeed("Skipped installing CocoaPods because operating system is not on macOS.");
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const packageManager = new PackageManager.CocoaPodsPackageManager({
|
|
57
|
+
cwd: path.join(projectRoot, platform),
|
|
58
|
+
silent: !(env.EXPO_DEBUG || env.CI),
|
|
59
|
+
});
|
|
60
|
+
if (!(await packageManager.isCLIInstalledAsync())) {
|
|
61
|
+
try {
|
|
62
|
+
// prompt user -- do you want to install cocoapods right now?
|
|
63
|
+
step.text = "CocoaPods CLI not found in your PATH, installing it now.";
|
|
64
|
+
step.stopAndPersist();
|
|
65
|
+
await PackageManager.CocoaPodsPackageManager.installCLIAsync({
|
|
66
|
+
nonInteractive: true,
|
|
67
|
+
spawnOptions: {
|
|
68
|
+
...packageManager.options,
|
|
69
|
+
// Don't silence this part
|
|
70
|
+
stdio: ["inherit", "inherit", "pipe"],
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
step.succeed("Installed CocoaPods CLI.");
|
|
74
|
+
step = logNewSection("Running `pod install` in the `ios` directory.");
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
step.stopAndPersist({
|
|
78
|
+
symbol: "⚠️ ",
|
|
79
|
+
text: chalk.red("Unable to install the CocoaPods CLI."),
|
|
80
|
+
});
|
|
81
|
+
if (error instanceof PackageManager.CocoaPodsError) {
|
|
82
|
+
Log.log(error.message);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
Log.log(`Unknown error: ${error.message}`);
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
await packageManager.installAsync({ spinner: step });
|
|
92
|
+
// Create cached list for later
|
|
93
|
+
await hasPackageJsonDependencyListChangedAsync(projectRoot).catch(() => null);
|
|
94
|
+
step.succeed(`Installed CocoaPods for ${platform}.`);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
step.stopAndPersist({
|
|
99
|
+
symbol: "⚠️ ",
|
|
100
|
+
text: chalk.red(`Something went wrong running \`pod install\` in the \`${platform}\` directory.`),
|
|
101
|
+
});
|
|
102
|
+
if (error instanceof PackageManager.CocoaPodsError) {
|
|
103
|
+
Log.log(error.message);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
Log.log(`Unknown error: ${error.message}`);
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function doesProjectUseCocoaPods(projectRoot, platform) {
|
|
112
|
+
return fs.existsSync(path.join(projectRoot, platform, "Podfile"));
|
|
113
|
+
}
|
|
114
|
+
function isLockfileCreated(projectRoot, platform) {
|
|
115
|
+
const podfileLockPath = path.join(projectRoot, platform, "Podfile.lock");
|
|
116
|
+
return fs.existsSync(podfileLockPath);
|
|
117
|
+
}
|
|
118
|
+
function isPodFolderCreated(projectRoot, platform) {
|
|
119
|
+
const podFolderPath = path.join(projectRoot, platform, "Pods");
|
|
120
|
+
return fs.existsSync(podFolderPath);
|
|
121
|
+
}
|
|
122
|
+
// TODO: Same process but with app.config changes + default plugins.
|
|
123
|
+
// This will ensure the user is prompted for extra setup.
|
|
124
|
+
export async function maybePromptToSyncPodsAsync(projectRoot, platform) {
|
|
125
|
+
if (!doesProjectUseCocoaPods(projectRoot, platform)) {
|
|
126
|
+
// Project does not use CocoaPods
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (!isLockfileCreated(projectRoot, platform) || !isPodFolderCreated(projectRoot, platform)) {
|
|
130
|
+
if (!(await installCocoaPodsAsync(projectRoot, platform))) {
|
|
131
|
+
throw new AbortCommandError();
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Getting autolinked packages can be heavy, optimize around checking every time.
|
|
136
|
+
if (!(await hasPackageJsonDependencyListChangedAsync(projectRoot))) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
await promptToInstallPodsAsync(projectRoot, platform, []);
|
|
140
|
+
}
|
|
141
|
+
async function promptToInstallPodsAsync(projectRoot, platform, missingPods) {
|
|
142
|
+
if (missingPods?.length) {
|
|
143
|
+
Log.log(`Could not find the following native modules: ${missingPods
|
|
144
|
+
.map((pod) => chalk.bold(pod))
|
|
145
|
+
.join(", ")}. Did you forget to run "${chalk.bold("pod install")}" ?`);
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
if (!(await installCocoaPodsAsync(projectRoot, platform))) {
|
|
149
|
+
throw new AbortCommandError();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
await fs.promises.rm(path.join(getTempPrebuildFolder(projectRoot), CACHED_PACKAGE_JSON), {
|
|
154
|
+
recursive: true,
|
|
155
|
+
force: true,
|
|
156
|
+
});
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import Debug from "debug";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Log } from "./log.js";
|
|
5
|
+
import { configurePackageManager, installDependenciesAsync, resolvePackageManager, } from "./resolve-package-manager.js";
|
|
6
|
+
import { installPodsAsync, logProjectReady } from "./template.js";
|
|
7
|
+
const debug = Debug("expo-desktop:create-app:createAsyncUtils");
|
|
8
|
+
/**
|
|
9
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L255
|
|
10
|
+
*/
|
|
11
|
+
export async function configureNodeDependenciesAsync(projectRoot, packageManager) {
|
|
12
|
+
try {
|
|
13
|
+
await configurePackageManager(projectRoot, packageManager, { silent: false });
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
debug(`Error configuring package manager: %O`, error);
|
|
17
|
+
Log.error(`Something went wrong configuring the package manager. Check your ${packageManager} logs. Continuing to create the app.`);
|
|
18
|
+
Log.exception(error);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L247-L253
|
|
23
|
+
*/
|
|
24
|
+
export function getChangeDirectoryPath(projectRoot) {
|
|
25
|
+
const cdPath = path.relative(process.cwd(), projectRoot);
|
|
26
|
+
if (cdPath.length <= projectRoot.length) {
|
|
27
|
+
return cdPath;
|
|
28
|
+
}
|
|
29
|
+
return projectRoot;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L285
|
|
33
|
+
*/
|
|
34
|
+
export async function installCocoaPodsAsync(projectRoot, platform) {
|
|
35
|
+
let podsInstalled = false;
|
|
36
|
+
try {
|
|
37
|
+
podsInstalled = await installPodsAsync(projectRoot, platform);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
debug(`Error installing CocoaPods for ${platform === "ios" ? "iOS" : "macOS"}: %O`, error);
|
|
41
|
+
}
|
|
42
|
+
return podsInstalled;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L270
|
|
46
|
+
*/
|
|
47
|
+
export async function installNodeDependenciesAsync(projectRoot, packageManager) {
|
|
48
|
+
try {
|
|
49
|
+
await installDependenciesAsync(projectRoot, packageManager, { silent: false });
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
debug(`Error installing node modules: %O`, error);
|
|
53
|
+
Log.error(`Something went wrong installing JavaScript dependencies. Check your ${packageManager} logs. Continuing to create the app.`);
|
|
54
|
+
Log.exception(error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L296
|
|
59
|
+
*/
|
|
60
|
+
export function logNodeInstallWarning(cdPath, packageManager, needsPods) {
|
|
61
|
+
console.log(`\n⚠️ Before running your app, make sure you have modules installed:\n`);
|
|
62
|
+
console.log(` cd ${cdPath || "."}${path.sep}`);
|
|
63
|
+
console.log(` ${packageManager} install`);
|
|
64
|
+
if (needsPods && process.platform === "darwin") {
|
|
65
|
+
console.log(` pod install --project-directory=ios && pod install --project-directory=macos`);
|
|
66
|
+
}
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
*
|
|
71
|
+
* @param projectRoot
|
|
72
|
+
* @param props
|
|
73
|
+
* @see https://github.com/expo/expo/blob/6e418b5947dd8806ac97c19eb959ded3a1b14ea2/packages/create-expo/src/createAsync.ts#L60
|
|
74
|
+
*/
|
|
75
|
+
export async function setupDependenciesAsync(projectRoot, props) {
|
|
76
|
+
const shouldInstall = props.install;
|
|
77
|
+
const packageManager = resolvePackageManager();
|
|
78
|
+
// Configure package manager, which is unrelated to installing or not
|
|
79
|
+
await configureNodeDependenciesAsync(projectRoot, packageManager);
|
|
80
|
+
// Install dependencies
|
|
81
|
+
let podsInstalledIos = false;
|
|
82
|
+
const needsPodsInstalledIos = fs.existsSync(path.join(projectRoot, "ios"));
|
|
83
|
+
let podsInstalledMacos = false;
|
|
84
|
+
const needsPodsInstalledMacos = fs.existsSync(path.join(projectRoot, "macos"));
|
|
85
|
+
if (shouldInstall) {
|
|
86
|
+
// Yarn refuses to install if it finds an ancestor directory with a
|
|
87
|
+
// package.json or yarn.lock. I'd rather it install as a separate workspace
|
|
88
|
+
// and that users merge it into their monorepo manually as needed, so we
|
|
89
|
+
// populate an empty yarn.lock file as they suggest.
|
|
90
|
+
if (packageManager === "yarn") {
|
|
91
|
+
await fs.promises.appendFile(path.join(projectRoot, "yarn.lock"), "", "utf-8");
|
|
92
|
+
}
|
|
93
|
+
await installNodeDependenciesAsync(projectRoot, packageManager);
|
|
94
|
+
if (needsPodsInstalledIos) {
|
|
95
|
+
podsInstalledIos = await installCocoaPodsAsync(projectRoot, "ios");
|
|
96
|
+
}
|
|
97
|
+
if (needsPodsInstalledMacos) {
|
|
98
|
+
podsInstalledMacos = await installCocoaPodsAsync(projectRoot, "macos");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const cdPath = getChangeDirectoryPath(projectRoot);
|
|
102
|
+
console.log();
|
|
103
|
+
logProjectReady({ cdPath, packageManager });
|
|
104
|
+
if (!shouldInstall) {
|
|
105
|
+
logNodeInstallWarning(cdPath, packageManager, (needsPodsInstalledIos && !podsInstalledIos) ||
|
|
106
|
+
(needsPodsInstalledMacos && !podsInstalledMacos));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// https://github.com/expo/expo/blob/main/packages/create-expo/src/createFileTransform.ts
|
|
2
|
+
import Debug from "debug";
|
|
3
|
+
import { TarTypeFlag } from "multitars";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import picomatch from "picomatch";
|
|
6
|
+
const debug = Debug("expo-desktop:create-app:fileTransform");
|
|
7
|
+
export function sanitizedName(name) {
|
|
8
|
+
return name
|
|
9
|
+
.replace(/[\W_]+/g, "")
|
|
10
|
+
.normalize("NFD")
|
|
11
|
+
.replace(/[\u0300-\u036f]/g, "");
|
|
12
|
+
}
|
|
13
|
+
// Directories that can be added to the template with an underscore instead of a dot, e.g. `.vscode` and be added with `_vscode`.
|
|
14
|
+
const SUPPORTED_DIRECTORIES = ["eas", "vscode", "github", "cursor"];
|
|
15
|
+
const SUPPORTED_DIRECTORIES_PATTERN = new RegExp(`(^|/|\\\\)_(${SUPPORTED_DIRECTORIES.join("|")})(/|\\\\|$)`);
|
|
16
|
+
function renameDirectories(input, typeflag) {
|
|
17
|
+
if (typeflag === TarTypeFlag.FILE || typeflag === TarTypeFlag.DIRECTORY) {
|
|
18
|
+
// Detect if the file contains one of the supported directories
|
|
19
|
+
// and rename it to the correct format.
|
|
20
|
+
// For example, if the file is `_vscode`, we want to rename it to `.vscode`.
|
|
21
|
+
input = input.replace(SUPPORTED_DIRECTORIES_PATTERN, (match, p1, p2, p3) => `${p1}.${p2}${p3}`);
|
|
22
|
+
}
|
|
23
|
+
return input;
|
|
24
|
+
}
|
|
25
|
+
function renameConfigs(input, typeflag) {
|
|
26
|
+
if (typeflag === TarTypeFlag.FILE && /_?gitignore$/.test(path.basename(input))) {
|
|
27
|
+
// Rename `gitignore` and `_gitignore`, because npm ignores files named
|
|
28
|
+
// `.gitignore` when publishing.
|
|
29
|
+
// See: https://github.com/npm/npm/issues/1862
|
|
30
|
+
input = input.replace(/_?gitignore$/, ".gitignore");
|
|
31
|
+
}
|
|
32
|
+
if (typeflag === TarTypeFlag.FILE && path.basename(input) === "NuGet_Config") {
|
|
33
|
+
// Rename `NuGet_Config` to `NuGet.config`.
|
|
34
|
+
input = input.replace(/NuGet_Config$/, "NuGet.config");
|
|
35
|
+
}
|
|
36
|
+
return input;
|
|
37
|
+
}
|
|
38
|
+
export function createEntryRenamer(name) {
|
|
39
|
+
return (input, typeflag) => {
|
|
40
|
+
if (name) {
|
|
41
|
+
// Rewrite paths for bare workflow
|
|
42
|
+
input = input
|
|
43
|
+
.replace(/HelloWorld/g, input.includes("android") ? sanitizedName(name.toLowerCase()) : sanitizedName(name))
|
|
44
|
+
/*
|
|
45
|
+
* The Windows template placeholder name is "MyApp" rather than
|
|
46
|
+
* "HelloWorld". The React Native Windows CLI renames files like
|
|
47
|
+
* MyApp.cpp to `${filesafeName}.cpp`, which we used to do too (via the
|
|
48
|
+
* commented-out code below).
|
|
49
|
+
*
|
|
50
|
+
* However, I've decided to fix it to "MyApp", because by keeping the
|
|
51
|
+
* names stable as MyApp, we can avoid desyncs between dirty
|
|
52
|
+
* prebuilds (which don't rename files) and clean prebuilds (which do).
|
|
53
|
+
*/
|
|
54
|
+
// .replace(/MyApp/g, sanitizedName(name))
|
|
55
|
+
.replace(/helloworld/g, sanitizedName(name).toLowerCase());
|
|
56
|
+
}
|
|
57
|
+
input = renameConfigs(input, typeflag);
|
|
58
|
+
input = renameDirectories(input, typeflag);
|
|
59
|
+
return input;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function createGlobFilter(globPattern, options) {
|
|
63
|
+
const matcher = picomatch(globPattern, options);
|
|
64
|
+
debug("filter: created for pattern %s (%s)", globPattern);
|
|
65
|
+
return (path) => {
|
|
66
|
+
const included = matcher(path);
|
|
67
|
+
debug("filter: %s - %s", included ? "include" : "exclude", path);
|
|
68
|
+
return included;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function fileExistsSync(file) {
|
|
4
|
+
return !!fs
|
|
5
|
+
.statSync(file, {
|
|
6
|
+
throwIfNoEntry: false,
|
|
7
|
+
})
|
|
8
|
+
?.isFile();
|
|
9
|
+
}
|
|
10
|
+
export function directoryExistsSync(file) {
|
|
11
|
+
return !!fs
|
|
12
|
+
.statSync(file, {
|
|
13
|
+
throwIfNoEntry: false,
|
|
14
|
+
})
|
|
15
|
+
?.isDirectory();
|
|
16
|
+
}
|
|
17
|
+
export async function directoryExistsAsync(file) {
|
|
18
|
+
return (await fs.promises.stat(file).catch(() => null))?.isDirectory() ?? false;
|
|
19
|
+
}
|
|
20
|
+
export async function fileExistsAsync(file) {
|
|
21
|
+
return (await fs.promises.stat(file).catch(() => null))?.isFile() ?? false;
|
|
22
|
+
}
|
|
23
|
+
export const ensureDirectoryAsync = (path) => fs.promises.mkdir(path, { recursive: true });
|
|
24
|
+
export const ensureDirectory = (path) => {
|
|
25
|
+
fs.mkdirSync(path, {
|
|
26
|
+
recursive: true,
|
|
27
|
+
});
|
|
28
|
+
};
|
|
29
|
+
export const copySync = (src, dest) => {
|
|
30
|
+
const destParent = path.dirname(dest);
|
|
31
|
+
if (!fs.existsSync(destParent))
|
|
32
|
+
ensureDirectory(destParent);
|
|
33
|
+
fs.cpSync(src, dest, {
|
|
34
|
+
recursive: true,
|
|
35
|
+
force: true,
|
|
36
|
+
});
|
|
37
|
+
};
|
|
38
|
+
export const copyAsync = async (src, dest) => {
|
|
39
|
+
const destParent = path.dirname(dest);
|
|
40
|
+
if (!fs.existsSync(destParent)) {
|
|
41
|
+
await fs.promises.mkdir(destParent, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
await fs.promises.cp(src, dest, {
|
|
44
|
+
recursive: true,
|
|
45
|
+
force: true,
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
export const removeAsync = (path) => {
|
|
49
|
+
return fs.promises.rm(path, {
|
|
50
|
+
recursive: true,
|
|
51
|
+
force: true,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
export function maybeRealpathSync(target) {
|
|
55
|
+
try {
|
|
56
|
+
return fs.realpathSync(target);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function isPathInside(child, parent) {
|
|
63
|
+
const relative = path.relative(parent, child);
|
|
64
|
+
return !!relative && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
65
|
+
}
|
|
66
|
+
export function getConflictsForDirectory(projectRoot) {
|
|
67
|
+
return fs
|
|
68
|
+
.readdirSync(projectRoot)
|
|
69
|
+
.filter((file) => !(/\.iml$/.test(file) || tolerableFiles.includes(file)));
|
|
70
|
+
}
|
|
71
|
+
// Any of these files are allowed to exist in the projectRoot
|
|
72
|
+
const tolerableFiles = [
|
|
73
|
+
// System
|
|
74
|
+
".DS_Store",
|
|
75
|
+
"Thumbs.db",
|
|
76
|
+
// Git
|
|
77
|
+
".git",
|
|
78
|
+
".gitattributes",
|
|
79
|
+
".gitignore",
|
|
80
|
+
// Project
|
|
81
|
+
".npmignore",
|
|
82
|
+
"LICENSE",
|
|
83
|
+
"docs",
|
|
84
|
+
".idea",
|
|
85
|
+
// Package manager
|
|
86
|
+
"npm-debug.log",
|
|
87
|
+
"yarn-debug.log",
|
|
88
|
+
"yarn-error.log",
|
|
89
|
+
];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import GetEnv from "getenv";
|
|
2
|
+
const { boolish } = GetEnv;
|
|
3
|
+
class Env {
|
|
4
|
+
/** Enable debug logging */
|
|
5
|
+
get EXPO_DEBUG() {
|
|
6
|
+
return boolish("EXPO_DEBUG", false);
|
|
7
|
+
}
|
|
8
|
+
/** Enable the beta version of Expo (TODO: Should this just be in the beta version of expo releases?) */
|
|
9
|
+
get EXPO_BETA() {
|
|
10
|
+
return boolish("EXPO_BETA", false);
|
|
11
|
+
}
|
|
12
|
+
/** Is running in non-interactive CI mode */
|
|
13
|
+
get CI() {
|
|
14
|
+
return boolish("CI", false);
|
|
15
|
+
}
|
|
16
|
+
/** Disable all API caches. Does not disable bundler caches. */
|
|
17
|
+
get EXPO_NO_CACHE() {
|
|
18
|
+
return boolish("EXPO_NO_CACHE", false);
|
|
19
|
+
}
|
|
20
|
+
/** Disable telemetry (analytics) */
|
|
21
|
+
get EXPO_NO_TELEMETRY() {
|
|
22
|
+
return boolish("EXPO_NO_TELEMETRY", false);
|
|
23
|
+
}
|
|
24
|
+
// https://github.com/expo/expo/blob/main/packages/%40expo/cli/src/utils/env.ts
|
|
25
|
+
/** Skip warning users about a dirty git status */
|
|
26
|
+
get EXPO_NO_GIT_STATUS() {
|
|
27
|
+
return boolish("EXPO_NO_GIT_STATUS", true);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const env = new Env();
|