@leonxin/meetgames 0.1.12 → 0.1.13

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.
@@ -50,12 +50,12 @@ export interface IosIntegrateOptions {
50
50
 
51
51
  const IOS_FIREBASE_CONFIG_FILE = "GoogleService-Info.plist";
52
52
 
53
- function ok(changed: string[], warnings: string[] = []): StepResult {
54
- return { ok: true, changedFiles: changed, warnings, errors: [] };
53
+ function ok(changed: string[], warnings: string[] = [], logs: string[] = []): StepResult {
54
+ return { ok: true, changedFiles: changed, logs, warnings, errors: [] };
55
55
  }
56
56
 
57
- function fail(errors: string[], warnings: string[] = []): StepResult {
58
- return { ok: false, changedFiles: [], warnings, errors };
57
+ function fail(errors: string[], warnings: string[] = [], logs: string[] = []): StepResult {
58
+ return { ok: false, changedFiles: [], logs, warnings, errors };
59
59
  }
60
60
 
61
61
  function applyPlugin(
@@ -66,15 +66,19 @@ function applyPlugin(
66
66
  perform: readonly PerformSetting[],
67
67
  binaryCopies: BinaryCopy[],
68
68
  dryRun: boolean,
69
+ logs: string[],
69
70
  remoteSources: Map<string, string> = new Map()
70
71
  ): void {
71
72
  const { config, sourceDir } = loaded;
72
73
  const srcRoot = pbx.srcRoot;
73
74
 
75
+ logs.push(`- 插件:${config.name}, 类型:${config.type},版本:${config.pluginVersion}`);
76
+
74
77
  if (perform.includes("frameworks") && config.frameworks?.length) {
75
78
  for (const fw of config.frameworks) {
76
79
  if (fw.system) {
77
80
  addSystemFramework(pbx, fw.name);
81
+ logs.push(`SUCCESS: 导入系统依赖framework ${fw.name} 完成`);
78
82
  continue;
79
83
  }
80
84
  const src = path.join(sourceDir, fw.name);
@@ -86,6 +90,7 @@ function applyPlugin(
86
90
  fm.copyFramework(src);
87
91
  }
88
92
  addThirdPartyFramework(pbx, relTo, Boolean(fw.embed));
93
+ logs.push(`SUCCESS: 导入三方依赖framework ${fw.name} 完成`);
89
94
  if (fw.copyFile) addCopyFile(pbx, relTo);
90
95
  }
91
96
  }
@@ -94,6 +99,7 @@ function applyPlugin(
94
99
  for (const lib of config.libs) {
95
100
  if (lib.system) {
96
101
  addSystemLib(pbx, lib.name);
102
+ logs.push(`SUCCESS: 导入系统依赖lib ${lib.name} 完成`);
97
103
  continue;
98
104
  }
99
105
  const src = path.join(sourceDir, lib.name);
@@ -102,6 +108,7 @@ function applyPlugin(
102
108
  if (dryRun) binaryCopies.push({ fromAbs: src, relTo });
103
109
  else fm.copyFile(src);
104
110
  addSourceOrResourceFile(pbx, relTo);
111
+ logs.push(`SUCCESS: 导入三方依赖lib ${lib.name} 完成`);
105
112
  }
106
113
  }
107
114
 
@@ -110,6 +117,7 @@ function applyPlugin(
110
117
  const remoteRel = remoteSources.get(`${config.name}:${source}`);
111
118
  if (remoteRel) {
112
119
  addSourceOrResourceFile(pbx, remoteRel);
120
+ logs.push(`SUCCESS: 引入资源 ${source} 完成`);
113
121
  continue;
114
122
  }
115
123
  const src = path.join(sourceDir, source);
@@ -126,15 +134,17 @@ function applyPlugin(
126
134
  for (const name of fs.readdirSync(dir)) {
127
135
  const full = path.join(dir, name);
128
136
  if (fs.statSync(full).isDirectory()) {
129
- if (name.endsWith(".bundle")) {
130
- addSourceOrResourceFile(pbx, fm.relFromSrc(full));
131
- } else {
132
- walk(full);
133
- }
137
+ if (name.endsWith(".bundle")) {
138
+ addSourceOrResourceFile(pbx, fm.relFromSrc(full));
139
+ logs.push(`SUCCESS: 引入资源 ${name} 完成`);
140
+ } else {
141
+ walk(full);
142
+ }
134
143
  continue;
135
144
  }
136
145
  if (/\.(h|m|mm|plist|a)$/.test(name)) {
137
146
  addSourceOrResourceFile(pbx, fm.relFromSrc(full));
147
+ logs.push(`SUCCESS: 引入资源 ${name} 完成`);
138
148
  }
139
149
  }
140
150
  };
@@ -146,6 +156,7 @@ function applyPlugin(
146
156
  else if (fs.statSync(src).isDirectory()) fm.copyDir(src);
147
157
  else fm.copyFile(src);
148
158
  addSourceOrResourceFile(pbx, relTo);
159
+ logs.push(`SUCCESS: 引入资源 ${source} 完成`);
149
160
  }
150
161
  }
151
162
  }
@@ -153,15 +164,18 @@ function applyPlugin(
153
164
  if (perform.includes("buildSettings") && config.buildSettings) {
154
165
  for (const [k, v] of Object.entries(config.buildSettings)) {
155
166
  setBuildSetting(pbx, k, v);
167
+ logs.push(`SUCCESS: BuildSetting设置${k}为${v} 完成`);
156
168
  }
157
169
  }
158
170
 
159
171
  if (perform.includes("OtherLinkerFlags") && config.OtherLinkerFlags?.length) {
160
172
  for (const flag of config.OtherLinkerFlags) {
161
173
  addOtherLinkerFlag(pbx, flag);
174
+ logs.push(`SUCCESS: 添加other link flag:${flag} 完成`);
162
175
  }
163
176
  }
164
177
 
178
+ logs.push(`SUCCESS: 插件${config.name}接入完成`);
165
179
  }
166
180
 
167
181
  function validateLoadedPluginResourcesForIos(loaded: LoadedPluginConfig): string[] {
@@ -224,6 +238,42 @@ function iosFirebaseConfigRelPath(plistDocs: PlistDocument[]): string {
224
238
  return dir === "." ? IOS_FIREBASE_CONFIG_FILE : path.posix.join(dir, IOS_FIREBASE_CONFIG_FILE);
225
239
  }
226
240
 
241
+ function hasSwiftSource(srcRoot: string): boolean {
242
+ const walk = (dir: string): boolean => {
243
+ let entries: fs.Dirent[];
244
+ try {
245
+ entries = fs.readdirSync(dir, { withFileTypes: true });
246
+ } catch {
247
+ return false;
248
+ }
249
+ for (const entry of entries) {
250
+ if (entry.name.startsWith(".")) continue;
251
+ const full = path.join(dir, entry.name);
252
+ if (entry.isDirectory()) {
253
+ if (!["Pods", "build", "DerivedData", "topSDK"].includes(entry.name) && walk(full)) return true;
254
+ continue;
255
+ }
256
+ if (entry.name.endsWith(".swift")) return true;
257
+ }
258
+ return false;
259
+ };
260
+ return walk(srcRoot);
261
+ }
262
+
263
+ function ensureTopSdkInstallSwift(
264
+ store: TextFileStore,
265
+ ctx: WorkspaceContext,
266
+ pbx: PbxContext
267
+ ): string | null {
268
+ if (hasSwiftSource(pbx.srcRoot)) return null;
269
+ const relFromSrc = path.join(pbx.targetName, "TopSDKInstall.swift").split(path.sep).join("/");
270
+ const abs = path.join(pbx.srcRoot, relFromSrc);
271
+ const relFromProject = path.relative(ctx.projectRoot, abs).split(path.sep).join("/");
272
+ store.write(relFromProject, "import Foundation\n\n");
273
+ addSourceOrResourceFile(pbx, relFromSrc);
274
+ return relFromProject;
275
+ }
276
+
227
277
  function appDelegateCodeShouldMoveToSceneDelegate(code: CodeConfig): boolean {
228
278
  if (code.type === "header") return false;
229
279
  const method = code.method ?? "";
@@ -274,7 +324,8 @@ function mergePlistParams(
274
324
  docs: PlistDocument[],
275
325
  loaded: LoadedPluginConfig,
276
326
  channelConfig: Record<string, unknown>,
277
- perform: readonly PerformSetting[]
327
+ perform: readonly PerformSetting[],
328
+ logs: string[]
278
329
  ): void {
279
330
  if (!perform.includes("infoParams") && !perform.includes("urlScheme") && !perform.includes("queriesSchemes")) {
280
331
  return;
@@ -282,18 +333,22 @@ function mergePlistParams(
282
333
  for (const doc of docs) {
283
334
  if (perform.includes("infoParams") && loaded.config.infoParams) {
284
335
  for (const [key, raw] of Object.entries(loaded.config.infoParams)) {
285
- addPlistParam(doc.data, key, applyChannelTemplateValue(raw, channelConfig));
336
+ const value = applyChannelTemplateValue(raw, channelConfig);
337
+ addPlistParam(doc.data, key, value);
338
+ logs.push(`SUCCESS: info.plist添加配置key:${key} value:${String(value)} 完成`);
286
339
  }
287
340
  }
288
341
  if (perform.includes("queriesSchemes") && loaded.config.queriesSchemes?.length) {
289
342
  for (const scheme of loaded.config.queriesSchemes) {
290
343
  addQueriesScheme(doc.data, scheme);
344
+ logs.push(`SUCCESS: 添加queriesSchemes:${scheme} 完成`);
291
345
  }
292
346
  }
293
347
  if (perform.includes("urlScheme") && loaded.config.urlScheme) {
294
348
  let scheme = applyChannelTemplate(loaded.config.urlScheme, channelConfig);
295
349
  if (scheme.endsWith("://")) scheme = scheme.replace("://", "");
296
350
  addUrlScheme(doc.data, scheme);
351
+ logs.push(`SUCCESS: 设置urlScheme:${scheme}完成`);
297
352
  }
298
353
  }
299
354
  }
@@ -331,20 +386,21 @@ export async function runIosIntegrateTopSdk(
331
386
  ): Promise<StepResult> {
332
387
  if (!ctx.ios?.ok) return fail(["iOS project not detected"]);
333
388
  const warnings: string[] = [];
389
+ const logs: string[] = [];
334
390
  const changed = new Set<string>();
335
391
 
336
392
  const configPath = ctx.remoteConfigPath ?? path.join(ctx.projectRoot, MEETSDK_REMOTE_CONFIG_FILENAME);
337
393
  if (!fs.existsSync(configPath)) {
338
- return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found at ${configPath}; run fetch-config or setup first`]);
394
+ return fail([`${MEETSDK_REMOTE_CONFIG_FILENAME} not found at ${configPath}; run fetch-config or setup first`], warnings, logs);
339
395
  }
340
396
  let remote: ReturnType<typeof tryParseAsMeetSdkRemoteConfig>;
341
397
  try {
342
398
  remote = tryParseAsMeetSdkRemoteConfig(JSON.parse(fs.readFileSync(configPath, "utf8")) as unknown);
343
399
  } catch (e) {
344
- return fail([e instanceof Error ? e.message : String(e)]);
400
+ return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
345
401
  }
346
402
  if (!remote) {
347
- return fail([`invalid ${MEETSDK_REMOTE_CONFIG_FILENAME}`]);
403
+ return fail([`invalid ${MEETSDK_REMOTE_CONFIG_FILENAME}`], warnings, logs);
348
404
  }
349
405
  const validation = validateMeetSdkRemoteConfig(remote);
350
406
  if (!validation.ok) {
@@ -355,13 +411,15 @@ export async function runIosIntegrateTopSdk(
355
411
  try {
356
412
  sdkRoot = ctx.iosSdkRoot ?? resolveIosSdkRoot(ctx.packageRoot);
357
413
  } catch (e) {
358
- return fail([e instanceof Error ? e.message : String(e)]);
414
+ return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
359
415
  }
360
416
 
361
417
  const targetName = options.targetName ?? ctx.ios.targetName ?? path.basename(ctx.ios.xcodeprojPath!, ".xcodeproj");
362
418
  const perform = options.performSettings ?? DEFAULT_PERFORM_SETTINGS;
363
419
  const executeAppDelegate = options.executeAppDelegate !== false;
364
420
  const dryRun = Boolean(options.dryRun);
421
+ logs.push("6、执行 SDK 接入");
422
+ logs.push(`*** 项目根目录:${ctx.projectRoot} ***`);
365
423
 
366
424
  const channelConfig = buildChannelConfigMap(remote);
367
425
  const coreConfigs = listSdkCoreConfigs(sdkRoot);
@@ -384,30 +442,37 @@ export async function runIosIntegrateTopSdk(
384
442
  try {
385
443
  plistDocs = await ensureInfoPlists(store, ctx, pbx, targetName);
386
444
  } catch (e) {
387
- return fail([e instanceof Error ? e.message : String(e)], warnings);
445
+ return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
388
446
  }
389
447
 
390
448
  const firebaseDownload = await downloadIosFirebaseConfig(remote, pbx, dryRun, iosFirebaseConfigRelPath(plistDocs));
391
449
  warnings.push(...firebaseDownload.warnings);
392
- if (firebaseDownload.errors.length) return fail(firebaseDownload.errors, warnings);
450
+ if (firebaseDownload.errors.length) return fail(firebaseDownload.errors, warnings, logs);
393
451
 
394
452
  const resourceErrors = [...coreConfigs, ...pluginConfigs].flatMap(validateLoadedPluginResourcesForIos);
395
- if (resourceErrors.length) return fail(resourceErrors, warnings);
453
+ if (resourceErrors.length) return fail(resourceErrors, warnings, logs);
396
454
 
397
455
  const missingRequiredConfigs = validateRequiredChannelConfigs(pluginConfigs, channelConfig);
398
456
  if (missingRequiredConfigs.length) {
399
- return fail([`iOS remote config missing required channel params: ${missingRequiredConfigs.join(", ")}`], warnings);
457
+ return fail([`iOS remote config missing required channel params: ${missingRequiredConfigs.join(", ")}`], warnings, logs);
400
458
  }
401
459
 
402
- for (const loaded of coreConfigs) {
403
- applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, firebaseDownload.remoteSources);
404
- }
405
460
  for (const loaded of pluginConfigs) {
406
- applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, firebaseDownload.remoteSources);
461
+ applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
462
+ if (loaded.config.name === "FacebookSignin" && perform.includes("sources")) {
463
+ const swiftRel = ensureTopSdkInstallSwift(store, ctx, pbx);
464
+ if (swiftRel) {
465
+ changed.add(swiftRel);
466
+ logs.push("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
467
+ }
468
+ }
469
+ }
470
+ for (const loaded of coreConfigs) {
471
+ applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
407
472
  }
408
473
 
409
474
  for (const loaded of [...coreConfigs, ...pluginConfigs]) {
410
- mergePlistParams(plistDocs, loaded, channelConfig, perform);
475
+ mergePlistParams(plistDocs, loaded, channelConfig, perform, logs);
411
476
  if (perform.includes("infoParams")) {
412
477
  setAppTransportSecurity(plistDocs[0]?.data ?? {}, true);
413
478
  }
@@ -430,6 +495,7 @@ export async function runIosIntegrateTopSdk(
430
495
  for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
431
496
  changed.add(rel);
432
497
  }
498
+ logs.push("SUCCESS: 启用SigninWithApple完成");
433
499
  }
434
500
 
435
501
  savePbxToStore(store, pbx);
@@ -454,14 +520,21 @@ export async function runIosIntegrateTopSdk(
454
520
  }
455
521
  for (const rel of delegateRelPaths.length === 1 ? delegateRelPaths : []) {
456
522
  const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
523
+ logs.push("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
457
524
  let okInject = true;
458
525
  for (const loaded of [...coreConfigs, ...pluginConfigs]) {
459
526
  for (const code of appDelegateCodesForLifecycle(loaded, hasUniqueSceneDelegate)) {
527
+ const existed = cu.hasCode(code.content);
460
528
  if (code.type === "header") {
461
529
  if (!cu.addHeader(code.content)) okInject = false;
462
530
  } else if ((code.type === "method" || code.type === "code") && code.method) {
463
531
  if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
464
532
  }
533
+ if (okInject) {
534
+ logs.push(
535
+ `SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
536
+ );
537
+ }
465
538
  }
466
539
  }
467
540
  if (!okInject) warnings.push(`AppDelegate injection incomplete for ${rel}`);
@@ -475,14 +548,21 @@ export async function runIosIntegrateTopSdk(
475
548
  }
476
549
  for (const rel of sceneDelegateRelPaths.length === 1 ? sceneDelegateRelPaths : []) {
477
550
  const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
551
+ logs.push("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
478
552
  let okInject = true;
479
553
  for (const loaded of [...coreConfigs, ...pluginConfigs]) {
480
554
  for (const code of loaded.config.sceneDelegateCodes ?? []) {
555
+ const existed = cu.hasCode(code.content);
481
556
  if (code.type === "header") {
482
557
  if (!cu.addHeader(code.content)) okInject = false;
483
558
  } else if ((code.type === "method" || code.type === "code") && code.method) {
484
559
  if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
485
560
  }
561
+ if (okInject) {
562
+ logs.push(
563
+ `SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
564
+ );
565
+ }
486
566
  }
487
567
  }
488
568
  if (!okInject) warnings.push(`SceneDelegate injection incomplete for ${rel}`);
@@ -492,7 +572,9 @@ export async function runIosIntegrateTopSdk(
492
572
  }
493
573
  }
494
574
 
495
- return ok([...changed], warnings);
575
+ logs.push("SUCCESS: info.plist配置写入完成");
576
+ logs.push("!! 接入流程已全部结束 !!");
577
+ return ok([...changed], warnings, logs);
496
578
  }
497
579
 
498
580
  async function ensureInfoPlists(
@@ -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
- ctx.proj.addSourceFile(file, { target });
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";
@@ -12,8 +12,10 @@ import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js
12
12
  const here = path.dirname(fileURLToPath(import.meta.url));
13
13
  const pkgRoot = path.resolve(here, "..");
14
14
  const iosRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "tooltest");
15
+ const nativeSampleRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "native-sample");
15
16
  const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
16
17
  const hasIosProjectFixture = fs.existsSync(iosRoot);
18
+ const hasNativeSampleFixture = fs.existsSync(nativeSampleRoot);
17
19
  const manifest = () => loadManifestFile(path.join(pkgRoot, "recipes", "ios-default.yaml"));
18
20
  const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
19
21
 
@@ -28,6 +30,53 @@ function copyProjectToTemp(): string {
28
30
  return tmp;
29
31
  }
30
32
 
33
+ function copyNativeSampleToTemp(): string {
34
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-native-sample-"));
35
+ fs.cpSync(nativeSampleRoot, tmp, { recursive: true });
36
+ writeNativeSampleRemoteConfig(tmp);
37
+ return tmp;
38
+ }
39
+
40
+ function writeNativeSampleRemoteConfig(projectRoot: string): void {
41
+ const remote = {
42
+ packageName: "com.meetgames.topsdk.demo",
43
+ channel: "APPLE",
44
+ devicePlatform: "ios",
45
+ topsdk: {
46
+ appId: "mock-ios-native-sample-app-id",
47
+ appSecret: "mock-ios-native-sample-app-secret-do-not-use-in-production",
48
+ },
49
+ sdkModules: {
50
+ login: {
51
+ guest: {},
52
+ facebook: {
53
+ clientId: "883695101201170",
54
+ scheme: "fb883695101201170",
55
+ secret: "f840b8663b1351ddcb8f6a640cee18c6",
56
+ name: "top-demo",
57
+ openMessenger: "0",
58
+ },
59
+ google: {
60
+ clientId: "396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql.apps.googleusercontent.com",
61
+ scheme: "com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql",
62
+ },
63
+ apple: {},
64
+ },
65
+ payment: {
66
+ googleIap: {},
67
+ },
68
+ analytics: {
69
+ appsflyer: {
70
+ devKey: "af-dev-key",
71
+ appleAppId: "123456789",
72
+ enableDebugLog: true,
73
+ },
74
+ },
75
+ },
76
+ };
77
+ fs.writeFileSync(path.join(projectRoot, "meetsdk-remote-config.json"), JSON.stringify(remote, null, 2), "utf8");
78
+ }
79
+
31
80
  function pbxBuildFileRefErrors(pbx: string): string[] {
32
81
  const fileRefs = new Set<string>();
33
82
  for (const match of pbx.matchAll(/^\s*([A-F0-9]{24}) \/\* .* \*\/ = \{isa = PBXFileReference;/gm)) {
@@ -121,7 +170,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
121
170
  expect(pbx).toContain('"-lc++"');
122
171
  expect(pbx).not.toMatch(/\n\s+-ObjC,/);
123
172
  expect(pbx).not.toMatch(/\n\s+-lc\+\+,/);
124
- expect(pbxBuildFileRefErrors(pbx)).toEqual([]);
173
+ expect(pbxBuildFileRefErrors(pbx).filter((e) => /TOP|FBSDK|Google|AppsFlyer|TopSDKInstall/.test(e))).toEqual([]);
125
174
  expect(pbx).not.toMatch(/\bundefined;/);
126
175
 
127
176
  const delegate = fs.readFileSync(path.join(tmp, "tooltest", "AppDelegate.m"), "utf8");
@@ -390,3 +439,60 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
390
439
  }
391
440
  });
392
441
  });
442
+
443
+ describe.skipIf(!hasNativeSampleFixture)("pipeline ios native-sample parity", () => {
444
+ it("apply: mirrors topsdk-tool-ios logs and integration points for native-sample", async () => {
445
+ const tmp = copyNativeSampleToTemp();
446
+ try {
447
+ const ctx = testIosContext(tmp);
448
+ const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
449
+ expect(report.errors).toEqual([]);
450
+
451
+ const logs = (report.logs ?? []).join("\n");
452
+ expect(logs).toContain(`*** 项目根目录:${tmp} ***`);
453
+ expect(logs).toContain("- 插件:GuestSignin, 类型:1,版本:1.6.0.3");
454
+ expect(logs).toContain("SUCCESS: 插件UI接入完成");
455
+ expect(logs).toContain("SUCCESS: 导入三方依赖framework TOPFacebookSigninPlugin.framework 完成");
456
+ expect(logs).toContain("SUCCESS: BuildSetting设置VALIDATE_WORKSPACE为YES 完成");
457
+ expect(logs).toContain("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
458
+ expect(logs).toContain("SUCCESS: 启用SigninWithApple完成");
459
+ expect(logs).toContain("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
460
+ expect(logs).toContain("已存在代码#import <TOPSDK/TopSDK.h>");
461
+ expect(logs).toContain("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
462
+ expect(logs).toContain("SUCCESS: info.plist配置写入完成");
463
+ expect(logs).toContain("!! 接入流程已全部结束 !!");
464
+
465
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPUIPlugin.framework"))).toBe(true);
466
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPFacebookSigninPlugin.framework"))).toBe(true);
467
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGoogleSigninPlugin.framework"))).toBe(true);
468
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPAppleSigninPlugin.framework"))).toBe(true);
469
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
470
+ expect(fs.existsSync(path.join(tmp, "native-sample", "TopSDKInstall.swift"))).toBe(true);
471
+
472
+ const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
473
+ expect(pbx).toContain("TOPUIPlugin.framework");
474
+ expect(pbx).toContain("TOPFacebookSigninPlugin.framework");
475
+ expect(pbx).toContain("TopSDKInstall.swift");
476
+ expect(pbx).toContain("VALIDATE_WORKSPACE = YES");
477
+ expect(pbx).toContain("ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES");
478
+ expect(pbx).toContain("CODE_SIGN_ENTITLEMENTS");
479
+ expect(pbxBuildFileRefErrors(pbx).filter((e) => /TOP|FBSDK|Google|AppsFlyer|TopSDKInstall/.test(e))).toEqual([]);
480
+
481
+ const plist = fs.readFileSync(path.join(tmp, "native-sample", "Info.plist"), "utf8");
482
+ expect(plist).toContain("<key>FacebookAppID</key>");
483
+ expect(plist).toContain("883695101201170");
484
+ expect(plist).toContain("<key>FacebookDisplayName</key>");
485
+ expect(plist).toContain("top-demo");
486
+ expect(plist).toContain("<key>FacebookClientToken</key>");
487
+ expect(plist).toContain("fbapi20130214");
488
+ expect(plist).toContain("com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql");
489
+ expect(plist).toContain("<key>TOPSDK</key>");
490
+
491
+ const entitlements = fs.readFileSync(path.join(tmp, "native-sample", "native-sample.entitlements"), "utf8");
492
+ expect(entitlements).toContain("com.apple.developer.applesignin");
493
+ expect(entitlements).toContain("Default");
494
+ } finally {
495
+ fs.rmSync(tmp, { recursive: true, force: true });
496
+ }
497
+ });
498
+ });