@truewatchtech/cocos-sdk 0.1.0-alpha.4 → 0.1.0-alpha.6
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 +49 -1
- package/bin/cli.cjs +48 -18
- package/bin/legacy-asset-hashes.json +32 -0
- package/bin/managed-assets.cjs +67 -0
- package/dist/creator2/core/client.d.ts +23 -7
- package/dist/creator2/core/client.d.ts.map +1 -1
- package/dist/creator2/core/client.js +74 -28
- package/dist/creator2/core/client.js.map +1 -1
- package/dist/creator2/core/index.d.ts +1 -1
- package/dist/creator2/core/index.d.ts.map +1 -1
- package/dist/creator2/core/index.js.map +1 -1
- package/dist/creator2/core/plugin-api.d.ts +15 -0
- package/dist/creator2/core/plugin-api.d.ts.map +1 -0
- package/dist/creator2/core/plugin-api.js +3 -0
- package/dist/creator2/core/plugin-api.js.map +1 -0
- package/dist/creator2/core/types.d.ts +0 -78
- package/dist/creator2/core/types.d.ts.map +1 -1
- package/dist/creator2/core/validation.d.ts +0 -11
- package/dist/creator2/core/validation.d.ts.map +1 -1
- package/dist/creator2/core/validation.js +0 -63
- package/dist/creator2/core/validation.js.map +1 -1
- package/dist/creator2/core/version.d.ts +1 -1
- package/dist/creator2/core/version.js +1 -1
- package/dist/creator2/creator2/auto.d.ts +1 -3
- package/dist/creator2/creator2/auto.d.ts.map +1 -1
- package/dist/creator2/creator2/auto.js +0 -25
- package/dist/creator2/creator2/auto.js.map +1 -1
- package/dist/creator2/creator2/index.d.ts +0 -7
- package/dist/creator2/creator2/index.d.ts.map +1 -1
- package/dist/creator2/creator2/index.js +1 -13
- package/dist/creator2/creator2/index.js.map +1 -1
- package/dist/creator2/creator2/transport.d.ts +0 -1
- package/dist/creator2/creator2/transport.d.ts.map +1 -1
- package/dist/creator2/creator2/transport.js +0 -4
- package/dist/creator2/creator2/transport.js.map +1 -1
- package/dist/creator3/core/client.d.ts +23 -7
- package/dist/creator3/core/client.d.ts.map +1 -1
- package/dist/creator3/core/client.js +72 -28
- package/dist/creator3/core/client.js.map +1 -1
- package/dist/creator3/core/index.d.ts +1 -1
- package/dist/creator3/core/index.d.ts.map +1 -1
- package/dist/creator3/core/index.js.map +1 -1
- package/dist/creator3/core/plugin-api.d.ts +15 -0
- package/dist/creator3/core/plugin-api.d.ts.map +1 -0
- package/dist/creator3/core/plugin-api.js +2 -0
- package/dist/creator3/core/plugin-api.js.map +1 -0
- package/dist/creator3/core/types.d.ts +0 -78
- package/dist/creator3/core/types.d.ts.map +1 -1
- package/dist/creator3/core/validation.d.ts +0 -11
- package/dist/creator3/core/validation.d.ts.map +1 -1
- package/dist/creator3/core/validation.js +0 -61
- package/dist/creator3/core/validation.js.map +1 -1
- package/dist/creator3/core/version.d.ts +1 -1
- package/dist/creator3/core/version.js +1 -1
- package/dist/creator3/creator3/auto.d.ts +1 -3
- package/dist/creator3/creator3/auto.d.ts.map +1 -1
- package/dist/creator3/creator3/auto.js +1 -24
- package/dist/creator3/creator3/auto.js.map +1 -1
- package/dist/creator3/creator3/index.d.ts +0 -7
- package/dist/creator3/creator3/index.d.ts.map +1 -1
- package/dist/creator3/creator3/index.js +1 -12
- package/dist/creator3/creator3/index.js.map +1 -1
- package/dist/creator3/creator3/transport.d.ts +0 -1
- package/dist/creator3/creator3/transport.d.ts.map +1 -1
- package/dist/creator3/creator3/transport.js +0 -4
- package/dist/creator3/creator3/transport.js.map +1 -1
- package/extensions/creator2/install-native.cjs +118 -28
- package/extensions/creator2/install-spm.cjs +198 -0
- package/extensions/creator2/legacy-asset-hashes.json +32 -0
- package/extensions/creator2/managed-assets.cjs +67 -0
- package/extensions/creator3/install-native.cjs +118 -28
- package/extensions/creator3/install-spm.cjs +198 -0
- package/extensions/creator3/legacy-asset-hashes.json +32 -0
- package/extensions/creator3/managed-assets.cjs +67 -0
- package/native/android/src/main/java/com/ft/sdk/cocos/FTBridgeTransport.java +85 -0
- package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosBridge.java +2 -192
- package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosBridgeModules.java +9 -0
- package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosBridgeProvider.java +1 -1
- package/native/ios/FTCocosBridge.m +8 -203
- package/native/ios/FTCocosBridge.podspec +3 -4
- package/native/ios/Package.swift +25 -0
- package/native/ios/include/FTCocosBridge.h +2 -0
- package/package.json +10 -5
- package/plugin-api/package.json +4 -0
- package/components/creator2/ReplayPrivacy.ts +0 -23
- package/components/creator3/ReplayPrivacy.ts +0 -25
- package/dist/creator2/core/replay-async-transport.d.ts +0 -4
- package/dist/creator2/core/replay-async-transport.d.ts.map +0 -1
- package/dist/creator2/core/replay-async-transport.js +0 -67
- package/dist/creator2/core/replay-async-transport.js.map +0 -1
- package/dist/creator2/core/replay-budget.d.ts +0 -28
- package/dist/creator2/core/replay-budget.d.ts.map +0 -1
- package/dist/creator2/core/replay-budget.js +0 -113
- package/dist/creator2/core/replay-budget.js.map +0 -1
- package/dist/creator2/core/replay-diagnostics.d.ts +0 -36
- package/dist/creator2/core/replay-diagnostics.d.ts.map +0 -1
- package/dist/creator2/core/replay-diagnostics.js +0 -40
- package/dist/creator2/core/replay-diagnostics.js.map +0 -1
- package/dist/creator2/core/replay-encoding.d.ts +0 -31
- package/dist/creator2/core/replay-encoding.d.ts.map +0 -1
- package/dist/creator2/core/replay-encoding.js +0 -63
- package/dist/creator2/core/replay-encoding.js.map +0 -1
- package/dist/creator2/core/replay-file.d.ts +0 -5
- package/dist/creator2/core/replay-file.d.ts.map +0 -1
- package/dist/creator2/core/replay-file.js +0 -101
- package/dist/creator2/core/replay-file.js.map +0 -1
- package/dist/creator2/core/replay-pixels.d.ts +0 -3
- package/dist/creator2/core/replay-pixels.d.ts.map +0 -1
- package/dist/creator2/core/replay-pixels.js +0 -16
- package/dist/creator2/core/replay-pixels.js.map +0 -1
- package/dist/creator2/core/replay-privacy-nodes.d.ts +0 -14
- package/dist/creator2/core/replay-privacy-nodes.d.ts.map +0 -1
- package/dist/creator2/core/replay-privacy-nodes.js +0 -18
- package/dist/creator2/core/replay-privacy-nodes.js.map +0 -1
- package/dist/creator2/core/replay-privacy.d.ts +0 -15
- package/dist/creator2/core/replay-privacy.d.ts.map +0 -1
- package/dist/creator2/core/replay-privacy.js +0 -36
- package/dist/creator2/core/replay-privacy.js.map +0 -1
- package/dist/creator2/core/replay.d.ts +0 -99
- package/dist/creator2/core/replay.d.ts.map +0 -1
- package/dist/creator2/core/replay.js +0 -611
- package/dist/creator2/core/replay.js.map +0 -1
- package/dist/creator2/creator2/capture.d.ts +0 -17
- package/dist/creator2/creator2/capture.d.ts.map +0 -1
- package/dist/creator2/creator2/capture.js +0 -168
- package/dist/creator2/creator2/capture.js.map +0 -1
- package/dist/creator3/core/replay-async-transport.d.ts +0 -4
- package/dist/creator3/core/replay-async-transport.d.ts.map +0 -1
- package/dist/creator3/core/replay-async-transport.js +0 -16
- package/dist/creator3/core/replay-async-transport.js.map +0 -1
- package/dist/creator3/core/replay-budget.d.ts +0 -28
- package/dist/creator3/core/replay-budget.d.ts.map +0 -1
- package/dist/creator3/core/replay-budget.js +0 -105
- package/dist/creator3/core/replay-budget.js.map +0 -1
- package/dist/creator3/core/replay-diagnostics.d.ts +0 -36
- package/dist/creator3/core/replay-diagnostics.d.ts.map +0 -1
- package/dist/creator3/core/replay-diagnostics.js +0 -36
- package/dist/creator3/core/replay-diagnostics.js.map +0 -1
- package/dist/creator3/core/replay-encoding.d.ts +0 -31
- package/dist/creator3/core/replay-encoding.d.ts.map +0 -1
- package/dist/creator3/core/replay-encoding.js +0 -62
- package/dist/creator3/core/replay-encoding.js.map +0 -1
- package/dist/creator3/core/replay-file.d.ts +0 -5
- package/dist/creator3/core/replay-file.d.ts.map +0 -1
- package/dist/creator3/core/replay-file.js +0 -31
- package/dist/creator3/core/replay-file.js.map +0 -1
- package/dist/creator3/core/replay-pixels.d.ts +0 -3
- package/dist/creator3/core/replay-pixels.d.ts.map +0 -1
- package/dist/creator3/core/replay-pixels.js +0 -13
- package/dist/creator3/core/replay-pixels.js.map +0 -1
- package/dist/creator3/core/replay-privacy-nodes.d.ts +0 -14
- package/dist/creator3/core/replay-privacy-nodes.d.ts.map +0 -1
- package/dist/creator3/core/replay-privacy-nodes.js +0 -15
- package/dist/creator3/core/replay-privacy-nodes.js.map +0 -1
- package/dist/creator3/core/replay-privacy.d.ts +0 -15
- package/dist/creator3/core/replay-privacy.d.ts.map +0 -1
- package/dist/creator3/core/replay-privacy.js +0 -33
- package/dist/creator3/core/replay-privacy.js.map +0 -1
- package/dist/creator3/core/replay.d.ts +0 -99
- package/dist/creator3/core/replay.d.ts.map +0 -1
- package/dist/creator3/core/replay.js +0 -534
- package/dist/creator3/core/replay.js.map +0 -1
- package/dist/creator3/creator3/capture.d.ts +0 -17
- package/dist/creator3/creator3/capture.d.ts.map +0 -1
- package/dist/creator3/creator3/capture.js +0 -112
- package/dist/creator3/creator3/capture.js.map +0 -1
- package/dist/creator3/creator3/replay-render-cycle.d.ts +0 -9
- package/dist/creator3/creator3/replay-render-cycle.d.ts.map +0 -1
- package/dist/creator3/creator3/replay-render-cycle.js +0 -12
- package/dist/creator3/creator3/replay-render-cycle.js.map +0 -1
- package/native/android/src/main/java/com/ft/sdk/cocos/FTCocosReplayImageJobs.java +0 -55
- package/native/ios/FTCocosReplayImageJobs.h +0 -11
- package/native/ios/FTCocosReplayImageJobs.m +0 -54
- package/native/shared/FTReplayFileBridge.h +0 -62
- package/native/shared/FTReplayFileWorker.h +0 -88
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { execFileSync } = require('child_process');
|
|
6
|
+
const { syncManagedDirectory } = require('./managed-assets.cjs');
|
|
6
7
|
|
|
7
8
|
const BEGIN = '/* COCOS_SDK_BEGIN */';
|
|
8
9
|
const END = '/* COCOS_SDK_END */';
|
|
@@ -22,19 +23,38 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
22
23
|
buildRoot = path.resolve(buildRoot);
|
|
23
24
|
extensionRoot = path.resolve(extensionRoot);
|
|
24
25
|
if (!fs.existsSync(buildRoot)) return;
|
|
26
|
+
const dependencyManager = readIosDependencyManager(buildRoot, extensionRoot);
|
|
25
27
|
const nativeSource = path.join(extensionRoot, 'native');
|
|
26
28
|
if (!fs.existsSync(nativeSource)) {
|
|
27
29
|
logger.warn(`[cocos-sdk] Native bridge not found at ${nativeSource}`);
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
32
|
+
const replayDescriptor = path.join(extensionRoot, 'replay-integration.json');
|
|
33
|
+
const replay = fs.existsSync(replayDescriptor) ? JSON.parse(fs.readFileSync(replayDescriptor, 'utf8')) : null;
|
|
34
|
+
const installedFile = path.join(extensionRoot, 'sdk-integration.json');
|
|
35
|
+
const installed = fs.existsSync(installedFile) ? JSON.parse(fs.readFileSync(installedFile, 'utf8')) : null;
|
|
36
|
+
if (installed?.replay && !replay) throw new Error('[cocos-sdk] Replay assets are missing. Run truewatch-cocos install --replay.');
|
|
37
|
+
if (replay && (replay.schemaVersion !== 1 || !installed || replay.baseVersion !== installed.version || replay.version !== installed.version || replay.iosSdkVersion !== '1.6.8-alpha.5')) {
|
|
38
|
+
throw new Error('[cocos-sdk] Replay native integration version does not match the base SDK. Reinstall matching packages.');
|
|
39
|
+
}
|
|
40
|
+
const replaySource = path.join(extensionRoot, 'replay-native');
|
|
30
41
|
const buildNative = path.join(buildRoot, 'cocos-sdk-native');
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.forEach((file) => patchReplayFileBridge(file, replayHeader));
|
|
42
|
+
const sources = [{ root: nativeSource }];
|
|
43
|
+
if (replay) sources.push({ root: replaySource, prefix: 'replay/' });
|
|
44
|
+
if (dependencyManager === 'spm') {
|
|
45
|
+
sources.push({ root: path.join(nativeSource, 'ios'), prefix: 'FTCocosBridge/' });
|
|
46
|
+
if (replay) sources.push({ root: path.join(replaySource, 'ios'), prefix: 'FTCocosReplayBridge/' });
|
|
37
47
|
}
|
|
48
|
+
const modules = 'android/src/main/java/com/ft/sdk/cocos/FTCocosBridgeModules.java';
|
|
49
|
+
const dispatcher = fs.readFileSync(path.join(nativeSource, modules), 'utf8');
|
|
50
|
+
const configuredDispatcher = replay ? dispatcher.replace(
|
|
51
|
+
' return "{',
|
|
52
|
+
' if ("session-replay".equals(endpoint)) return FTCocosReplayBridge.invokeLocal(method, payload);\n return "{',
|
|
53
|
+
) : dispatcher;
|
|
54
|
+
syncManagedDirectory(buildNative, sources, { [modules]: configuredDispatcher });
|
|
55
|
+
const files = findFiles(buildRoot, 7);
|
|
56
|
+
const replayHeader = replay ? path.join(buildNative, 'replay', 'shared', 'FTReplayFileBridge.h') : null;
|
|
57
|
+
findReplayEntryFiles(files).forEach(file => patchReplayFileBridge(file, replayHeader));
|
|
38
58
|
const gradlePropertiesFiles = files.filter((file) => path.basename(file) === 'gradle.properties');
|
|
39
59
|
const gradleFiles = uniqueFiles([
|
|
40
60
|
...files.filter((file) => /(?:^|\/)app\/build\.gradle$/.test(normalize(file))),
|
|
@@ -47,7 +67,10 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
47
67
|
const iosApplicationProjects = xcodeProjects
|
|
48
68
|
.map((projectFile) => ({ projectFile, target: findIosApplicationTarget(projectFile) }))
|
|
49
69
|
.filter((project) => project.target);
|
|
50
|
-
if (
|
|
70
|
+
if (dependencyManager === 'spm' && iosApplicationProjects.length > 0) {
|
|
71
|
+
migrateManagedPods(podfiles, logger);
|
|
72
|
+
}
|
|
73
|
+
if (dependencyManager === 'cocoapods' && podfiles.length === 0) {
|
|
51
74
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
52
75
|
const projectDirectory = path.dirname(path.dirname(projectFile));
|
|
53
76
|
const podfile = path.join(projectDirectory, 'Podfile');
|
|
@@ -57,12 +80,21 @@ function installNative(buildRoot, extensionRoot, logger = console) {
|
|
|
57
80
|
}
|
|
58
81
|
iosApplicationProjects.forEach(({ projectFile, target }) => {
|
|
59
82
|
patchCocos2IosConfiguration(projectFile, target);
|
|
83
|
+
if (dependencyManager === 'spm') {
|
|
84
|
+
const spm = require('./install-spm.cjs');
|
|
85
|
+
if (!replay) spm.removeSwiftPackages(projectFile, ['FTCocosReplayBridge']);
|
|
86
|
+
spm.installSwiftPackage(projectFile, path.join(buildNative, 'FTCocosBridge'), 'FTCocosBridge', target);
|
|
87
|
+
if (replay) spm.installSwiftPackage(projectFile, path.join(buildNative, 'FTCocosReplayBridge'), 'FTCocosReplayBridge', target);
|
|
88
|
+
} else if (fs.readFileSync(projectFile, 'utf8').includes('XCLocalSwiftPackageReference')
|
|
89
|
+
|| fs.existsSync(path.join(path.dirname(projectFile), 'cocos-sdk-spm.json'))) {
|
|
90
|
+
require('./install-spm.cjs').removeSwiftPackages(projectFile);
|
|
91
|
+
}
|
|
60
92
|
});
|
|
61
|
-
gradleFiles.forEach((file) => patchGradle(file, buildNative));
|
|
93
|
+
gradleFiles.forEach((file) => patchGradle(file, buildNative, replay));
|
|
62
94
|
gradlePropertiesFiles.forEach(patchGradleProperties);
|
|
63
|
-
podfiles.forEach((file) => patchPodfile(file, buildNative));
|
|
95
|
+
if (dependencyManager === 'cocoapods') podfiles.forEach((file) => patchPodfile(file, buildNative, replay));
|
|
64
96
|
logger.info(
|
|
65
|
-
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${podfiles.length} iOS project files).`,
|
|
97
|
+
`[cocos-sdk] Installed native bridge (${gradleFiles.length} Android, ${dependencyManager === 'spm' ? iosApplicationProjects.length : podfiles.length} iOS project files; iOS: ${dependencyManager}).`,
|
|
66
98
|
);
|
|
67
99
|
}
|
|
68
100
|
|
|
@@ -75,6 +107,12 @@ function patchReplayFileBridge(file, header) {
|
|
|
75
107
|
// Creator 3 shares Game.cpp across iOS/Android builds. Keep its include stable
|
|
76
108
|
// when another platform's build directory is cleaned or regenerated.
|
|
77
109
|
const sharedDirectory = path.join(path.dirname(file), 'cocos-sdk-replay');
|
|
110
|
+
if (!header) {
|
|
111
|
+
const next = original.replace(markedPattern(includeBegin, includeEnd), '').replace(markedPattern(initBegin, initEnd), '');
|
|
112
|
+
if (fs.existsSync(sharedDirectory)) syncManagedDirectory(sharedDirectory, []);
|
|
113
|
+
if (next !== original) fs.writeFileSync(file, next);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
78
116
|
const include = `${includeBegin}\n#include "cocos-sdk-replay/FTReplayFileBridge.h"\n${includeEnd}`;
|
|
79
117
|
const init = `${initBegin}\n ft_cocos::installReplayFileBridge();\n ${initEnd}\n `;
|
|
80
118
|
let next = original;
|
|
@@ -90,7 +128,7 @@ function patchReplayFileBridge(file, header) {
|
|
|
90
128
|
next = markedPattern(includeBegin, includeEnd).test(next)
|
|
91
129
|
? next.replace(markedPattern(includeBegin, includeEnd), include)
|
|
92
130
|
: `${include}\n${next}`;
|
|
93
|
-
|
|
131
|
+
syncManagedDirectory(sharedDirectory, [{ root: path.dirname(header) }]);
|
|
94
132
|
if (next !== original) fs.writeFileSync(file, next);
|
|
95
133
|
}
|
|
96
134
|
|
|
@@ -241,34 +279,37 @@ function compareVersions(left, right) {
|
|
|
241
279
|
return 0;
|
|
242
280
|
}
|
|
243
281
|
|
|
244
|
-
function patchGradle(file, nativeRoot) {
|
|
282
|
+
function patchGradle(file, nativeRoot, replay) {
|
|
245
283
|
const javaDir = normalize(path.join(nativeRoot, 'android', 'src', 'main', 'java'));
|
|
284
|
+
const javaDirs = [javaDir];
|
|
285
|
+
if (replay) javaDirs.push(normalize(path.join(nativeRoot, 'replay', 'android', 'src', 'main', 'java')));
|
|
246
286
|
replaceMarkedBlock(file, [
|
|
247
287
|
BEGIN,
|
|
248
288
|
'android {',
|
|
249
|
-
` sourceSets { main.java.srcDirs += ['${escapeGroovy(
|
|
289
|
+
` sourceSets { main.java.srcDirs += [${javaDirs.map(dir => `'${escapeGroovy(dir)}'`).join(', ')}] }`,
|
|
250
290
|
'}',
|
|
251
291
|
'repositories {',
|
|
252
292
|
" maven { url 'https://mvnrepo.truewatch.com/repository/maven-releases' }",
|
|
253
293
|
'}',
|
|
254
294
|
'dependencies {',
|
|
255
|
-
" implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-
|
|
295
|
+
" implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-sdk:1.7.6-alpha03'",
|
|
256
296
|
" implementation 'com.truewatch.ft.mobile.sdk.tracker.agent:ft-native:1.1.3'",
|
|
257
|
-
|
|
297
|
+
...(replay ? [` implementation '${escapeGroovy(replay.androidReplay)}'`] : []),
|
|
258
298
|
" implementation 'com.google.code.gson:gson:2.10.1'",
|
|
259
299
|
" implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.22')",
|
|
260
300
|
" implementation 'androidx.appcompat:appcompat:1.1.0'",
|
|
261
|
-
|
|
301
|
+
...(replay ? [` implementation '${escapeGroovy(replay.androidFragment)}'`] : []),
|
|
262
302
|
'}',
|
|
263
303
|
END,
|
|
264
304
|
].join('\n'), BEGIN, END, [[LEGACY_BEGIN, LEGACY_END]]);
|
|
265
305
|
}
|
|
266
306
|
|
|
267
|
-
function patchPodfile(file, nativeRoot) {
|
|
307
|
+
function patchPodfile(file, nativeRoot, replay) {
|
|
268
308
|
const iosDir = normalize(path.join(nativeRoot, 'ios'));
|
|
269
309
|
const block = [
|
|
270
310
|
POD_BEGIN,
|
|
271
311
|
`pod 'FTCocosBridge', :path => '${escapeRuby(iosDir)}'`,
|
|
312
|
+
...(replay ? [`pod 'FTCocosReplayBridge', :path => '${escapeRuby(normalize(path.join(nativeRoot, 'replay', 'ios')))}'`] : []),
|
|
272
313
|
POD_END,
|
|
273
314
|
].join('\n');
|
|
274
315
|
const original = fs.readFileSync(file, 'utf8');
|
|
@@ -298,6 +339,11 @@ function replaceIndentedMarkedBlock(contents, begin, end, block) {
|
|
|
298
339
|
|
|
299
340
|
function findIosApplicationTarget(projectFile) {
|
|
300
341
|
const contents = fs.readFileSync(projectFile, 'utf8');
|
|
342
|
+
if (contents.includes('COCOS_SDK_XCODE_PROJECT')
|
|
343
|
+
|| fs.existsSync(path.join(path.dirname(projectFile), 'cocos-sdk-spm.json'))) {
|
|
344
|
+
const { readProject, applicationTarget } = require('./install-spm.cjs');
|
|
345
|
+
return applicationTarget(readProject(projectFile))?.[1].name || null;
|
|
346
|
+
}
|
|
301
347
|
const nativeTarget = /\/\* ([^*]+) \*\/ = \{\s*isa = PBXNativeTarget;([\s\S]*?)\n\s*\};/g;
|
|
302
348
|
const targets = [];
|
|
303
349
|
let match;
|
|
@@ -352,16 +398,6 @@ function markedPattern(begin, end) {
|
|
|
352
398
|
return new RegExp(`${escapeRegExp(begin)}[\\s\\S]*?${escapeRegExp(end)}`, 'm');
|
|
353
399
|
}
|
|
354
400
|
|
|
355
|
-
function copyDirectory(source, destination) {
|
|
356
|
-
fs.mkdirSync(destination, { recursive: true });
|
|
357
|
-
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
358
|
-
const from = path.join(source, entry.name);
|
|
359
|
-
const to = path.join(destination, entry.name);
|
|
360
|
-
if (entry.isDirectory()) copyDirectory(from, to);
|
|
361
|
-
else fs.copyFileSync(from, to);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
401
|
function findFiles(root, maxDepth) {
|
|
366
402
|
const result = [];
|
|
367
403
|
function visit(directory, depth) {
|
|
@@ -382,4 +418,58 @@ function escapeGroovy(value) { return value.replace(/\\/g, '\\\\').replace(/'/g,
|
|
|
382
418
|
function escapeRuby(value) { return value.replace(/\\/g, '\\\\').replace(/'/g, "\\'"); }
|
|
383
419
|
function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
384
420
|
|
|
385
|
-
|
|
421
|
+
function readIosDependencyManager(buildRoot, extensionRoot) {
|
|
422
|
+
for (const start of [buildRoot, extensionRoot]) {
|
|
423
|
+
let directory = path.resolve(start);
|
|
424
|
+
while (true) {
|
|
425
|
+
const file = path.join(directory, 'cocos-sdk.config.json');
|
|
426
|
+
if (fs.existsSync(file)) {
|
|
427
|
+
const config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
428
|
+
const manager = config.ios?.dependencyManager ?? 'cocoapods';
|
|
429
|
+
if (!['spm', 'cocoapods'].includes(manager)) {
|
|
430
|
+
throw new Error(`[cocos-sdk] Invalid ios.dependencyManager in ${file}: expected spm or cocoapods.`);
|
|
431
|
+
}
|
|
432
|
+
return manager;
|
|
433
|
+
}
|
|
434
|
+
const parent = path.dirname(directory);
|
|
435
|
+
if (parent === directory) break;
|
|
436
|
+
directory = parent;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return 'cocoapods';
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function migrateManagedPods(podfiles, logger) {
|
|
443
|
+
for (const file of podfiles) {
|
|
444
|
+
const original = fs.readFileSync(file, 'utf8');
|
|
445
|
+
let next = original;
|
|
446
|
+
for (const [begin, end] of [
|
|
447
|
+
[POD_BEGIN, POD_END], [LEGACY_POD_BEGIN, LEGACY_POD_END], [LEGACY_BEGIN, LEGACY_END],
|
|
448
|
+
['# COCOS_HYBRID_SAMPLE_BEGIN', '# COCOS_HYBRID_SAMPLE_END'],
|
|
449
|
+
]) next = next.replace(markedPattern(begin, end), '');
|
|
450
|
+
if (/^\s*pod\s+['"](?:FTCocosBridge|FTCocosReplayBridge|HybridSampleHost|TrueWatchSDK)(?:\/[^'"]*)?['"]/m.test(next)) {
|
|
451
|
+
throw new Error(`[cocos-sdk] Remove the manually declared native SDK Pod from ${file} before switching to SPM.`);
|
|
452
|
+
}
|
|
453
|
+
if (next !== original) fs.writeFileSync(file, next);
|
|
454
|
+
const directory = path.dirname(file);
|
|
455
|
+
const existingLock = path.join(directory, 'Podfile.lock');
|
|
456
|
+
const hasSDKPods = fs.existsSync(existingLock)
|
|
457
|
+
&& /^\s*- (?:FTCocosBridge|FTCocosReplayBridge|HybridSampleHost|TrueWatchSDK)(?:\/|\s|:)/m.test(fs.readFileSync(existingLock, 'utf8'));
|
|
458
|
+
if (next === original && !hasSDKPods) continue;
|
|
459
|
+
if (fs.existsSync(path.join(directory, 'Podfile.lock')) || fs.existsSync(path.join(directory, 'Pods'))) {
|
|
460
|
+
logger.info('[cocos-sdk] Updating existing Pods integration for the SPM switch.');
|
|
461
|
+
try {
|
|
462
|
+
execFileSync('pod', ['install'], { cwd: directory, encoding: 'utf8', maxBuffer: 4 * 1024 * 1024 });
|
|
463
|
+
} catch (error) {
|
|
464
|
+
fs.writeFileSync(file, original);
|
|
465
|
+
throw new Error(`[cocos-sdk] Unable to update existing Pods in ${directory}: ${error.message}`);
|
|
466
|
+
}
|
|
467
|
+
const lock = path.join(directory, 'Podfile.lock');
|
|
468
|
+
if (fs.existsSync(lock) && /^\s*- TrueWatchSDK(?:\/|\s|:)/m.test(fs.readFileSync(lock, 'utf8'))) {
|
|
469
|
+
throw new Error(`[cocos-sdk] Another Pod still depends on the native SDK in ${lock}; migrate that dependency before using SPM.`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
module.exports = { installNative, readIosDependencyManager };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { createHash } = require('crypto');
|
|
6
|
+
const { execFileSync } = require('child_process');
|
|
7
|
+
|
|
8
|
+
function readProject(file) {
|
|
9
|
+
// plutil understands both Xcode's OpenStep format and XML plists, without Ruby/gems.
|
|
10
|
+
return JSON.parse(execFileSync('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file], {
|
|
11
|
+
encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function writeProject(file, project) {
|
|
16
|
+
const next = `// !$*UTF8*$!\n// COCOS_SDK_XCODE_PROJECT\n${serialize(project)}\n`;
|
|
17
|
+
if (fs.readFileSync(file, 'utf8') !== next) fs.writeFileSync(file, next);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function serialize(value, depth = 0) {
|
|
21
|
+
const indent = '\t'.repeat(depth);
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return `(\n${value.map((entry) => `${indent}\t${serialize(entry, depth + 1)},\n`).join('')}${indent})`;
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
return `{\n${Object.entries(value).sort(([a], [b]) => a === 'isa' ? -1 : b === 'isa' ? 1 : a.localeCompare(b)).map(([key, entry]) => (
|
|
27
|
+
`${indent}\t${serialize(key)} = ${serialize(entry, depth + 1)};\n`
|
|
28
|
+
)).join('')}${indent}}`;
|
|
29
|
+
}
|
|
30
|
+
const text = String(value);
|
|
31
|
+
return /^[A-Za-z0-9_./]+$/.test(text) ? text : JSON.stringify(text);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function applicationTarget(project, preferredName) {
|
|
35
|
+
const targets = Object.entries(project.objects).filter(([, object]) => (
|
|
36
|
+
object.isa === 'PBXNativeTarget' && object.productType === 'com.apple.product-type.application'
|
|
37
|
+
));
|
|
38
|
+
return targets.find(([, object]) => object.name === preferredName)
|
|
39
|
+
|| targets.find(([, object]) => /(?:^|[-_.])(?:mobile|ios)$/i.test(object.name))
|
|
40
|
+
|| targets[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function add(list, value) {
|
|
44
|
+
if (!list.includes(value)) list.push(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function identifier(value) {
|
|
48
|
+
return createHash('sha256').update(`cocos-sdk:${value}`).digest('hex').slice(0, 24).toUpperCase();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function installSwiftPackage(projectFile, packageDirectory, productName, targetName) {
|
|
52
|
+
if (!fs.existsSync(path.join(packageDirectory, 'Package.swift'))) {
|
|
53
|
+
throw new Error(`[cocos-sdk] Missing Swift package: ${packageDirectory}`);
|
|
54
|
+
}
|
|
55
|
+
const project = readProject(projectFile);
|
|
56
|
+
const selected = applicationTarget(project, targetName);
|
|
57
|
+
if (!selected) throw new Error(`[cocos-sdk] No application target in ${projectFile}`);
|
|
58
|
+
const [targetID, target] = selected;
|
|
59
|
+
const objects = project.objects;
|
|
60
|
+
const root = objects[project.rootObject];
|
|
61
|
+
const projectDirectory = path.resolve(path.dirname(path.dirname(projectFile)), root.projectDirPath || '.');
|
|
62
|
+
const relativePath = path.relative(projectDirectory, packageDirectory).split(path.sep).join('/');
|
|
63
|
+
const packageID = identifier(`package:${productName}`);
|
|
64
|
+
const productID = identifier(`product:${targetID}:${productName}`);
|
|
65
|
+
const buildID = identifier(`framework:${targetID}:${productName}`);
|
|
66
|
+
const existing = (target.packageProductDependencies || []).find((id) => (
|
|
67
|
+
id !== productID && objects[id]?.productName === productName
|
|
68
|
+
));
|
|
69
|
+
if (existing) throw new Error(`[cocos-sdk] ${productName} is already managed manually in ${target.name}; remove that reference before automatic installation.`);
|
|
70
|
+
|
|
71
|
+
objects[packageID] = { isa: 'XCLocalSwiftPackageReference', relativePath };
|
|
72
|
+
objects[productID] = { isa: 'XCSwiftPackageProductDependency', productName };
|
|
73
|
+
objects[buildID] = { isa: 'PBXBuildFile', productRef: productID };
|
|
74
|
+
add(root.packageReferences ||= [], packageID);
|
|
75
|
+
add(target.packageProductDependencies ||= [], productID);
|
|
76
|
+
let phaseID = (target.buildPhases || []).find((id) => objects[id]?.isa === 'PBXFrameworksBuildPhase');
|
|
77
|
+
if (!phaseID) {
|
|
78
|
+
phaseID = identifier(`frameworks:${targetID}`);
|
|
79
|
+
objects[phaseID] = { isa: 'PBXFrameworksBuildPhase', buildActionMask: '2147483647', files: [], runOnlyForDeploymentPostprocessing: '0' };
|
|
80
|
+
add(target.buildPhases ||= [], phaseID);
|
|
81
|
+
}
|
|
82
|
+
add(objects[phaseID].files ||= [], buildID);
|
|
83
|
+
const configurations = objects[target.buildConfigurationList]?.buildConfigurations || [];
|
|
84
|
+
for (const configID of configurations) {
|
|
85
|
+
const settings = objects[configID].buildSettings ||= {};
|
|
86
|
+
const flags = settings.OTHER_LDFLAGS;
|
|
87
|
+
if (Array.isArray(flags)) {
|
|
88
|
+
add(flags, '$(inherited)');
|
|
89
|
+
add(flags, '-ObjC');
|
|
90
|
+
} else {
|
|
91
|
+
const parts = [flags || '$(inherited)'];
|
|
92
|
+
if (!String(flags || '').includes('$(inherited)')) add(parts, '$(inherited)');
|
|
93
|
+
if (!/(?:^|\s)-ObjC(?:\s|$)/.test(flags || '')) add(parts, '-ObjC');
|
|
94
|
+
settings.OTHER_LDFLAGS = parts.join(' ');
|
|
95
|
+
}
|
|
96
|
+
if (Number(settings.IPHONEOS_DEPLOYMENT_TARGET || 0) < 12) settings.IPHONEOS_DEPLOYMENT_TARGET = '12.0';
|
|
97
|
+
settings.CLANG_ENABLE_MODULES = 'YES';
|
|
98
|
+
}
|
|
99
|
+
preserveCmakeIntegration(projectFile, project, { packageDirectory, productName, targetName: target.name });
|
|
100
|
+
writeProject(projectFile, project);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const RESTORE_BEGIN = '# COCOS_SDK_SPM_RESTORE_BEGIN';
|
|
104
|
+
const RESTORE_END = '# COCOS_SDK_SPM_RESTORE_END';
|
|
105
|
+
const restorePattern = /\n?# COCOS_SDK_SPM_RESTORE_BEGIN[\s\S]*?# COCOS_SDK_SPM_RESTORE_END\n?/g;
|
|
106
|
+
|
|
107
|
+
function shellQuote(value) {
|
|
108
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function preserveCmakeIntegration(projectFile, project, entry) {
|
|
112
|
+
const objects = project.objects;
|
|
113
|
+
const zeroCheck = Object.values(objects).find((object) => object.name === 'ZERO_CHECK');
|
|
114
|
+
if (!zeroCheck) return;
|
|
115
|
+
// CMake's SYMROOT/OBJROOT overrides select Xcode's legacy build locations,
|
|
116
|
+
// which reject packages. Keep CONFIGURATION_BUILD_DIR (engine archives).
|
|
117
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
118
|
+
const state = fs.existsSync(stateFile)
|
|
119
|
+
? JSON.parse(fs.readFileSync(stateFile, 'utf8')) : { packages: [], buildLocations: {} };
|
|
120
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
121
|
+
if (object.isa !== 'XCBuildConfiguration') continue;
|
|
122
|
+
for (const key of ['SYMROOT', 'OBJROOT']) {
|
|
123
|
+
if (object.buildSettings?.[key] === undefined) continue;
|
|
124
|
+
(state.buildLocations[id] ||= {})[key] = object.buildSettings[key];
|
|
125
|
+
delete object.buildSettings[key];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const relativeDirectory = path.relative(path.dirname(projectFile), entry.packageDirectory);
|
|
129
|
+
state.packages = state.packages.filter((item) => item.productName !== entry.productName);
|
|
130
|
+
state.packages.push({ ...entry, packageDirectory: relativeDirectory });
|
|
131
|
+
state.packages.sort((a, b) => a.productName.localeCompare(b.productName));
|
|
132
|
+
const helper = path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs');
|
|
133
|
+
if (path.resolve(helper) !== path.resolve(__filename)) fs.copyFileSync(__filename, helper);
|
|
134
|
+
const restore = [
|
|
135
|
+
RESTORE_BEGIN,
|
|
136
|
+
`${process.versions.electron ? 'ELECTRON_RUN_AS_NODE=1 ' : ''}${shellQuote(process.execPath)} ${shellQuote(helper)} --restore`,
|
|
137
|
+
RESTORE_END,
|
|
138
|
+
].join('\n');
|
|
139
|
+
for (const id of zeroCheck.buildPhases || []) {
|
|
140
|
+
const phase = objects[id];
|
|
141
|
+
if (phase?.isa !== 'PBXShellScriptBuildPhase') continue;
|
|
142
|
+
phase.shellScript = `${(phase.shellScript || '').replace(restorePattern, '').trimEnd()}\n${restore}\n`;
|
|
143
|
+
}
|
|
144
|
+
const serialized = `${JSON.stringify(state, null, 2)}\n`;
|
|
145
|
+
if (!fs.existsSync(stateFile) || fs.readFileSync(stateFile, 'utf8') !== serialized) fs.writeFileSync(stateFile, serialized);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function removeSwiftPackages(projectFile, productNames = ['FTCocosBridge', 'FTCocosReplayBridge', 'HybridSampleHost']) {
|
|
149
|
+
const project = readProject(projectFile);
|
|
150
|
+
const objects = project.objects;
|
|
151
|
+
const removed = new Set();
|
|
152
|
+
for (const productName of productNames) {
|
|
153
|
+
const packageID = identifier(`package:${productName}`);
|
|
154
|
+
if (!objects[packageID]) continue;
|
|
155
|
+
removed.add(packageID);
|
|
156
|
+
for (const [id, object] of Object.entries(objects)) {
|
|
157
|
+
if (object.isa !== 'PBXNativeTarget') continue;
|
|
158
|
+
removed.add(identifier(`product:${id}:${productName}`));
|
|
159
|
+
removed.add(identifier(`framework:${id}:${productName}`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const id of removed) delete objects[id];
|
|
163
|
+
for (const object of Object.values(objects)) {
|
|
164
|
+
for (const key of ['packageReferences', 'packageProductDependencies', 'files']) {
|
|
165
|
+
if (Array.isArray(object[key])) object[key] = object[key].filter((id) => !removed.has(id));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
}
|
|
169
|
+
const stateFile = path.join(path.dirname(projectFile), 'cocos-sdk-spm.json');
|
|
170
|
+
if (fs.existsSync(stateFile)) {
|
|
171
|
+
const state = JSON.parse(fs.readFileSync(stateFile, 'utf8'));
|
|
172
|
+
state.packages = state.packages.filter(item => !productNames.includes(item.productName));
|
|
173
|
+
if (state.packages.length) {
|
|
174
|
+
fs.writeFileSync(stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
175
|
+
} else {
|
|
176
|
+
for (const [id, settings] of Object.entries(state.buildLocations)) {
|
|
177
|
+
if (objects[id]) Object.assign(objects[id].buildSettings ||= {}, settings);
|
|
178
|
+
}
|
|
179
|
+
for (const object of Object.values(objects)) {
|
|
180
|
+
if (object.isa === 'PBXShellScriptBuildPhase') object.shellScript = (object.shellScript || '').replace(restorePattern, '');
|
|
181
|
+
}
|
|
182
|
+
fs.unlinkSync(stateFile);
|
|
183
|
+
fs.rmSync(path.join(path.dirname(projectFile), 'cocos-sdk-spm.cjs'), { force: true });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
writeProject(projectFile, project);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { installSwiftPackage, removeSwiftPackages, readProject, applicationTarget };
|
|
191
|
+
|
|
192
|
+
if (require.main === module && process.argv[2] === '--restore') {
|
|
193
|
+
const directory = path.dirname(__filename);
|
|
194
|
+
const state = JSON.parse(fs.readFileSync(path.join(directory, 'cocos-sdk-spm.json'), 'utf8'));
|
|
195
|
+
for (const item of state.packages) {
|
|
196
|
+
installSwiftPackage(path.join(directory, 'project.pbxproj'), path.resolve(directory, item.packageDirectory), item.productName, item.targetName);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[
|
|
2
|
+
"058341971ed42ddfde76c28ce19fcc0bba5020fec1f7e1c613c7bda7d2df7677",
|
|
3
|
+
"11ee16e609096ccfe90b17f61d87ab65275a39df4bef6f8c29577e7395aa00f8",
|
|
4
|
+
"15338071a78f1505265cc8efbdc9628e9362f7a2c9fc00989d57461126f8f3f5",
|
|
5
|
+
"1a51af27d68cdf0bf3ce709c9cd60f9b8a2d7defd46d06896ce9a916e1dfc767",
|
|
6
|
+
"2caa0d3d542f5cca0b6b7f3aa2851c7e296b08fce286202a844b52920b1f6dc0",
|
|
7
|
+
"3189144de638b936e14d64ddbf9d4c95e95146ceecc357021473ca8adcc757d1",
|
|
8
|
+
"48ff77a12a7b9b59172f5470a642f23e4c61e9f7e10ee6d9d3f4f41377d696ac",
|
|
9
|
+
"4d2dcd924f1073b8a6fb94d342bee33a281f93c19302104be172e5de9981b3f0",
|
|
10
|
+
"51c71e12df61cfe7e75c3d97080e31a398fbea8719a3e964cec5dd332df1202c",
|
|
11
|
+
"54340b775f6ba897ade2df2d9fcfc39d17372e246e951d77dd166f00265815a8",
|
|
12
|
+
"5f33108f27f38836214f2c11badd7bb76ba9ec090f9d5b1490834a9257976939",
|
|
13
|
+
"71e6ade990a65d1bec51fec02c94bfe804e591d47df7ed9a271c981e9cf1d71c",
|
|
14
|
+
"83df7e51a5e4f76aab6310870c665fc80334808c64fb9c92fee46b466e1b58c3",
|
|
15
|
+
"85ad9622480d954c33a4f9442a391f6d3d9d617a2f1261e5ad769170c6e9cf12",
|
|
16
|
+
"86cf732195aad676898520192ac8aa878267877dc09f85fcf425279d8badc9b8",
|
|
17
|
+
"910792f383c25cd46ddc7d851080a687fa532a93d605fb709b7baff9bb2789cf",
|
|
18
|
+
"95bdc516c4e1b72833af3bec13b18e3c2ac6ae83de284c3f711e97765ed1b461",
|
|
19
|
+
"a748564f0bba03effbf4aa381308f60eedcb618cbd2a37e1c67cdbee596e9ceb",
|
|
20
|
+
"a8a57b5892e7a5063078d561fce790b44d900d2bb3869e43ed4c4de82f1f62ed",
|
|
21
|
+
"cd03df57f7861d19b0a8e0a97e8aff1fa08e5c26d4f43152743968245632535b",
|
|
22
|
+
"d26fb42c6bee416eaf29bc2ed8579edc7a28ca3d0e18433dcb8abea11fac9920",
|
|
23
|
+
"d2d171e4ef1777facdb6fe665105372ea2e6452028d4c32f5e2ccc74dacc8850",
|
|
24
|
+
"d538d9eebaf181687ebf8537fc6ce7c8fae59fa424daeb45c3f900a8e210ecfc",
|
|
25
|
+
"d6c2cefddabd773d471138aad619ee9f90d0f39f18053efa4d43d8771668e410",
|
|
26
|
+
"df0be5a293b5259b02438e2555832a114548fed03ecdde5b80bbc621c800998d",
|
|
27
|
+
"df8e3014e583d60922cd1b7f7f16964c433b260a7e1a7e6c285cb08ce2d9e7e4",
|
|
28
|
+
"e70ca2a537cec56f8424ff4d551c1e97af133938fc7ba863a3944859dcfd194d",
|
|
29
|
+
"f0211acbaaa312bd5a4478bba81fa98293c855692ceca69f141600ff991c5904",
|
|
30
|
+
"f09f9b35ddfb17a7c4e4439384afae93ded6ee0285e778ad013d320cfc880cab",
|
|
31
|
+
"fa88198c5434613ff5ab2d44dce6e7eb8b825085b5b498402810eb144cf81f96"
|
|
32
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { createHash } = require('crypto');
|
|
5
|
+
const legacyHashes = new Set(require('./legacy-asset-hashes.json'));
|
|
6
|
+
// Older combined installations predate the ownership manifest. Never leave a
|
|
7
|
+
// locally modified obsolete Replay bridge compiling beside the new bridge.
|
|
8
|
+
const legacyReplayNames = new Set(['FTCocosReplayImageJobs.java', 'FTCocosReplayImageJobs.h', 'FTCocosReplayImageJobs.m', 'FTReplayFileBridge.h', 'FTReplayFileWorker.h']);
|
|
9
|
+
const manifestName = '.cocos-sdk-managed.json';
|
|
10
|
+
const hash = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
|
|
12
|
+
function files(root) {
|
|
13
|
+
const result = [];
|
|
14
|
+
if (!fs.existsSync(root)) return result;
|
|
15
|
+
function visit(directory) {
|
|
16
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
17
|
+
if (entry.name === manifestName || entry.name === '.DS_Store') continue;
|
|
18
|
+
const file = path.join(directory, entry.name);
|
|
19
|
+
if (entry.isSymbolicLink()) throw new Error(`[cocos-sdk] Refusing to copy symbolic link: ${file}`);
|
|
20
|
+
if (entry.isDirectory()) visit(file);
|
|
21
|
+
else result.push(path.relative(root, file).split(path.sep).join('/'));
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
visit(root);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Reconciles only files written by the SDK, preserving unrelated project assets. */
|
|
29
|
+
function syncManagedDirectory(destination, sources, overrides = {}) {
|
|
30
|
+
const desired = new Map();
|
|
31
|
+
for (const { root, prefix = '' } of sources) {
|
|
32
|
+
if (!fs.existsSync(root)) throw new Error(`[cocos-sdk] Missing package assets: ${root}`);
|
|
33
|
+
for (const file of files(root)) desired.set(prefix + file, fs.readFileSync(path.join(root, file)));
|
|
34
|
+
}
|
|
35
|
+
for (const [file, content] of Object.entries(overrides)) desired.set(file, Buffer.from(content));
|
|
36
|
+
const manifest = path.join(destination, manifestName);
|
|
37
|
+
const previous = fs.existsSync(manifest) ? JSON.parse(fs.readFileSync(manifest, 'utf8')) : {};
|
|
38
|
+
const existing = files(destination);
|
|
39
|
+
const remove = [];
|
|
40
|
+
// Check every conflict before writing or deleting any managed file.
|
|
41
|
+
for (const file of existing) {
|
|
42
|
+
const digest = hash(fs.readFileSync(path.join(destination, file)));
|
|
43
|
+
const old = previous[file];
|
|
44
|
+
const next = desired.get(file);
|
|
45
|
+
const legacy = legacyHashes.has(digest);
|
|
46
|
+
if (!next && !old && !legacy && legacyReplayNames.has(path.basename(file))) {
|
|
47
|
+
throw new Error(`[cocos-sdk] Preserve or reconcile modified legacy Replay asset before reinstalling: ${path.join(destination, file)}`);
|
|
48
|
+
}
|
|
49
|
+
if ((old || next) && digest !== old && !legacy && (!next || digest !== hash(next))) {
|
|
50
|
+
throw new Error(`[cocos-sdk] Preserve or reconcile modified SDK asset before reinstalling: ${path.join(destination, file)}`);
|
|
51
|
+
}
|
|
52
|
+
if (!next && (old || legacy) && !file.endsWith('.meta')) remove.push(file);
|
|
53
|
+
}
|
|
54
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
55
|
+
for (const file of remove) fs.unlinkSync(path.join(destination, file));
|
|
56
|
+
const nextManifest = {};
|
|
57
|
+
for (const [file, content] of desired) {
|
|
58
|
+
const target = path.resolve(destination, file);
|
|
59
|
+
if (!target.startsWith(path.resolve(destination) + path.sep)) throw new Error('Invalid SDK asset path');
|
|
60
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
61
|
+
if (!fs.existsSync(target) || hash(fs.readFileSync(target)) !== hash(content)) fs.writeFileSync(target, content);
|
|
62
|
+
nextManifest[file] = hash(content);
|
|
63
|
+
}
|
|
64
|
+
fs.writeFileSync(manifest, `${JSON.stringify(nextManifest, null, 2)}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { syncManagedDirectory };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package com.ft.sdk.cocos;
|
|
2
|
+
|
|
3
|
+
import android.app.Activity;
|
|
4
|
+
import android.app.ActivityManager;
|
|
5
|
+
import android.app.Application;
|
|
6
|
+
import android.content.Context;
|
|
7
|
+
import android.net.Uri;
|
|
8
|
+
import android.os.Build;
|
|
9
|
+
import android.os.Bundle;
|
|
10
|
+
import android.os.Process;
|
|
11
|
+
import java.util.List;
|
|
12
|
+
|
|
13
|
+
/** Shared process routing. Optional modules are selected by the generated allowlist. */
|
|
14
|
+
public final class FTBridgeTransport {
|
|
15
|
+
private FTBridgeTransport() {}
|
|
16
|
+
public static String invoke(String endpoint, String method, String payload) {
|
|
17
|
+
String forwarded = forwardToMainProcess(endpoint, method, payload);
|
|
18
|
+
return forwarded != null ? forwarded : FTCocosBridgeModules.invokeLocal(endpoint, method, payload);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private static String forwardToMainProcess(String endpoint, String method, String payload) {
|
|
22
|
+
Activity activity = resolveActivity();
|
|
23
|
+
if (activity == null || !isSecondaryProcess(activity)) return null;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
Bundle arguments = new Bundle();
|
|
27
|
+
arguments.putString("endpoint", endpoint);
|
|
28
|
+
arguments.putString(FTCocosBridgeProvider.ARGUMENT_PAYLOAD, payload);
|
|
29
|
+
Uri uri = Uri.parse("content://" + activity.getPackageName()
|
|
30
|
+
+ FTCocosBridgeProvider.AUTHORITY_SUFFIX);
|
|
31
|
+
Bundle result = activity.getContentResolver().call(
|
|
32
|
+
uri,
|
|
33
|
+
FTCocosBridgeProvider.METHOD_INVOKE,
|
|
34
|
+
method,
|
|
35
|
+
arguments);
|
|
36
|
+
return result == null
|
|
37
|
+
? null
|
|
38
|
+
: result.getString(FTCocosBridgeProvider.RESULT_RESPONSE);
|
|
39
|
+
} catch (IllegalArgumentException providerUnavailable) {
|
|
40
|
+
// Existing integrations may not have registered the optional bridge
|
|
41
|
+
// provider yet. Preserve their same-process behavior.
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private static boolean isSecondaryProcess(Context context) {
|
|
47
|
+
String currentProcess = currentProcessName(context);
|
|
48
|
+
String mainProcess = context.getApplicationInfo().processName;
|
|
49
|
+
return currentProcess != null && mainProcess != null && !mainProcess.equals(currentProcess);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private static String currentProcessName(Context context) {
|
|
53
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
54
|
+
return Application.getProcessName();
|
|
55
|
+
}
|
|
56
|
+
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
|
|
57
|
+
if (manager == null) return null;
|
|
58
|
+
List<ActivityManager.RunningAppProcessInfo> processes = manager.getRunningAppProcesses();
|
|
59
|
+
if (processes == null) return null;
|
|
60
|
+
int currentPid = Process.myPid();
|
|
61
|
+
for (ActivityManager.RunningAppProcessInfo process : processes) {
|
|
62
|
+
if (process.pid == currentPid) return process.processName;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public static Activity resolveActivity() {
|
|
68
|
+
String[] classNames = {"com.cocos.lib.CocosActivity", "org.cocos2dx.lib.Cocos2dxActivity"};
|
|
69
|
+
String[] methodNames = {"getContext", "getActivity"};
|
|
70
|
+
for (String className : classNames) {
|
|
71
|
+
for (String methodName : methodNames) {
|
|
72
|
+
try {
|
|
73
|
+
Object value = Class.forName(className).getMethod(methodName).invoke(null);
|
|
74
|
+
if (value instanceof Activity) return (Activity) value;
|
|
75
|
+
if (value instanceof Context && ((Context) value).getApplicationContext() instanceof Activity) {
|
|
76
|
+
return (Activity) ((Context) value).getApplicationContext();
|
|
77
|
+
}
|
|
78
|
+
} catch (Throwable ignored) {
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|