@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.
- package/cli/README.md +135 -0
- package/cli/commands/config/config.ts +10 -0
- package/cli/commands/config/sync.ts +22 -0
- package/cli/commands/expo/_setup.ts +244 -0
- package/cli/commands/expo/build.ts +96 -0
- package/cli/commands/expo/dev.ts +82 -0
- package/cli/commands/expo/expo.ts +23 -0
- package/cli/commands/expo/install/cocoapods.rb +35 -0
- package/cli/commands/expo/launch.ts +15 -0
- package/cli/commands/expo/update.ts +16 -0
- package/cli/commands/nextjs/dev.ts +17 -0
- package/cli/commands/nextjs/nextjs.ts +10 -0
- package/cli/commands/uds.ts +10 -0
- package/cli/commands/version.ts +11 -0
- package/cli/env.d.ts +15 -0
- package/cli/eslint.config.mjs +8 -0
- package/cli/tsconfig.json +10 -0
- package/cli/uds-cli +7 -0
- package/cli/utils/configWorker.ts +9 -0
- package/cli/utils/getCommandHelp.ts +65 -0
- package/cli/utils/setupConfigWorker.ts +81 -0
- package/cli/utils/sortKeys.ts +27 -0
- package/cli/utils/types.ts +13 -0
- package/dist/{chunk-JKZI2WLD.js → chunk-AWTLI4D3.js} +1 -1
- package/dist/chunk-D4K3CXV6.js +0 -0
- package/dist/{chunk-Z34QGHWU.js → chunk-MBOOJIH7.js} +1 -1
- package/dist/chunk-MFA2Y7DA.js +1 -0
- package/dist/chunk-P7GR6E3K.js +1 -0
- package/dist/chunk-PQBOZFJV.js +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +7 -40
- package/dist/index.d.ts +7 -40
- package/dist/index.js +1 -1
- package/dist/{index.native-VVqy3X9H.d.cts → index.native-3ww4C4UV.d.cts} +1 -1
- package/dist/{index.native-DzfcCYUh.d.ts → index.native-9kYJrUPa.d.ts} +1 -1
- package/dist/index.native.cjs +1 -0
- package/dist/index.native.d.cts +1493 -0
- package/dist/index.native.d.ts +1493 -0
- package/dist/index.native.js +1 -0
- package/dist/tailwindPlugin.cjs +1 -1
- package/dist/tailwindPlugin.d.cts +1 -1
- package/dist/tailwindPlugin.d.ts +1 -1
- package/dist/tailwindPlugin.js +1 -1
- package/dist/tokens/index.cjs +1 -1
- package/dist/tokens/index.d.cts +3 -3
- package/dist/tokens/index.d.ts +3 -3
- package/dist/tokens/index.js +1 -1
- package/dist/tokens/index.native.cjs +1 -1
- package/dist/tokens/index.native.d.cts +2 -2
- package/dist/tokens/index.native.d.ts +2 -2
- package/dist/tokens/index.native.js +1 -1
- package/dist/tokens/parseTokens.cjs +1 -1
- package/dist/tokens/parseTokens.d.cts +1 -1
- package/dist/tokens/parseTokens.d.ts +1 -1
- package/dist/tokens/parseTokens.js +1 -1
- package/dist/tokens/parseTokens.native.d.cts +1 -1
- package/dist/tokens/parseTokens.native.d.ts +1 -1
- package/dist/tokens/parseTokens.native.js +1 -1
- package/dist/types-J4DLS6Xj.d.cts +38 -0
- package/dist/types-J4DLS6Xj.d.ts +38 -0
- package/dist/{types-VgTlNoi_.d.cts → types-hirL9Qk5.d.cts} +1 -0
- package/dist/{types-VgTlNoi_.d.ts → types-hirL9Qk5.d.ts} +1 -0
- package/fonts/mobile.cjs +29 -0
- package/fonts/mobile.d.ts +3 -0
- package/fonts/yahoo-icons.ttf +0 -0
- package/fonts/yahoo-sans-beta-bold.otf +0 -0
- package/fonts/yahoo-sans-beta-medium.otf +0 -0
- package/fonts/yahoo-sans-beta-regular.otf +0 -0
- package/fonts/yahoo-sans-black.otf +0 -0
- package/fonts/yahoo-sans-bold.otf +0 -0
- package/fonts/yahoo-sans-condensed-black.otf +0 -0
- package/fonts/yahoo-sans-condensed-bold.otf +0 -0
- package/fonts/yahoo-sans-condensed-light.otf +0 -0
- package/fonts/yahoo-sans-condensed-medium.otf +0 -0
- package/fonts/yahoo-sans-condensed-regular.otf +0 -0
- package/fonts/yahoo-sans-extrabold.otf +0 -0
- package/fonts/yahoo-sans-extralight.otf +0 -0
- package/fonts/yahoo-sans-italic.otf +0 -0
- package/fonts/yahoo-sans-light.otf +0 -0
- package/fonts/yahoo-sans-medium.otf +0 -0
- package/fonts/yahoo-sans-regular.otf +0 -0
- package/fonts/yahoo-sans-semibold.otf +0 -0
- package/fonts/yahoo-serif-display-black.otf +0 -0
- package/fonts/yahoo-serif-display-bold.otf +0 -0
- package/fonts/yahoo-serif-display-extrabold.otf +0 -0
- package/fonts/yahoo-serif-display-light.otf +0 -0
- package/fonts/yahoo-serif-display-regular.otf +0 -0
- package/fonts/yahoo-serif-text-bold.otf +0 -0
- package/fonts/yahoo-serif-text-italic.otf +0 -0
- package/fonts/yahoo-serif-text-regular.otf +0 -0
- package/package.json +114 -49
- package/bin/uds +0 -0
- 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,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
|
+
};
|