react-native-debug-toolkit 3.3.2 → 3.3.3
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 +7 -1
- package/README.zh-CN.md +7 -1
- package/bin/debug-toolkit.js +16 -1
- package/ios/DebugToolkitDevConnect.h +17 -0
- package/ios/DebugToolkitDevConnect.mm +293 -212
- package/lib/commonjs/features/devConnect/DevConnectTab.js +127 -126
- package/lib/commonjs/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/commonjs/features/devConnect/nativeDevConnect.js +0 -12
- package/lib/commonjs/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/module/features/devConnect/DevConnectTab.js +129 -128
- package/lib/module/features/devConnect/DevConnectTab.js.map +1 -1
- package/lib/module/features/devConnect/nativeDevConnect.js +0 -11
- package/lib/module/features/devConnect/nativeDevConnect.js.map +1 -1
- package/lib/typescript/src/features/devConnect/DevConnectTab.d.ts.map +1 -1
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts +6 -4
- package/lib/typescript/src/features/devConnect/nativeDevConnect.d.ts.map +1 -1
- package/package.json +4 -2
- package/scripts/android-debug-bundle.gradle +23 -0
- package/scripts/eas-postinstall.sh +6 -0
- package/scripts/embed-android.js +116 -0
- package/scripts/embed-expo.js +109 -0
- package/scripts/embed-ios.js +119 -0
- package/scripts/embed.js +224 -0
- package/src/features/devConnect/DevConnectTab.tsx +92 -92
- package/src/features/devConnect/nativeDevConnect.ts +8 -16
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const APPLY_FROM_MARKER =
|
|
7
|
+
'// react-native-debug-toolkit: debug bundle embed';
|
|
8
|
+
const APPLY_FROM_LINE =
|
|
9
|
+
APPLY_FROM_MARKER +
|
|
10
|
+
'\napply from: "../../node_modules/react-native-debug-toolkit/scripts/android-debug-bundle.gradle"';
|
|
11
|
+
|
|
12
|
+
function findBuildGradle(projectRoot) {
|
|
13
|
+
const gradlePath = path.join(projectRoot, 'android', 'app', 'build.gradle');
|
|
14
|
+
const gradleKtsPath = path.join(
|
|
15
|
+
projectRoot,
|
|
16
|
+
'android',
|
|
17
|
+
'app',
|
|
18
|
+
'build.gradle.kts'
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(gradleKtsPath)) {
|
|
22
|
+
return 'android/app/build.gradle.kts';
|
|
23
|
+
}
|
|
24
|
+
if (fs.existsSync(gradlePath)) {
|
|
25
|
+
return 'android/app/build.gradle';
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureAssetsDir(projectRoot) {
|
|
31
|
+
const assetsDir = path.join(
|
|
32
|
+
projectRoot,
|
|
33
|
+
'android',
|
|
34
|
+
'app',
|
|
35
|
+
'src',
|
|
36
|
+
'main',
|
|
37
|
+
'assets'
|
|
38
|
+
);
|
|
39
|
+
if (!fs.existsSync(assetsDir)) {
|
|
40
|
+
fs.mkdirSync(assetsDir, { recursive: true });
|
|
41
|
+
console.log(' Created android/app/src/main/assets/');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function injectGradleApply(projectRoot, buildGradleRelPath, entryFile, options) {
|
|
46
|
+
const fullPath = path.join(projectRoot, buildGradleRelPath);
|
|
47
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
48
|
+
|
|
49
|
+
// Idempotent check
|
|
50
|
+
if (content.includes(APPLY_FROM_MARKER)) {
|
|
51
|
+
console.log(
|
|
52
|
+
` ${buildGradleRelPath}: apply-from already present. Skipping.`
|
|
53
|
+
);
|
|
54
|
+
return { buildGradle: buildGradleRelPath, entryFile };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Set entry file as ext property if non-default
|
|
58
|
+
let entryFileExt = '';
|
|
59
|
+
if (entryFile && entryFile !== 'index.js') {
|
|
60
|
+
entryFileExt = `\nproject.ext.debugToolkitEntryFile = '${entryFile}'`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
content += '\n\n' + APPLY_FROM_LINE + entryFileExt + '\n';
|
|
64
|
+
fs.writeFileSync(fullPath, content);
|
|
65
|
+
|
|
66
|
+
ensureAssetsDir(projectRoot);
|
|
67
|
+
console.log(` ${buildGradleRelPath}: Injected apply-from for debug bundle.`);
|
|
68
|
+
|
|
69
|
+
return { buildGradle: buildGradleRelPath, entryFile };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function undoGradleApply(projectRoot, config) {
|
|
73
|
+
if (!config.android) {
|
|
74
|
+
console.log(' No Android configuration found. Skipping.');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const fullPath = path.join(projectRoot, config.android.buildGradle);
|
|
79
|
+
if (!fs.existsSync(fullPath)) {
|
|
80
|
+
console.log(` ${config.android.buildGradle} not found. Skipping.`);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let content = fs.readFileSync(fullPath, 'utf-8');
|
|
85
|
+
if (!content.includes(APPLY_FROM_MARKER)) {
|
|
86
|
+
console.log(' apply-from not found. Skipping.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Remove the marker line, apply-from line, and optional entryFile ext
|
|
91
|
+
const lines = content.split('\n');
|
|
92
|
+
const filtered = lines.filter((line) => {
|
|
93
|
+
if (line === APPLY_FROM_MARKER) return false;
|
|
94
|
+
if (
|
|
95
|
+
line.startsWith('apply from:') &&
|
|
96
|
+
line.includes('react-native-debug-toolkit/scripts/android-debug-bundle.gradle')
|
|
97
|
+
)
|
|
98
|
+
return false;
|
|
99
|
+
if (line.startsWith("project.ext.debugToolkitEntryFile = '")) return false;
|
|
100
|
+
return true;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Trim trailing empty lines introduced by our injection
|
|
104
|
+
while (filtered.length > 0 && filtered[filtered.length - 1].trim() === '') {
|
|
105
|
+
filtered.pop();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fs.writeFileSync(fullPath, filtered.join('\n') + '\n');
|
|
109
|
+
console.log(' Removed apply-from from build.gradle.');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
module.exports = {
|
|
113
|
+
findBuildGradle,
|
|
114
|
+
injectGradleApply,
|
|
115
|
+
undoGradleApply,
|
|
116
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function isExpoProject(projectRoot) {
|
|
7
|
+
// app.json with expo field
|
|
8
|
+
const appJsonPath = path.join(projectRoot, 'app.json');
|
|
9
|
+
if (fs.existsSync(appJsonPath)) {
|
|
10
|
+
try {
|
|
11
|
+
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
|
|
12
|
+
if (appJson.expo) return true;
|
|
13
|
+
} catch (_) {}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// app.config.js or app.config.ts
|
|
17
|
+
if (
|
|
18
|
+
fs.existsSync(path.join(projectRoot, 'app.config.js')) ||
|
|
19
|
+
fs.existsSync(path.join(projectRoot, 'app.config.ts'))
|
|
20
|
+
) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// expo in package.json dependencies
|
|
25
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
26
|
+
if (fs.existsSync(pkgPath)) {
|
|
27
|
+
try {
|
|
28
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
29
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
30
|
+
if (deps.expo) return true;
|
|
31
|
+
} catch (_) {}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isExpoGo(projectRoot) {
|
|
38
|
+
// Expo Go doesn't support embedded bundles
|
|
39
|
+
// If no ios/ or android/ dirs, likely Expo Go
|
|
40
|
+
const hasNativeDirs =
|
|
41
|
+
fs.existsSync(path.join(projectRoot, 'ios')) ||
|
|
42
|
+
fs.existsSync(path.join(projectRoot, 'android'));
|
|
43
|
+
|
|
44
|
+
if (!hasNativeDirs) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isPrebuilt(projectRoot) {
|
|
51
|
+
// Dev client / bare workflow after prebuild
|
|
52
|
+
return (
|
|
53
|
+
fs.existsSync(path.join(projectRoot, 'ios')) &&
|
|
54
|
+
fs.existsSync(path.join(projectRoot, 'android'))
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getExpoEntryPoint(projectRoot) {
|
|
59
|
+
const appJsonPath = path.join(projectRoot, 'app.json');
|
|
60
|
+
if (fs.existsSync(appJsonPath)) {
|
|
61
|
+
try {
|
|
62
|
+
const appJson = JSON.parse(fs.readFileSync(appJsonPath, 'utf-8'));
|
|
63
|
+
if (appJson.expo && appJson.expo.entryPoint) {
|
|
64
|
+
return appJson.expo.entryPoint;
|
|
65
|
+
}
|
|
66
|
+
} catch (_) {}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Expo default with .expo virtual entry
|
|
70
|
+
const virtualEntry = path.join(
|
|
71
|
+
projectRoot,
|
|
72
|
+
'.expo',
|
|
73
|
+
'.virtual-metro-entry'
|
|
74
|
+
);
|
|
75
|
+
if (fs.existsSync(virtualEntry)) {
|
|
76
|
+
return '.expo/.virtual-metro-entry';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function checkExpo(projectRoot, options) {
|
|
83
|
+
const expo = isExpoProject(projectRoot);
|
|
84
|
+
if (!expo) {
|
|
85
|
+
return { expo: false, skip: false };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (isExpoGo(projectRoot)) {
|
|
89
|
+
return {
|
|
90
|
+
expo: true,
|
|
91
|
+
skip: true,
|
|
92
|
+
reason:
|
|
93
|
+
'Expo Go does not support embedded bundles. Use expo-dev-client or run npx expo prebuild first.',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!isPrebuilt(projectRoot)) {
|
|
98
|
+
return {
|
|
99
|
+
expo: true,
|
|
100
|
+
skip: true,
|
|
101
|
+
reason:
|
|
102
|
+
'Native directories not found. Run npx expo prebuild first, then retry.',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { expo: true, skip: false, entryPoint: getExpoEntryPoint(projectRoot) };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { isExpoProject, checkExpo, getExpoEntryPoint };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const pbxproj = require('pbxproj');
|
|
6
|
+
|
|
7
|
+
const BUNDLE_PHASE_NAME = 'Bundle React Native code and images';
|
|
8
|
+
const FORCE_BUNDLING_LINE = 'export FORCE_BUNDLING=1';
|
|
9
|
+
const CONFIG_FILE = '.debug-toolkit-embed.json';
|
|
10
|
+
|
|
11
|
+
function findXcodeproj(projectRoot) {
|
|
12
|
+
const iosDir = path.join(projectRoot, 'ios');
|
|
13
|
+
if (!fs.existsSync(iosDir)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const entries = fs.readdirSync(iosDir).filter(
|
|
17
|
+
(e) => e.endsWith('.xcodeproj') && e !== 'Pods.xcodeproj'
|
|
18
|
+
);
|
|
19
|
+
if (entries.length === 0) {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
return entries;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findPbxproj(xcodeprojPath) {
|
|
26
|
+
const pbxPath = path.join(xcodeprojPath, 'project.pbxproj');
|
|
27
|
+
if (!fs.existsSync(pbxPath)) {
|
|
28
|
+
throw new Error(`project.pbxproj not found at ${pbxPath}`);
|
|
29
|
+
}
|
|
30
|
+
return pbxPath;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getBundleScriptPhase(pbxFile) {
|
|
34
|
+
const proj = new pbxproj(pbxFile).parseSync();
|
|
35
|
+
const sections = proj.hash.project.objects.PBXShellScriptBuildPhase;
|
|
36
|
+
if (!sections) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
for (const [key, section] of Object.entries(sections)) {
|
|
40
|
+
if (
|
|
41
|
+
section.name === BUNDLE_PHASE_NAME ||
|
|
42
|
+
section.name === `"${BUNDLE_PHASE_NAME}"`
|
|
43
|
+
) {
|
|
44
|
+
return { id: key, section, proj, pbxFile };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function injectForceBundling(projectRoot, xcodeprojRelPath, entryFile, options) {
|
|
51
|
+
const xcodeprojPath = path.join(projectRoot, xcodeprojRelPath);
|
|
52
|
+
const pbxFile = findPbxproj(xcodeprojPath);
|
|
53
|
+
const result = getBundleScriptPhase(pbxFile);
|
|
54
|
+
if (!result) {
|
|
55
|
+
throw new Error(
|
|
56
|
+
`"${BUNDLE_PHASE_NAME}" script phase not found in ${xcodeprojRelPath}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const scriptBody = result.section.shellScript;
|
|
61
|
+
// Already injected — idempotent
|
|
62
|
+
if (scriptBody && scriptBody.includes(FORCE_BUNDLING_LINE)) {
|
|
63
|
+
console.log(` ${xcodeprojRelPath}: FORCE_BUNDLING already set. Skipping.`);
|
|
64
|
+
return { xcodeproj: xcodeprojRelPath, scriptPhaseId: result.id, entryFile };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Decode shellScript (it's usually a quoted string)
|
|
68
|
+
let decoded = scriptBody;
|
|
69
|
+
try {
|
|
70
|
+
decoded = JSON.parse(scriptBody);
|
|
71
|
+
} catch (_) {
|
|
72
|
+
// Not JSON-encoded, use as-is
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const newScript = FORCE_BUNDLING_LINE + '\n' + decoded;
|
|
76
|
+
result.section.shellScript = JSON.stringify(newScript);
|
|
77
|
+
|
|
78
|
+
fs.writeFileSync(pbxFile, result.proj.writeSync());
|
|
79
|
+
console.log(` ${xcodeprojRelPath}: Injected FORCE_BUNDLING=1`);
|
|
80
|
+
|
|
81
|
+
return { xcodeproj: xcodeprojRelPath, scriptPhaseId: result.id, entryFile };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function undoForceBundling(projectRoot, config) {
|
|
85
|
+
if (!config.ios) {
|
|
86
|
+
console.log(' No iOS configuration found. Skipping.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const xcodeprojPath = path.join(projectRoot, config.ios.xcodeproj);
|
|
91
|
+
const pbxFile = findPbxproj(xcodeprojPath);
|
|
92
|
+
const result = getBundleScriptPhase(pbxFile);
|
|
93
|
+
if (!result) {
|
|
94
|
+
console.log(' Bundle script phase not found. Skipping.');
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const scriptBody = result.section.shellScript;
|
|
99
|
+
let decoded = scriptBody;
|
|
100
|
+
try {
|
|
101
|
+
decoded = JSON.parse(scriptBody);
|
|
102
|
+
} catch (_) {}
|
|
103
|
+
|
|
104
|
+
if (!decoded.includes(FORCE_BUNDLING_LINE)) {
|
|
105
|
+
console.log(' FORCE_BUNDLING not found in script. Skipping.');
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const newScript = decoded
|
|
110
|
+
.split('\n')
|
|
111
|
+
.filter((line) => line !== FORCE_BUNDLING_LINE)
|
|
112
|
+
.join('\n');
|
|
113
|
+
result.section.shellScript = JSON.stringify(newScript);
|
|
114
|
+
|
|
115
|
+
fs.writeFileSync(pbxFile, result.proj.writeSync());
|
|
116
|
+
console.log(' Removed FORCE_BUNDLING=1 from script phase.');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { findXcodeproj, injectForceBundling, undoForceBundling };
|
package/scripts/embed.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const readline = require('readline');
|
|
6
|
+
|
|
7
|
+
const ios = require('./embed-ios');
|
|
8
|
+
const android = require('./embed-android');
|
|
9
|
+
const expo = require('./embed-expo');
|
|
10
|
+
|
|
11
|
+
const CONFIG_FILE = '.debug-toolkit-embed.json';
|
|
12
|
+
|
|
13
|
+
function readConfig(projectRoot) {
|
|
14
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
15
|
+
if (!fs.existsSync(configPath)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeConfig(projectRoot, config) {
|
|
22
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
23
|
+
const payload = { version: 1, ...config };
|
|
24
|
+
fs.writeFileSync(configPath, JSON.stringify(payload, null, 2) + '\n');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deleteConfig(projectRoot) {
|
|
28
|
+
const configPath = path.join(projectRoot, CONFIG_FILE);
|
|
29
|
+
if (fs.existsSync(configPath)) {
|
|
30
|
+
fs.unlinkSync(configPath);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function detectEntryFile(projectRoot) {
|
|
35
|
+
const candidates = [
|
|
36
|
+
'index.js',
|
|
37
|
+
'index.ts',
|
|
38
|
+
'index.tsx',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
for (const candidate of candidates) {
|
|
42
|
+
if (fs.existsSync(path.join(projectRoot, candidate))) {
|
|
43
|
+
return candidate;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Expo virtual entry
|
|
48
|
+
if (fs.existsSync(path.join(projectRoot, '.expo', '.virtual-metro-entry'))) {
|
|
49
|
+
return '.expo/.virtual-metro-entry';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return 'index.js';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function prompt(question, options) {
|
|
56
|
+
if (options && options.yes) {
|
|
57
|
+
return Promise.resolve(true);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const rl = readline.createInterface({
|
|
61
|
+
input: process.stdin,
|
|
62
|
+
output: process.stdout,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return new Promise((resolve) => {
|
|
66
|
+
rl.question(question + ' [Y/n] ', (answer) => {
|
|
67
|
+
rl.close();
|
|
68
|
+
resolve(answer.toLowerCase() !== 'n');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function embed(projectRoot, argv) {
|
|
74
|
+
const args = argv || [];
|
|
75
|
+
const yes = args.includes('--yes');
|
|
76
|
+
const platformIdx = args.indexOf('--platform');
|
|
77
|
+
const platform =
|
|
78
|
+
platformIdx >= 0 ? args[platformIdx + 1] : null; // 'ios' | 'android'
|
|
79
|
+
|
|
80
|
+
console.log('debug-toolkit embed');
|
|
81
|
+
console.log('---');
|
|
82
|
+
|
|
83
|
+
// Check Expo
|
|
84
|
+
const expoResult = expo.checkExpo(projectRoot, { yes });
|
|
85
|
+
if (expoResult.expo) {
|
|
86
|
+
console.log('Detected Expo project.');
|
|
87
|
+
if (expoResult.skip) {
|
|
88
|
+
console.log(` Skipping: ${expoResult.reason}`);
|
|
89
|
+
if (platform === null || platform === 'ios') {
|
|
90
|
+
// nothing
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
console.log(' Native directories found — proceeding.');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const existingConfig = readConfig(projectRoot);
|
|
98
|
+
const config = {};
|
|
99
|
+
|
|
100
|
+
// Entry file
|
|
101
|
+
const entryFile =
|
|
102
|
+
(expoResult.entryPoint) || detectEntryFile(projectRoot);
|
|
103
|
+
console.log(`Entry file: ${entryFile}`);
|
|
104
|
+
|
|
105
|
+
// iOS
|
|
106
|
+
if (platform === null || platform === 'ios') {
|
|
107
|
+
const xcodeprojEntries = ios.findXcodeproj(projectRoot);
|
|
108
|
+
if (xcodeprojEntries && xcodeprojEntries.length > 0) {
|
|
109
|
+
let chosen = xcodeprojEntries[0];
|
|
110
|
+
if (xcodeprojEntries.length > 1) {
|
|
111
|
+
if (yes) {
|
|
112
|
+
console.log(
|
|
113
|
+
` Multiple Xcode projects found. Using: ${chosen}`
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
console.log(' Multiple Xcode projects found:');
|
|
117
|
+
xcodeprojEntries.forEach((e, i) =>
|
|
118
|
+
console.log(` ${i + 1}. ${e}`)
|
|
119
|
+
);
|
|
120
|
+
const idx = await prompt(
|
|
121
|
+
` Select project [1-${xcodeprojEntries.length}]`,
|
|
122
|
+
{ yes }
|
|
123
|
+
);
|
|
124
|
+
// Default to first
|
|
125
|
+
chosen = xcodeprojEntries[0];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const xcodeprojRelPath = path.join('ios', chosen);
|
|
130
|
+
const shouldEmbed =
|
|
131
|
+
yes ||
|
|
132
|
+
(await prompt(
|
|
133
|
+
` Inject FORCE_BUNDLING into ${xcodeprojRelPath}?`,
|
|
134
|
+
{ yes }
|
|
135
|
+
));
|
|
136
|
+
|
|
137
|
+
if (shouldEmbed !== false) {
|
|
138
|
+
config.ios = ios.injectForceBundling(
|
|
139
|
+
projectRoot,
|
|
140
|
+
xcodeprojRelPath,
|
|
141
|
+
entryFile,
|
|
142
|
+
{ yes }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
console.log(' No Xcode project found. Skipping iOS.');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Android
|
|
151
|
+
if (platform === null || platform === 'android') {
|
|
152
|
+
const buildGradle = android.findBuildGradle(projectRoot);
|
|
153
|
+
if (buildGradle) {
|
|
154
|
+
const shouldEmbed =
|
|
155
|
+
yes ||
|
|
156
|
+
(await prompt(
|
|
157
|
+
` Inject debug bundle task into ${buildGradle}?`,
|
|
158
|
+
{ yes }
|
|
159
|
+
));
|
|
160
|
+
|
|
161
|
+
if (shouldEmbed !== false) {
|
|
162
|
+
config.android = android.injectGradleApply(
|
|
163
|
+
projectRoot,
|
|
164
|
+
buildGradle,
|
|
165
|
+
entryFile,
|
|
166
|
+
{ yes }
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
console.log(' No Android build.gradle found. Skipping Android.');
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Write config
|
|
175
|
+
if (Object.keys(config).length > 0) {
|
|
176
|
+
writeConfig(projectRoot, config);
|
|
177
|
+
console.log(`\nConfiguration saved to ${CONFIG_FILE}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
console.log('\nDone. Debug builds will now embed a JS bundle.');
|
|
181
|
+
console.log('Use DevConnect to switch Metro hosts at runtime.');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function undo(projectRoot, argv) {
|
|
185
|
+
const args = argv || [];
|
|
186
|
+
const yes = args.includes('--yes');
|
|
187
|
+
|
|
188
|
+
console.log('debug-toolkit embed --undo');
|
|
189
|
+
console.log('---');
|
|
190
|
+
|
|
191
|
+
const config = readConfig(projectRoot);
|
|
192
|
+
if (!config) {
|
|
193
|
+
console.log(`No ${CONFIG_FILE} found. Nothing to undo.`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const shouldUndo =
|
|
198
|
+
yes || (await prompt('Remove all debug-toolkit embed injections?', { yes }));
|
|
199
|
+
|
|
200
|
+
if (shouldUndo === false) {
|
|
201
|
+
console.log('Cancelled.');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
ios.undoForceBundling(projectRoot, config);
|
|
206
|
+
android.undoGradleApply(projectRoot, config);
|
|
207
|
+
|
|
208
|
+
deleteConfig(projectRoot);
|
|
209
|
+
console.log(`\nRemoved ${CONFIG_FILE}.`);
|
|
210
|
+
console.log('Done.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function main(argv) {
|
|
214
|
+
const projectRoot = process.cwd();
|
|
215
|
+
const args = argv || [];
|
|
216
|
+
|
|
217
|
+
if (args.includes('--undo')) {
|
|
218
|
+
return undo(projectRoot, args);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return embed(projectRoot, args);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = { main, embed, undo };
|