react-native-debug-toolkit 3.3.3 → 3.3.4
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 +35 -2
- package/README.zh-CN.md +35 -2
- package/app.plugin.js +51 -0
- package/bin/debug-toolkit.js +13 -12
- package/dev-client.js +3 -0
- package/ios/DebugToolkitDevConnect.mm +8 -41
- package/package.json +4 -2
- package/scripts/bundle/android.js +101 -0
- package/scripts/bundle/cli.js +57 -0
- package/scripts/bundle/doctor.js +38 -0
- package/scripts/bundle/ios.js +179 -0
- package/scripts/bundle/setup.js +39 -0
- package/scripts/debug-bundle.gradle +147 -0
- package/scripts/android-debug-bundle.gradle +0 -23
- package/scripts/eas-postinstall.sh +0 -6
- package/scripts/embed-android.js +0 -116
- package/scripts/embed-expo.js +0 -109
- package/scripts/embed-ios.js +0 -119
- package/scripts/embed.js +0 -224
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const xcode = require('xcode');
|
|
6
|
+
|
|
7
|
+
const PHASE_NAME = 'Bundle React Native code and images';
|
|
8
|
+
const BEGIN = '# react-native-debug-toolkit: begin debug bundle';
|
|
9
|
+
const END = '# react-native-debug-toolkit: end debug bundle';
|
|
10
|
+
const BLOCK = `${BEGIN}\nexport FORCE_BUNDLING=1\n${END}`;
|
|
11
|
+
|
|
12
|
+
function stripQuotes(value) {
|
|
13
|
+
return String(value || '').replace(/^"|"$/g, '');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function findProjects(cwd) {
|
|
17
|
+
const iosDir = path.join(cwd, 'ios');
|
|
18
|
+
if (!fs.existsSync(iosDir)) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return fs.readdirSync(iosDir)
|
|
23
|
+
.filter((entry) => entry.endsWith('.xcodeproj') && entry !== 'Pods.xcodeproj')
|
|
24
|
+
.map((entry) => path.join(iosDir, entry, 'project.pbxproj'))
|
|
25
|
+
.filter((file) => fs.existsSync(file));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function decodeScript(value) {
|
|
29
|
+
if (!value) {
|
|
30
|
+
return '';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(value);
|
|
35
|
+
} catch {
|
|
36
|
+
return stripQuotes(value);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function encodeScript(value) {
|
|
41
|
+
return JSON.stringify(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function phaseName(phase) {
|
|
45
|
+
return stripQuotes(phase.name || phase.comment);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isAppTarget(target) {
|
|
49
|
+
return stripQuotes(target.productType) === 'com.apple.product-type.application';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function targetName(target) {
|
|
53
|
+
return stripQuotes(target.name || target.productName || target.comment);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function targetBuildPhaseIds(target) {
|
|
57
|
+
return (target.buildPhases || []).map((phaseRef) => {
|
|
58
|
+
if (typeof phaseRef === 'string') {
|
|
59
|
+
return phaseRef;
|
|
60
|
+
}
|
|
61
|
+
return phaseRef.value;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function sectionEntries(objects, sectionName, isaName) {
|
|
66
|
+
const section = objects[sectionName];
|
|
67
|
+
if (section) {
|
|
68
|
+
return Object.entries(section)
|
|
69
|
+
.filter(([key]) => !key.endsWith('_comment'));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return Object.entries(objects)
|
|
73
|
+
.filter(([key, value]) => !key.endsWith('_comment') && value && value.isa === isaName)
|
|
74
|
+
.map(([key, value]) => [key, { ...value, comment: objects[`${key}_comment`] }]);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function loadTarget(cwd, iosTarget) {
|
|
78
|
+
const projects = findProjects(cwd);
|
|
79
|
+
if (projects.length !== 1) {
|
|
80
|
+
throw new Error(`Expected one Xcode project, found ${projects.length}. Pass --ios-target after selecting the app project.`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pbxFile = projects[0];
|
|
84
|
+
const proj = xcode.project(pbxFile).parseSync();
|
|
85
|
+
const objects = proj.hash.project.objects;
|
|
86
|
+
const nativeTargets = sectionEntries(objects, 'PBXNativeTarget', 'PBXNativeTarget');
|
|
87
|
+
const shellPhases = new Map(sectionEntries(objects, 'PBXShellScriptBuildPhase', 'PBXShellScriptBuildPhase'));
|
|
88
|
+
|
|
89
|
+
const appTargets = nativeTargets
|
|
90
|
+
.filter(([, target]) => isAppTarget(target));
|
|
91
|
+
|
|
92
|
+
const matches = iosTarget
|
|
93
|
+
? appTargets.filter(([, target]) => targetName(target) === iosTarget)
|
|
94
|
+
: appTargets;
|
|
95
|
+
|
|
96
|
+
if (matches.length !== 1) {
|
|
97
|
+
throw new Error(`Expected one iOS app target, found ${matches.length}. Pass --ios-target <name>.`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const [, target] = matches[0];
|
|
101
|
+
for (const phaseId of targetBuildPhaseIds(target)) {
|
|
102
|
+
const phase = shellPhases.get(phaseId);
|
|
103
|
+
if (!phase || phaseName(phase) !== PHASE_NAME) {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const script = decodeScript(phase.shellScript);
|
|
108
|
+
if (!script.includes('react-native-xcode.sh') && !script.includes('with-environment.sh')) {
|
|
109
|
+
throw new Error(`${PHASE_NAME} does not call React Native bundling script.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return { pbxFile, proj, phase };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error(`${PHASE_NAME} phase not found on iOS app target.`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function insertBlock(script) {
|
|
119
|
+
if (script.includes(BEGIN)) {
|
|
120
|
+
return script;
|
|
121
|
+
}
|
|
122
|
+
return `${BLOCK}\n${script}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function removeBlock(script) {
|
|
126
|
+
return script.replace(
|
|
127
|
+
/# react-native-debug-toolkit: begin debug bundle\r?\nexport FORCE_BUNDLING=1\r?\n# react-native-debug-toolkit: end debug bundle\r?\n?/g,
|
|
128
|
+
'',
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function writeProject(ctx, script) {
|
|
133
|
+
ctx.phase.shellScript = encodeScript(script);
|
|
134
|
+
fs.writeFileSync(ctx.pbxFile, ctx.proj.writeSync());
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function setupIosBundle(options) {
|
|
138
|
+
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
139
|
+
const script = decodeScript(ctx.phase.shellScript);
|
|
140
|
+
const next = insertBlock(script);
|
|
141
|
+
|
|
142
|
+
if (next === script) {
|
|
143
|
+
return { ok: true, changed: false };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
writeProject(ctx, next);
|
|
147
|
+
return { ok: true, changed: true };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function undoIosBundle(options) {
|
|
151
|
+
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
152
|
+
const script = decodeScript(ctx.phase.shellScript);
|
|
153
|
+
const next = removeBlock(script);
|
|
154
|
+
|
|
155
|
+
if (next === script) {
|
|
156
|
+
return { ok: true, changed: false };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
writeProject(ctx, next);
|
|
160
|
+
return { ok: true, changed: true };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function checkIosBundle(options) {
|
|
164
|
+
const ctx = loadTarget(options.cwd, options.iosTarget);
|
|
165
|
+
const script = decodeScript(ctx.phase.shellScript);
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
ok: script.includes(BLOCK),
|
|
169
|
+
changed: false,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
setupIosBundle,
|
|
175
|
+
undoIosBundle,
|
|
176
|
+
checkIosBundle,
|
|
177
|
+
BEGIN,
|
|
178
|
+
END,
|
|
179
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { setupIosBundle, undoIosBundle, checkIosBundle } = require('./ios');
|
|
4
|
+
const { setupAndroidBundle, undoAndroidBundle, checkAndroidBundle } = require('./android');
|
|
5
|
+
|
|
6
|
+
async function setupBundle(options) {
|
|
7
|
+
const platforms = options.platform === 'all' ? ['ios', 'android'] : [options.platform];
|
|
8
|
+
const results = [];
|
|
9
|
+
|
|
10
|
+
for (const platform of platforms) {
|
|
11
|
+
if (platform === 'ios') {
|
|
12
|
+
if (options.undo) {
|
|
13
|
+
results.push(undoIosBundle(options));
|
|
14
|
+
} else if (options.check) {
|
|
15
|
+
results.push(checkIosBundle(options));
|
|
16
|
+
} else {
|
|
17
|
+
results.push(setupIosBundle(options));
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (platform === 'android') {
|
|
23
|
+
if (options.undo) {
|
|
24
|
+
results.push(undoAndroidBundle(options));
|
|
25
|
+
} else if (options.check) {
|
|
26
|
+
results.push(checkAndroidBundle(options));
|
|
27
|
+
} else {
|
|
28
|
+
results.push(setupAndroidBundle(options));
|
|
29
|
+
}
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = { setupBundle };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// react-native-debug-toolkit: persistent debug/development bundle generation
|
|
2
|
+
|
|
3
|
+
import java.util.Locale
|
|
4
|
+
import javax.inject.Inject
|
|
5
|
+
import org.gradle.api.DefaultTask
|
|
6
|
+
import org.gradle.api.GradleException
|
|
7
|
+
import org.gradle.api.file.DirectoryProperty
|
|
8
|
+
import org.gradle.api.file.RegularFileProperty
|
|
9
|
+
import org.gradle.api.provider.ListProperty
|
|
10
|
+
import org.gradle.api.provider.Property
|
|
11
|
+
import org.gradle.api.tasks.Input
|
|
12
|
+
import org.gradle.api.tasks.InputDirectory
|
|
13
|
+
import org.gradle.api.tasks.InputFile
|
|
14
|
+
import org.gradle.api.tasks.Optional
|
|
15
|
+
import org.gradle.api.tasks.OutputDirectory
|
|
16
|
+
import org.gradle.api.tasks.TaskAction
|
|
17
|
+
import org.gradle.process.ExecOperations
|
|
18
|
+
|
|
19
|
+
abstract class DebugToolkitBundleTask extends DefaultTask {
|
|
20
|
+
@Inject
|
|
21
|
+
abstract ExecOperations getExecOperations()
|
|
22
|
+
|
|
23
|
+
@InputDirectory
|
|
24
|
+
abstract DirectoryProperty getRoot()
|
|
25
|
+
|
|
26
|
+
@InputFile
|
|
27
|
+
abstract RegularFileProperty getCliFile()
|
|
28
|
+
|
|
29
|
+
@InputFile
|
|
30
|
+
@Optional
|
|
31
|
+
abstract RegularFileProperty getEntryFile()
|
|
32
|
+
|
|
33
|
+
@InputFile
|
|
34
|
+
@Optional
|
|
35
|
+
abstract RegularFileProperty getBundleConfig()
|
|
36
|
+
|
|
37
|
+
@Input
|
|
38
|
+
abstract ListProperty<String> getNodeExecutableAndArgs()
|
|
39
|
+
|
|
40
|
+
@Input
|
|
41
|
+
abstract Property<String> getBundleCommand()
|
|
42
|
+
|
|
43
|
+
@Input
|
|
44
|
+
abstract Property<String> getBundleAssetName()
|
|
45
|
+
|
|
46
|
+
@Input
|
|
47
|
+
abstract ListProperty<String> getExtraPackagerArgs()
|
|
48
|
+
|
|
49
|
+
@OutputDirectory
|
|
50
|
+
abstract DirectoryProperty getJsBundleDir()
|
|
51
|
+
|
|
52
|
+
@OutputDirectory
|
|
53
|
+
abstract DirectoryProperty getResourcesDir()
|
|
54
|
+
|
|
55
|
+
@TaskAction
|
|
56
|
+
void run() {
|
|
57
|
+
def rootFile = root.get().asFile
|
|
58
|
+
def bundleOutput = new File(jsBundleDir.get().asFile, bundleAssetName.get())
|
|
59
|
+
def entry = resolveEntryFile(rootFile)
|
|
60
|
+
jsBundleDir.get().asFile.mkdirs()
|
|
61
|
+
resourcesDir.get().asFile.mkdirs()
|
|
62
|
+
|
|
63
|
+
def command = []
|
|
64
|
+
command.addAll(nodeExecutableAndArgs.get())
|
|
65
|
+
command.add(cliFile.get().asFile.absolutePath)
|
|
66
|
+
command.add(bundleCommand.get())
|
|
67
|
+
command.addAll([
|
|
68
|
+
"--platform", "android",
|
|
69
|
+
"--dev", "true",
|
|
70
|
+
"--entry-file", entry.absolutePath,
|
|
71
|
+
"--bundle-output", bundleOutput.absolutePath,
|
|
72
|
+
"--assets-dest", resourcesDir.get().asFile.absolutePath,
|
|
73
|
+
])
|
|
74
|
+
|
|
75
|
+
if (bundleConfig.isPresent()) {
|
|
76
|
+
command.add("--config")
|
|
77
|
+
command.add(bundleConfig.get().asFile.absolutePath)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
command.addAll(extraPackagerArgs.get())
|
|
81
|
+
|
|
82
|
+
execOperations.exec {
|
|
83
|
+
workingDir(rootFile)
|
|
84
|
+
commandLine(command)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private File resolveEntryFile(File rootFile) {
|
|
89
|
+
if (entryFile.isPresent()) {
|
|
90
|
+
return entryFile.get().asFile
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
def androidEntry = new File(rootFile, "index.android.js")
|
|
94
|
+
return androidEntry.exists() ? androidEntry : new File(rootFile, "index.js")
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def toolkitCapitalize = { String value ->
|
|
99
|
+
value.length() == 0 ? value : value.substring(0, 1).toUpperCase(Locale.ROOT) + value.substring(1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
def toolkitReactExt = project.extensions.findByName("react")
|
|
103
|
+
def toolkitAndroidComponents = project.extensions.findByName("androidComponents")
|
|
104
|
+
|
|
105
|
+
if (toolkitAndroidComponents == null) {
|
|
106
|
+
throw new GradleException("react-native-debug-toolkit debug-bundle.gradle requires the Android Gradle Plugin.")
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
def toolkitDefaultRoot = project.rootProject.layout.projectDirectory.dir("../")
|
|
110
|
+
def toolkitDebuggableVariants = toolkitReactExt?.debuggableVariants?.getOrElse(["debug"]) ?: ["debug"]
|
|
111
|
+
|
|
112
|
+
toolkitAndroidComponents.onVariants(toolkitAndroidComponents.selector().all()) { variant ->
|
|
113
|
+
def variantName = variant.name
|
|
114
|
+
def isDebuggable = toolkitDebuggableVariants.any { it.equalsIgnoreCase(variantName) }
|
|
115
|
+
|
|
116
|
+
if (!isDebuggable) {
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def capName = toolkitCapitalize(variantName)
|
|
121
|
+
def bundleTask = tasks.register("createDebugToolkit${capName}JsAndAssets", DebugToolkitBundleTask) {
|
|
122
|
+
group = "react"
|
|
123
|
+
description = "Generate embedded React Native JS bundle for ${variantName} debug build."
|
|
124
|
+
|
|
125
|
+
root.set(toolkitReactExt?.root ?: toolkitDefaultRoot)
|
|
126
|
+
cliFile.set(toolkitReactExt?.cliFile ?: root.file("node_modules/react-native/cli.js"))
|
|
127
|
+
if (toolkitReactExt?.entryFile?.isPresent()) {
|
|
128
|
+
entryFile.set(toolkitReactExt.entryFile)
|
|
129
|
+
}
|
|
130
|
+
if (toolkitReactExt?.bundleConfig?.isPresent()) {
|
|
131
|
+
bundleConfig.set(toolkitReactExt.bundleConfig)
|
|
132
|
+
}
|
|
133
|
+
nodeExecutableAndArgs.set(toolkitReactExt?.nodeExecutableAndArgs ?: ["node"])
|
|
134
|
+
bundleCommand.set(toolkitReactExt?.bundleCommand ?: "bundle")
|
|
135
|
+
bundleAssetName.set(toolkitReactExt?.bundleAssetName ?: "index.android.bundle")
|
|
136
|
+
extraPackagerArgs.set(toolkitReactExt?.extraPackagerArgs ?: [])
|
|
137
|
+
jsBundleDir.set(project.layout.buildDirectory.dir("generated/assets/react-native-debug-toolkit/${variantName}"))
|
|
138
|
+
resourcesDir.set(project.layout.buildDirectory.dir("generated/res/react-native-debug-toolkit/${variantName}"))
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
variant.sources.assets?.addGeneratedSourceDirectory(bundleTask) { task ->
|
|
142
|
+
task.jsBundleDir
|
|
143
|
+
}
|
|
144
|
+
variant.sources.res?.addGeneratedSourceDirectory(bundleTask) { task ->
|
|
145
|
+
task.resourcesDir
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// react-native-debug-toolkit: Debug bundle generation for Android
|
|
2
|
-
// Applied via: apply from: "../../node_modules/react-native-debug-toolkit/scripts/android-debug-bundle.gradle"
|
|
3
|
-
|
|
4
|
-
tasks.register('generateDebugBundle', Exec) {
|
|
5
|
-
commandLine 'npx', 'react-native', 'bundle',
|
|
6
|
-
'--platform', 'android',
|
|
7
|
-
'--dev', 'true',
|
|
8
|
-
'--entry-file', project.ext.has('debugToolkitEntryFile')
|
|
9
|
-
? project.ext.debugToolkitEntryFile : 'index.js',
|
|
10
|
-
'--bundle-output', "${projectDir}/src/main/assets/index.android.bundle",
|
|
11
|
-
'--assets-dest', "${projectDir}/src/main/res"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
afterEvaluate {
|
|
15
|
-
// Disable for release variants
|
|
16
|
-
tasks.matching { it.name.contains('Release') }.configureEach {
|
|
17
|
-
tasks.named('generateDebugBundle').get().enabled = false
|
|
18
|
-
}
|
|
19
|
-
// Wire as preBuild dependency for debug variants
|
|
20
|
-
android.applicationVariants.matching { it.buildType.name == 'debug' }.configureEach {
|
|
21
|
-
preBuildProvider.configure { dependsOn 'generateDebugBundle' }
|
|
22
|
-
}
|
|
23
|
-
}
|
package/scripts/embed-android.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
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
|
-
};
|
package/scripts/embed-expo.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
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 };
|