eas-cli 16.24.0 → 16.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,12 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.runLocalBuildAsync = exports.LocalBuildMode = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const eas_build_job_1 = require("@expo/eas-build-job");
5
6
  const spawn_async_1 = tslib_1.__importDefault(require("@expo/spawn-async"));
6
7
  const semver_1 = tslib_1.__importDefault(require("semver"));
8
+ const api_1 = require("../api");
7
9
  const log_1 = tslib_1.__importDefault(require("../log"));
8
10
  const ora_1 = require("../ora");
9
11
  const PLUGIN_PACKAGE_NAME = 'eas-cli-local-build-plugin';
10
- const PLUGIN_PACKAGE_VERSION = '1.0.171';
12
+ const PLUGIN_PACKAGE_VERSION = eas_build_job_1.version; // should match version of @expo/eas-build-job
11
13
  var LocalBuildMode;
12
14
  (function (LocalBuildMode) {
13
15
  /**
@@ -44,6 +46,7 @@ async function runLocalBuildAsync(job, metadata, options, env) {
44
46
  ...env,
45
47
  ...process.env,
46
48
  EAS_LOCAL_BUILD_WORKINGDIR: options.workingdir ?? process.env.EAS_LOCAL_BUILD_WORKINGDIR,
49
+ __API_SERVER_URL: (0, api_1.getExpoApiBaseUrl)(),
47
50
  ...(options.skipCleanup || options.skipNativeBuild
48
51
  ? { EAS_LOCAL_BUILD_SKIP_CLEANUP: '1' }
49
52
  : {}),
@@ -2,17 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.initializeGitRepositoryAsync = exports.installProjectDependenciesAsync = exports.cloneTemplateAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
5
6
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
7
  const path_1 = tslib_1.__importDefault(require("path"));
7
- const log_1 = tslib_1.__importDefault(require("../../log"));
8
+ const utils_1 = require("./utils");
8
9
  const git_1 = require("../../onboarding/git");
9
10
  const installDependencies_1 = require("../../onboarding/installDependencies");
10
11
  const runCommand_1 = require("../../onboarding/runCommand");
12
+ const ora_1 = require("../../ora");
11
13
  async function cloneTemplateAsync(targetProjectDir) {
12
14
  const githubUsername = 'expo';
13
15
  const githubRepositoryName = 'expo-template-default';
14
- log_1.default.log(`📂 Cloning the project to ${targetProjectDir}`);
15
- log_1.default.newLine();
16
+ const spinner = (0, ora_1.ora)(`${chalk_1.default.bold(`Cloning the project to ${(0, utils_1.printDirectory)(targetProjectDir)}`)}`).start();
16
17
  const cloneMethod = (await (0, git_1.canAccessRepositoryUsingSshAsync)({
17
18
  githubUsername,
18
19
  githubRepositoryName,
@@ -24,11 +25,14 @@ async function cloneTemplateAsync(targetProjectDir) {
24
25
  githubRepositoryName,
25
26
  targetProjectDir,
26
27
  cloneMethod,
28
+ showOutput: false,
27
29
  });
30
+ spinner.succeed(`Cloned the project to ${(0, utils_1.printDirectory)(finalTargetProjectDirectory)}`);
28
31
  return finalTargetProjectDirectory;
29
32
  }
30
33
  exports.cloneTemplateAsync = cloneTemplateAsync;
31
34
  async function installProjectDependenciesAsync(projectDir, packageManager) {
35
+ const spinner = (0, ora_1.ora)(`${chalk_1.default.bold('Installing project dependencies')}`).start();
32
36
  await (0, installDependencies_1.installDependenciesAsync)({
33
37
  outputLevel: 'none',
34
38
  projectDir,
@@ -36,16 +40,20 @@ async function installProjectDependenciesAsync(projectDir, packageManager) {
36
40
  });
37
41
  const dependencies = ['expo-updates', '@expo/metro-runtime'];
38
42
  for (const dependency of dependencies) {
43
+ spinner.text = `Installing ${chalk_1.default.bold(dependency)}`;
39
44
  await (0, runCommand_1.runCommandAsync)({
40
45
  cwd: projectDir,
41
46
  command: 'npx',
42
47
  args: ['expo', 'install', dependency],
43
48
  showOutput: false,
49
+ showSpinner: false,
44
50
  });
45
51
  }
52
+ spinner.succeed(`Installed project dependencies`);
46
53
  }
47
54
  exports.installProjectDependenciesAsync = installProjectDependenciesAsync;
48
55
  async function initializeGitRepositoryAsync(projectDir) {
56
+ const spinner = (0, ora_1.ora)(`${chalk_1.default.bold('Initializing Git repository')}`).start();
49
57
  await fs_extra_1.default.remove(path_1.default.join(projectDir, '.git'));
50
58
  const commands = [['init'], ['add', '.'], ['commit', '-m', 'Initial commit']];
51
59
  for (const args of commands) {
@@ -54,8 +62,9 @@ async function initializeGitRepositoryAsync(projectDir) {
54
62
  command: 'git',
55
63
  args,
56
64
  showOutput: false,
65
+ showSpinner: false,
57
66
  });
58
- log_1.default.log();
59
67
  }
68
+ spinner.succeed(`Initialized Git repository`);
60
69
  }
61
70
  exports.initializeGitRepositoryAsync = initializeGitRepositoryAsync;
@@ -1,7 +1,7 @@
1
1
  import { Choice } from '../../prompts';
2
2
  import { Actor } from '../../user/User';
3
3
  import { ExpoGraphqlClient } from '../context/contextUtils/createGraphqlClient';
4
- export declare function generateProjectConfigAsync(actor: Actor, pathArg: string | undefined, options: {
4
+ export declare function generateProjectConfigAsync(pathArg: string | undefined, options: {
5
5
  graphqlClient: ExpoGraphqlClient;
6
6
  projectAccount: string;
7
7
  }): Promise<{
@@ -10,13 +10,12 @@ export declare function generateProjectConfigAsync(actor: Actor, pathArg: string
10
10
  }>;
11
11
  export declare function promptForProjectAccountAsync(actor: Actor): Promise<string>;
12
12
  export declare function getAccountChoices(actor: Actor, permissionsMap?: Map<string, boolean>): Choice[];
13
- export declare function generateProjectNameVariations(actor: Actor, baseName: string): string[];
14
13
  /**
15
14
  * Finds an available project name that doesn't conflict with either:
16
15
  * Local filesystem (directory already exists)
17
16
  * Remote server (project already exists on Expo)
18
17
  */
19
- export declare function findAvailableProjectNameAsync(actor: Actor, baseName: string, parentDirectory: string, { graphqlClient, projectAccount, }: {
18
+ export declare function findAvailableProjectNameAsync(baseName: string, parentDirectory: string, { graphqlClient, projectAccount, }: {
20
19
  graphqlClient: ExpoGraphqlClient;
21
20
  projectAccount: string;
22
21
  }): Promise<{
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.findAvailableProjectNameAsync = exports.generateProjectNameVariations = exports.getAccountChoices = exports.promptForProjectAccountAsync = exports.generateProjectConfigAsync = void 0;
3
+ exports.findAvailableProjectNameAsync = exports.getAccountChoices = exports.promptForProjectAccountAsync = exports.generateProjectConfigAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
6
  const nanoid_1 = require("nanoid");
7
7
  const path_1 = tslib_1.__importDefault(require("path"));
8
+ const utils_1 = require("./utils");
8
9
  const generated_1 = require("../../graphql/generated");
9
10
  const log_1 = tslib_1.__importDefault(require("../../log"));
10
11
  const fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync_1 = require("../../project/fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync");
11
12
  const prompts_1 = require("../../prompts");
12
- const User_1 = require("../../user/User");
13
13
  function validateProjectPath(resolvedPath) {
14
14
  const normalizedPath = path_1.default.normalize(resolvedPath);
15
15
  // Check for path traversal attempts
@@ -23,8 +23,8 @@ function validateProjectPath(resolvedPath) {
23
23
  throw new Error(`Invalid project path: "${resolvedPath}". Cannot create projects in system directories.`);
24
24
  }
25
25
  }
26
- async function generateProjectConfigAsync(actor, pathArg, options) {
27
- let baseName = 'new-expo-project';
26
+ async function generateProjectConfigAsync(pathArg, options) {
27
+ let baseName = 'expo-project';
28
28
  let parentDirectory = process.cwd();
29
29
  if (pathArg) {
30
30
  const resolvedPath = path_1.default.isAbsolute(pathArg) ? pathArg : path_1.default.resolve(process.cwd(), pathArg);
@@ -32,9 +32,18 @@ async function generateProjectConfigAsync(actor, pathArg, options) {
32
32
  baseName = path_1.default.basename(resolvedPath);
33
33
  parentDirectory = path_1.default.dirname(resolvedPath);
34
34
  }
35
+ else {
36
+ baseName = (await (0, prompts_1.promptAsync)({
37
+ type: 'text',
38
+ name: 'name',
39
+ message: 'What would you like to name your project?',
40
+ initial: 'expo-project',
41
+ })).name;
42
+ }
35
43
  // Find an available name checking both local filesystem and remote server
36
- const { projectName, projectDirectory } = await findAvailableProjectNameAsync(actor, baseName, parentDirectory, options);
37
- log_1.default.withInfo(`Using project directory: ${projectDirectory}`);
44
+ const { projectName, projectDirectory } = await findAvailableProjectNameAsync(baseName, parentDirectory, options);
45
+ log_1.default.withInfo(`Using project name: ${projectName}`);
46
+ log_1.default.withInfo(`Using project directory: ${(0, utils_1.printDirectory)(projectDirectory)}`);
38
47
  return {
39
48
  projectName,
40
49
  projectDirectory,
@@ -88,16 +97,6 @@ function getAccountChoices(actor, permissionsMap) {
88
97
  });
89
98
  }
90
99
  exports.getAccountChoices = getAccountChoices;
91
- function generateProjectNameVariations(actor, baseName) {
92
- const username = (0, User_1.getActorUsername)(actor);
93
- const date = new Date().toISOString().split('T')[0];
94
- return [
95
- baseName,
96
- `${baseName}-${username}-${date}`,
97
- `${baseName}-${username}-${date}-${(0, nanoid_1.nanoid)(6)}`,
98
- ];
99
- }
100
- exports.generateProjectNameVariations = generateProjectNameVariations;
101
100
  async function verifyProjectDoesNotExistAsync(graphqlClient, accountName, projectName, { silent = false } = {}) {
102
101
  const existingProjectId = await (0, fetchOrCreateProjectIDForWriteToConfigWithConfirmationAsync_1.findProjectIdByAccountNameAndSlugNullableAsync)(graphqlClient, accountName, projectName);
103
102
  const doesNotExist = existingProjectId === null;
@@ -111,26 +110,18 @@ async function verifyProjectDoesNotExistAsync(graphqlClient, accountName, projec
111
110
  * Local filesystem (directory already exists)
112
111
  * Remote server (project already exists on Expo)
113
112
  */
114
- async function findAvailableProjectNameAsync(actor, baseName, parentDirectory, { graphqlClient, projectAccount, }) {
115
- const nameVariations = generateProjectNameVariations(actor, baseName);
116
- for (let i = 0; i < nameVariations.length; i++) {
117
- const nameVariation = nameVariations[i];
118
- const proposedDirectory = path_1.default.join(parentDirectory, nameVariation);
119
- const usingVariant = i !== 0;
120
- const localExists = await fs_extra_1.default.pathExists(proposedDirectory);
121
- if (localExists) {
122
- continue;
123
- }
124
- const remoteAvailable = await verifyProjectDoesNotExistAsync(graphqlClient, projectAccount, nameVariation, { silent: usingVariant });
125
- if (!remoteAvailable) {
126
- continue;
127
- }
128
- log_1.default.withInfo(`Using ${usingVariant ? 'alternate ' : ''}project name: ${nameVariation}`);
129
- return {
130
- projectName: nameVariation,
131
- projectDirectory: proposedDirectory,
132
- };
113
+ async function findAvailableProjectNameAsync(baseName, parentDirectory, { graphqlClient, projectAccount, }) {
114
+ let projectName = baseName;
115
+ let projectDirectory = path_1.default.join(parentDirectory, projectName);
116
+ const localExists = await fs_extra_1.default.pathExists(projectDirectory);
117
+ const remoteAvailable = await verifyProjectDoesNotExistAsync(graphqlClient, projectAccount, projectName);
118
+ if (localExists || !remoteAvailable) {
119
+ projectName = `${baseName}-${(0, nanoid_1.nanoid)(6)}`;
120
+ projectDirectory = path_1.default.join(parentDirectory, projectName);
133
121
  }
134
- throw new Error(`Unable to find a unique project name for "${baseName}". All generated variations already exist.`);
122
+ return {
123
+ projectName,
124
+ projectDirectory,
125
+ };
135
126
  }
136
127
  exports.findAvailableProjectNameAsync = findAvailableProjectNameAsync;
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.updateReadmeAsync = exports.copyProjectTemplatesAsync = exports.updatePackageJsonAsync = exports.generateEasConfigAsync = exports.generateAppConfigAsync = exports.cleanAndPrefix = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_json_1 = require("@expo/eas-json");
6
- const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
6
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
8
7
  const path_1 = tslib_1.__importDefault(require("path"));
9
8
  const ts_deepmerge_1 = tslib_1.__importDefault(require("ts-deepmerge"));
10
9
  const api_1 = require("../../api");
11
- const log_1 = tslib_1.__importStar(require("../../log"));
12
10
  const easCli_1 = require("../../utils/easCli");
13
11
  // Android package names must start with a lowercase letter
14
12
  // schemes must start with a lowercase letter and can only contain lowercase letters, digits, "+", "." or "-"
@@ -57,8 +55,6 @@ async function generateAppConfigAsync(projectDir, app) {
57
55
  const mergedConfig = (0, ts_deepmerge_1.default)(baseExpoConfig, expoConfig);
58
56
  const appJsonPath = path_1.default.join(projectDir, 'app.json');
59
57
  await fs_extra_1.default.writeJson(appJsonPath, { expo: mergedConfig }, { spaces: 2 });
60
- log_1.default.withTick(`Generated ${chalk_1.default.bold('app.json')}. ${(0, log_1.learnMore)('https://docs.expo.dev/versions/latest/config/app/')}`);
61
- log_1.default.log();
62
58
  }
63
59
  exports.generateAppConfigAsync = generateAppConfigAsync;
64
60
  async function generateEasConfigAsync(projectDir) {
@@ -104,8 +100,6 @@ async function generateEasConfigAsync(projectDir) {
104
100
  };
105
101
  const easJsonPath = path_1.default.join(projectDir, 'eas.json');
106
102
  await fs_extra_1.default.writeJson(easJsonPath, easJson, { spaces: 2 });
107
- log_1.default.withTick(`Generated ${chalk_1.default.bold('eas.json')}. ${(0, log_1.learnMore)('https://docs.expo.dev/build-reference/eas-json/')}`);
108
- log_1.default.log();
109
103
  }
110
104
  exports.generateEasConfigAsync = generateEasConfigAsync;
111
105
  async function updatePackageJsonAsync(projectDir) {
@@ -114,24 +108,23 @@ async function updatePackageJsonAsync(projectDir) {
114
108
  if (!packageJson.scripts) {
115
109
  packageJson.scripts = {};
116
110
  }
117
- packageJson.scripts.preview = 'npx eas-cli@latest workflow:run publish-preview-update.yml';
111
+ packageJson.scripts.draft = 'npx eas-cli@latest workflow:run create-draft.yml';
118
112
  packageJson.scripts['development-builds'] =
119
113
  'npx eas-cli@latest workflow:run create-development-builds.yml';
120
114
  packageJson.scripts.deploy = 'npx eas-cli@latest workflow:run deploy-to-production.yml';
121
115
  await fs_extra_1.default.writeJson(packageJsonPath, packageJson, { spaces: 2 });
122
- log_1.default.withTick('Updated package.json with scripts');
123
- log_1.default.log();
124
116
  }
125
117
  exports.updatePackageJsonAsync = updatePackageJsonAsync;
126
118
  async function copyProjectTemplatesAsync(projectDir) {
127
- const templatesSourceDir = path_1.default.join(__dirname, 'templates', '.eas', 'workflows');
128
- const easWorkflowsTargetDir = path_1.default.join(projectDir, '.eas', 'workflows');
129
- await fs_extra_1.default.copy(templatesSourceDir, easWorkflowsTargetDir, {
119
+ const templatesSourceDir = path_1.default.join(__dirname, 'templates');
120
+ // Copy everything from templates to projectDir, skipping readme-additions.md
121
+ await fs_extra_1.default.copy(templatesSourceDir, projectDir, {
130
122
  overwrite: true,
131
123
  errorOnExist: false,
124
+ filter: (src) => {
125
+ return !src.endsWith('readme-additions.md');
126
+ },
132
127
  });
133
- log_1.default.withTick('Created EAS workflow files');
134
- log_1.default.log();
135
128
  }
136
129
  exports.copyProjectTemplatesAsync = copyProjectTemplatesAsync;
137
130
  async function updateReadmeAsync(projectDir, packageManager) {
@@ -161,7 +154,5 @@ async function updateReadmeAsync(projectDir, packageManager) {
161
154
  }
162
155
  mergedReadme = mergedReadme.replaceAll('npm run', `${packageManager} run`);
163
156
  await fs_extra_1.default.writeFile(projectReadmePath, mergedReadme);
164
- log_1.default.withTick('Updated README.md with EAS configuration details');
165
- log_1.default.log();
166
157
  }
167
158
  exports.updateReadmeAsync = updateReadmeAsync;
@@ -1,4 +1,4 @@
1
- name: Publish preview update
1
+ name: Create draft
2
2
 
3
3
  on:
4
4
  push:
@@ -10,3 +10,6 @@ jobs:
10
10
  type: update
11
11
  params:
12
12
  branch: ${{ github.ref_name || 'test' }}
13
+ deploy_website:
14
+ name: Deploy website
15
+ type: deploy
@@ -0,0 +1,163 @@
1
+ # AGENTS.md
2
+
3
+ ## Project Overview
4
+
5
+ This is an Expo/React Native mobile application. Prioritize mobile-first patterns, performance, and cross-platform compatibility.
6
+
7
+ ## Documentation Resources
8
+
9
+ When working on this project, **always consult the official Expo documentation** available at:
10
+
11
+ - **https://docs.expo.dev/llms.txt** - Index of all available documentation files
12
+ - **https://docs.expo.dev/llms-full.txt** - Complete Expo documentation including Expo Router, Expo Modules API, development process
13
+ - **https://docs.expo.dev/llms-eas.txt** - Complete EAS (Expo Application Services) documentation
14
+ - **https://docs.expo.dev/llms-sdk.txt** - Complete Expo SDK documentation
15
+ - **https://reactnative.dev/docs/getting-started** - Complete React Native documentation
16
+
17
+ These documentation files are specifically formatted for AI agents and should be your **primary reference** for:
18
+
19
+ - Expo APIs and best practices
20
+ - Expo Router navigation patterns
21
+ - EAS Build, Submit, and Update workflows
22
+ - Expo SDK modules and their usage
23
+ - Development and deployment processes
24
+
25
+ ## Project Structure
26
+
27
+ ```
28
+ /
29
+ ├── app/ # Expo Router file-based routing
30
+ │ ├── (tabs)/ # Tab-based navigation screens
31
+ │ │ ├── index.tsx # Home screen
32
+ │ │ ├── explore.tsx # Explore screen
33
+ │ │ └── _layout.tsx # Tabs layout
34
+ │ ├── _layout.tsx # Root layout with theme provider
35
+ │ └── modal.tsx # Modal screen example
36
+ ├── components/ # Reusable React components
37
+ │ ├── ui/ # UI primitives (IconSymbol, Collapsible)
38
+ │ └── ... # Feature components (themed, haptic, parallax)
39
+ ├── constants/ # App-wide constants (theme, colors)
40
+ ├── hooks/ # Custom React hooks (color scheme, theme)
41
+ ├── assets/ # Static assets (images, fonts)
42
+ ├── scripts/ # Utility scripts (reset-project)
43
+ ├── .eas/workflows/ # EAS Workflows (CI/CD automation)
44
+ ├── app.json # Expo configuration
45
+ ├── eas.json # EAS Build/Submit configuration
46
+ └── package.json # Dependencies and scripts
47
+ ```
48
+
49
+ ## Essential Commands
50
+
51
+ ### Development
52
+
53
+ ```bash
54
+ npx expo start # Start dev server
55
+ npx expo start --clear # Clear cache and start dev server
56
+ npx expo install <package> # Install packages with compatible versions
57
+ npx expo install --check # Check which installed packages need to be updated
58
+ npx expo install --fix # Automatically update any invalid package versions
59
+ npm run development-builds # Create development builds (workflow)
60
+ npm run reset-project # Reset to blank template
61
+ ```
62
+
63
+ ### Building & Testing
64
+
65
+ ```bash
66
+ npx expo doctor # Check project health and dependencies
67
+ npx expo lint # Run ESLint
68
+ npm run draft # Publish preview update and website (workflow)
69
+ ```
70
+
71
+ ### Production
72
+
73
+ ```bash
74
+ npx eas-cli@latest build --platform ios -s # Use EAS to build for iOS platform and submit to App Store
75
+ npx eas-cli@latest build --platform android -s # Use EAS to build for Android platform and submit to Google Play Store
76
+ npm run deploy # Deploy to production (workflow)
77
+ ```
78
+
79
+ ## Development Guidelines
80
+
81
+ ### Code Style & Standards
82
+
83
+ - **TypeScript First**: Use TypeScript for all new code with strict type checking
84
+ - **Naming Conventions**: Use meaningful, descriptive names for variables, functions, and components
85
+ - **Self-Documenting Code**: Write clear, readable code that explains itself; only add comments for complex business logic or design decisions
86
+ - **React 19 Patterns**: Follow modern React patterns including:
87
+ - Function components with hooks
88
+ - Enable React Compiler
89
+ - Proper dependency arrays in useEffect
90
+ - Memoization when appropriate (useMemo, useCallback)
91
+ - Error boundaries for better error handling
92
+
93
+ ### Navigation & Routing
94
+
95
+ - Use **Expo Router** for all navigation
96
+ - Import `Link`, `router`, and `useLocalSearchParams` from `expo-router`
97
+ - Docs: https://docs.expo.dev/router/introduction/
98
+
99
+ ### Recommended Libraries
100
+
101
+ - **Navigation**: `expo-router` for navigation
102
+ - **Images**: `expo-image` for optimized image handling and caching
103
+ - **Animations**: `react-native-reanimated` for performant animations on native thread
104
+ - **Gestures**: `react-native-gesture-handler` for native gesture recognition
105
+ - **Storage**: Use `expo-sqlite` for persistent storage, `expo-sqlite/kv-store` for simple key-value storage
106
+
107
+ ## Debugging & Development Tools
108
+
109
+ ### DevTools Integration
110
+
111
+ - **React Native DevTools**: Use MCP `open_devtools` command to launch debugging tools
112
+ - **Network Inspection**: Monitor API calls and network requests in DevTools
113
+ - **Element Inspector**: Debug component hierarchy and styles
114
+ - **Performance Profiler**: Identify performance bottlenecks
115
+ - **Logging**: Use `console.log` for debugging (remove before production), `console.warn` for deprecation notices, `console.error` for actual errors, and implement error boundaries for production error handling
116
+
117
+ ### Testing & Quality Assurance
118
+
119
+ #### Automated Testing with MCP Tools
120
+
121
+ Developers can configure the Expo MCP server with the following doc: https://docs.expo.dev/eas/ai/mcp/
122
+
123
+ - **Component Testing**: Add `testID` props to components for automation
124
+ - **Visual Testing**: Use MCP `automation_take_screenshot` to verify UI appearance
125
+ - **Interaction Testing**: Use MCP `automation_tap_by_testid` to simulate user interactions
126
+ - **View Verification**: Use MCP `automation_find_view_by_testid` to validate component rendering
127
+
128
+ ## EAS Workflows CI/CD
129
+
130
+ This project is pre-configured with **EAS Workflows** for automating development and release processes. Workflows are defined in `.eas/workflows/` directory.
131
+
132
+ When working with EAS Workflows, **always refer to**:
133
+
134
+ - https://docs.expo.dev/eas/workflows/ for workflow examples
135
+ - The `.eas/workflows/` directory for existing workflow configurations
136
+ - You can check that a workflow YAML is valid using the workflows schema: https://exp.host/--/api/v2/workflows/schema
137
+
138
+ ### Build Profiles (eas.json)
139
+
140
+ - **development**: Development builds with dev client
141
+ - **development-simulator**: Development builds for iOS simulator
142
+ - **preview**: Internal distribution preview builds
143
+ - **production**: Production builds with auto-increment
144
+
145
+ ## Troubleshooting
146
+
147
+ ### Expo Go Errors & Development Builds
148
+
149
+ If there are errors in **Expo Go** or the project is not running, create a **development build**. **Expo Go** is a sandbox environment with a limited set of native modules. To create development builds, run `eas build:dev`. Additionally, after installing new packages or adding config plugins, new development builds are often required.
150
+
151
+ ## AI Agent Instructions
152
+
153
+ When working on this project:
154
+
155
+ 1. **Always start by consulting the appropriate documentation**:
156
+
157
+ - For general Expo questions: https://docs.expo.dev/llms-full.txt
158
+ - For EAS/deployment questions: https://docs.expo.dev/llms-eas.txt
159
+ - For SDK/API questions: https://docs.expo.dev/llms-sdk.txt
160
+
161
+ 2. **Understand before implementing**: Read the relevant docs section before writing code
162
+
163
+ 3. **Follow existing patterns**: Look at existing components and screens for patterns to follow
@@ -0,0 +1,9 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ **Note**: This project uses `AGENTS.md` files for detailed guidance.
6
+
7
+ ## Primary Reference
8
+
9
+ Please see `AGENTS.md` in this same directory for the main project documentation and guidance.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Formats a directory path for display.
3
+ * If the directory is within the current working directory, returns a relative path.
4
+ * Otherwise, returns the absolute path.
5
+ */
6
+ export declare function printDirectory(directory: string): string;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printDirectory = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ /**
7
+ * Formats a directory path for display.
8
+ * If the directory is within the current working directory, returns a relative path.
9
+ * Otherwise, returns the absolute path.
10
+ */
11
+ function printDirectory(directory) {
12
+ const cwd = process.cwd();
13
+ const absoluteDir = path_1.default.isAbsolute(directory) ? directory : path_1.default.resolve(cwd, directory);
14
+ const relativePath = path_1.default.relative(cwd, absoluteDir);
15
+ // If the relative path doesn't start with '..' it means it's within or at the cwd
16
+ if (!relativePath.startsWith('..') && !path_1.default.isAbsolute(relativePath)) {
17
+ return relativePath !== '' ? `./${relativePath}` : '.';
18
+ }
19
+ return absoluteDir;
20
+ }
21
+ exports.printDirectory = printDirectory;
@@ -17,10 +17,12 @@ const flags_1 = require("../../commandUtils/flags");
17
17
  const generated_1 = require("../../graphql/generated");
18
18
  const log_1 = tslib_1.__importStar(require("../../log"));
19
19
  const platform_1 = require("../../platform");
20
+ const projectUtils_1 = require("../../project/projectUtils");
20
21
  const prompts_1 = require("../../prompts");
21
22
  const uniq_1 = tslib_1.__importDefault(require("../../utils/expodash/uniq"));
22
23
  const json_1 = require("../../utils/json");
23
24
  const statuspageService_1 = require("../../utils/statuspageService");
25
+ const checkForOverages_1 = require("../../utils/usage/checkForOverages");
24
26
  class Build extends EasCommand_1.default {
25
27
  static description = 'start a build';
26
28
  static flags = {
@@ -120,6 +122,12 @@ class Build extends EasCommand_1.default {
120
122
  await (0, statuspageService_1.maybeWarnAboutEasOutagesAsync)(graphqlClient, flags.autoSubmit
121
123
  ? [generated_1.StatuspageServiceName.EasBuild, generated_1.StatuspageServiceName.EasSubmit]
122
124
  : [generated_1.StatuspageServiceName.EasBuild]);
125
+ const { projectId } = await getDynamicPrivateProjectConfigAsync();
126
+ const account = await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId);
127
+ await (0, checkForOverages_1.maybeWarnAboutUsageOveragesAsync)({
128
+ graphqlClient,
129
+ accountId: account.id,
130
+ });
123
131
  }
124
132
  const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
125
133
  const { buildProfiles } = await (0, runBuildAndSubmit_1.runBuildAndSubmitAsync)({
@@ -10,6 +10,7 @@ const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasComm
10
10
  const commands_1 = require("../../commandUtils/new/commands");
11
11
  const configs_1 = require("../../commandUtils/new/configs");
12
12
  const projectFiles_1 = require("../../commandUtils/new/projectFiles");
13
+ const utils_1 = require("../../commandUtils/new/utils");
13
14
  const AppMutation_1 = require("../../graphql/mutations/AppMutation");
14
15
  const AppQuery_1 = require("../../graphql/queries/AppQuery");
15
16
  const log_1 = tslib_1.__importStar(require("../../log"));
@@ -18,7 +19,7 @@ const ora_1 = require("../../ora");
18
19
  const expoConfig_1 = require("../../project/expoConfig");
19
20
  async function generateConfigsAsync(args, actor, graphqlClient) {
20
21
  const projectAccount = await (0, configs_1.promptForProjectAccountAsync)(actor);
21
- const { projectName, projectDirectory } = await (0, configs_1.generateProjectConfigAsync)(actor, args.path, {
22
+ const { projectName, projectDirectory } = await (0, configs_1.generateProjectConfigAsync)(args.path, {
22
23
  graphqlClient,
23
24
  projectAccount,
24
25
  });
@@ -51,16 +52,20 @@ async function createProjectAsync({ graphqlClient, actor, projectDirectory, proj
51
52
  await (0, expoConfig_1.createOrModifyExpoConfigAsync)(projectDirectory, {
52
53
  extra: { ...exp.extra, eas: { ...exp.extra?.eas, projectId } },
53
54
  }, { skipSDKVersionRequirement: true });
54
- log_1.default.withTick(`Project successfully linked (ID: ${chalk_1.default.bold(projectId)}) (modified app.json)`);
55
+ log_1.default.withInfo(`Project successfully linked (ID: ${chalk_1.default.bold(projectId)})`);
55
56
  return projectId;
56
57
  }
57
58
  exports.createProjectAsync = createProjectAsync;
58
59
  async function generateProjectFilesAsync(projectDir, app, packageManager) {
60
+ const spinner = (0, ora_1.ora)(`Generating project files`).start();
59
61
  await (0, projectFiles_1.generateAppConfigAsync)(projectDir, app);
60
62
  await (0, projectFiles_1.generateEasConfigAsync)(projectDir);
61
63
  await (0, projectFiles_1.updatePackageJsonAsync)(projectDir);
62
64
  await (0, projectFiles_1.copyProjectTemplatesAsync)(projectDir);
63
65
  await (0, projectFiles_1.updateReadmeAsync)(projectDir, packageManager);
66
+ spinner.succeed(`Generated project files`);
67
+ log_1.default.withInfo(`Generated ${chalk_1.default.bold('app.json')}. ${(0, log_1.learnMore)('https://docs.expo.dev/versions/latest/config/app/')}`);
68
+ log_1.default.withInfo(`Generated ${chalk_1.default.bold('eas.json')}. ${(0, log_1.learnMore)('https://docs.expo.dev/build-reference/eas-json/')}`);
64
69
  }
65
70
  exports.generateProjectFilesAsync = generateProjectFilesAsync;
66
71
  class New extends EasCommand_1.default {
@@ -91,9 +96,7 @@ class New extends EasCommand_1.default {
91
96
  if (actor.__typename === 'Robot') {
92
97
  throw new Error('This command is not available for robot users. Make sure you are not using a robot token and try again.');
93
98
  }
94
- log_1.default.warn('This command is not yet implemented. It will create a new project, but it may not be fully configured.');
95
99
  log_1.default.log(`👋 Welcome to Expo, ${actor.username}!`);
96
- log_1.default.newLine();
97
100
  const { projectName, projectDirectory: targetProjectDirectory, projectAccount, } = await generateConfigsAsync(args, actor, graphqlClient);
98
101
  const projectDirectory = await (0, commands_1.cloneTemplateAsync)(targetProjectDirectory);
99
102
  const packageManager = flags['package-manager'];
@@ -109,12 +112,12 @@ class New extends EasCommand_1.default {
109
112
  await generateProjectFilesAsync(projectDirectory, app, packageManager);
110
113
  await (0, commands_1.initializeGitRepositoryAsync)(projectDirectory);
111
114
  log_1.default.log('🎉 We finished creating your new project.');
115
+ log_1.default.newLine();
112
116
  log_1.default.log('Next steps:');
113
- log_1.default.withInfo(`Run \`cd ${projectDirectory}\` to navigate to your project.`);
114
- log_1.default.withInfo(`Run \`${packageManager} run preview\` to create a preview build on EAS. ${(0, log_1.learnMore)('https://docs.expo.dev/eas/workflows/examples/publish-preview-update/')}`);
117
+ log_1.default.withInfo(`Run \`cd ${(0, utils_1.printDirectory)(projectDirectory)}\` to navigate to your project.`);
118
+ log_1.default.withInfo(`Run \`${packageManager} run draft\` to create a preview on EAS. ${(0, log_1.learnMore)('https://docs.expo.dev/eas/workflows/examples/publish-preview-update/')}`);
115
119
  log_1.default.withInfo(`Run \`${packageManager} run start\` to start developing locally. ${(0, log_1.learnMore)('https://docs.expo.dev/get-started/start-developing/')}`);
116
120
  log_1.default.withInfo(`See the README.md for more information about your project.`);
117
- log_1.default.newLine();
118
121
  }
119
122
  }
120
123
  exports.default = New;