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.
Files changed (61) hide show
  1. package/build/cli.js +42 -46
  2. package/build/common/expo/cmd.js +2 -0
  3. package/build/common/expo/cocoapods.js +159 -0
  4. package/build/common/expo/create-async-utils.js +108 -0
  5. package/build/common/expo/create-file-transform.js +70 -0
  6. package/build/common/expo/dir.js +89 -0
  7. package/build/common/expo/env.js +30 -0
  8. package/build/common/expo/error.js +112 -0
  9. package/build/common/expo/generate-agent-files.js +55 -0
  10. package/build/common/expo/git-cli.js +55 -0
  11. package/build/common/expo/git.js +73 -0
  12. package/build/common/expo/github.js +72 -0
  13. package/build/common/expo/interactive.js +5 -0
  14. package/build/common/expo/is-module-symlinked.js +42 -0
  15. package/build/common/expo/legacy-templates.js +34 -0
  16. package/build/common/expo/log.js +48 -0
  17. package/build/common/expo/node-modules.js +14 -0
  18. package/build/common/expo/npm.js +218 -0
  19. package/build/common/expo/ora.js +84 -0
  20. package/build/common/expo/prompts-cli.js +103 -0
  21. package/build/common/expo/resolve-package-manager.js +128 -0
  22. package/build/common/expo/tar.js +119 -0
  23. package/build/common/expo/template.js +681 -0
  24. package/build/common/expo/url.js +56 -0
  25. package/build/common/node-env.js +38 -0
  26. package/build/common/npm.js +5 -20
  27. package/build/common/read-app-name-from-config.js +20 -0
  28. package/build/config/expo/config-async.js +96 -0
  29. package/build/create-app/command.js +25 -137
  30. package/build/create-app/create-expo-desktop-app.js +57 -840
  31. package/build/create-app/resolve-project-root.js +74 -0
  32. package/build/create-app/template-versions.js +72 -0
  33. package/build/prebuild/command.js +201 -80
  34. package/build/prebuild/create-temp-path.js +22 -0
  35. package/build/prebuild/ensure-config-async.js +237 -0
  36. package/build/prebuild/expo/clear-native-folder.js +154 -0
  37. package/build/prebuild/expo/configure-project-async.js +74 -0
  38. package/build/prebuild/expo/copy-template-files.js +57 -0
  39. package/build/prebuild/expo/inline-modules.js +63 -0
  40. package/build/prebuild/expo/merge-gitignore-paths.js +155 -0
  41. package/build/prebuild/expo/resolve-local-template.js +120 -0
  42. package/build/prebuild/expo/resolve-options.js +98 -0
  43. package/build/prebuild/expo/resolve-template.js +144 -0
  44. package/build/prebuild/expo/update-package-json.js +150 -0
  45. package/build/prebuild/expo/validate-template-platforms.js +16 -0
  46. package/build/prebuild/modify-config-async.js +29 -0
  47. package/build/prebuild/update-from-template-async.js +112 -0
  48. package/build/prebuild/update-package-json-async.js +166 -0
  49. package/build/prebuild/validate-application-id.js +31 -0
  50. package/build/prebuild/validate-url.js +17 -0
  51. package/package.json +22 -4
  52. package/build/common/apply-config-plugins.js +0 -32
  53. package/build/common/apply-windows-cpp-app-template.js +0 -367
  54. package/build/common/child-process.js +0 -152
  55. package/build/common/clack.js +0 -9
  56. package/build/common/git.js +0 -43
  57. package/build/common/preserve-file.js +0 -13
  58. package/build/common/shescape.js +0 -34
  59. package/build/common/template.js +0 -308
  60. package/build/create-app/preview-file-tree.js +0 -48
  61. 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
- "filesafe-name": {
17
- type: "string",
18
- description: `The ${kleur.bold("filesafe name")} for the app in alphanumeric format ${grey("(Example: 'MyApp123')")}`,
19
- valueHint: "name",
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: "desktop|macos|windows",
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,2 @@
1
+ import packageJson from "../../../package.json" with { type: "json" };
2
+ export const CLI_NAME = packageJson.name;
@@ -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();