@leonxin/meetgames 0.1.13 → 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/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +17 -1
- package/dist/ios/channelConfig.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +163 -92
- package/dist/ios/integrate.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 +30 -2
- package/package.json +1 -1
- package/src/android/manifest.ts +11 -0
- package/src/android/meetSdkRemoteGradle.ts +125 -7
- package/src/ios/channelConfig.ts +17 -1
- package/src/ios/integrate.ts +166 -91
- 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 +122 -41
- 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,6 +51,29 @@ export interface IosIntegrateOptions {
|
|
|
50
51
|
|
|
51
52
|
const IOS_FIREBASE_CONFIG_FILE = "GoogleService-Info.plist";
|
|
52
53
|
|
|
54
|
+
function logValue(value: unknown): string {
|
|
55
|
+
if (value === undefined) return "<missing>";
|
|
56
|
+
if (typeof value === "string") return value;
|
|
57
|
+
return JSON.stringify(value);
|
|
58
|
+
}
|
|
59
|
+
|
|
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
|
+
|
|
53
77
|
function ok(changed: string[], warnings: string[] = [], logs: string[] = []): StepResult {
|
|
54
78
|
return { ok: true, changedFiles: changed, logs, warnings, errors: [] };
|
|
55
79
|
}
|
|
@@ -70,7 +94,6 @@ function applyPlugin(
|
|
|
70
94
|
remoteSources: Map<string, string> = new Map()
|
|
71
95
|
): void {
|
|
72
96
|
const { config, sourceDir } = loaded;
|
|
73
|
-
const srcRoot = pbx.srcRoot;
|
|
74
97
|
|
|
75
98
|
logs.push(`- 插件:${config.name}, 类型:${config.type},版本:${config.pluginVersion}`);
|
|
76
99
|
|
|
@@ -163,7 +186,13 @@ function applyPlugin(
|
|
|
163
186
|
|
|
164
187
|
if (perform.includes("buildSettings") && config.buildSettings) {
|
|
165
188
|
for (const [k, v] of Object.entries(config.buildSettings)) {
|
|
166
|
-
|
|
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
|
+
}
|
|
167
196
|
logs.push(`SUCCESS: BuildSetting设置${k}为${v} 完成`);
|
|
168
197
|
}
|
|
169
198
|
}
|
|
@@ -174,8 +203,6 @@ function applyPlugin(
|
|
|
174
203
|
logs.push(`SUCCESS: 添加other link flag:${flag} 完成`);
|
|
175
204
|
}
|
|
176
205
|
}
|
|
177
|
-
|
|
178
|
-
logs.push(`SUCCESS: 插件${config.name}接入完成`);
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
function validateLoadedPluginResourcesForIos(loaded: LoadedPluginConfig): string[] {
|
|
@@ -325,7 +352,8 @@ function mergePlistParams(
|
|
|
325
352
|
loaded: LoadedPluginConfig,
|
|
326
353
|
channelConfig: Record<string, unknown>,
|
|
327
354
|
perform: readonly PerformSetting[],
|
|
328
|
-
logs: string[]
|
|
355
|
+
logs: string[],
|
|
356
|
+
dirtyDocs: Set<PlistDocument>
|
|
329
357
|
): void {
|
|
330
358
|
if (!perform.includes("infoParams") && !perform.includes("urlScheme") && !perform.includes("queriesSchemes")) {
|
|
331
359
|
return;
|
|
@@ -334,20 +362,31 @@ function mergePlistParams(
|
|
|
334
362
|
if (perform.includes("infoParams") && loaded.config.infoParams) {
|
|
335
363
|
for (const [key, raw] of Object.entries(loaded.config.infoParams)) {
|
|
336
364
|
const value = applyChannelTemplateValue(raw, channelConfig);
|
|
337
|
-
|
|
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);
|
|
338
371
|
logs.push(`SUCCESS: info.plist添加配置key:${key} value:${String(value)} 完成`);
|
|
339
372
|
}
|
|
340
373
|
}
|
|
341
374
|
if (perform.includes("queriesSchemes") && loaded.config.queriesSchemes?.length) {
|
|
342
375
|
for (const scheme of loaded.config.queriesSchemes) {
|
|
376
|
+
const before = cloneValue(doc.data.LSApplicationQueriesSchemes ?? []);
|
|
343
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);
|
|
344
380
|
logs.push(`SUCCESS: 添加queriesSchemes:${scheme} 完成`);
|
|
345
381
|
}
|
|
346
382
|
}
|
|
347
383
|
if (perform.includes("urlScheme") && loaded.config.urlScheme) {
|
|
348
384
|
let scheme = applyChannelTemplate(loaded.config.urlScheme, channelConfig);
|
|
349
385
|
if (scheme.endsWith("://")) scheme = scheme.replace("://", "");
|
|
386
|
+
const before = cloneValue(doc.data.CFBundleURLTypes ?? []);
|
|
350
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);
|
|
351
390
|
logs.push(`SUCCESS: 设置urlScheme:${scheme}完成`);
|
|
352
391
|
}
|
|
353
392
|
}
|
|
@@ -378,6 +417,89 @@ function buildTopSdkPluginInfo(
|
|
|
378
417
|
return { pluginsInfo, dataPluginsInfo };
|
|
379
418
|
}
|
|
380
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
|
+
|
|
381
503
|
export async function runIosIntegrateTopSdk(
|
|
382
504
|
ctx: WorkspaceContext,
|
|
383
505
|
store: TextFileStore,
|
|
@@ -444,6 +566,7 @@ export async function runIosIntegrateTopSdk(
|
|
|
444
566
|
} catch (e) {
|
|
445
567
|
return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
|
|
446
568
|
}
|
|
569
|
+
const dirtyPlistDocs = new Set<PlistDocument>();
|
|
447
570
|
|
|
448
571
|
const firebaseDownload = await downloadIosFirebaseConfig(remote, pbx, dryRun, iosFirebaseConfigRelPath(plistDocs));
|
|
449
572
|
warnings.push(...firebaseDownload.warnings);
|
|
@@ -466,112 +589,64 @@ export async function runIosIntegrateTopSdk(
|
|
|
466
589
|
logs.push("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
|
|
467
590
|
}
|
|
468
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}接入完成`);
|
|
469
608
|
}
|
|
470
609
|
for (const loaded of coreConfigs) {
|
|
471
610
|
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
for (const loaded of [...coreConfigs, ...pluginConfigs]) {
|
|
475
|
-
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs);
|
|
611
|
+
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
|
|
476
612
|
if (perform.includes("infoParams")) {
|
|
477
|
-
|
|
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
|
+
}
|
|
478
619
|
}
|
|
620
|
+
if (executeAppDelegate) {
|
|
621
|
+
injectDelegateCodes([loaded], ctx, store, pbx, warnings, changed, logs);
|
|
622
|
+
}
|
|
623
|
+
logs.push(`SUCCESS: 插件${loaded.config.name}接入完成`);
|
|
479
624
|
}
|
|
480
625
|
for (const doc of plistDocs) {
|
|
481
626
|
if (perform.includes("infoParams")) {
|
|
482
627
|
const { pluginsInfo, dataPluginsInfo } = buildTopSdkPluginInfo(pluginConfigs, channelConfig);
|
|
483
|
-
|
|
628
|
+
const before = doc.data.TOPSDK;
|
|
629
|
+
const nextTopSdk = {
|
|
484
630
|
APP_ID: remote.topsdk.appId,
|
|
485
631
|
Plugins: pluginsInfo,
|
|
486
632
|
dataPlugins: dataPluginsInfo,
|
|
487
|
-
}
|
|
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);
|
|
488
639
|
}
|
|
489
640
|
const rel = path.relative(ctx.projectRoot, path.join(pbx.srcRoot, doc.relPath)).split(path.sep).join("/");
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
if (perform.includes("infoParams") && pluginConfigs.some((p) => p.config.name === "AppleSignin")) {
|
|
495
|
-
for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
|
|
641
|
+
if (dirtyPlistDocs.has(doc)) {
|
|
642
|
+
store.write(rel, buildPlistXml(doc.data));
|
|
496
643
|
changed.add(rel);
|
|
497
644
|
}
|
|
498
|
-
logs.push("SUCCESS: 启用SigninWithApple完成");
|
|
499
645
|
}
|
|
500
646
|
|
|
501
647
|
savePbxToStore(store, pbx);
|
|
502
648
|
changed.add(pbx.rel);
|
|
503
649
|
|
|
504
|
-
if (executeAppDelegate) {
|
|
505
|
-
const sceneDelegateRelPaths: string[] = [];
|
|
506
|
-
for (const abs of findSceneDelegateFiles(pbx.srcRoot)) {
|
|
507
|
-
sceneDelegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
508
|
-
}
|
|
509
|
-
const hasUniqueSceneDelegate = sceneDelegateRelPaths.length === 1;
|
|
510
|
-
|
|
511
|
-
const delegateRelPaths: string[] = [];
|
|
512
|
-
for (const abs of findDelegateFiles(pbx.srcRoot)) {
|
|
513
|
-
delegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
514
|
-
}
|
|
515
|
-
if (!delegateRelPaths.length) {
|
|
516
|
-
warnings.push("no UIApplicationDelegate .m/.mm found; skipped AppDelegate injection (SwiftUI @main is not supported yet)");
|
|
517
|
-
}
|
|
518
|
-
if (delegateRelPaths.length > 1) {
|
|
519
|
-
warnings.push(`multiple UIApplicationDelegate files found; skipped AppDelegate injection: ${delegateRelPaths.join(", ")}`);
|
|
520
|
-
}
|
|
521
|
-
for (const rel of delegateRelPaths.length === 1 ? delegateRelPaths : []) {
|
|
522
|
-
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
523
|
-
logs.push("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
|
|
524
|
-
let okInject = true;
|
|
525
|
-
for (const loaded of [...coreConfigs, ...pluginConfigs]) {
|
|
526
|
-
for (const code of appDelegateCodesForLifecycle(loaded, hasUniqueSceneDelegate)) {
|
|
527
|
-
const existed = cu.hasCode(code.content);
|
|
528
|
-
if (code.type === "header") {
|
|
529
|
-
if (!cu.addHeader(code.content)) okInject = false;
|
|
530
|
-
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
531
|
-
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
532
|
-
}
|
|
533
|
-
if (okInject) {
|
|
534
|
-
logs.push(
|
|
535
|
-
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
if (!okInject) warnings.push(`AppDelegate injection incomplete for ${rel}`);
|
|
541
|
-
const before = store.read(rel);
|
|
542
|
-
cu.applyToStore((c) => store.write(rel, c));
|
|
543
|
-
if (store.read(rel) !== before) changed.add(rel);
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
if (sceneDelegateRelPaths.length > 1) {
|
|
547
|
-
warnings.push(`multiple UIWindowSceneDelegate files found; skipped SceneDelegate injection: ${sceneDelegateRelPaths.join(", ")}`);
|
|
548
|
-
}
|
|
549
|
-
for (const rel of sceneDelegateRelPaths.length === 1 ? sceneDelegateRelPaths : []) {
|
|
550
|
-
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
551
|
-
logs.push("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
|
|
552
|
-
let okInject = true;
|
|
553
|
-
for (const loaded of [...coreConfigs, ...pluginConfigs]) {
|
|
554
|
-
for (const code of loaded.config.sceneDelegateCodes ?? []) {
|
|
555
|
-
const existed = cu.hasCode(code.content);
|
|
556
|
-
if (code.type === "header") {
|
|
557
|
-
if (!cu.addHeader(code.content)) okInject = false;
|
|
558
|
-
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
559
|
-
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
560
|
-
}
|
|
561
|
-
if (okInject) {
|
|
562
|
-
logs.push(
|
|
563
|
-
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
564
|
-
);
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
if (!okInject) warnings.push(`SceneDelegate injection incomplete for ${rel}`);
|
|
569
|
-
const before = store.read(rel);
|
|
570
|
-
cu.applyToStore((c) => store.write(rel, c));
|
|
571
|
-
if (store.read(rel) !== before) changed.add(rel);
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
650
|
logs.push("SUCCESS: info.plist配置写入完成");
|
|
576
651
|
logs.push("!! 接入流程已全部结束 !!");
|
|
577
652
|
return ok([...changed], warnings, logs);
|
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) => {
|
package/tests/doctor.test.ts
CHANGED
|
@@ -11,15 +11,55 @@ import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js
|
|
|
11
11
|
|
|
12
12
|
const pkgRoot = path.resolve(__dirname, "..");
|
|
13
13
|
const androidLatestRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
|
|
14
|
-
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "
|
|
15
|
-
const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
|
|
14
|
+
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "native-sample");
|
|
16
15
|
const hasIosProjectFixture = fs.existsSync(iosProjectRoot);
|
|
17
16
|
const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
|
|
18
17
|
|
|
19
18
|
function copyIosProjectToTemp(): string {
|
|
20
19
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-doctor-"));
|
|
21
20
|
fs.cpSync(iosProjectRoot, tmp, { recursive: true });
|
|
22
|
-
fs.
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
path.join(tmp, "meetsdk-remote-config.json"),
|
|
23
|
+
JSON.stringify(
|
|
24
|
+
{
|
|
25
|
+
packageName: "com.meetgames.topsdk.demo",
|
|
26
|
+
channel: "APPLE",
|
|
27
|
+
devicePlatform: "ios",
|
|
28
|
+
topsdk: {
|
|
29
|
+
appId: "mock-ios-native-sample-app-id",
|
|
30
|
+
appSecret: "mock-ios-native-sample-app-secret-do-not-use-in-production",
|
|
31
|
+
},
|
|
32
|
+
sdkModules: {
|
|
33
|
+
login: {
|
|
34
|
+
guest: {},
|
|
35
|
+
facebook: {
|
|
36
|
+
clientId: "883695101201170",
|
|
37
|
+
scheme: "fb883695101201170",
|
|
38
|
+
secret: "f840b8663b1351ddcb8f6a640cee18c6",
|
|
39
|
+
name: "top-demo",
|
|
40
|
+
openMessenger: "0",
|
|
41
|
+
},
|
|
42
|
+
google: {
|
|
43
|
+
clientId: "396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql.apps.googleusercontent.com",
|
|
44
|
+
scheme: "com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql",
|
|
45
|
+
},
|
|
46
|
+
apple: {},
|
|
47
|
+
},
|
|
48
|
+
payment: { googleIap: {} },
|
|
49
|
+
analytics: {
|
|
50
|
+
appsflyer: {
|
|
51
|
+
devKey: "af-dev-key",
|
|
52
|
+
appleAppId: "123456789",
|
|
53
|
+
enableDebugLog: true,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
null,
|
|
59
|
+
2
|
|
60
|
+
),
|
|
61
|
+
"utf8"
|
|
62
|
+
);
|
|
23
63
|
return tmp;
|
|
24
64
|
}
|
|
25
65
|
|
|
@@ -14,7 +14,7 @@ import { loadBuiltInMeetSdkDefaultConfig } from "../src/config/meetSdkDefaultCon
|
|
|
14
14
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const refPath = path.join(here, "..", "fixtures", "topsdk-config-reference.json");
|
|
16
16
|
const offlineMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.mock.json");
|
|
17
|
-
const offlineIosMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.ios-
|
|
17
|
+
const offlineIosMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.ios-native-sample.json");
|
|
18
18
|
const downloadShapePath = path.join(here, "..", "fixtures", "meetsdk-remote-config.download-shape.json");
|
|
19
19
|
|
|
20
20
|
const mapCtx = {
|
|
@@ -150,7 +150,7 @@ android {
|
|
|
150
150
|
expect(modOut.content.indexOf(TOPSDK_PLUGIN_AUTO_START)).toBeLessThan(modOut.content.indexOf("dependencies"));
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("
|
|
153
|
+
it("keeps an existing apply plugin with the same id instead of duplicating or rewriting", () => {
|
|
154
154
|
const modIn = `
|
|
155
155
|
apply plugin: 'com.android.application'
|
|
156
156
|
apply plugin: 'com.google.gms.google-services'
|
|
@@ -173,7 +173,7 @@ dependencies { }
|
|
|
173
173
|
.split("\n")
|
|
174
174
|
.filter((l) => l.includes("com.google.gms.google-services"));
|
|
175
175
|
expect(pluginLines.length).toBe(1);
|
|
176
|
-
expect(modOut.content).toContain(TOPSDK_PLUGIN_AUTO_START);
|
|
176
|
+
expect(modOut.content).not.toContain(TOPSDK_PLUGIN_AUTO_START);
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it("updates classpath by group:artifact instead of duplicating", () => {
|
|
@@ -40,7 +40,10 @@ function testProjectRoot(...names: string[]): string {
|
|
|
40
40
|
return path.join(androidFixtureRoot, names[0] ?? "");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function runAndroidPipelineDryRun(
|
|
43
|
+
async function runAndroidPipelineDryRun(
|
|
44
|
+
projectRoot: string,
|
|
45
|
+
opts?: { requirePackageNameInPatch?: boolean; requireRemoteValuesInPatch?: boolean; allowEmptyPatch?: boolean }
|
|
46
|
+
) {
|
|
44
47
|
const manifest = loadManifestFile(path.join(recipeFixtureRoot, "android-default.fixture.yaml"));
|
|
45
48
|
const cfg = JSON.parse(fs.readFileSync(path.join(projectRoot, "meetsdk-remote-config.json"), "utf8")) as {
|
|
46
49
|
packageName: string;
|
|
@@ -51,20 +54,32 @@ async function runAndroidPipelineDryRun(projectRoot: string, opts?: { requirePac
|
|
|
51
54
|
expect(ctx.android?.ok).toBe(true);
|
|
52
55
|
const { report, patch, binaryCopies } = await runPipeline(ctx, manifest, { dryRun: true });
|
|
53
56
|
expect(report.errors).toEqual([]);
|
|
54
|
-
|
|
57
|
+
if (!opts?.allowEmptyPatch) {
|
|
58
|
+
expect(patch.length).toBeGreaterThan(0);
|
|
59
|
+
}
|
|
55
60
|
expect(binaryCopies).toEqual([]);
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
if (!opts?.allowEmptyPatch || patch.length > 0) {
|
|
62
|
+
expect(patch).toContain("TOPSDK REPO AUTO");
|
|
63
|
+
expect(patch).toContain("TOPSDK AUTO");
|
|
64
|
+
}
|
|
58
65
|
if (opts?.requirePackageNameInPatch !== false) {
|
|
59
66
|
expect(patch).toContain(cfg.packageName);
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
if (opts?.requireRemoteValuesInPatch !== false) {
|
|
69
|
+
expect(patch).toContain(cfg.topsdk.appId);
|
|
70
|
+
expect(patch).toContain(cfg.channel);
|
|
71
|
+
expect(patch).toContain("facebook_app_id");
|
|
72
|
+
expect(patch).toContain("top_app_id");
|
|
73
|
+
}
|
|
65
74
|
return { ctx, patch };
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
function copyAndroidLatestToTemp(): string {
|
|
78
|
+
const tmp = fs.mkdtempSync(path.join(fs.realpathSync("/tmp"), "meet-android-idempotent-"));
|
|
79
|
+
fs.cpSync(androidLatestRoot, tmp, { recursive: true });
|
|
80
|
+
return tmp;
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
describe("pipeline android fixture", () => {
|
|
69
84
|
beforeEach(() => {
|
|
70
85
|
stubSdkHomeVersion();
|
|
@@ -85,11 +100,50 @@ describe("pipeline android fixture", () => {
|
|
|
85
100
|
const hasPowerRaidConfig = fs.existsSync(path.join(powerRaidRoot, "meetsdk-remote-config.json"));
|
|
86
101
|
|
|
87
102
|
it.skipIf(!hasPowerRaidConfig)("dry-run on power-raid (:launcher)", async () => {
|
|
88
|
-
const { ctx, patch } = await runAndroidPipelineDryRun(powerRaidRoot, {
|
|
103
|
+
const { ctx, patch } = await runAndroidPipelineDryRun(powerRaidRoot, {
|
|
104
|
+
requirePackageNameInPatch: false,
|
|
105
|
+
requireRemoteValuesInPatch: false,
|
|
106
|
+
allowEmptyPatch: true,
|
|
107
|
+
});
|
|
89
108
|
expect(ctx.android?.ok && ctx.android.moduleName).toBe(":launcher");
|
|
90
|
-
expect(patch).toContain("launcher/build.gradle");
|
|
91
109
|
const addedLines = patch.split("\n").filter((line) => line.startsWith("+")).join("\n");
|
|
92
110
|
expect(addedLines).not.toContain("//Firebase");
|
|
93
111
|
expect(addedLines).not.toContain("//Appsflyer");
|
|
94
112
|
});
|
|
113
|
+
|
|
114
|
+
it("apply is idempotent and logs Android value updates", async () => {
|
|
115
|
+
const tmp = copyAndroidLatestToTemp();
|
|
116
|
+
try {
|
|
117
|
+
const manifest = loadManifestFile(path.join(recipeFixtureRoot, "android-default.fixture.yaml"));
|
|
118
|
+
const buildGradlePath = path.join(tmp, "app", "build.gradle");
|
|
119
|
+
const beforeGradle = fs.readFileSync(buildGradlePath, "utf8");
|
|
120
|
+
fs.writeFileSync(
|
|
121
|
+
buildGradlePath,
|
|
122
|
+
beforeGradle.replace(
|
|
123
|
+
'versionName "1.0"',
|
|
124
|
+
`versionName "1.0"
|
|
125
|
+
resValue('string', 'top_app_id', 'old-app-id')
|
|
126
|
+
resValue('string', 'facebook_app_id', 'old-facebook-id')`
|
|
127
|
+
),
|
|
128
|
+
"utf8"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const ctx = buildWorkspaceContext(tmp, pkgRoot);
|
|
132
|
+
const first = await runPipeline(ctx, manifest, { dryRun: false });
|
|
133
|
+
expect(first.report.errors).toEqual([]);
|
|
134
|
+
const logs = (first.report.logs ?? []).join("\n");
|
|
135
|
+
expect(logs).toContain("Android applicationId com.example.myapplication -> com.meet.integrate.androidsample");
|
|
136
|
+
expect(logs).toContain("Android resValue top_app_id string:old-app-id -> string:mock-topsdk-app-id");
|
|
137
|
+
expect(logs).toContain("Android resValue facebook_app_id string:old-facebook-id -> string:0000000000000000");
|
|
138
|
+
|
|
139
|
+
const second = await runPipeline(buildWorkspaceContext(tmp, pkgRoot), manifest, { dryRun: false });
|
|
140
|
+
expect(second.report.errors).toEqual([]);
|
|
141
|
+
const after = fs.readFileSync(buildGradlePath, "utf8");
|
|
142
|
+
expect((after.match(/resValue\('string', 'top_app_id'/g) ?? []).length).toBe(1);
|
|
143
|
+
expect((after.match(/resValue\('string', 'facebook_app_id'/g) ?? []).length).toBe(1);
|
|
144
|
+
expect((after.match(/applicationId "com.meet.integrate.androidsample"/g) ?? []).length).toBe(1);
|
|
145
|
+
} finally {
|
|
146
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
95
149
|
});
|