@leonxin/meetgames 0.1.12 → 0.1.14
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/dist/android/manifest.d.ts.map +1 -1
- package/dist/android/manifest.js +9 -0
- package/dist/android/manifest.js.map +1 -1
- package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
- package/dist/android/meetSdkRemoteGradle.js +126 -6
- package/dist/android/meetSdkRemoteGradle.js.map +1 -1
- package/dist/contracts/types.d.ts +3 -0
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +3 -0
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/reporter.d.ts.map +1 -1
- package/dist/core/reporter.js +4 -0
- package/dist/core/reporter.js.map +1 -1
- package/dist/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +18 -2
- package/dist/ios/channelConfig.js.map +1 -1
- package/dist/ios/codeUtils.d.ts +1 -0
- package/dist/ios/codeUtils.d.ts.map +1 -1
- package/dist/ios/codeUtils.js +3 -0
- package/dist/ios/codeUtils.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +242 -97
- package/dist/ios/integrate.js.map +1 -1
- package/dist/ios/pbxprojEditor.d.ts.map +1 -1
- package/dist/ios/pbxprojEditor.js +52 -1
- package/dist/ios/pbxprojEditor.js.map +1 -1
- package/dist/ops/handlers.d.ts.map +1 -1
- package/dist/ops/handlers.js +35 -3
- package/dist/ops/handlers.js.map +1 -1
- package/docs/API.md +1 -1
- package/docs/INTEGRATION.md +48 -1
- package/package.json +1 -1
- package/src/android/manifest.ts +11 -0
- package/src/android/meetSdkRemoteGradle.ts +125 -7
- package/src/contracts/types.ts +3 -0
- package/src/core/pipeline.ts +3 -0
- package/src/core/reporter.ts +3 -0
- package/src/ios/channelConfig.ts +18 -2
- package/src/ios/codeUtils.ts +4 -0
- package/src/ios/integrate.ts +253 -96
- package/src/ios/pbxprojEditor.ts +51 -1
- package/src/ops/handlers.ts +38 -3
- package/tests/doctor.test.ts +43 -3
- package/tests/meetSdkRemoteConfig.test.ts +1 -1
- package/tests/meetSdkRemoteGradle.test.ts +2 -2
- package/tests/pipeline.android.test.ts +64 -10
- package/tests/pipeline.ios.test.ts +219 -32
- package/tests/platformSelection.test.ts +4 -4
package/src/ios/integrate.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
addSystemLib,
|
|
32
32
|
addThirdPartyFramework,
|
|
33
33
|
findInfoPlistPathsFromPbx,
|
|
34
|
+
getTargetBuildSettings,
|
|
34
35
|
loadPbxFromStore,
|
|
35
36
|
savePbxToStore,
|
|
36
37
|
setBuildSetting,
|
|
@@ -50,12 +51,35 @@ export interface IosIntegrateOptions {
|
|
|
50
51
|
|
|
51
52
|
const IOS_FIREBASE_CONFIG_FILE = "GoogleService-Info.plist";
|
|
52
53
|
|
|
53
|
-
function
|
|
54
|
-
|
|
54
|
+
function logValue(value: unknown): string {
|
|
55
|
+
if (value === undefined) return "<missing>";
|
|
56
|
+
if (typeof value === "string") return value;
|
|
57
|
+
return JSON.stringify(value);
|
|
55
58
|
}
|
|
56
59
|
|
|
57
|
-
function
|
|
58
|
-
|
|
60
|
+
function pushValueChangeLog(logs: string[], label: string, before: unknown, after: unknown): void {
|
|
61
|
+
if (valuesEqual(before, after)) {
|
|
62
|
+
logs.push(`SUCCESS: ${label} 未变化 value:${logValue(after)}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
logs.push(`SUCCESS: ${label} ${logValue(before)} -> ${logValue(after)}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function valuesEqual(before: unknown, after: unknown): boolean {
|
|
69
|
+
return JSON.stringify(before) === JSON.stringify(after);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function cloneValue(value: unknown): unknown {
|
|
73
|
+
if (value === undefined) return undefined;
|
|
74
|
+
return JSON.parse(JSON.stringify(value)) as unknown;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function ok(changed: string[], warnings: string[] = [], logs: string[] = []): StepResult {
|
|
78
|
+
return { ok: true, changedFiles: changed, logs, warnings, errors: [] };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function fail(errors: string[], warnings: string[] = [], logs: string[] = []): StepResult {
|
|
82
|
+
return { ok: false, changedFiles: [], logs, warnings, errors };
|
|
59
83
|
}
|
|
60
84
|
|
|
61
85
|
function applyPlugin(
|
|
@@ -66,15 +90,18 @@ function applyPlugin(
|
|
|
66
90
|
perform: readonly PerformSetting[],
|
|
67
91
|
binaryCopies: BinaryCopy[],
|
|
68
92
|
dryRun: boolean,
|
|
93
|
+
logs: string[],
|
|
69
94
|
remoteSources: Map<string, string> = new Map()
|
|
70
95
|
): void {
|
|
71
96
|
const { config, sourceDir } = loaded;
|
|
72
|
-
|
|
97
|
+
|
|
98
|
+
logs.push(`- 插件:${config.name}, 类型:${config.type},版本:${config.pluginVersion}`);
|
|
73
99
|
|
|
74
100
|
if (perform.includes("frameworks") && config.frameworks?.length) {
|
|
75
101
|
for (const fw of config.frameworks) {
|
|
76
102
|
if (fw.system) {
|
|
77
103
|
addSystemFramework(pbx, fw.name);
|
|
104
|
+
logs.push(`SUCCESS: 导入系统依赖framework ${fw.name} 完成`);
|
|
78
105
|
continue;
|
|
79
106
|
}
|
|
80
107
|
const src = path.join(sourceDir, fw.name);
|
|
@@ -86,6 +113,7 @@ function applyPlugin(
|
|
|
86
113
|
fm.copyFramework(src);
|
|
87
114
|
}
|
|
88
115
|
addThirdPartyFramework(pbx, relTo, Boolean(fw.embed));
|
|
116
|
+
logs.push(`SUCCESS: 导入三方依赖framework ${fw.name} 完成`);
|
|
89
117
|
if (fw.copyFile) addCopyFile(pbx, relTo);
|
|
90
118
|
}
|
|
91
119
|
}
|
|
@@ -94,6 +122,7 @@ function applyPlugin(
|
|
|
94
122
|
for (const lib of config.libs) {
|
|
95
123
|
if (lib.system) {
|
|
96
124
|
addSystemLib(pbx, lib.name);
|
|
125
|
+
logs.push(`SUCCESS: 导入系统依赖lib ${lib.name} 完成`);
|
|
97
126
|
continue;
|
|
98
127
|
}
|
|
99
128
|
const src = path.join(sourceDir, lib.name);
|
|
@@ -102,6 +131,7 @@ function applyPlugin(
|
|
|
102
131
|
if (dryRun) binaryCopies.push({ fromAbs: src, relTo });
|
|
103
132
|
else fm.copyFile(src);
|
|
104
133
|
addSourceOrResourceFile(pbx, relTo);
|
|
134
|
+
logs.push(`SUCCESS: 导入三方依赖lib ${lib.name} 完成`);
|
|
105
135
|
}
|
|
106
136
|
}
|
|
107
137
|
|
|
@@ -110,6 +140,7 @@ function applyPlugin(
|
|
|
110
140
|
const remoteRel = remoteSources.get(`${config.name}:${source}`);
|
|
111
141
|
if (remoteRel) {
|
|
112
142
|
addSourceOrResourceFile(pbx, remoteRel);
|
|
143
|
+
logs.push(`SUCCESS: 引入资源 ${source} 完成`);
|
|
113
144
|
continue;
|
|
114
145
|
}
|
|
115
146
|
const src = path.join(sourceDir, source);
|
|
@@ -126,15 +157,17 @@ function applyPlugin(
|
|
|
126
157
|
for (const name of fs.readdirSync(dir)) {
|
|
127
158
|
const full = path.join(dir, name);
|
|
128
159
|
if (fs.statSync(full).isDirectory()) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
160
|
+
if (name.endsWith(".bundle")) {
|
|
161
|
+
addSourceOrResourceFile(pbx, fm.relFromSrc(full));
|
|
162
|
+
logs.push(`SUCCESS: 引入资源 ${name} 完成`);
|
|
163
|
+
} else {
|
|
164
|
+
walk(full);
|
|
165
|
+
}
|
|
134
166
|
continue;
|
|
135
167
|
}
|
|
136
168
|
if (/\.(h|m|mm|plist|a)$/.test(name)) {
|
|
137
169
|
addSourceOrResourceFile(pbx, fm.relFromSrc(full));
|
|
170
|
+
logs.push(`SUCCESS: 引入资源 ${name} 完成`);
|
|
138
171
|
}
|
|
139
172
|
}
|
|
140
173
|
};
|
|
@@ -146,22 +179,30 @@ function applyPlugin(
|
|
|
146
179
|
else if (fs.statSync(src).isDirectory()) fm.copyDir(src);
|
|
147
180
|
else fm.copyFile(src);
|
|
148
181
|
addSourceOrResourceFile(pbx, relTo);
|
|
182
|
+
logs.push(`SUCCESS: 引入资源 ${source} 完成`);
|
|
149
183
|
}
|
|
150
184
|
}
|
|
151
185
|
}
|
|
152
186
|
|
|
153
187
|
if (perform.includes("buildSettings") && config.buildSettings) {
|
|
154
188
|
for (const [k, v] of Object.entries(config.buildSettings)) {
|
|
155
|
-
|
|
189
|
+
const beforeValues = [...new Set(getTargetBuildSettings(pbx).map((settings) => settings[k]))];
|
|
190
|
+
if (beforeValues.length === 0 || beforeValues.some((before) => !valuesEqual(before, v))) {
|
|
191
|
+
setBuildSetting(pbx, k, v);
|
|
192
|
+
}
|
|
193
|
+
for (const before of beforeValues.length ? beforeValues : [undefined]) {
|
|
194
|
+
pushValueChangeLog(logs, `BuildSetting设置${k}`, before, v);
|
|
195
|
+
}
|
|
196
|
+
logs.push(`SUCCESS: BuildSetting设置${k}为${v} 完成`);
|
|
156
197
|
}
|
|
157
198
|
}
|
|
158
199
|
|
|
159
200
|
if (perform.includes("OtherLinkerFlags") && config.OtherLinkerFlags?.length) {
|
|
160
201
|
for (const flag of config.OtherLinkerFlags) {
|
|
161
202
|
addOtherLinkerFlag(pbx, flag);
|
|
203
|
+
logs.push(`SUCCESS: 添加other link flag:${flag} 完成`);
|
|
162
204
|
}
|
|
163
205
|
}
|
|
164
|
-
|
|
165
206
|
}
|
|
166
207
|
|
|
167
208
|
function validateLoadedPluginResourcesForIos(loaded: LoadedPluginConfig): string[] {
|
|
@@ -224,6 +265,42 @@ function iosFirebaseConfigRelPath(plistDocs: PlistDocument[]): string {
|
|
|
224
265
|
return dir === "." ? IOS_FIREBASE_CONFIG_FILE : path.posix.join(dir, IOS_FIREBASE_CONFIG_FILE);
|
|
225
266
|
}
|
|
226
267
|
|
|
268
|
+
function hasSwiftSource(srcRoot: string): boolean {
|
|
269
|
+
const walk = (dir: string): boolean => {
|
|
270
|
+
let entries: fs.Dirent[];
|
|
271
|
+
try {
|
|
272
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
273
|
+
} catch {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
if (entry.name.startsWith(".")) continue;
|
|
278
|
+
const full = path.join(dir, entry.name);
|
|
279
|
+
if (entry.isDirectory()) {
|
|
280
|
+
if (!["Pods", "build", "DerivedData", "topSDK"].includes(entry.name) && walk(full)) return true;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (entry.name.endsWith(".swift")) return true;
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
};
|
|
287
|
+
return walk(srcRoot);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function ensureTopSdkInstallSwift(
|
|
291
|
+
store: TextFileStore,
|
|
292
|
+
ctx: WorkspaceContext,
|
|
293
|
+
pbx: PbxContext
|
|
294
|
+
): string | null {
|
|
295
|
+
if (hasSwiftSource(pbx.srcRoot)) return null;
|
|
296
|
+
const relFromSrc = path.join(pbx.targetName, "TopSDKInstall.swift").split(path.sep).join("/");
|
|
297
|
+
const abs = path.join(pbx.srcRoot, relFromSrc);
|
|
298
|
+
const relFromProject = path.relative(ctx.projectRoot, abs).split(path.sep).join("/");
|
|
299
|
+
store.write(relFromProject, "import Foundation\n\n");
|
|
300
|
+
addSourceOrResourceFile(pbx, relFromSrc);
|
|
301
|
+
return relFromProject;
|
|
302
|
+
}
|
|
303
|
+
|
|
227
304
|
function appDelegateCodeShouldMoveToSceneDelegate(code: CodeConfig): boolean {
|
|
228
305
|
if (code.type === "header") return false;
|
|
229
306
|
const method = code.method ?? "";
|
|
@@ -274,7 +351,9 @@ function mergePlistParams(
|
|
|
274
351
|
docs: PlistDocument[],
|
|
275
352
|
loaded: LoadedPluginConfig,
|
|
276
353
|
channelConfig: Record<string, unknown>,
|
|
277
|
-
perform: readonly PerformSetting[]
|
|
354
|
+
perform: readonly PerformSetting[],
|
|
355
|
+
logs: string[],
|
|
356
|
+
dirtyDocs: Set<PlistDocument>
|
|
278
357
|
): void {
|
|
279
358
|
if (!perform.includes("infoParams") && !perform.includes("urlScheme") && !perform.includes("queriesSchemes")) {
|
|
280
359
|
return;
|
|
@@ -282,18 +361,33 @@ function mergePlistParams(
|
|
|
282
361
|
for (const doc of docs) {
|
|
283
362
|
if (perform.includes("infoParams") && loaded.config.infoParams) {
|
|
284
363
|
for (const [key, raw] of Object.entries(loaded.config.infoParams)) {
|
|
285
|
-
|
|
364
|
+
const value = applyChannelTemplateValue(raw, channelConfig);
|
|
365
|
+
const before = doc.data[key];
|
|
366
|
+
if (!valuesEqual(before, value)) {
|
|
367
|
+
addPlistParam(doc.data, key, value);
|
|
368
|
+
dirtyDocs.add(doc);
|
|
369
|
+
}
|
|
370
|
+
pushValueChangeLog(logs, `info.plist配置key:${key}`, before, value);
|
|
371
|
+
logs.push(`SUCCESS: info.plist添加配置key:${key} value:${String(value)} 完成`);
|
|
286
372
|
}
|
|
287
373
|
}
|
|
288
374
|
if (perform.includes("queriesSchemes") && loaded.config.queriesSchemes?.length) {
|
|
289
375
|
for (const scheme of loaded.config.queriesSchemes) {
|
|
376
|
+
const before = cloneValue(doc.data.LSApplicationQueriesSchemes ?? []);
|
|
290
377
|
addQueriesScheme(doc.data, scheme);
|
|
378
|
+
if (!valuesEqual(before, doc.data.LSApplicationQueriesSchemes)) dirtyDocs.add(doc);
|
|
379
|
+
pushValueChangeLog(logs, `info.plist queriesSchemes ${scheme}`, before, doc.data.LSApplicationQueriesSchemes);
|
|
380
|
+
logs.push(`SUCCESS: 添加queriesSchemes:${scheme} 完成`);
|
|
291
381
|
}
|
|
292
382
|
}
|
|
293
383
|
if (perform.includes("urlScheme") && loaded.config.urlScheme) {
|
|
294
384
|
let scheme = applyChannelTemplate(loaded.config.urlScheme, channelConfig);
|
|
295
385
|
if (scheme.endsWith("://")) scheme = scheme.replace("://", "");
|
|
386
|
+
const before = cloneValue(doc.data.CFBundleURLTypes ?? []);
|
|
296
387
|
addUrlScheme(doc.data, scheme);
|
|
388
|
+
if (!valuesEqual(before, doc.data.CFBundleURLTypes)) dirtyDocs.add(doc);
|
|
389
|
+
pushValueChangeLog(logs, `info.plist URL Scheme ${scheme}`, before, doc.data.CFBundleURLTypes);
|
|
390
|
+
logs.push(`SUCCESS: 设置urlScheme:${scheme}完成`);
|
|
297
391
|
}
|
|
298
392
|
}
|
|
299
393
|
}
|
|
@@ -323,6 +417,89 @@ function buildTopSdkPluginInfo(
|
|
|
323
417
|
return { pluginsInfo, dataPluginsInfo };
|
|
324
418
|
}
|
|
325
419
|
|
|
420
|
+
function injectDelegateCodes(
|
|
421
|
+
loadedConfigs: LoadedPluginConfig[],
|
|
422
|
+
ctx: WorkspaceContext,
|
|
423
|
+
store: TextFileStore,
|
|
424
|
+
pbx: PbxContext,
|
|
425
|
+
warnings: string[],
|
|
426
|
+
changed: Set<string>,
|
|
427
|
+
logs: string[]
|
|
428
|
+
): void {
|
|
429
|
+
const hasAppDelegateCodes = loadedConfigs.some((loaded) => (loaded.config.appDelegateCodes ?? []).length > 0);
|
|
430
|
+
const hasSceneDelegateCodes = loadedConfigs.some((loaded) => (loaded.config.sceneDelegateCodes ?? []).length > 0);
|
|
431
|
+
if (!hasAppDelegateCodes && !hasSceneDelegateCodes) return;
|
|
432
|
+
|
|
433
|
+
const sceneDelegateRelPaths: string[] = [];
|
|
434
|
+
for (const abs of findSceneDelegateFiles(pbx.srcRoot)) {
|
|
435
|
+
sceneDelegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
436
|
+
}
|
|
437
|
+
const hasUniqueSceneDelegate = sceneDelegateRelPaths.length === 1;
|
|
438
|
+
|
|
439
|
+
const delegateRelPaths: string[] = [];
|
|
440
|
+
for (const abs of findDelegateFiles(pbx.srcRoot)) {
|
|
441
|
+
delegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
442
|
+
}
|
|
443
|
+
if (hasAppDelegateCodes && !delegateRelPaths.length) {
|
|
444
|
+
warnings.push("no UIApplicationDelegate .m/.mm found; skipped AppDelegate injection (SwiftUI @main is not supported yet)");
|
|
445
|
+
}
|
|
446
|
+
if (hasAppDelegateCodes && delegateRelPaths.length > 1) {
|
|
447
|
+
warnings.push(`multiple UIApplicationDelegate files found; skipped AppDelegate injection: ${delegateRelPaths.join(", ")}`);
|
|
448
|
+
}
|
|
449
|
+
for (const rel of hasAppDelegateCodes && delegateRelPaths.length === 1 ? delegateRelPaths : []) {
|
|
450
|
+
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
451
|
+
logs.push("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
|
|
452
|
+
let okInject = true;
|
|
453
|
+
for (const loaded of loadedConfigs) {
|
|
454
|
+
for (const code of appDelegateCodesForLifecycle(loaded, hasUniqueSceneDelegate)) {
|
|
455
|
+
const existed = cu.hasCode(code.content);
|
|
456
|
+
if (code.type === "header") {
|
|
457
|
+
if (!cu.addHeader(code.content)) okInject = false;
|
|
458
|
+
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
459
|
+
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
460
|
+
}
|
|
461
|
+
if (okInject) {
|
|
462
|
+
logs.push(
|
|
463
|
+
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (!okInject) warnings.push(`AppDelegate injection incomplete for ${rel}`);
|
|
469
|
+
const before = store.read(rel);
|
|
470
|
+
cu.applyToStore((c) => store.write(rel, c));
|
|
471
|
+
if (store.read(rel) !== before) changed.add(rel);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (hasSceneDelegateCodes && sceneDelegateRelPaths.length > 1) {
|
|
475
|
+
warnings.push(`multiple UIWindowSceneDelegate files found; skipped SceneDelegate injection: ${sceneDelegateRelPaths.join(", ")}`);
|
|
476
|
+
}
|
|
477
|
+
for (const rel of hasSceneDelegateCodes && sceneDelegateRelPaths.length === 1 ? sceneDelegateRelPaths : []) {
|
|
478
|
+
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
479
|
+
logs.push("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
|
|
480
|
+
let okInject = true;
|
|
481
|
+
for (const loaded of loadedConfigs) {
|
|
482
|
+
for (const code of loaded.config.sceneDelegateCodes ?? []) {
|
|
483
|
+
const existed = cu.hasCode(code.content);
|
|
484
|
+
if (code.type === "header") {
|
|
485
|
+
if (!cu.addHeader(code.content)) okInject = false;
|
|
486
|
+
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
487
|
+
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
488
|
+
}
|
|
489
|
+
if (okInject) {
|
|
490
|
+
logs.push(
|
|
491
|
+
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (!okInject) warnings.push(`SceneDelegate injection incomplete for ${rel}`);
|
|
497
|
+
const before = store.read(rel);
|
|
498
|
+
cu.applyToStore((c) => store.write(rel, c));
|
|
499
|
+
if (store.read(rel) !== before) changed.add(rel);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
326
503
|
export async function runIosIntegrateTopSdk(
|
|
327
504
|
ctx: WorkspaceContext,
|
|
328
505
|
store: TextFileStore,
|
|
@@ -331,20 +508,21 @@ export async function runIosIntegrateTopSdk(
|
|
|
331
508
|
): Promise<StepResult> {
|
|
332
509
|
if (!ctx.ios?.ok) return fail(["iOS project not detected"]);
|
|
333
510
|
const warnings: string[] = [];
|
|
511
|
+
const logs: string[] = [];
|
|
334
512
|
const changed = new Set<string>();
|
|
335
513
|
|
|
336
514
|
const configPath = ctx.remoteConfigPath ?? path.join(ctx.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
|
|
337
515
|
if (!fs.existsSync(configPath)) {
|
|
338
|
-
return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found at ${configPath}; run fetch-config or setup first`]);
|
|
516
|
+
return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found at ${configPath}; run fetch-config or setup first`], warnings, logs);
|
|
339
517
|
}
|
|
340
518
|
let remote: ReturnType<typeof tryParseAsMeetSdkRemoteConfig>;
|
|
341
519
|
try {
|
|
342
520
|
remote = tryParseAsMeetSdkRemoteConfig(JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown);
|
|
343
521
|
} catch (e) {
|
|
344
|
-
return fail([e instanceof Error ? e.message : String(e)]);
|
|
522
|
+
return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
|
|
345
523
|
}
|
|
346
524
|
if (!remote) {
|
|
347
|
-
return fail([`invalid ${MEETSDK_REMOTE_CONFIG_FILENAME}`]);
|
|
525
|
+
return fail([`invalid ${MEETSDK_REMOTE_CONFIG_FILENAME}`], warnings, logs);
|
|
348
526
|
}
|
|
349
527
|
const validation = validateMeetSdkRemoteConfig(remote);
|
|
350
528
|
if (!validation.ok) {
|
|
@@ -355,13 +533,15 @@ export async function runIosIntegrateTopSdk(
|
|
|
355
533
|
try {
|
|
356
534
|
sdkRoot = ctx.iosSdkRoot ?? resolveIosSdkRoot(ctx.packageRoot);
|
|
357
535
|
} catch (e) {
|
|
358
|
-
return fail([e instanceof Error ? e.message : String(e)]);
|
|
536
|
+
return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
|
|
359
537
|
}
|
|
360
538
|
|
|
361
539
|
const targetName = options.targetName ?? ctx.ios.targetName ?? path.basename(ctx.ios.xcodeprojPath!, ".xcodeproj");
|
|
362
540
|
const perform = options.performSettings ?? DEFAULT_PERFORM_SETTINGS;
|
|
363
541
|
const executeAppDelegate = options.executeAppDelegate !== false;
|
|
364
542
|
const dryRun = Boolean(options.dryRun);
|
|
543
|
+
logs.push("6、执行 SDK 接入");
|
|
544
|
+
logs.push(`*** 项目根目录:${ctx.projectRoot} ***`);
|
|
365
545
|
|
|
366
546
|
const channelConfig = buildChannelConfigMap(remote);
|
|
367
547
|
const coreConfigs = listSdkCoreConfigs(sdkRoot);
|
|
@@ -384,50 +564,82 @@ export async function runIosIntegrateTopSdk(
|
|
|
384
564
|
try {
|
|
385
565
|
plistDocs = await ensureInfoPlists(store, ctx, pbx, targetName);
|
|
386
566
|
} catch (e) {
|
|
387
|
-
return fail([e instanceof Error ? e.message : String(e)], warnings);
|
|
567
|
+
return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
|
|
388
568
|
}
|
|
569
|
+
const dirtyPlistDocs = new Set<PlistDocument>();
|
|
389
570
|
|
|
390
571
|
const firebaseDownload = await downloadIosFirebaseConfig(remote, pbx, dryRun, iosFirebaseConfigRelPath(plistDocs));
|
|
391
572
|
warnings.push(...firebaseDownload.warnings);
|
|
392
|
-
if (firebaseDownload.errors.length) return fail(firebaseDownload.errors, warnings);
|
|
573
|
+
if (firebaseDownload.errors.length) return fail(firebaseDownload.errors, warnings, logs);
|
|
393
574
|
|
|
394
575
|
const resourceErrors = [...coreConfigs, ...pluginConfigs].flatMap(validateLoadedPluginResourcesForIos);
|
|
395
|
-
if (resourceErrors.length) return fail(resourceErrors, warnings);
|
|
576
|
+
if (resourceErrors.length) return fail(resourceErrors, warnings, logs);
|
|
396
577
|
|
|
397
578
|
const missingRequiredConfigs = validateRequiredChannelConfigs(pluginConfigs, channelConfig);
|
|
398
579
|
if (missingRequiredConfigs.length) {
|
|
399
|
-
return fail([`iOS remote config missing required channel params: ${missingRequiredConfigs.join(", ")}`], warnings);
|
|
580
|
+
return fail([`iOS remote config missing required channel params: ${missingRequiredConfigs.join(", ")}`], warnings, logs);
|
|
400
581
|
}
|
|
401
582
|
|
|
402
|
-
for (const loaded of coreConfigs) {
|
|
403
|
-
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, firebaseDownload.remoteSources);
|
|
404
|
-
}
|
|
405
583
|
for (const loaded of pluginConfigs) {
|
|
406
|
-
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, firebaseDownload.remoteSources);
|
|
584
|
+
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
|
|
585
|
+
if (loaded.config.name === "FacebookSignin" && perform.includes("sources")) {
|
|
586
|
+
const swiftRel = ensureTopSdkInstallSwift(store, ctx, pbx);
|
|
587
|
+
if (swiftRel) {
|
|
588
|
+
changed.add(swiftRel);
|
|
589
|
+
logs.push("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
|
|
593
|
+
if (perform.includes("infoParams")) {
|
|
594
|
+
const doc = plistDocs[0];
|
|
595
|
+
if (doc) {
|
|
596
|
+
const before = cloneValue(doc.data.NSAppTransportSecurity);
|
|
597
|
+
setAppTransportSecurity(doc.data, true);
|
|
598
|
+
if (!valuesEqual(before, doc.data.NSAppTransportSecurity)) dirtyPlistDocs.add(doc);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
if (perform.includes("infoParams") && loaded.config.name === "AppleSignin") {
|
|
602
|
+
for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
|
|
603
|
+
changed.add(rel);
|
|
604
|
+
}
|
|
605
|
+
logs.push("SUCCESS: 启用SigninWithApple完成");
|
|
606
|
+
}
|
|
607
|
+
logs.push(`SUCCESS: 插件${loaded.config.name}接入完成`);
|
|
407
608
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
mergePlistParams(plistDocs, loaded, channelConfig, perform);
|
|
609
|
+
for (const loaded of coreConfigs) {
|
|
610
|
+
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
|
|
611
|
+
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
|
|
411
612
|
if (perform.includes("infoParams")) {
|
|
412
|
-
|
|
613
|
+
const doc = plistDocs[0];
|
|
614
|
+
if (doc) {
|
|
615
|
+
const before = cloneValue(doc.data.NSAppTransportSecurity);
|
|
616
|
+
setAppTransportSecurity(doc.data, true);
|
|
617
|
+
if (!valuesEqual(before, doc.data.NSAppTransportSecurity)) dirtyPlistDocs.add(doc);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (executeAppDelegate) {
|
|
621
|
+
injectDelegateCodes([loaded], ctx, store, pbx, warnings, changed, logs);
|
|
413
622
|
}
|
|
623
|
+
logs.push(`SUCCESS: 插件${loaded.config.name}接入完成`);
|
|
414
624
|
}
|
|
415
625
|
for (const doc of plistDocs) {
|
|
416
626
|
if (perform.includes("infoParams")) {
|
|
417
627
|
const { pluginsInfo, dataPluginsInfo } = buildTopSdkPluginInfo(pluginConfigs, channelConfig);
|
|
418
|
-
|
|
628
|
+
const before = doc.data.TOPSDK;
|
|
629
|
+
const nextTopSdk = {
|
|
419
630
|
APP_ID: remote.topsdk.appId,
|
|
420
631
|
Plugins: pluginsInfo,
|
|
421
632
|
dataPlugins: dataPluginsInfo,
|
|
422
|
-
}
|
|
633
|
+
};
|
|
634
|
+
if (!valuesEqual(before, nextTopSdk)) {
|
|
635
|
+
addPlistParam(doc.data, "TOPSDK", nextTopSdk);
|
|
636
|
+
dirtyPlistDocs.add(doc);
|
|
637
|
+
}
|
|
638
|
+
pushValueChangeLog(logs, "info.plist配置key:TOPSDK", before, nextTopSdk);
|
|
423
639
|
}
|
|
424
640
|
const rel = path.relative(ctx.projectRoot, path.join(pbx.srcRoot, doc.relPath)).split(path.sep).join("/");
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
if (perform.includes("infoParams") && pluginConfigs.some((p) => p.config.name === "AppleSignin")) {
|
|
430
|
-
for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
|
|
641
|
+
if (dirtyPlistDocs.has(doc)) {
|
|
642
|
+
store.write(rel, buildPlistXml(doc.data));
|
|
431
643
|
changed.add(rel);
|
|
432
644
|
}
|
|
433
645
|
}
|
|
@@ -435,64 +647,9 @@ export async function runIosIntegrateTopSdk(
|
|
|
435
647
|
savePbxToStore(store, pbx);
|
|
436
648
|
changed.add(pbx.rel);
|
|
437
649
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
sceneDelegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
442
|
-
}
|
|
443
|
-
const hasUniqueSceneDelegate = sceneDelegateRelPaths.length === 1;
|
|
444
|
-
|
|
445
|
-
const delegateRelPaths: string[] = [];
|
|
446
|
-
for (const abs of findDelegateFiles(pbx.srcRoot)) {
|
|
447
|
-
delegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
448
|
-
}
|
|
449
|
-
if (!delegateRelPaths.length) {
|
|
450
|
-
warnings.push("no UIApplicationDelegate .m/.mm found; skipped AppDelegate injection (SwiftUI @main is not supported yet)");
|
|
451
|
-
}
|
|
452
|
-
if (delegateRelPaths.length > 1) {
|
|
453
|
-
warnings.push(`multiple UIApplicationDelegate files found; skipped AppDelegate injection: ${delegateRelPaths.join(", ")}`);
|
|
454
|
-
}
|
|
455
|
-
for (const rel of delegateRelPaths.length === 1 ? delegateRelPaths : []) {
|
|
456
|
-
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
457
|
-
let okInject = true;
|
|
458
|
-
for (const loaded of [...coreConfigs, ...pluginConfigs]) {
|
|
459
|
-
for (const code of appDelegateCodesForLifecycle(loaded, hasUniqueSceneDelegate)) {
|
|
460
|
-
if (code.type === "header") {
|
|
461
|
-
if (!cu.addHeader(code.content)) okInject = false;
|
|
462
|
-
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
463
|
-
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (!okInject) warnings.push(`AppDelegate injection incomplete for ${rel}`);
|
|
468
|
-
const before = store.read(rel);
|
|
469
|
-
cu.applyToStore((c) => store.write(rel, c));
|
|
470
|
-
if (store.read(rel) !== before) changed.add(rel);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (sceneDelegateRelPaths.length > 1) {
|
|
474
|
-
warnings.push(`multiple UIWindowSceneDelegate files found; skipped SceneDelegate injection: ${sceneDelegateRelPaths.join(", ")}`);
|
|
475
|
-
}
|
|
476
|
-
for (const rel of sceneDelegateRelPaths.length === 1 ? sceneDelegateRelPaths : []) {
|
|
477
|
-
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
478
|
-
let okInject = true;
|
|
479
|
-
for (const loaded of [...coreConfigs, ...pluginConfigs]) {
|
|
480
|
-
for (const code of loaded.config.sceneDelegateCodes ?? []) {
|
|
481
|
-
if (code.type === "header") {
|
|
482
|
-
if (!cu.addHeader(code.content)) okInject = false;
|
|
483
|
-
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
484
|
-
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (!okInject) warnings.push(`SceneDelegate injection incomplete for ${rel}`);
|
|
489
|
-
const before = store.read(rel);
|
|
490
|
-
cu.applyToStore((c) => store.write(rel, c));
|
|
491
|
-
if (store.read(rel) !== before) changed.add(rel);
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
return ok([...changed], warnings);
|
|
650
|
+
logs.push("SUCCESS: info.plist配置写入完成");
|
|
651
|
+
logs.push("!! 接入流程已全部结束 !!");
|
|
652
|
+
return ok([...changed], warnings, logs);
|
|
496
653
|
}
|
|
497
654
|
|
|
498
655
|
async function ensureInfoPlists(
|
package/src/ios/pbxprojEditor.ts
CHANGED
|
@@ -290,7 +290,12 @@ export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: str
|
|
|
290
290
|
if (file.endsWith(".h")) {
|
|
291
291
|
ctx.proj.addHeaderFile(file, { target });
|
|
292
292
|
} else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift")) {
|
|
293
|
-
|
|
293
|
+
try {
|
|
294
|
+
ctx.proj.addSourceFile(file, { target });
|
|
295
|
+
} catch (e) {
|
|
296
|
+
if (!String(e instanceof Error ? e.message : e).includes("path")) throw e;
|
|
297
|
+
addSourceFileManually(ctx, file);
|
|
298
|
+
}
|
|
294
299
|
} else {
|
|
295
300
|
ensureResourcesBuildPhase(ctx.proj, ctx.targetName);
|
|
296
301
|
const opt: Record<string, unknown> = { target };
|
|
@@ -306,6 +311,51 @@ export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: str
|
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
|
|
314
|
+
function ensureSourcesBuildPhase(proj: XcodeProject, targetName: string): void {
|
|
315
|
+
const target = targetKey(proj, targetName);
|
|
316
|
+
try {
|
|
317
|
+
proj.pbxSourcesBuildPhaseObj(target);
|
|
318
|
+
return;
|
|
319
|
+
} catch {
|
|
320
|
+
proj.addBuildPhase([], "PBXSourcesBuildPhase", "Sources", target);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function sourceFileType(file: string): string {
|
|
325
|
+
if (file.endsWith(".swift")) return "sourcecode.swift";
|
|
326
|
+
if (file.endsWith(".mm")) return "sourcecode.cpp.objcpp";
|
|
327
|
+
return "sourcecode.c.objc";
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function addSourceFileManually(ctx: PbxContext, file: string): void {
|
|
331
|
+
const basename = path.basename(file);
|
|
332
|
+
ensureSourcesBuildPhase(ctx.proj, ctx.targetName);
|
|
333
|
+
const sources = ctx.proj.pbxSourcesBuildPhaseObj(targetKey(ctx.proj, ctx.targetName));
|
|
334
|
+
const files = (sources.files ??= []) as Array<{ value?: string; comment?: string }>;
|
|
335
|
+
if (JSON.stringify(files).includes(basename)) return;
|
|
336
|
+
|
|
337
|
+
const fileRefUuid = ctx.proj.generateUuid();
|
|
338
|
+
const buildUuid = ctx.proj.generateUuid();
|
|
339
|
+
const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
|
|
340
|
+
const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
|
|
341
|
+
|
|
342
|
+
fileRefSection[fileRefUuid] = {
|
|
343
|
+
isa: "PBXFileReference",
|
|
344
|
+
name: `"${basename}"`,
|
|
345
|
+
path: `"${file}"`,
|
|
346
|
+
sourceTree: '"<group>"',
|
|
347
|
+
lastKnownFileType: sourceFileType(file),
|
|
348
|
+
};
|
|
349
|
+
fileRefSection[`${fileRefUuid}_comment`] = basename;
|
|
350
|
+
buildFileSection[buildUuid] = {
|
|
351
|
+
isa: "PBXBuildFile",
|
|
352
|
+
fileRef: fileRefUuid,
|
|
353
|
+
fileRef_comment: basename,
|
|
354
|
+
};
|
|
355
|
+
buildFileSection[`${buildUuid}_comment`] = `${basename} in Sources`;
|
|
356
|
+
files.push({ value: buildUuid, comment: `${basename} in Sources` });
|
|
357
|
+
}
|
|
358
|
+
|
|
309
359
|
function resourceFileType(file: string, lastKnownFileType?: string): string {
|
|
310
360
|
if (lastKnownFileType) return lastKnownFileType;
|
|
311
361
|
if (file.endsWith(".bundle")) return "wrapper.plug-in";
|
package/src/ops/handlers.ts
CHANGED
|
@@ -25,14 +25,49 @@ import { loadMeetSdkDefaultConfigWithLatestAndroidVersion } from "../config/meet
|
|
|
25
25
|
import { insertPermissions } from "../android/manifest.js";
|
|
26
26
|
import type { TextFileStore } from "./fileStore.js";
|
|
27
27
|
|
|
28
|
-
function ok(changed: string[], warnings: string[] = []): StepResult {
|
|
29
|
-
return { ok: true, changedFiles: changed, warnings, errors: [] };
|
|
28
|
+
function ok(changed: string[], warnings: string[] = [], logs: string[] = []): StepResult {
|
|
29
|
+
return { ok: true, changedFiles: changed, logs, warnings, errors: [] };
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function fail(errors: string[]): StepResult {
|
|
33
33
|
return { ok: false, changedFiles: [], warnings: [], errors };
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function androidApplicationId(content: string): string | undefined {
|
|
37
|
+
const match = content.match(/^\s*applicationId\s+(?:=+\s*)?["']([^"']*)["']/m);
|
|
38
|
+
return match?.[1];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function androidResValues(content: string): Map<string, string> {
|
|
42
|
+
const out = new Map<string, string>();
|
|
43
|
+
const re = /resValue\s*\(\s*['"]([^'"]+)['"]\s*,\s*['"]([^'"]+)['"]\s*,\s*(['"])(.*?)\3\s*\)/g;
|
|
44
|
+
for (const match of content.matchAll(re)) {
|
|
45
|
+
out.set(match[2], `${match[1]}:${match[4]}`);
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function androidConfigChangeLogs(before: string, after: string): string[] {
|
|
51
|
+
const logs: string[] = [];
|
|
52
|
+
const beforeAppId = androidApplicationId(before);
|
|
53
|
+
const afterAppId = androidApplicationId(after);
|
|
54
|
+
if (afterAppId !== undefined && beforeAppId !== afterAppId) {
|
|
55
|
+
logs.push(`[meetgames] Android applicationId ${beforeAppId === undefined ? "<missing>" : beforeAppId} -> ${afterAppId}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const beforeRes = androidResValues(before);
|
|
59
|
+
const afterRes = androidResValues(after);
|
|
60
|
+
for (const [key, next] of afterRes) {
|
|
61
|
+
const prev = beforeRes.get(key);
|
|
62
|
+
if (prev === undefined) {
|
|
63
|
+
logs.push(`[meetgames] Android resValue ${key} <missing> -> ${next}`);
|
|
64
|
+
} else if (prev !== next) {
|
|
65
|
+
logs.push(`[meetgames] Android resValue ${key} ${prev} -> ${next}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return logs;
|
|
69
|
+
}
|
|
70
|
+
|
|
36
71
|
function readRootBuildGradle(projectRoot: string): { rel: string; abs: string } | null {
|
|
37
72
|
const a = path.join(projectRoot, "build.gradle");
|
|
38
73
|
if (fs.existsSync(a)) return { rel: "build.gradle", abs: a };
|
|
@@ -183,7 +218,7 @@ export const opHandlers: Record<string, OpHandler> = {
|
|
|
183
218
|
|
|
184
219
|
store.write(modRel, modU.content);
|
|
185
220
|
if (modU.changed) changed.push(modRel);
|
|
186
|
-
return ok(changed, [...warnings, ...modU.warnings]);
|
|
221
|
+
return ok(changed, [...warnings, ...modU.warnings], androidConfigChangeLogs(modBefore, modU.content));
|
|
187
222
|
},
|
|
188
223
|
|
|
189
224
|
"gradle.insertRepositories": (ctx, store, args, _dry, _bc) => {
|