@yahoo/uds 0.1.13 → 0.1.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. package/cli/README.md +57 -5
  2. package/cli/commands/expo/_setup.ts +100 -73
  3. package/cli/commands/expo/build.ts +84 -3
  4. package/cli/commands/expo/dev.ts +51 -41
  5. package/cli/commands/expo/install/cocoapods.rb +35 -0
  6. package/cli/commands/purge.ts +15 -0
  7. package/cli/commands/{config/sync.ts → sync.ts} +2 -2
  8. package/cli/utils/configWorker.ts +21 -1
  9. package/cli/utils/purgeCSS.ts +139 -0
  10. package/cli/utils/setupConfigWorker.ts +12 -13
  11. package/dist/{chunk-P7GR6E3K.js → chunk-AHFH5E5L.js} +1 -1
  12. package/dist/{chunk-MBOOJIH7.js → chunk-FLBMVDKG.js} +1 -1
  13. package/dist/{chunk-AWTLI4D3.js → chunk-U3UPAQ7V.js} +1 -1
  14. package/dist/fixtures/index.cjs +1 -1
  15. package/dist/fixtures/index.d.cts +2 -2
  16. package/dist/fixtures/index.d.ts +2 -2
  17. package/dist/fixtures/index.js +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.cts +40 -31
  20. package/dist/index.d.ts +40 -31
  21. package/dist/index.js +1 -1
  22. package/dist/{index.native-9kYJrUPa.d.ts → index.native-TvtXtTXg.d.ts} +2 -2
  23. package/dist/{index.native-3ww4C4UV.d.cts → index.native-dgGFONLf.d.cts} +2 -2
  24. package/dist/index.native.cjs +1 -1
  25. package/dist/index.native.d.cts +10 -24
  26. package/dist/index.native.d.ts +10 -24
  27. package/dist/index.native.js +1 -1
  28. package/dist/tailwindPlugin.cjs +1 -1
  29. package/dist/tailwindPlugin.d.cts +1 -1
  30. package/dist/tailwindPlugin.d.ts +1 -1
  31. package/dist/tailwindPlugin.js +1 -1
  32. package/dist/tailwindPurge.cjs +4 -0
  33. package/dist/tailwindPurge.d.cts +16 -0
  34. package/dist/tailwindPurge.d.ts +16 -0
  35. package/dist/tailwindPurge.js +4 -0
  36. package/dist/tokens/index.cjs +1 -1
  37. package/dist/tokens/index.d.cts +3 -3
  38. package/dist/tokens/index.d.ts +3 -3
  39. package/dist/tokens/index.js +1 -1
  40. package/dist/tokens/index.native.cjs +1 -1
  41. package/dist/tokens/index.native.d.cts +2 -2
  42. package/dist/tokens/index.native.d.ts +2 -2
  43. package/dist/tokens/index.native.js +1 -1
  44. package/dist/tokens/parseTokens.cjs +1 -1
  45. package/dist/tokens/parseTokens.d.cts +12 -12
  46. package/dist/tokens/parseTokens.d.ts +12 -12
  47. package/dist/tokens/parseTokens.js +1 -1
  48. package/dist/tokens/parseTokens.native.d.cts +2 -2
  49. package/dist/tokens/parseTokens.native.d.ts +2 -2
  50. package/dist/{types-J4DLS6Xj.d.cts → types-3GXulqnG.d.cts} +1 -1
  51. package/dist/{types-J4DLS6Xj.d.ts → types-3GXulqnG.d.ts} +1 -1
  52. package/dist/{types-hirL9Qk5.d.cts → types-8OHfDki5.d.cts} +47 -54
  53. package/dist/{types-hirL9Qk5.d.ts → types-8OHfDki5.d.ts} +47 -54
  54. package/package.json +21 -18
  55. package/cli/commands/config/config.ts +0 -10
  56. package/cli/commands/nextjs/dev.ts +0 -17
  57. package/cli/commands/nextjs/nextjs.ts +0 -10
package/cli/README.md CHANGED
@@ -1,16 +1,68 @@
1
- # UDS ClI
1
+ # UDS Cli
2
2
 
3
3
  We leverage Bluebun, which is a CLI framework inspired by [Gluegun](https://github.com/infinitered/gluegun), but specifically designed to be used with [Bun](https://bun.sh), the new JS runtime.
4
4
 
5
5
  Bluebun relies on Bun APIs and is designed to be extremely fast, with no-dependencies.
6
6
 
7
- # Standalone executable
8
-
9
- We use Bun to build our standalone executable. See [Bun build docs](https://bun.sh/docs/bundler/executables) for more details. The uds package.json's `build:cli` script handles building the CLI. The bin is output to the `dist/uds` binary file and this path is defined in the package.json's bin field.
7
+ > Trying to add a new command? Please see the "Adding a Command" section below
10
8
 
11
9
  # Commands
12
10
 
13
- ## Command structure
11
+ In any consumer of `@yahoo/uds` the following commands are available:
12
+
13
+ > Please note: If you are _not_ running the CLI from a package.json script you will need to add `bun` before the binary in order to run it directly. i.e. `bun uds purge`
14
+
15
+ ## Config
16
+
17
+ ### Using args
18
+
19
+ | Arg | Required | Default |
20
+ | ------- | -------- | --------------- |
21
+ | id | true | |
22
+ | outFile | false | ./uds.config.ts |
23
+
24
+ ```shell
25
+ uds sync --id [id] --outFile [path]
26
+ ```
27
+
28
+ ```shell
29
+ uds sync --id [id]
30
+ ```
31
+
32
+ ### Using ENV vars
33
+
34
+ | Env Var | Required | Default |
35
+ | ------------ | -------- | --------------- |
36
+ | UDS_ID | true | |
37
+ | UDS_OUT_FILE | false | ./uds.config.ts |
38
+
39
+ ```shell
40
+ UDS_ID=[id] uds sync --outFile [path]
41
+ ```
42
+
43
+ ```shell
44
+ UDS_ID=[id] UDS_OUT_FILE=[path] uds sync
45
+ ```
46
+
47
+ ## Expo
48
+
49
+ ```shell
50
+ uds expo build --profile [profile] --platform [ios|android]
51
+ uds expo dev --profile [profile] --platform [ios|android]
52
+ uds expo launch --profile [profile] --platform [ios|android]
53
+ uds expo update --profile [profile] --platform [ios|android]
54
+ uds expo --help
55
+ ```
56
+
57
+ ## Purge CSS
58
+
59
+ ```shell
60
+ uds purge
61
+ ```
62
+
63
+ ## Adding a command
64
+
65
+ > Please note: Adding nested commands, i.e. uds expo dev, appears to not work correctly when UDS is consumed from npm. As a workaround, please see code for expo/expo.ts for re-routing sub-commands from the root command file. To verify your CLI command works correctly you should run `npm pack` within the packages/uds directory. Once you have your generated tarball you should copy that tarball to a test application such as https://github.com/yahoo-design/uds-nextjs-demo, then add `"@yahoo/uds": "file:./tarball-generated-from-npm-pack.tgz` to it's dependencies and run an install. Now you should be able to run `bun uds [your command name]` to test your functionality.
14
66
 
15
67
  Commands are organized in a tree structure. The root command is the name of the CLI (uds), and then we can have subcommands under that, and subcommands under those, and so on.
16
68
 
@@ -1,22 +1,23 @@
1
1
  import { EasJsonAccessor, EasJsonUtils, Platform } from '@expo/eas-json';
2
2
  import { type Props, print } from 'bluebun';
3
- import { $ } from 'bun';
3
+ import { $, semver, which } from 'bun';
4
+ import fs from 'node:fs';
5
+ import os from 'node:os';
4
6
 
5
7
  export interface MobileProps extends Props {
6
8
  options: {
7
9
  profile: string;
8
- platform: Platform.IOS | Platform.ANDROID;
10
+ platform: Platform;
9
11
  jsEngine?: 'hermes' | 'jsc';
10
12
  debug?: boolean;
11
13
  };
12
14
  }
13
15
 
14
- async function needsBinary(lib: string) {
15
- const whichLib = await $`which ${lib}`.text();
16
- return whichLib.includes('not found');
16
+ export async function needsBinary(lib: string) {
17
+ return which(lib) === null;
17
18
  }
18
19
 
19
- async function needsBrewFormula(formula: string) {
20
+ export async function needsBrewFormula(formula: string) {
20
21
  return (await $`brew list --formula | grep ${formula} | wc -l`.text()).trim() === '0';
21
22
  }
22
23
 
@@ -28,87 +29,108 @@ export async function setup({
28
29
  env?: Partial<typeof Bun.env>;
29
30
  }) {
30
31
  const { profile, platform, jsEngine = 'hermes', debug = false } = props.options;
32
+ const isIOS = platform === Platform.IOS;
33
+ const isAndroid = platform === Platform.ANDROID;
34
+ const pathAsString = await $`echo $PATH`.text();
35
+ const needsBunInPath = !pathAsString.includes('.bun');
31
36
 
32
37
  /* -------------------------------------------------------------------------- */
33
- /* HOMEBREW SETUP */
38
+ /* VERIFY BUN IS IN PATH */
34
39
  /* -------------------------------------------------------------------------- */
35
- const needsBrew = await needsBinary('brew');
36
- if (needsBrew) {
37
- console.log('Installing brew...');
38
- await $`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`;
39
- console.log('Installing brew...Complete');
40
-
40
+ if (needsBunInPath) {
41
41
  /**
42
- * This portion of logic was ported from the homebrew installer script
43
- * https://github.com/Homebrew/install/blob/master/install.sh#L153C5-L153C63.
44
- * However, their script just alerts users to the fact that they need to add
45
- * brew to their path, but doesn't do it for them. This logic attempts to do that
46
- * so we can automatically proceed to the next steps of auto-installing
47
- * the necessary dependencies for local development.
42
+ * If BUN_INSTALL is not in the path, we need to add it.
43
+ * https://bun.sh/docs/installation#checking-installation:~:text=shell%27s%20configuration%20file.
48
44
  */
49
- const machineArch = await $`uname -m`.text();
50
- const isLinux = (await $`uname`.text()) === 'Linux';
51
- const shellType = await $`echo $SHELL`.text();
52
- const homebrewPrefix = machineArch.includes('arm64') ? '/opt/homebrew' : '/usr/local';
53
- let shellRcFile;
54
-
55
- if (isLinux) {
56
- shellRcFile = shellType.includes('zsh') ? '.zshrc' : '.bashrc';
57
- } else {
58
- shellRcFile = shellType.includes('zsh') ? '.zprofile' : '.bash_profile';
59
- }
60
-
61
- const shellRcPath = `${Bun.env.HOME}/${shellRcFile}`;
62
- let shellRcContents = '';
63
- try {
64
- shellRcContents = await Bun.file(shellRcPath).text();
65
- } catch (err) {
66
- // Ignore if it doesn't exist. We create it below.
67
- }
68
-
69
- if (!shellRcContents.includes('homebrew')) {
70
- console.log('Adding brew to $PATH...');
71
- const newShellRcContents = `${shellRcContents}\neval "$(${homebrewPrefix}/bin/brew shellenv)"`;
72
- await Bun.write(shellRcPath, newShellRcContents);
73
- }
45
+ console.write(
46
+ 'BUN is not installed globally or is not available in your $PATH. Adding to your $PATH...',
47
+ );
48
+ const whichShell = await $`echo $SHELL`.text();
49
+ const homeDirectory = os.homedir();
50
+ const shellRcFile = whichShell.includes('zsh') ? '.zshrc' : '.bashrc';
51
+ fs.appendFileSync(
52
+ `${homeDirectory}/${shellRcFile}`,
53
+ '\n# bun\nexport BUN_INSTALL="$HOME/.bun"\nexport PATH="$BUN_INSTALL/bin:$PATH"',
54
+ );
55
+ await $`source $HOME/${shellRcFile}`;
74
56
  }
75
57
 
76
58
  /* -------------------------------------------------------------------------- */
77
- /* FASTLANE SETUP */
59
+ /* XCODE SETUP */
78
60
  /* -------------------------------------------------------------------------- */
79
- const needsFastlane = await needsBrewFormula('fastlane');
80
- if (needsFastlane) {
81
- console.log('Installing fastlane...');
82
- await $`brew install fastlane`;
83
- }
61
+ if (isIOS) {
62
+ const xcodePath = await $`xcode-select -p`.text();
63
+
64
+ if (xcodePath) {
65
+ const isInvalidXcodePath = !xcodePath.startsWith('/Applications');
66
+
67
+ if (isInvalidXcodePath) {
68
+ console.write(`
69
+ /* -------------------------------------------------------------------------- */
70
+ /* XCODDE PATH IS INVALID */
71
+ /* -------------------------------------------------------------------------- */
72
+
73
+ Run the following command to set the correct path to XCode:
74
+
75
+ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
76
+ `);
77
+ }
78
+ } else {
79
+ console.write(`
80
+ /* -------------------------------------------------------------------------- */
81
+ /* XCODE NOT INSTALLED */
82
+ /* -------------------------------------------------------------------------- */
83
+
84
+ You must have XCode installed before continuing...
85
+
86
+ Visit https://apps.apple.com/us/app/xcode/id497799835?mt=12 to download.
87
+ `);
88
+ throw new Error('XCode not installed');
89
+ }
84
90
 
85
- /* -------------------------------------------------------------------------- */
86
- /* COCOAPODS SETUP */
87
- /* -------------------------------------------------------------------------- */
88
- const needsCocoapods = await needsBrewFormula('cocoapods');
89
- if (needsCocoapods) {
90
- console.log('Installing cocoapods...');
91
- await $`brew install cocoapods`;
91
+ /**
92
+ * Installing the XCode command line tools & simulators normally
93
+ * requires opening up XCode to install.
94
+ * This conditional is to avoid having to do that.
95
+ *
96
+ * If xcode command line tools and simulators have already been installed, that's
97
+ * fine. This command is really fast and will continue running if already available.
98
+ *
99
+ * https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes#Install-and-manage-Simulator-runtimes-from-the-command-line
100
+ */
101
+ await $`xcodebuild -runFirstLaunch`;
102
+ await $`xcodebuild -downloadPlatform iOS`;
92
103
  }
93
104
 
94
105
  /* -------------------------------------------------------------------------- */
95
106
  /* JAVA DEVELOPMENT KIT */
96
107
  /* -------------------------------------------------------------------------- */
97
- if (platform === 'android') {
108
+ if (isAndroid) {
98
109
  const jdkPath = await $`brew info --cask zulu17`.text();
99
110
  const needsJdk = jdkPath.includes('unavailable');
100
111
  if (needsJdk) {
101
- console.log('Installing Java Development Kit for Android...');
112
+ console.write('Installing Java Development Kit for Android...');
102
113
  await $`brew install --cask zulu17`;
103
114
  }
104
115
  }
105
116
 
106
117
  /* -------------------------------------------------------------------------- */
107
- /* EXPO SETUP */
118
+ /* EAS CONFIG SETUP */
108
119
  /* -------------------------------------------------------------------------- */
120
+ /**
121
+ * eas.json is used to define build profiles for different environments.
122
+ * For example, you may have a profile for development, staging, and production.
123
+ *
124
+ * The EAS CLI is used to build and run an app locally based on the eas.json,
125
+ * but it isn't opinionated about how you define your profiles.
126
+ *
127
+ * We however want to enforce some conventions to make it easier to get started.
128
+ * We also want to store our builds outside of EAS ecosystem since they auto
129
+ * delete after 30 days & our builds infrequently.
130
+ *
131
+ */
109
132
  const appDirectory = Bun.env.PWD;
110
133
  const easJsonAccessor = EasJsonAccessor.fromProjectPath(appDirectory);
111
-
112
134
  const easProfiles = await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor);
113
135
 
114
136
  if (!easProfiles.includes(profile)) {
@@ -126,20 +148,22 @@ export async function setup({
126
148
  }
127
149
 
128
150
  const easJsonConfig = await EasJsonUtils.getBuildProfileAsync(easJsonAccessor, platform, profile);
129
-
130
151
  const easCliConfig = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
131
152
  const easCliVersion = easCliConfig?.version ?? 'latest';
132
153
 
133
154
  /* -------------------------------------------------------------------------- */
134
155
  /* EAS CLI SETUP */
135
156
  /* -------------------------------------------------------------------------- */
157
+ /**
158
+ * EAS CLI is used to build and run an app locally.
159
+ */
136
160
  const needsEasCli = await needsBinary('eas');
137
161
  if (needsEasCli) {
138
- await $`bun install eas-cli@${easCliVersion} -g`;
162
+ await $`bun install eas-cli@${easCliVersion} -g`.quiet();
139
163
  } else {
140
164
  const currentEasCliVersion = await $`eas --version`.text();
141
- if (currentEasCliVersion !== easCliVersion) {
142
- await $`bun install eas-cli@${easCliVersion} -g`;
165
+ if (!semver.satisfies(currentEasCliVersion, easCliVersion)) {
166
+ await $`bun install eas-cli@${easCliVersion} -g`.quiet();
143
167
  }
144
168
  }
145
169
 
@@ -150,7 +174,7 @@ export async function setup({
150
174
  } = easJsonConfig.env ?? {};
151
175
 
152
176
  // TODO: Add additional checks for ensuring this value adheres to Android formatting specs
153
- if (platform === Platform.ANDROID && !androidId) {
177
+ if (isAndroid && !androidId) {
154
178
  throw new Error(`
155
179
  APP_ANDROID_BUNDLE_IDENTIFIER must be defined in eas.json within the ${profile} > env config.
156
180
  See https://docs.expo.dev/build-reference/variables/#setting-plaintext-environment-variables-in-easjson for information about env variables in eas.json
@@ -174,7 +198,7 @@ export async function setup({
174
198
  }
175
199
 
176
200
  // TODO: Add additional checks for ensuring this value adheres to Uniform Type Identifier
177
- if (platform === Platform.IOS && !appleId) {
201
+ if (isIOS && !appleId) {
178
202
  throw new Error(`
179
203
  APP_APPLE_BUNDLE_IDENTIFIER must be defined in eas.json within the ${profile} > env config.
180
204
  See https://docs.expo.dev/build-reference/variables/#setting-plaintext-environment-variables-in-easjson for information about env variables in eas.json
@@ -197,11 +221,13 @@ export async function setup({
197
221
  }
198
222
 
199
223
  const outputName = `${platform}-${profile}-${jsEngine}`;
200
- // TODO: make this configurable
201
- const prebuildsDir = `${appDirectory}/prebuilds`;
224
+ const prebuildsDir = `${appDirectory}/prebuilds`; // TODO: make this configurable
202
225
  const outputDir = `${prebuildsDir}/${outputName}`;
203
226
  const outputFileBase = `${outputDir}/${outputName}`;
204
227
 
228
+ /* -------------------------------------------------------------------------- */
229
+ /* ENVIRONMENT VARIABLES */
230
+ /* -------------------------------------------------------------------------- */
205
231
  let envVars = Bun.env;
206
232
 
207
233
  envVars = {
@@ -226,6 +252,7 @@ export async function setup({
226
252
  envVars = { ...envVars, ...envOpts };
227
253
  }
228
254
 
255
+ /** Change the default environment variables for shells created by this instance. */
229
256
  $.env(envVars);
230
257
 
231
258
  const output = {
@@ -233,10 +260,10 @@ export async function setup({
233
260
  dir: outputDir,
234
261
  prebuildsDir: prebuildsDir,
235
262
  fileBase: outputFileBase,
236
- artifact: platform === 'ios' ? `${outputFileBase}.tar.gz` : `${outputFileBase}.zip`,
237
- app: platform === 'ios' ? `${outputFileBase}.app` : 'todo fix android',
263
+ artifact: isIOS ? `${outputFileBase}.tar.gz` : `${outputFileBase}.zip`,
264
+ app: isIOS ? `${outputFileBase}.app` : 'todo fix android',
238
265
  get launchFile() {
239
- return platform === 'ios' ? this.artifact : this.apk.signed;
266
+ return isIOS ? this.artifact : this.apk.signed;
240
267
  },
241
268
  apk: {
242
269
  contents: `${outputFileBase}/build`,
@@ -248,7 +275,7 @@ export async function setup({
248
275
  };
249
276
 
250
277
  return {
251
- scheme: platform === 'ios' ? appleId : androidId,
278
+ scheme: isIOS ? appleId : androidId,
252
279
  channel: easJsonConfig.channel,
253
280
  debug,
254
281
  profile,
@@ -1,6 +1,5 @@
1
- import { $ } from 'bun';
2
- import type { MobileProps } from './_setup';
3
- import { setup } from './_setup';
1
+ import { $, semver } from 'bun';
2
+ import { setup, needsBinary, needsBrewFormula, type MobileProps } from './_setup';
4
3
 
5
4
  export default {
6
5
  name: 'build',
@@ -10,6 +9,88 @@ export default {
10
9
  props,
11
10
  });
12
11
 
12
+ /* ----- Homebrew, Fastlane, and Cocoapods are only required for builds. ---- */
13
+ /* -------------------------------------------------------------------------- */
14
+ /* HOMEBREW SETUP */
15
+ /* -------------------------------------------------------------------------- */
16
+ const needsBrew = await needsBinary('brew');
17
+ if (needsBrew) {
18
+ console.write('Installing brew...');
19
+ await $`/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"`;
20
+ console.write('Installing brew...Complete');
21
+
22
+ /**
23
+ * This logic was ported from the homebrew installer script
24
+ * https://github.com/Homebrew/install/blob/master/install.sh#L153C5-L153C63.
25
+ * However, their script just alerts users to the fact that they need to add
26
+ * brew to their path, but doesn't do it for them. This logic attempts to do that
27
+ * so we can automatically proceed to the next steps of auto-installing
28
+ * the necessary dependencies for local development.
29
+ */
30
+ const machineArch = await $`uname -m`.text();
31
+ const isLinux = (await $`uname`.text()) === 'Linux';
32
+ const shellType = await $`echo $SHELL`.text();
33
+ const homebrewPrefix = machineArch.includes('arm64') ? '/opt/homebrew' : '/usr/local';
34
+ let shellRcFile;
35
+
36
+ if (isLinux) {
37
+ shellRcFile = shellType.includes('zsh') ? '.zshrc' : '.bashrc';
38
+ } else {
39
+ shellRcFile = shellType.includes('zsh') ? '.zprofile' : '.bash_profile';
40
+ }
41
+
42
+ const shellRcPath = `${Bun.env.HOME}/${shellRcFile}`;
43
+ let shellRcContents = '';
44
+ try {
45
+ shellRcContents = await Bun.file(shellRcPath).text();
46
+ } catch (err) {
47
+ // Ignore if it doesn't exist. We create it below.
48
+ }
49
+
50
+ if (!shellRcContents.includes('homebrew')) {
51
+ console.write('Adding brew to $PATH...');
52
+ const newShellRcContents = `${shellRcContents}\neval "$(${homebrewPrefix}/bin/brew shellenv)"`;
53
+ await Bun.write(shellRcPath, newShellRcContents);
54
+ }
55
+ }
56
+
57
+ /* -------------------------------------------------------------------------- */
58
+ /* FASTLANE SETUP */
59
+ /* -------------------------------------------------------------------------- */
60
+ const needsFastlane = await needsBrewFormula('fastlane');
61
+ if (needsFastlane) {
62
+ console.write('Installing fastlane...');
63
+ await $`brew install fastlane`;
64
+ }
65
+
66
+ /* -------------------------------------------------------------------------- */
67
+ /* COCOAPODS SETUP */
68
+ /* -------------------------------------------------------------------------- */
69
+ const needsCocoapods = await needsBrewFormula('cocoapods');
70
+ /** https://github.com/facebook/react-native/issues/42698#issuecomment-1915670708 */
71
+ const validCocoapodsVersion = '1.14.3';
72
+
73
+ const installCocoapods = async () =>
74
+ await $`brew install ${import.meta.dirname}/install/cocoapods.rb`;
75
+
76
+ if (needsCocoapods) {
77
+ console.write('Installing cocoapods...');
78
+ await installCocoapods();
79
+ } else {
80
+ const cocoapodsVersion = await $`pod --version`.text();
81
+ const needsDowngrade = semver.satisfies(cocoapodsVersion, `>${validCocoapodsVersion}`);
82
+ if (needsDowngrade) {
83
+ await $`brew unlink cocoapods`;
84
+ await $`brew uninstall cocoapods`;
85
+ console.write(
86
+ `Downgrading cocoapods from ${cocoapodsVersion} to ${validCocoapodsVersion}...`,
87
+ );
88
+ await installCocoapods();
89
+ await $`brew link cocoapods`;
90
+ }
91
+ }
92
+
13
93
  await $`eas build --local --non-interactive --json --clear-cache --platform ${platform} --profile ${profile} --output ${output.artifact}`;
94
+ console.write('You can now run your dev command to start the app!');
14
95
  },
15
96
  };
@@ -1,5 +1,5 @@
1
1
  import { Props } from 'bluebun';
2
- import { $ } from 'bun';
2
+ import { $, sleep } from 'bun';
3
3
  import { setup, type MobileProps } from './_setup';
4
4
 
5
5
  interface MobileStartProps extends Props {
@@ -12,7 +12,7 @@ interface MobileStartProps extends Props {
12
12
  type SimDevice = { udid: string; name: string; state: 'Booted' | 'Shutdown' };
13
13
  type SimList = {
14
14
  devices: {
15
- ['com.apple.CoreSimulator.SimRuntime.iOS-17-2']: SimDevice[];
15
+ [key: string]: SimDevice[];
16
16
  };
17
17
  };
18
18
 
@@ -20,7 +20,6 @@ export default {
20
20
  name: 'dev',
21
21
  description: '🚧 Dev',
22
22
  run: async (props: MobileStartProps) => {
23
- console.log('running dev');
24
23
  const { platform, scheme, output } = await setup({
25
24
  props,
26
25
  env: {
@@ -28,52 +27,63 @@ export default {
28
27
  },
29
28
  });
30
29
 
31
- if (platform === 'ios') {
32
- const xcodePath = await $`xcode-select -p`.text();
33
- const xcodeExists = xcodePath.startsWith('/Applications');
30
+ const extraArgs = [];
34
31
 
35
- if (xcodeExists) {
36
- await $`open -a simulator`;
37
- const deviceList = (await $`xcrun simctl list devices available -j -e`.json()) as SimList;
32
+ if (props.options.clear) {
33
+ extraArgs.push('--clear');
34
+ }
35
+ const extrArgsString = extraArgs.join(' ');
38
36
 
37
+ try {
38
+ /**
39
+ * Get list of devices.
40
+ * If there is a booted device, open the simulator on that device.
41
+ * If no booted device is found, open the simulator on iPhone 15 Pro Max.
42
+ * * https://github.com/expo/orbit/blob/main/packages/eas-shared/src/run/ios/simulator.ts#L98
43
+ *
44
+ * TODO:
45
+ * Should we open app on all booted devices if there are multiple open?
46
+ * Should we prompt user to pick from one of the booted devices?
47
+ * Should we prompt user to pick a device if there are no booted devices?
48
+ */
49
+ if (platform === 'ios') {
50
+ const deviceList =
51
+ (await $`xcrun simctl list devices available --json -e`.json()) as SimList;
39
52
  const devices = Object.values(deviceList?.devices).flatMap((item) => item);
53
+ const bootedDevice = devices.find((device: SimDevice) => device.state === 'Booted');
54
+ if (!bootedDevice) {
55
+ const iphone15ProMax = devices.find((device) => device.name === 'iPhone 15 Pro Max');
56
+ const iphone15Udid = iphone15ProMax?.udid;
57
+ await $`open -a Simulator --args -CurrentDeviceUDID ${iphone15Udid}`;
58
+ }
59
+ const appContainerPath =
60
+ await $`xcrun simctl get_app_container booted ${scheme} data`.text();
40
61
 
41
- if (devices.length === 0) {
42
- throw new Error('No devices found');
43
- } else {
44
- const isBooted = devices.find((device: SimDevice) => device.state === 'Booted');
45
- if (!isBooted) {
46
- const iphone15Max = devices.find(
47
- (device: SimDevice) => device.name === 'iPhone 15 Pro Max',
48
- );
49
- if (!iphone15Max) {
50
- throw new Error('iPhone 15 Pro Max not found');
51
- } else {
52
- const udid = iphone15Max.udid;
53
- await $`xcrun simctl boot ${udid}`;
54
- }
55
- }
62
+ if (!appContainerPath) {
63
+ console.write('App not installed on booted device. Installing...');
64
+ await $`eas build:run --platform ${platform} --path ${output.launchFile}`;
56
65
  }
57
- } else {
58
- console.log(
59
- 'You must have XCode installed before continuing... Visit https://apps.apple.com/us/app/xcode/id497799835?mt=12 to download.',
60
- );
61
- throw new Error('XCode not installed');
66
+ /**
67
+ * In dev mode we use a debug build, which requires a metro server to serve
68
+ * the Javascript bundle.
69
+ *
70
+ * To avoid the, "Could not find metro server" error screen on launch,
71
+ * we delay the launch of the app until after the metro server has started.
72
+ */
62
73
  }
63
- }
64
74
 
65
- await $`eas build:run --platform ${platform} --path ${output.launchFile}`;
75
+ sleep(3000).then(async () => {
76
+ /**
77
+ * Launch the app on the booted device
78
+ * https://github.com/expo/eas-cli?tab=readme-ov-file#eas-buildrun
79
+ */
80
+ await $`eas build:run --platform ${platform} --path ${output.launchFile}`;
81
+ });
66
82
 
67
- const extraArgs = [];
68
-
69
- if (props.options.clear) {
70
- extraArgs.push('--clear');
83
+ console.write('Starting Metro server...');
84
+ await $`expo start --${platform} --dev-client --localhost --scheme ${scheme} ${extrArgsString}`;
85
+ } catch (err) {
86
+ throw err;
71
87
  }
72
-
73
- const extrArgsString = extraArgs.join(' ');
74
-
75
- console.log(`Starting metro server...`);
76
-
77
- await $`expo start --${platform} --dev-client --localhost --scheme ${scheme} ${extrArgsString}`;
78
88
  },
79
89
  };
@@ -0,0 +1,35 @@
1
+ class Cocoapods < Formula
2
+ desc "Dependency manager for Cocoa projects"
3
+ homepage "https://cocoapods.org/"
4
+ url "https://github.com/CocoaPods/CocoaPods/archive/refs/tags/1.14.3.tar.gz"
5
+ sha256 "de05766e5771e0cef7af89f73b0e42a1f1c52a76ce1288592cd9511bcd688a9e"
6
+ license "MIT"
7
+ revision 1
8
+
9
+ bottle do
10
+ sha256 cellar: :any, arm64_sonoma: "0fb8e638fb4901b6c578c44ae1af0098a0b3530e7a339bf43f2fb67f2819d412"
11
+ sha256 cellar: :any, arm64_ventura: "e3d0c8624df429cb30c5cf818f3a358d4f678b374410e9fbc8fde090889f9b61"
12
+ sha256 cellar: :any, arm64_monterey: "a6df519bae3f51b1609cfcd017b4d47cb688200780ffb9b27d57a5dc05ea93de"
13
+ sha256 cellar: :any, sonoma: "5c2ee41824fcb154b46f9fa967f203fbf9009d2051f8c898375d69d333052988"
14
+ sha256 cellar: :any, ventura: "91459cb108161201a81fdd0e96a126e9843be8213112c208051e8e72ce9736f9"
15
+ sha256 cellar: :any, monterey: "316b0954e21f76c013d8c581c589e4f884231687eb2253a9f9a38a77a87728a6"
16
+ sha256 cellar: :any_skip_relocation, x86_64_linux: "2d5be1290e8161d9a49b3fd191fc8423aac29f61fbc84148c8ba08cdc03d8d84"
17
+ end
18
+
19
+ depends_on "pkg-config" => :build
20
+ depends_on "ruby"
21
+ uses_from_macos "libffi", since: :catalina
22
+
23
+ def install
24
+ ENV["GEM_HOME"] = libexec
25
+ system "gem", "build", "cocoapods.gemspec"
26
+ system "gem", "install", "cocoapods-#{version}.gem"
27
+ # Other executables don't work currently.
28
+ bin.install libexec/"bin/pod", libexec/"bin/xcodeproj"
29
+ bin.env_script_all_files(libexec/"bin", GEM_HOME: ENV["GEM_HOME"])
30
+ end
31
+
32
+ test do
33
+ system "#{bin}/pod", "list"
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ import { Props, spinStart, spinStop } from 'bluebun';
2
+
3
+ import { purge } from '../utils/purgeCSS';
4
+
5
+ export default {
6
+ name: 'purge',
7
+ description: `Purge unused CSS`,
8
+ run: async (props: Props) => {
9
+ spinStart('Purging css...');
10
+
11
+ await purge();
12
+
13
+ spinStop('✅ Purging css done!');
14
+ },
15
+ };
@@ -1,6 +1,6 @@
1
1
  import { Props } from 'bluebun';
2
- import { setupConfigWorker } from '../../utils/setupConfigWorker';
3
- import { SyncOptions } from '../../utils/types';
2
+ import { setupConfigWorker } from '../utils/setupConfigWorker';
3
+ import { SyncOptions } from '../utils/types';
4
4
 
5
5
  interface SyncProps extends Props {
6
6
  options: SyncOptions;