@yahoo/uds 0.1.12 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (93) hide show
  1. package/cli/README.md +135 -0
  2. package/cli/commands/config/config.ts +10 -0
  3. package/cli/commands/config/sync.ts +22 -0
  4. package/cli/commands/expo/_setup.ts +244 -0
  5. package/cli/commands/expo/build.ts +96 -0
  6. package/cli/commands/expo/dev.ts +82 -0
  7. package/cli/commands/expo/expo.ts +23 -0
  8. package/cli/commands/expo/install/cocoapods.rb +35 -0
  9. package/cli/commands/expo/launch.ts +15 -0
  10. package/cli/commands/expo/update.ts +16 -0
  11. package/cli/commands/nextjs/dev.ts +17 -0
  12. package/cli/commands/nextjs/nextjs.ts +10 -0
  13. package/cli/commands/uds.ts +10 -0
  14. package/cli/commands/version.ts +11 -0
  15. package/cli/env.d.ts +15 -0
  16. package/cli/eslint.config.mjs +8 -0
  17. package/cli/tsconfig.json +10 -0
  18. package/cli/uds-cli +7 -0
  19. package/cli/utils/configWorker.ts +9 -0
  20. package/cli/utils/getCommandHelp.ts +65 -0
  21. package/cli/utils/setupConfigWorker.ts +81 -0
  22. package/cli/utils/sortKeys.ts +27 -0
  23. package/cli/utils/types.ts +13 -0
  24. package/dist/{chunk-JKZI2WLD.js → chunk-AWTLI4D3.js} +1 -1
  25. package/dist/chunk-D4K3CXV6.js +0 -0
  26. package/dist/{chunk-Z34QGHWU.js → chunk-MBOOJIH7.js} +1 -1
  27. package/dist/chunk-MFA2Y7DA.js +1 -0
  28. package/dist/chunk-P7GR6E3K.js +1 -0
  29. package/dist/chunk-PQBOZFJV.js +1 -0
  30. package/dist/index.cjs +1 -1
  31. package/dist/index.d.cts +7 -40
  32. package/dist/index.d.ts +7 -40
  33. package/dist/index.js +1 -1
  34. package/dist/{index.native-VVqy3X9H.d.cts → index.native-3ww4C4UV.d.cts} +1 -1
  35. package/dist/{index.native-DzfcCYUh.d.ts → index.native-9kYJrUPa.d.ts} +1 -1
  36. package/dist/index.native.cjs +1 -0
  37. package/dist/index.native.d.cts +1493 -0
  38. package/dist/index.native.d.ts +1493 -0
  39. package/dist/index.native.js +1 -0
  40. package/dist/tailwindPlugin.cjs +1 -1
  41. package/dist/tailwindPlugin.d.cts +1 -1
  42. package/dist/tailwindPlugin.d.ts +1 -1
  43. package/dist/tailwindPlugin.js +1 -1
  44. package/dist/tokens/index.cjs +1 -1
  45. package/dist/tokens/index.d.cts +3 -3
  46. package/dist/tokens/index.d.ts +3 -3
  47. package/dist/tokens/index.js +1 -1
  48. package/dist/tokens/index.native.cjs +1 -1
  49. package/dist/tokens/index.native.d.cts +2 -2
  50. package/dist/tokens/index.native.d.ts +2 -2
  51. package/dist/tokens/index.native.js +1 -1
  52. package/dist/tokens/parseTokens.cjs +1 -1
  53. package/dist/tokens/parseTokens.d.cts +1 -1
  54. package/dist/tokens/parseTokens.d.ts +1 -1
  55. package/dist/tokens/parseTokens.js +1 -1
  56. package/dist/tokens/parseTokens.native.d.cts +1 -1
  57. package/dist/tokens/parseTokens.native.d.ts +1 -1
  58. package/dist/tokens/parseTokens.native.js +1 -1
  59. package/dist/types-J4DLS6Xj.d.cts +38 -0
  60. package/dist/types-J4DLS6Xj.d.ts +38 -0
  61. package/dist/{types-VgTlNoi_.d.cts → types-hirL9Qk5.d.cts} +1 -0
  62. package/dist/{types-VgTlNoi_.d.ts → types-hirL9Qk5.d.ts} +1 -0
  63. package/fonts/mobile.cjs +29 -0
  64. package/fonts/mobile.d.ts +3 -0
  65. package/fonts/yahoo-icons.ttf +0 -0
  66. package/fonts/yahoo-sans-beta-bold.otf +0 -0
  67. package/fonts/yahoo-sans-beta-medium.otf +0 -0
  68. package/fonts/yahoo-sans-beta-regular.otf +0 -0
  69. package/fonts/yahoo-sans-black.otf +0 -0
  70. package/fonts/yahoo-sans-bold.otf +0 -0
  71. package/fonts/yahoo-sans-condensed-black.otf +0 -0
  72. package/fonts/yahoo-sans-condensed-bold.otf +0 -0
  73. package/fonts/yahoo-sans-condensed-light.otf +0 -0
  74. package/fonts/yahoo-sans-condensed-medium.otf +0 -0
  75. package/fonts/yahoo-sans-condensed-regular.otf +0 -0
  76. package/fonts/yahoo-sans-extrabold.otf +0 -0
  77. package/fonts/yahoo-sans-extralight.otf +0 -0
  78. package/fonts/yahoo-sans-italic.otf +0 -0
  79. package/fonts/yahoo-sans-light.otf +0 -0
  80. package/fonts/yahoo-sans-medium.otf +0 -0
  81. package/fonts/yahoo-sans-regular.otf +0 -0
  82. package/fonts/yahoo-sans-semibold.otf +0 -0
  83. package/fonts/yahoo-serif-display-black.otf +0 -0
  84. package/fonts/yahoo-serif-display-bold.otf +0 -0
  85. package/fonts/yahoo-serif-display-extrabold.otf +0 -0
  86. package/fonts/yahoo-serif-display-light.otf +0 -0
  87. package/fonts/yahoo-serif-display-regular.otf +0 -0
  88. package/fonts/yahoo-serif-text-bold.otf +0 -0
  89. package/fonts/yahoo-serif-text-italic.otf +0 -0
  90. package/fonts/yahoo-serif-text-regular.otf +0 -0
  91. package/package.json +114 -49
  92. package/bin/uds +0 -0
  93. package/dist/chunk-7FQGDIJ2.js +0 -1
package/cli/README.md ADDED
@@ -0,0 +1,135 @@
1
+ # UDS ClI
2
+
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
+
5
+ Bluebun relies on Bun APIs and is designed to be extremely fast, with no-dependencies.
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.
10
+
11
+ # Commands
12
+
13
+ ## Command structure
14
+
15
+ 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
+
17
+ For example, a command structure might look like this:
18
+
19
+ ```
20
+ cli/
21
+ commands/
22
+ pizza.ts # pizza
23
+ help.ts # pizza help
24
+ bake/
25
+ bake.ts # pizza bake
26
+ cheese.ts # pizza bake cheese
27
+ pepperoni.ts # pizza bake pepperoni
28
+ ```
29
+
30
+ ## Command files
31
+
32
+ Commands are exported as defaults from each command file. They look like this:
33
+
34
+ ```typescript
35
+ import { type Props } from 'bluebun';
36
+
37
+ export default {
38
+ name: 'bake',
39
+ description: 'Bake a pizza',
40
+ alias: ['b'],
41
+ run: async (props: Props) => {
42
+ // bake it!
43
+ },
44
+ };
45
+ ```
46
+
47
+ ## Command properties
48
+
49
+ Commands have the following properties:
50
+
51
+ - `name` - the name of the command
52
+ - `description` - a description of the command for the automatic help system
53
+ - `alias` - an array of aliases for the command (can also be a single string)
54
+ - `run` - the function that is run when the command is run, usually `async`
55
+
56
+ ## Props
57
+
58
+ The `run` function is passed a `Props` object. This object contains the command path, the arguments, and the options, as well as a few other useful things.
59
+
60
+ Here are the properties available on the `props` object if we were to run `uds bake cheese convection --sliced --temp=400 --time 30`:
61
+
62
+ - `name` - the name of the CLI (uds)
63
+ - `cliPath` - the path to the CLI (./uds-cli)
64
+ - `argv` - the raw arguments passed to the CLI (e.g. `["/bin/bun", "/bin/uds", "bake", "cheese", "convection", "--sliced", "--temp=400", "--time", "30"]`)
65
+ - `commandPath` - the path to the command that was run (e.g. `["bake", "cheese"]`)
66
+ - `arguments` - the positional arguments passed to the command (e.g. `["convection"]`)
67
+ - `options` - the options passed to the command (e.g. `{ sliced: true, temp: 400, time: 30 }`)
68
+ - `first` - the first argument passed to the command (e.g. `"convection"`)
69
+ - `second` - the second argument passed to the command (e.g. `undefined`)
70
+ - `third` - the third argument passed to the command (e.g. `undefined`)
71
+
72
+ Given these props, we might have a command that looks like this:
73
+
74
+ ```typescript
75
+ import { type Props, spinStart, spinStop } from 'bluebun';
76
+ import { createPizza, slice } from './_pizza';
77
+ import { convectionBake, toasterBake, regularBake } from './_bakePizza';
78
+
79
+ export default {
80
+ name: 'bake',
81
+ description: 'Bake a pizza',
82
+ alias: ['b'],
83
+ run: async (props: Props) => {
84
+ const { first, second, third, options } = props;
85
+
86
+ const pizza = createPizza();
87
+
88
+ spinStart('Baking pizza...');
89
+ if (first === 'convection') {
90
+ await convectionBake(pizza, options.temp, options.time);
91
+ } else if (first === 'toaster') {
92
+ await toasterBake(pizza, options.temp, options.time);
93
+ } else {
94
+ await regularBake(pizza, options.temp, options.time);
95
+ }
96
+ spinStop('✅ Pizza baked!');
97
+
98
+ spinStart('Slicing pizza...');
99
+ if (options.sliced) {
100
+ await slice(pizza);
101
+ }
102
+ spinStop('✅ Pizza sliced!');
103
+ },
104
+ };
105
+ ```
106
+
107
+ # Reference
108
+
109
+ Bluebun comes with a number of built-in utilities that are useful. They're all exported from `bluebun` directly, so you can import them like this:
110
+
111
+ ```typescript
112
+ import { inputKey } from 'bluebun';
113
+ ```
114
+
115
+ Each of the following docs has usage examples and a Testing section that gives examples on how to write tests for them.
116
+
117
+ ## CLI
118
+
119
+ - [run](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/run.md) - run the CLI and command
120
+ - [cli](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/cli.md) - start the CLI without running the command
121
+ - [commandHelp](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/commandHelp.md) - get a list of all commands and their descriptions
122
+
123
+ ## User Interaction
124
+
125
+ - [ask](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/ask.md) - ask the user a question via a prompt
126
+ - [cursor](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/cursor.md) - manipulate the cursor
127
+ - [inputKey](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/inputKey.md) - wait for a single keypress
128
+ - [inputKeys](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/inputKeys.md) - wait for and handle multiple keypresses
129
+
130
+ ## Output
131
+
132
+ - [print](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/print.md) - print a string to the terminal
133
+ - [spinner](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/spinner.md) - start and stop a spinner
134
+ - [progress](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/progress.md) - start, update, and stop a progress bar
135
+ - [styles and colors](https://github.com/jamonholmgren/bluebun/blob/main/docs/reference/styles.md) - style and colorize text
@@ -0,0 +1,10 @@
1
+ import { type Props } from 'bluebun';
2
+ import { getCommandHelp } from '../../utils/getCommandHelp';
3
+
4
+ export default {
5
+ name: 'config',
6
+ description: '',
7
+ run: async (props: Props) => {
8
+ await getCommandHelp(props);
9
+ },
10
+ };
@@ -0,0 +1,22 @@
1
+ import { Props } from 'bluebun';
2
+ import { setupConfigWorker } from '../../utils/setupConfigWorker';
3
+ import { SyncOptions } from '../../utils/types';
4
+
5
+ interface SyncProps extends Props {
6
+ options: SyncOptions;
7
+ }
8
+
9
+ export default {
10
+ name: 'sync',
11
+ description: '🌈 Sync',
12
+ run: async ({ options }: SyncProps) => {
13
+ await setupConfigWorker({
14
+ ...options,
15
+ onUpdate: ({ worker }) => {
16
+ if (!options.watch) {
17
+ worker.terminate();
18
+ }
19
+ },
20
+ });
21
+ },
22
+ };
@@ -0,0 +1,244 @@
1
+ import { EasJsonAccessor, EasJsonUtils, Platform } from '@expo/eas-json';
2
+ import { type Props, print } from 'bluebun';
3
+ import { $, semver, which } from 'bun';
4
+
5
+ export interface MobileProps extends Props {
6
+ options: {
7
+ profile: string;
8
+ platform: Platform;
9
+ jsEngine?: 'hermes' | 'jsc';
10
+ debug?: boolean;
11
+ };
12
+ }
13
+
14
+ export async function needsBinary(lib: string) {
15
+ return which(lib) === null;
16
+ }
17
+
18
+ export async function needsBrewFormula(formula: string) {
19
+ return (await $`brew list --formula | grep ${formula} | wc -l`.text()).trim() === '0';
20
+ }
21
+
22
+ export async function setup({
23
+ props,
24
+ env: envOpts,
25
+ }: {
26
+ props: MobileProps;
27
+ env?: Partial<typeof Bun.env>;
28
+ }) {
29
+ const { profile, platform, jsEngine = 'hermes', debug = false } = props.options;
30
+ const isIOS = platform === Platform.IOS;
31
+ const isAndroid = platform === Platform.ANDROID;
32
+
33
+ /* -------------------------------------------------------------------------- */
34
+ /* XCODE SETUP */
35
+ /* -------------------------------------------------------------------------- */
36
+ if (isIOS) {
37
+ const xcodePath = await $`xcode-select -p`.text();
38
+ const needsXcode = !xcodePath.startsWith('/Applications');
39
+ const needsXcrun = await needsBinary('xcrun');
40
+
41
+ if (needsXcode) {
42
+ console.write(
43
+ 'You must have XCode installed before continuing... Visit https://apps.apple.com/us/app/xcode/id497799835?mt=12 to download.',
44
+ );
45
+ throw new Error('XCode not installed');
46
+ }
47
+
48
+ /**
49
+ * Installing the XCode command line tools & simulators normally
50
+ * requires opening up XCode to install.
51
+ * This conditional is to avoid having to do that.
52
+ */
53
+ if (needsXcrun) {
54
+ /**
55
+ * https://developer.apple.com/documentation/xcode/installing-additional-simulator-runtimes#Install-and-manage-Simulator-runtimes-from-the-command-line
56
+ */
57
+ await $`xcodebuild -runFirstLaunch`;
58
+ await $`xcodebuild -downloadPlatform iOS`;
59
+ }
60
+ }
61
+
62
+ /* -------------------------------------------------------------------------- */
63
+ /* JAVA DEVELOPMENT KIT */
64
+ /* -------------------------------------------------------------------------- */
65
+ if (isAndroid) {
66
+ const jdkPath = await $`brew info --cask zulu17`.text();
67
+ const needsJdk = jdkPath.includes('unavailable');
68
+ if (needsJdk) {
69
+ console.write('Installing Java Development Kit for Android...');
70
+ await $`brew install --cask zulu17`;
71
+ }
72
+ }
73
+
74
+ /* -------------------------------------------------------------------------- */
75
+ /* EAS CONFIG SETUP */
76
+ /* -------------------------------------------------------------------------- */
77
+ /**
78
+ * eas.json is used to define build profiles for different environments.
79
+ * For example, you may have a profile for development, staging, and production.
80
+ *
81
+ * The EAS CLI is used to build and run an app locally based on the eas.json,
82
+ * but it isn't opinionated about how you define your profiles.
83
+ *
84
+ * We however want to enforce some conventions to make it easier to get started.
85
+ * We also want to store our builds outside of EAS ecosystem since they auto
86
+ * delete after 30 days & our builds infrequently.
87
+ *
88
+ */
89
+ const appDirectory = Bun.env.PWD;
90
+ const easJsonAccessor = EasJsonAccessor.fromProjectPath(appDirectory);
91
+ const easProfiles = await EasJsonUtils.getBuildProfileNamesAsync(easJsonAccessor);
92
+
93
+ if (!easProfiles.includes(profile)) {
94
+ const profileNames = easProfiles.join(', ');
95
+ const suggestion = `Please add one or use ${profileNames}.`;
96
+
97
+ if (!profile) {
98
+ print(`No profile name was provided. ${suggestion}`);
99
+ } else {
100
+ print(
101
+ `A profile with name ${profile} does not exist as a key in eas.json > build object.\n${suggestion}`,
102
+ );
103
+ }
104
+ process.exit(0);
105
+ }
106
+
107
+ const easJsonConfig = await EasJsonUtils.getBuildProfileAsync(easJsonAccessor, platform, profile);
108
+ const easCliConfig = await EasJsonUtils.getCliConfigAsync(easJsonAccessor);
109
+ const easCliVersion = easCliConfig?.version ?? 'latest';
110
+
111
+ /* -------------------------------------------------------------------------- */
112
+ /* EAS CLI SETUP */
113
+ /* -------------------------------------------------------------------------- */
114
+ /**
115
+ * EAS CLI is used to build and run an app locally.
116
+ */
117
+ const needsEasCli = await needsBinary('eas');
118
+ if (needsEasCli) {
119
+ await $`bun install eas-cli@${easCliVersion} -g`.quiet();
120
+ } else {
121
+ const currentEasCliVersion = await $`eas --version`.text();
122
+ if (!semver.satisfies(currentEasCliVersion, easCliVersion)) {
123
+ await $`bun install eas-cli@${easCliVersion} -g`.quiet();
124
+ }
125
+ }
126
+
127
+ const {
128
+ APP_BUNDLE_IDENTIFIER,
129
+ APP_ANDROID_BUNDLE_IDENTIFIER: androidId = APP_BUNDLE_IDENTIFIER as string,
130
+ APP_APPLE_BUNDLE_IDENTIFIER: appleId = APP_BUNDLE_IDENTIFIER as string,
131
+ } = easJsonConfig.env ?? {};
132
+
133
+ // TODO: Add additional checks for ensuring this value adheres to Android formatting specs
134
+ if (isAndroid && !androidId) {
135
+ throw new Error(`
136
+ APP_ANDROID_BUNDLE_IDENTIFIER must be defined in eas.json within the ${profile} > env config.
137
+ See https://docs.expo.dev/build-reference/variables/#setting-plaintext-environment-variables-in-easjson for information about env variables in eas.json
138
+
139
+ This env variable is used to populate the package key in your app.config.(js|ts) > android
140
+ The format of this env variable must use DNS notation unique name for your app, which is a valid Android Application ID.
141
+
142
+ For example you could use, com.company.app, where com.company is our domain and app is our app.
143
+
144
+ We recommend having the release build use the simplest identifier such as com.company.app and your debug variants
145
+ adding additional context such as com.company.app_debug. When publishing, the release identifier must be unique on the Play Store.
146
+
147
+ The name may only contain lowercase and uppercase letters (a-z, A-Z), numbers (0-9) and underscores (_),
148
+ separated by periods (.). Each component of the name should start with a lowercase letter.
149
+
150
+ These formatting rules only applies to Android. iOS has different requirements.
151
+
152
+ See https://docs.expo.dev/versions/latest/config/app/#package for more details on formatting.
153
+ See https://docs.expo.dev/build-reference/variants/ for information about build variants.
154
+ `);
155
+ }
156
+
157
+ // TODO: Add additional checks for ensuring this value adheres to Uniform Type Identifier
158
+ if (isIOS && !appleId) {
159
+ throw new Error(`
160
+ APP_APPLE_BUNDLE_IDENTIFIER must be defined in eas.json within the ${profile} > env config.
161
+ See https://docs.expo.dev/build-reference/variables/#setting-plaintext-environment-variables-in-easjson for information about env variables in eas.json
162
+
163
+ This env variable is used to populate the package key in your app.config.(js|ts) > ios
164
+ The format of this env variable must use DNS notation unique name for your app, which is a valid Android Application ID.
165
+
166
+ For example you could use, com.company.app, where com.company is our domain and app is our app.
167
+
168
+ We recommend having the release build use the simplest identifier such as com.company.app and your debug variants
169
+ adding additional context such as com.company.app-debug. When publishing, the release identifier must be unique to the App Store.
170
+
171
+ The string format should be Uniform Type Identifier(UTI), which is alphanumeric characters (A-Z,a-z,0-9), hyphen (-), and period (.)
172
+
173
+ These formatting rules only applies to iOS. Android has different requirements.
174
+
175
+ See https://docs.expo.dev/versions/latest/config/app/#bundleidentifier for more details on formatting.
176
+ See https://docs.expo.dev/build-reference/variants/ for information about build variants.
177
+ `);
178
+ }
179
+
180
+ const outputName = `${platform}-${profile}-${jsEngine}`;
181
+ const prebuildsDir = `${appDirectory}/prebuilds`; // TODO: make this configurable
182
+ const outputDir = `${prebuildsDir}/${outputName}`;
183
+ const outputFileBase = `${outputDir}/${outputName}`;
184
+
185
+ /* -------------------------------------------------------------------------- */
186
+ /* ENVIRONMENT VARIABLES */
187
+ /* -------------------------------------------------------------------------- */
188
+ let envVars = Bun.env;
189
+
190
+ envVars = {
191
+ ...envVars,
192
+ APP_ANDROID_BUNDLE_IDENTIFIER: androidId,
193
+ APP_APPLE_BUNDLE_IDENTIFIER: appleId,
194
+ RCT_NO_LAUNCH_PACKAGER: '1',
195
+ EXPO_NO_TELEMETRY: '1',
196
+ EXPO_NO_WEB_SETUP: '1',
197
+ };
198
+
199
+ if (debug) {
200
+ envVars = {
201
+ ...envVars,
202
+ DEBUG: '*',
203
+ EAS_LOCAL_BUILD_SKIP_CLEANUP: '1',
204
+ EXPO_PROFILE: '1',
205
+ };
206
+ }
207
+
208
+ if (envOpts) {
209
+ envVars = { ...envVars, ...envOpts };
210
+ }
211
+
212
+ /** Change the default environment variables for shells created by this instance. */
213
+ $.env(envVars);
214
+
215
+ const output = {
216
+ name: outputName,
217
+ dir: outputDir,
218
+ prebuildsDir: prebuildsDir,
219
+ fileBase: outputFileBase,
220
+ artifact: isIOS ? `${outputFileBase}.tar.gz` : `${outputFileBase}.zip`,
221
+ app: isIOS ? `${outputFileBase}.app` : 'todo fix android',
222
+ get launchFile() {
223
+ return isIOS ? this.artifact : this.apk.signed;
224
+ },
225
+ apk: {
226
+ contents: `${outputFileBase}/build`,
227
+ rebuilt: `${outputFileBase}/binary-rebuilt.apk`,
228
+ rebuiltAligned: `${outputFileBase}/binary-rebuilt-aligned.apk`,
229
+ signed: `${outputFileBase}/binary.apk`,
230
+ test: `${outputFileBase}/testBinary.apk`,
231
+ },
232
+ };
233
+
234
+ return {
235
+ scheme: isIOS ? appleId : androidId,
236
+ channel: easJsonConfig.channel,
237
+ debug,
238
+ profile,
239
+ platform,
240
+ jsEngine,
241
+ appDirectory,
242
+ output,
243
+ };
244
+ }
@@ -0,0 +1,96 @@
1
+ import { $, semver } from 'bun';
2
+ import { setup, needsBinary, needsBrewFormula, type MobileProps } from './_setup';
3
+
4
+ export default {
5
+ name: 'build',
6
+ description: '🚀 Build',
7
+ run: async (props: MobileProps) => {
8
+ const { platform, profile, output } = await setup({
9
+ props,
10
+ });
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
+
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!');
95
+ },
96
+ };
@@ -0,0 +1,82 @@
1
+ import { Props } from 'bluebun';
2
+ import { $, sleep } from 'bun';
3
+ import { setup, type MobileProps } from './_setup';
4
+
5
+ interface MobileStartProps extends Props {
6
+ options: MobileProps['options'] & {
7
+ // Clear the cache when starting dev server
8
+ clear?: boolean;
9
+ };
10
+ }
11
+
12
+ type SimDevice = { udid: string; name: string; state: 'Booted' | 'Shutdown' };
13
+ type SimList = {
14
+ devices: {
15
+ [key: string]: SimDevice[];
16
+ };
17
+ };
18
+
19
+ export default {
20
+ name: 'dev',
21
+ description: '🚧 Dev',
22
+ run: async (props: MobileStartProps) => {
23
+ const { platform, scheme, output } = await setup({
24
+ props,
25
+ env: {
26
+ EXPO_USE_METRO_WORKSPACE_ROOT: '1',
27
+ },
28
+ });
29
+
30
+ const extraArgs = [];
31
+
32
+ if (props.options.clear) {
33
+ extraArgs.push('--clear');
34
+ }
35
+ const extrArgsString = extraArgs.join(' ');
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;
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
+ }
60
+
61
+ /**
62
+ * In dev mode we use a debug build, which requires a metro server to serve
63
+ * the Javascript bundle.
64
+ *
65
+ * To avoid the, "Could not find metro server" error screen on launch,
66
+ * we delay the launch of the app until after the metro server has started.
67
+ */
68
+ sleep(3000).then(async () => {
69
+ /**
70
+ * Launch the app on the booted device
71
+ * https://github.com/expo/eas-cli?tab=readme-ov-file#eas-buildrun
72
+ */
73
+ await $`eas build:run --platform ${platform} --path ${output.launchFile}`;
74
+ });
75
+
76
+ console.write('Starting Metro server...');
77
+ await $`expo start --${platform} --dev-client --localhost --scheme ${scheme} ${extrArgsString}`;
78
+ } catch (err) {
79
+ throw err;
80
+ }
81
+ },
82
+ };
@@ -0,0 +1,23 @@
1
+ import { type Props } from 'bluebun';
2
+ import { getCommandHelp } from '../../utils/getCommandHelp';
3
+
4
+ export default {
5
+ name: 'expo',
6
+ description: '',
7
+ run: async (props: Props) => {
8
+ switch (props?.first) {
9
+ case 'build':
10
+ return require('./build').default.run(props);
11
+ case 'dev':
12
+ return require('./dev').default.run(props);
13
+ case 'launch':
14
+ return require('./launch').default.run(props);
15
+ case 'update':
16
+ return require('./update').default.run(props);
17
+ default: {
18
+ await getCommandHelp(props);
19
+ break;
20
+ }
21
+ }
22
+ },
23
+ };
@@ -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 { $ } from 'bun';
2
+ import type { MobileProps } from './_setup';
3
+ import { setup } from './_setup';
4
+
5
+ export default {
6
+ name: 'launch',
7
+ description: '🚀 Launch',
8
+ run: async (props: MobileProps) => {
9
+ const { platform, output } = await setup({
10
+ props,
11
+ });
12
+
13
+ await $`eas build:run --platform ${platform} --path ${output.launchFile}`;
14
+ },
15
+ };