eas-cli 16.24.1 → 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.
- package/README.md +87 -87
- package/build/build/local.js +4 -1
- package/build/commandUtils/new/commands.js +13 -4
- package/build/commandUtils/new/configs.d.ts +2 -3
- package/build/commandUtils/new/configs.js +27 -36
- package/build/commandUtils/new/projectFiles.js +7 -16
- package/build/commandUtils/new/templates/.eas/workflows/{publish-preview-update.yml → create-draft.yml} +4 -1
- package/build/commandUtils/new/templates/AGENTS.md +163 -0
- package/build/commandUtils/new/templates/CLAUDE.md +9 -0
- package/build/commandUtils/new/utils.d.ts +6 -0
- package/build/commandUtils/new/utils.js +21 -0
- package/build/commands/build/index.js +8 -0
- package/build/commands/project/new.js +10 -7
- package/build/commands/workflow/run.js +10 -7
- package/build/graphql/generated.d.ts +35 -0
- package/build/graphql/queries/AccountUsageQuery.d.ts +5 -0
- package/build/graphql/queries/AccountUsageQuery.js +40 -0
- package/build/onboarding/git.d.ts +2 -1
- package/build/onboarding/git.js +2 -1
- package/build/onboarding/installDependencies.js +1 -0
- package/build/onboarding/runCommand.js +6 -1
- package/build/utils/usage/checkForOverages.d.ts +12 -0
- package/build/utils/usage/checkForOverages.js +59 -0
- package/oclif.manifest.json +2 -2
- package/package.json +4 -4
package/build/build/local.js
CHANGED
|
@@ -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 =
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
27
|
-
let baseName = '
|
|
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(
|
|
37
|
-
log_1.default.withInfo(`Using project
|
|
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(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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.
|
|
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'
|
|
128
|
-
|
|
129
|
-
await fs_extra_1.default.copy(templatesSourceDir,
|
|
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;
|
|
@@ -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,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)(
|
|
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.
|
|
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
|
|
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;
|