budexp 0.1.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/LICENSE +21 -0
- package/README.md +516 -0
- package/bin/budexp.js +162 -0
- package/package.json +63 -0
- package/src/commands/build.js +548 -0
- package/src/commands/check.js +130 -0
- package/src/commands/clean.js +92 -0
- package/src/commands/dev.js +186 -0
- package/src/utils/cleaner.js +377 -0
- package/src/utils/eas.js +198 -0
- package/src/utils/expo-doctor.js +1097 -0
- package/src/utils/logger.js +25 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// ============================================
|
|
2
|
+
// FILE: src/commands/check.js
|
|
3
|
+
// ============================================
|
|
4
|
+
const logger = require('../utils/logger');
|
|
5
|
+
const expoDoctor = require('../utils/expo-doctor');
|
|
6
|
+
const eas = require('../utils/eas');
|
|
7
|
+
const { execFileSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
async function checkCommand(type, buildId, options = {}) {
|
|
10
|
+
if (type === 'health') {
|
|
11
|
+
await checkHealth(options);
|
|
12
|
+
} else if (type === 'eas') {
|
|
13
|
+
await checkEAS();
|
|
14
|
+
} else if (type === 'eas:list') {
|
|
15
|
+
await checkEASList();
|
|
16
|
+
} else if (type === 'eas:view') {
|
|
17
|
+
await checkEASView(buildId);
|
|
18
|
+
} else if (type === 'eas:project') {
|
|
19
|
+
await checkEASProject();
|
|
20
|
+
} else if (type === 'fix') {
|
|
21
|
+
await checkFix(options);
|
|
22
|
+
} else {
|
|
23
|
+
logger.error(`Unknown check type: ${type}`);
|
|
24
|
+
logger.info('Available types: health, eas, eas:list, eas:view [build-id], eas:project, fix');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function checkHealth(options = {}) {
|
|
29
|
+
const doctorResult = await expoDoctor.runExpoDoctor({ openReport: options.open });
|
|
30
|
+
const issuesSummary = expoDoctor.getIssuesSummary(doctorResult.output);
|
|
31
|
+
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log('='.repeat(50));
|
|
34
|
+
console.log('Health Check Summary');
|
|
35
|
+
console.log('='.repeat(50));
|
|
36
|
+
|
|
37
|
+
if (!issuesSummary.hasIssues) {
|
|
38
|
+
logger.success('All checks passed! Your Expo project is healthy.');
|
|
39
|
+
} else {
|
|
40
|
+
logger.warning(issuesSummary.summary);
|
|
41
|
+
console.log('');
|
|
42
|
+
|
|
43
|
+
if (issuesSummary.errors && issuesSummary.errors.length > 0) {
|
|
44
|
+
console.log('Errors:');
|
|
45
|
+
issuesSummary.errors.forEach((issue, index) => {
|
|
46
|
+
console.log(` ${index + 1}. ${issue.message}`);
|
|
47
|
+
});
|
|
48
|
+
console.log('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (issuesSummary.warnings && issuesSummary.warnings.length > 0) {
|
|
52
|
+
console.log('Warnings:');
|
|
53
|
+
issuesSummary.warnings.forEach((issue, index) => {
|
|
54
|
+
console.log(` ${index + 1}. ${issue.message}`);
|
|
55
|
+
});
|
|
56
|
+
console.log('');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(`Detailed HTML report: ${doctorResult.reportPath}`);
|
|
61
|
+
console.log('='.repeat(50));
|
|
62
|
+
console.log('');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function checkEAS() {
|
|
66
|
+
await eas.displayEASStatus();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function checkEASList() {
|
|
70
|
+
await eas.listEASBuilds();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function checkEASView(buildId) {
|
|
74
|
+
if (!buildId) {
|
|
75
|
+
logger.error('Build ID is required');
|
|
76
|
+
logger.info('Usage: budexp check eas:view <build-id>');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await eas.viewEASBuild(buildId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function checkEASProject() {
|
|
83
|
+
await eas.displayEASProjectInfo();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function checkFix(options = {}) {
|
|
87
|
+
logger.info('Running health check and looking for fixes...');
|
|
88
|
+
console.log('');
|
|
89
|
+
|
|
90
|
+
const doctorResult = await expoDoctor.runExpoDoctor({ openReport: options.open });
|
|
91
|
+
const issuesSummary = expoDoctor.getIssuesSummary(doctorResult.output);
|
|
92
|
+
|
|
93
|
+
if (!issuesSummary.hasIssues) {
|
|
94
|
+
logger.success('No issues found! Your project is healthy.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
logger.warning(issuesSummary.summary);
|
|
99
|
+
console.log('');
|
|
100
|
+
|
|
101
|
+
// Try to run expo fix
|
|
102
|
+
logger.info('Attempting to fix issues with expo fix...');
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
execFileSync('npx', ['expo', 'fix'], { stdio: 'inherit' });
|
|
107
|
+
logger.success('expo fix completed');
|
|
108
|
+
console.log('');
|
|
109
|
+
|
|
110
|
+
// Run doctor again to see if issues are resolved
|
|
111
|
+
logger.info('Re-running health check to verify fixes...');
|
|
112
|
+
const newDoctorResult = await expoDoctor.runExpoDoctor({ openReport: options.open });
|
|
113
|
+
const newIssuesSummary = expoDoctor.getIssuesSummary(newDoctorResult.output);
|
|
114
|
+
|
|
115
|
+
if (!newIssuesSummary.hasIssues) {
|
|
116
|
+
logger.success('All issues have been resolved!');
|
|
117
|
+
} else {
|
|
118
|
+
logger.warning('Some issues remain. Please check the report for details.');
|
|
119
|
+
}
|
|
120
|
+
} catch (e) {
|
|
121
|
+
logger.error('Failed to run expo fix');
|
|
122
|
+
logger.info('Please check the HTML report for manual fixes:');
|
|
123
|
+
logger.info(doctorResult.reportPath);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
console.log('');
|
|
127
|
+
logger.info(`Detailed HTML report: ${doctorResult.reportPath}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = checkCommand;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const prompt = inquirer.default?.prompt || inquirer.prompt;
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const cleaner = require('../utils/cleaner');
|
|
5
|
+
|
|
6
|
+
async function cleanCommand(options) {
|
|
7
|
+
logger.info('Starting clean process...');
|
|
8
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
9
|
+
|
|
10
|
+
let cleanAll = options.all;
|
|
11
|
+
let cleanCache = options.cache;
|
|
12
|
+
let cleanNative = options.native;
|
|
13
|
+
|
|
14
|
+
// If no options specified, ask user
|
|
15
|
+
if (!cleanAll && !cleanCache && !cleanNative && isInteractive) {
|
|
16
|
+
const answer = await prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'list',
|
|
19
|
+
name: 'cleanType',
|
|
20
|
+
message: 'What do you want to clean?',
|
|
21
|
+
choices: [
|
|
22
|
+
{ name: 'Everything (caches, node_modules, native folders)', value: 'all' },
|
|
23
|
+
{ name: 'Caches only', value: 'cache' },
|
|
24
|
+
{ name: 'Native folders only', value: 'native' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
if (answer.cleanType === 'all') {
|
|
30
|
+
cleanAll = true;
|
|
31
|
+
} else if (answer.cleanType === 'cache') {
|
|
32
|
+
cleanCache = true;
|
|
33
|
+
} else {
|
|
34
|
+
cleanNative = true;
|
|
35
|
+
}
|
|
36
|
+
} else if (!cleanAll && !cleanCache && !cleanNative) {
|
|
37
|
+
logger.warning('No clean option provided in non-interactive mode.');
|
|
38
|
+
logger.info('Use --all, --cache, or --native.');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (cleanAll) {
|
|
43
|
+
logger.info('Cleaning everything...');
|
|
44
|
+
|
|
45
|
+
const bundleId = await cleaner.getBundleId();
|
|
46
|
+
const platform = 'all';
|
|
47
|
+
|
|
48
|
+
await cleaner.killRunningApps(bundleId, platform);
|
|
49
|
+
await cleaner.cleanWatchman();
|
|
50
|
+
await cleaner.cleanCaches();
|
|
51
|
+
await cleaner.deleteNativeFolders(platform);
|
|
52
|
+
await cleaner.cleanDependencies();
|
|
53
|
+
|
|
54
|
+
logger.success('✅ Everything cleaned!');
|
|
55
|
+
} else if (cleanCache) {
|
|
56
|
+
logger.info('Cleaning caches only...');
|
|
57
|
+
|
|
58
|
+
await cleaner.killRunningApps(null, 'all');
|
|
59
|
+
await cleaner.cleanWatchman();
|
|
60
|
+
await cleaner.cleanCaches();
|
|
61
|
+
|
|
62
|
+
logger.success('✅ Caches cleaned!');
|
|
63
|
+
} else if (cleanNative) {
|
|
64
|
+
logger.info('Cleaning native folders only...');
|
|
65
|
+
|
|
66
|
+
let platform = 'all';
|
|
67
|
+
|
|
68
|
+
if (isInteractive) {
|
|
69
|
+
const answer = await prompt([
|
|
70
|
+
{
|
|
71
|
+
type: 'list',
|
|
72
|
+
name: 'platform',
|
|
73
|
+
message: 'Select platform:',
|
|
74
|
+
choices: [
|
|
75
|
+
{ name: 'Android', value: 'android' },
|
|
76
|
+
{ name: 'iOS', value: 'ios' },
|
|
77
|
+
{ name: 'Both', value: 'all' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
platform = answer.platform;
|
|
82
|
+
} else {
|
|
83
|
+
logger.info('Non-interactive shell detected. Removing both native folders.');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await cleaner.deleteNativeFolders(platform);
|
|
87
|
+
|
|
88
|
+
logger.success('✅ Native folders cleaned!');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = cleanCommand;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const prompt = inquirer.default?.prompt || inquirer.prompt;
|
|
3
|
+
const logger = require('../utils/logger');
|
|
4
|
+
const cleaner = require('../utils/cleaner');
|
|
5
|
+
const expoDoctor = require('../utils/expo-doctor');
|
|
6
|
+
const { execFileSync, execSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
async function devCommand(options) {
|
|
9
|
+
logger.info('Starting development mode...');
|
|
10
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
11
|
+
|
|
12
|
+
// Determine platform
|
|
13
|
+
let platform = 'android'; // default
|
|
14
|
+
if (options.all) {
|
|
15
|
+
platform = 'all';
|
|
16
|
+
} else if (options.ios) {
|
|
17
|
+
platform = 'ios';
|
|
18
|
+
} else if (options.android) {
|
|
19
|
+
platform = 'android';
|
|
20
|
+
} else if (isInteractive) {
|
|
21
|
+
// Ask user if no platform specified
|
|
22
|
+
const answer = await prompt([
|
|
23
|
+
{
|
|
24
|
+
type: 'list',
|
|
25
|
+
name: 'platform',
|
|
26
|
+
message: 'Select platform:',
|
|
27
|
+
choices: [
|
|
28
|
+
{ name: 'Android', value: 'android' },
|
|
29
|
+
{ name: 'iOS', value: 'ios' },
|
|
30
|
+
{ name: 'Both', value: 'all' },
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
]);
|
|
34
|
+
platform = answer.platform;
|
|
35
|
+
} else {
|
|
36
|
+
logger.info('Non-interactive shell detected. Using default platform: android');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
logger.info(`Platform: ${platform}`);
|
|
40
|
+
console.log('');
|
|
41
|
+
|
|
42
|
+
// Step 1: Run expo-doctor
|
|
43
|
+
// logger.info('Step 1: Running health check...');
|
|
44
|
+
const doctorResult = await expoDoctor.runExpoDoctor({ openReport: options.open });
|
|
45
|
+
const issuesSummary = expoDoctor.getIssuesSummary(doctorResult.output);
|
|
46
|
+
|
|
47
|
+
if (issuesSummary.hasIssues) {
|
|
48
|
+
logger.warning(issuesSummary.summary);
|
|
49
|
+
logger.info(`Detailed report saved to: ${doctorResult.reportPath}`);
|
|
50
|
+
|
|
51
|
+
let shouldContinue = Boolean(options.yes);
|
|
52
|
+
|
|
53
|
+
if (!shouldContinue && isInteractive) {
|
|
54
|
+
const continueAnswer = await prompt([
|
|
55
|
+
{
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
name: 'continue',
|
|
58
|
+
message: 'Issues found. Do you want to continue anyway?',
|
|
59
|
+
default: false,
|
|
60
|
+
},
|
|
61
|
+
]);
|
|
62
|
+
shouldContinue = continueAnswer.continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!shouldContinue) {
|
|
66
|
+
logger.info('Aborted by user');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
logger.success('Health check passed!');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
// Step 2: Clean everything
|
|
76
|
+
logger.info('Step 2: Cleaning caches and dependencies...');
|
|
77
|
+
const bundleId = await cleaner.getBundleId();
|
|
78
|
+
|
|
79
|
+
await cleaner.killRunningApps(bundleId, platform);
|
|
80
|
+
await cleaner.cleanWatchman();
|
|
81
|
+
await cleaner.cleanCaches();
|
|
82
|
+
await cleaner.deleteNativeFolders(platform);
|
|
83
|
+
await cleaner.cleanDependencies();
|
|
84
|
+
|
|
85
|
+
console.log('');
|
|
86
|
+
|
|
87
|
+
// Step 3: Reinstall dependencies
|
|
88
|
+
logger.info('Step 3: Reinstalling dependencies...');
|
|
89
|
+
await cleaner.reinstallDependencies();
|
|
90
|
+
|
|
91
|
+
console.log('');
|
|
92
|
+
|
|
93
|
+
// Step 4: Rebuild native code
|
|
94
|
+
logger.info('Step 4: Rebuilding native code...');
|
|
95
|
+
await cleaner.rebuildNative(platform);
|
|
96
|
+
|
|
97
|
+
console.log('');
|
|
98
|
+
|
|
99
|
+
// Step 5: Run development build
|
|
100
|
+
logger.info('Step 5: Starting development build...');
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const runWithDeviceSelection = async (targetPlatform) => {
|
|
104
|
+
if (options.device) {
|
|
105
|
+
// Expo CLI will prompt to choose among available emulators/simulators and connected devices.
|
|
106
|
+
execFileSync('npx', ['expo', `run:${targetPlatform}`, '--device'], { stdio: 'inherit' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (targetPlatform === 'android') {
|
|
111
|
+
execSync('bun run android || npm run android || yarn android', { stdio: 'inherit' });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (targetPlatform === 'ios') {
|
|
116
|
+
execSync('bun run ios || npm run ios || yarn ios', { stdio: 'inherit' });
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (platform === 'android') {
|
|
122
|
+
await runWithDeviceSelection('android');
|
|
123
|
+
} else if (platform === 'ios') {
|
|
124
|
+
await runWithDeviceSelection('ios');
|
|
125
|
+
} else {
|
|
126
|
+
// For 'all', run Android first, then iOS
|
|
127
|
+
logger.info('Building Android first...');
|
|
128
|
+
await runWithDeviceSelection('android');
|
|
129
|
+
logger.info('Now building iOS...');
|
|
130
|
+
await runWithDeviceSelection('ios');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
logger.success('✅ Development build completed!');
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.log('');
|
|
136
|
+
logger.error('Failed to start development build');
|
|
137
|
+
|
|
138
|
+
// Provide helpful error messages based on common issues
|
|
139
|
+
// Note: Since we use stdio: 'inherit', the actual error output is already shown
|
|
140
|
+
// We check the error code and provide context-specific help
|
|
141
|
+
const errorCode = e.status || e.code;
|
|
142
|
+
const errorMessage = (e.message || e.toString()).toLowerCase();
|
|
143
|
+
|
|
144
|
+
// Check if it's a device/emulator issue (common exit codes: 1 for command errors)
|
|
145
|
+
if (errorCode === 1 || errorMessage.includes('device') || errorMessage.includes('emulator')) {
|
|
146
|
+
if (platform === 'android' || platform === 'all') {
|
|
147
|
+
console.log('');
|
|
148
|
+
logger.warning('No Android device or emulator found.');
|
|
149
|
+
console.log('');
|
|
150
|
+
logger.info('To fix this:');
|
|
151
|
+
logger.info(' 1. Connect an Android device via USB and enable USB debugging');
|
|
152
|
+
logger.info(' 2. Or start an Android emulator from Android Studio');
|
|
153
|
+
logger.info(
|
|
154
|
+
' 3. Or create an emulator: https://docs.expo.dev/workflow/android-studio-emulator'
|
|
155
|
+
);
|
|
156
|
+
console.log('');
|
|
157
|
+
logger.info('Once a device is available, you can run:');
|
|
158
|
+
logger.info(' budexp dev --android');
|
|
159
|
+
} else if (platform === 'ios') {
|
|
160
|
+
console.log('');
|
|
161
|
+
logger.warning('No iOS simulator found.');
|
|
162
|
+
console.log('');
|
|
163
|
+
logger.info('To fix this:');
|
|
164
|
+
logger.info(' 1. Open Xcode and start a simulator');
|
|
165
|
+
logger.info(' 2. Or run: xcrun simctl list devices');
|
|
166
|
+
logger.info(' 3. Or create a simulator in Xcode: Xcode > Window > Devices and Simulators');
|
|
167
|
+
console.log('');
|
|
168
|
+
logger.info('Once a simulator is available, you can run:');
|
|
169
|
+
logger.info(' budexp dev --ios');
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
console.log('');
|
|
173
|
+
logger.warning('Build failed. Check the error messages above for details.');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log('');
|
|
177
|
+
logger.info('Note: All cleanup and setup steps completed successfully.');
|
|
178
|
+
logger.info('The project is ready - you just need a device/emulator to run the build.');
|
|
179
|
+
console.log('');
|
|
180
|
+
|
|
181
|
+
// Exit gracefully instead of throwing
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
module.exports = devCommand;
|