@leonxin/meetgames 0.1.14 → 0.1.16

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.
Files changed (37) hide show
  1. package/config/meetsdk-ios.json +1 -1
  2. package/dist/config/meetSdkRemoteConfig.d.ts +2 -0
  3. package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
  4. package/dist/config/meetSdkRemoteConfig.js +14 -1
  5. package/dist/config/meetSdkRemoteConfig.js.map +1 -1
  6. package/dist/core/pipeline.js +1 -1
  7. package/dist/core/pipeline.js.map +1 -1
  8. package/dist/ios/channelConfig.d.ts.map +1 -1
  9. package/dist/ios/channelConfig.js +1 -0
  10. package/dist/ios/channelConfig.js.map +1 -1
  11. package/dist/ios/fileManager.js +2 -2
  12. package/dist/ios/fileManager.js.map +1 -1
  13. package/dist/ios/infoPlist.d.ts +0 -1
  14. package/dist/ios/infoPlist.d.ts.map +1 -1
  15. package/dist/ios/infoPlist.js +0 -5
  16. package/dist/ios/infoPlist.js.map +1 -1
  17. package/dist/ios/integrate.d.ts.map +1 -1
  18. package/dist/ios/integrate.js +26 -22
  19. package/dist/ios/integrate.js.map +1 -1
  20. package/dist/ios/pbxprojEditor.d.ts +1 -1
  21. package/dist/ios/pbxprojEditor.d.ts.map +1 -1
  22. package/dist/ios/pbxprojEditor.js +123 -34
  23. package/dist/ios/pbxprojEditor.js.map +1 -1
  24. package/dist/ops/fileStore.d.ts.map +1 -1
  25. package/dist/ops/fileStore.js +6 -5
  26. package/dist/ops/fileStore.js.map +1 -1
  27. package/package.json +1 -1
  28. package/src/config/meetSdkRemoteConfig.ts +13 -1
  29. package/src/core/pipeline.ts +1 -1
  30. package/src/ios/channelConfig.ts +1 -0
  31. package/src/ios/fileManager.ts +2 -2
  32. package/src/ios/infoPlist.ts +0 -7
  33. package/src/ios/integrate.ts +26 -20
  34. package/src/ios/pbxprojEditor.ts +135 -35
  35. package/src/ops/fileStore.ts +7 -6
  36. package/tests/meetSdkRemoteConfig.test.ts +2 -1
  37. package/tests/pipeline.ios.test.ts +31 -2
@@ -23,7 +23,7 @@ export class TopSdkFileManager {
23
23
  if (!fs.existsSync(srcPath)) throw new Error(`framework not found: ${srcPath}`);
24
24
  const dest = path.join(this.topSdkAbs, base);
25
25
  fs.rmSync(dest, { recursive: true, force: true });
26
- fs.cpSync(srcPath, dest, { recursive: true });
26
+ fs.cpSync(srcPath, dest, { recursive: true, verbatimSymlinks: true });
27
27
  return dest;
28
28
  }
29
29
 
@@ -32,7 +32,7 @@ export class TopSdkFileManager {
32
32
  const base = path.basename(srcPath);
33
33
  const dest = path.join(this.topSdkAbs, base);
34
34
  fs.rmSync(dest, { recursive: true, force: true });
35
- fs.cpSync(srcPath, dest, { recursive: true });
35
+ fs.cpSync(srcPath, dest, { recursive: true, verbatimSymlinks: true });
36
36
  return dest;
37
37
  }
38
38
 
@@ -21,13 +21,6 @@ export function addPlistParam(data: Record<string, unknown>, key: string, value:
21
21
  data[key] = value;
22
22
  }
23
23
 
24
- export function setAppTransportSecurity(data: Record<string, unknown>, open: boolean): void {
25
- const security =
26
- (data.NSAppTransportSecurity as Record<string, unknown> | undefined) ?? {};
27
- security.NSAllowsArbitraryLoads = open;
28
- data.NSAppTransportSecurity = security;
29
- }
30
-
31
24
  export function addQueriesScheme(data: Record<string, unknown>, scheme: string): void {
32
25
  if (!scheme) return;
33
26
  const schemes = Array.isArray(data.LSApplicationQueriesSchemes)
@@ -20,7 +20,6 @@ import {
20
20
  addUrlScheme,
21
21
  buildPlistXml,
22
22
  parsePlistXml,
23
- setAppTransportSecurity,
24
23
  type PlistDocument,
25
24
  } from "./infoPlist.js";
26
25
  import {
@@ -50,6 +49,11 @@ export interface IosIntegrateOptions {
50
49
  }
51
50
 
52
51
  const IOS_FIREBASE_CONFIG_FILE = "GoogleService-Info.plist";
52
+ const DEFAULT_IOS_BUILD_SETTINGS: Record<string, unknown> = {
53
+ FRAMEWORK_SEARCH_PATHS: ['"$(SRCROOT)/topSDK"', '"$(inherited)"', '"$(PROJECT_DIR)/topSDK"'],
54
+ LIBRARY_SEARCH_PATHS: '"$(SRCROOT)/topSDK"',
55
+ SWIFT_VERSION: "5.0",
56
+ };
53
57
 
54
58
  function logValue(value: unknown): string {
55
59
  if (value === undefined) return "<missing>";
@@ -293,14 +297,27 @@ function ensureTopSdkInstallSwift(
293
297
  pbx: PbxContext
294
298
  ): string | null {
295
299
  if (hasSwiftSource(pbx.srcRoot)) return null;
296
- const relFromSrc = path.join(pbx.targetName, "TopSDKInstall.swift").split(path.sep).join("/");
300
+ const relFromSrc = path.join("topSDK", "TopSDKInstall.swift").split(path.sep).join("/");
297
301
  const abs = path.join(pbx.srcRoot, relFromSrc);
298
302
  const relFromProject = path.relative(ctx.projectRoot, abs).split(path.sep).join("/");
299
- store.write(relFromProject, "import Foundation\n\n");
303
+ store.write(relFromProject, "");
300
304
  addSourceOrResourceFile(pbx, relFromSrc);
301
305
  return relFromProject;
302
306
  }
303
307
 
308
+ function applyDefaultIosBuildSettings(pbx: PbxContext, logs: string[]): void {
309
+ for (const [key, value] of Object.entries(DEFAULT_IOS_BUILD_SETTINGS)) {
310
+ const beforeValues = [...new Set(getTargetBuildSettings(pbx).map((settings) => settings[key]))];
311
+ if (beforeValues.length === 0 || beforeValues.some((before) => !valuesEqual(before, value))) {
312
+ setBuildSetting(pbx, key, value);
313
+ }
314
+ for (const before of beforeValues.length ? beforeValues : [undefined]) {
315
+ pushValueChangeLog(logs, `BuildSetting设置${key}`, before, value);
316
+ }
317
+ logs.push(`SUCCESS: BuildSetting设置${key}为${logValue(value)} 完成`);
318
+ }
319
+ }
320
+
304
321
  function appDelegateCodeShouldMoveToSceneDelegate(code: CodeConfig): boolean {
305
322
  if (code.type === "header") return false;
306
323
  const method = code.method ?? "";
@@ -406,7 +423,9 @@ function buildTopSdkPluginInfo(
406
423
  for (const [paramKey, rawValue] of Object.entries(loaded.config.pluginParams ?? {})) {
407
424
  params[paramKey] = applyChannelTemplate(String(rawValue), channelConfig);
408
425
  }
409
- info.params = params;
426
+ if (Object.keys(params).length > 0) {
427
+ info.params = params;
428
+ }
410
429
  }
411
430
  if (loaded.config.type === 4) {
412
431
  dataPluginsInfo.push(info);
@@ -558,6 +577,9 @@ export async function runIosIntegrateTopSdk(
558
577
 
559
578
  const xcodeprojPath = ctx.ios.xcodeprojPath!;
560
579
  const pbx = await loadPbxFromStore(store, ctx.projectRoot, xcodeprojPath, targetName);
580
+ if (perform.includes("buildSettings")) {
581
+ applyDefaultIosBuildSettings(pbx, logs);
582
+ }
561
583
  const fm = new TopSdkFileManager(pbx.srcRoot);
562
584
 
563
585
  let plistDocs: PlistDocument[];
@@ -590,14 +612,6 @@ export async function runIosIntegrateTopSdk(
590
612
  }
591
613
  }
592
614
  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
615
  if (perform.includes("infoParams") && loaded.config.name === "AppleSignin") {
602
616
  for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
603
617
  changed.add(rel);
@@ -609,14 +623,6 @@ export async function runIosIntegrateTopSdk(
609
623
  for (const loaded of coreConfigs) {
610
624
  applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
611
625
  mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
612
- if (perform.includes("infoParams")) {
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
626
  if (executeAppDelegate) {
621
627
  injectDelegateCodes([loaded], ctx, store, pbx, warnings, changed, logs);
622
628
  }
@@ -12,6 +12,13 @@ const xcode = require("xcode") as {
12
12
  project: (pbxprojPath: string) => XcodeProject;
13
13
  };
14
14
 
15
+ const EXPECTED_SIGNATURE_BY_XCFRAMEWORK: Record<string, string> = {
16
+ "FBAEMKit.xcframework": '"AppleDeveloperProgram:V9WTTPBFK9:Meta Platforms, Inc."',
17
+ "FBSDKCoreKit.xcframework": '"AppleDeveloperProgram:V9WTTPBFK9:Meta Platforms, Inc."',
18
+ "FBSDKCoreKit_Basics.xcframework": '"AppleDeveloperProgram:V9WTTPBFK9:Meta Platforms, Inc."',
19
+ "FBSDKLoginKit.xcframework": '"AppleDeveloperProgram:V9WTTPBFK9:Meta Platforms, Inc."',
20
+ };
21
+
15
22
  export interface PbxContext {
16
23
  rel: string;
17
24
  proj: XcodeProject;
@@ -103,7 +110,14 @@ function sanitizePbxBuildSettings(proj: XcodeProject): void {
103
110
  continue;
104
111
  }
105
112
  if (Array.isArray(raw)) {
106
- settings[key] = raw.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
113
+ const normalized = raw.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
114
+ const seen = new Set<string>();
115
+ settings[key] = normalized.filter((value) => {
116
+ const marker = typeof value === "string" ? unquotePbxString(value) : JSON.stringify(value);
117
+ if (seen.has(marker)) return false;
118
+ seen.add(marker);
119
+ return true;
120
+ });
107
121
  }
108
122
  }
109
123
  }
@@ -162,12 +176,15 @@ function ensureResourcesBuildPhase(proj: XcodeProject, targetName: string): void
162
176
  export function addThirdPartyFramework(ctx: PbxContext, relPathFromSrcRoot: string, embed: boolean): void {
163
177
  const file = relPathFromSrcRoot.split(path.sep).join("/");
164
178
  const target = targetKey(ctx.proj, ctx.targetName);
165
- ctx.proj.addFramework(file, {
166
- customFramework: true,
167
- embed,
168
- sign: true,
169
- target,
170
- });
179
+ const basename = path.basename(file);
180
+ const fileRefUuid = ensureFileRef(ctx, file, frameworkFileType(file));
181
+ addBuildFileToPhase(ctx, ensureFrameworksBuildPhase(ctx, target), fileRefUuid, basename, "Frameworks", ["Weak"]);
182
+ if (embed) {
183
+ addBuildFileToPhase(ctx, ensureCopyFilesBuildPhase(ctx, target), fileRefUuid, basename, "Embed Frameworks", [
184
+ "CodeSignOnCopy",
185
+ "RemoveHeadersOnCopy",
186
+ ]);
187
+ }
171
188
  }
172
189
 
173
190
  export function addCopyFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
@@ -202,13 +219,100 @@ function findFileRefUuid(ctx: PbxContext, file: string): string | null {
202
219
  return null;
203
220
  }
204
221
 
222
+ function frameworkFileType(file: string): string {
223
+ return file.endsWith(".xcframework") ? "wrapper.xcframework" : "wrapper.framework";
224
+ }
225
+
226
+ function expectedSignature(file: string): string | undefined {
227
+ return EXPECTED_SIGNATURE_BY_XCFRAMEWORK[path.basename(file)];
228
+ }
229
+
230
+ function topSdkFileReference(file: string, lastKnownFileType: string): Record<string, unknown> {
231
+ const basename = path.basename(file);
232
+ const ref: Record<string, unknown> = {
233
+ isa: "PBXFileReference",
234
+ };
235
+ const signature = expectedSignature(file);
236
+ if (signature) ref.expectedSignature = signature;
237
+ ref.lastKnownFileType = lastKnownFileType;
238
+ ref.name = basename;
239
+ ref.path = file;
240
+ ref.sourceTree = "SOURCE_ROOT";
241
+ return ref;
242
+ }
243
+
244
+ function normalizeTopSdkFileReference(ref: Record<string, unknown>, file: string, lastKnownFileType: string): void {
245
+ const normalized = topSdkFileReference(file, lastKnownFileType);
246
+ for (const [key, value] of Object.entries(normalized)) {
247
+ ref[key] = value;
248
+ }
249
+ delete ref.includeInIndex;
250
+ }
251
+
252
+ function ensureFileRef(ctx: PbxContext, file: string, lastKnownFileType: string): string {
253
+ const basename = path.basename(file);
254
+ const existing = findFileRefUuid(ctx, file);
255
+ const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
256
+ if (existing) {
257
+ const ref = fileRefSection[existing];
258
+ if (ref && typeof ref === "object") {
259
+ normalizeTopSdkFileReference(ref as Record<string, unknown>, file, lastKnownFileType);
260
+ }
261
+ return existing;
262
+ }
263
+
264
+ const uuid = ctx.proj.generateUuid();
265
+ fileRefSection[uuid] = topSdkFileReference(file, lastKnownFileType);
266
+ fileRefSection[`${uuid}_comment`] = basename;
267
+ return uuid;
268
+ }
269
+
270
+ function ensureFrameworksBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
271
+ try {
272
+ const phase = ctx.proj.pbxFrameworksBuildPhaseObj(target) as Record<string, unknown>;
273
+ phase.files = (phase.files as unknown[]) ?? [];
274
+ return phase;
275
+ } catch {
276
+ ctx.proj.addBuildPhase([], "PBXFrameworksBuildPhase", "Frameworks", target);
277
+ const phase = ctx.proj.pbxFrameworksBuildPhaseObj(target) as Record<string, unknown>;
278
+ phase.files = (phase.files as unknown[]) ?? [];
279
+ return phase;
280
+ }
281
+ }
282
+
283
+ function addBuildFileToPhase(
284
+ ctx: PbxContext,
285
+ phase: Record<string, unknown>,
286
+ fileRefUuid: string,
287
+ basename: string,
288
+ phaseName: string,
289
+ attributes?: string[]
290
+ ): void {
291
+ const files = (phase.files ??= []) as Array<{ value?: string; comment?: string }>;
292
+ if (files.some((file) => file.comment === `${basename} in ${phaseName}`)) return;
293
+
294
+ const buildUuid = ctx.proj.generateUuid();
295
+ const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
296
+ const buildFile: Record<string, unknown> = {
297
+ isa: "PBXBuildFile",
298
+ fileRef: fileRefUuid,
299
+ fileRef_comment: basename,
300
+ };
301
+ if (attributes?.length) {
302
+ buildFile.settings = { ATTRIBUTES: attributes };
303
+ }
304
+ buildFileSection[buildUuid] = buildFile;
305
+ buildFileSection[`${buildUuid}_comment`] = `${basename} in ${phaseName}`;
306
+ files.push({ value: buildUuid, comment: `${basename} in ${phaseName}` });
307
+ }
308
+
205
309
  function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
206
310
  const section = objectSection(ctx.proj, "PBXCopyFilesBuildPhase");
207
311
  for (const [uuid, raw] of Object.entries(section)) {
208
312
  if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
209
313
  const phase = raw as Record<string, unknown>;
210
314
  const name = String(phase.name ?? "").replace(/^"|"$/g, "");
211
- if (name === "Copy Files" || section[`${uuid}_comment`] === "Copy Files") {
315
+ if (name === "Embed Frameworks" || section[`${uuid}_comment`] === "Embed Frameworks") {
212
316
  phase.files = (phase.files as unknown[]) ?? [];
213
317
  return phase;
214
318
  }
@@ -221,15 +325,15 @@ function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<stri
221
325
  dstPath: '""',
222
326
  dstSubfolderSpec: 10,
223
327
  files: [],
224
- name: '"Copy Files"',
328
+ name: '"Embed Frameworks"',
225
329
  runOnlyForDeploymentPostprocessing: 0,
226
330
  };
227
331
  section[uuid] = phase;
228
- section[`${uuid}_comment`] = "Copy Files";
332
+ section[`${uuid}_comment`] = "Embed Frameworks";
229
333
  const native = ctx.proj.pbxNativeTargetSection?.()[target] as { buildPhases?: Array<{ value: string; comment: string }> };
230
334
  native.buildPhases ??= [];
231
335
  if (!native.buildPhases.some((p) => p.value === uuid)) {
232
- native.buildPhases.push({ value: uuid, comment: "Copy Files" });
336
+ native.buildPhases.push({ value: uuid, comment: "Embed Frameworks" });
233
337
  }
234
338
  return phase;
235
339
  }
@@ -244,15 +348,14 @@ function addCopyFileManually(ctx: PbxContext, target: string, file: string): voi
244
348
  if (!fileRefUuid) {
245
349
  fileRefUuid = ctx.proj.generateUuid();
246
350
  const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
247
- fileRefSection[fileRefUuid] = {
248
- isa: "PBXFileReference",
249
- name: `"${basename}"`,
250
- path: `"${file}"`,
251
- sourceTree: '"<group>"',
252
- lastKnownFileType: "wrapper.framework",
253
- includeInIndex: 0,
254
- };
351
+ fileRefSection[fileRefUuid] = topSdkFileReference(file, "wrapper.framework");
255
352
  fileRefSection[`${fileRefUuid}_comment`] = basename;
353
+ } else {
354
+ const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
355
+ const ref = fileRefSection[fileRefUuid];
356
+ if (ref && typeof ref === "object") {
357
+ normalizeTopSdkFileReference(ref as Record<string, unknown>, file, "wrapper.framework");
358
+ }
256
359
  }
257
360
 
258
361
  const buildUuid = ctx.proj.generateUuid();
@@ -289,9 +392,13 @@ export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: str
289
392
  const target = targetKey(ctx.proj, ctx.targetName);
290
393
  if (file.endsWith(".h")) {
291
394
  ctx.proj.addHeaderFile(file, { target });
292
- } else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift")) {
395
+ } else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift") || file.endsWith(".xcdatamodeld")) {
293
396
  try {
294
- ctx.proj.addSourceFile(file, { target });
397
+ if (file.endsWith(".xcdatamodeld")) {
398
+ addSourceFileManually(ctx, file, "wrapper.xcdatamodel");
399
+ } else {
400
+ ctx.proj.addSourceFile(file, { target });
401
+ }
295
402
  } catch (e) {
296
403
  if (!String(e instanceof Error ? e.message : e).includes("path")) throw e;
297
404
  addSourceFileManually(ctx, file);
@@ -327,7 +434,7 @@ function sourceFileType(file: string): string {
327
434
  return "sourcecode.c.objc";
328
435
  }
329
436
 
330
- function addSourceFileManually(ctx: PbxContext, file: string): void {
437
+ function addSourceFileManually(ctx: PbxContext, file: string, lastKnownFileType?: string): void {
331
438
  const basename = path.basename(file);
332
439
  ensureSourcesBuildPhase(ctx.proj, ctx.targetName);
333
440
  const sources = ctx.proj.pbxSourcesBuildPhaseObj(targetKey(ctx.proj, ctx.targetName));
@@ -341,10 +448,10 @@ function addSourceFileManually(ctx: PbxContext, file: string): void {
341
448
 
342
449
  fileRefSection[fileRefUuid] = {
343
450
  isa: "PBXFileReference",
344
- name: `"${basename}"`,
345
- path: `"${file}"`,
346
- sourceTree: '"<group>"',
347
- lastKnownFileType: sourceFileType(file),
451
+ name: basename,
452
+ path: file,
453
+ sourceTree: "SOURCE_ROOT",
454
+ lastKnownFileType: lastKnownFileType ?? sourceFileType(file),
348
455
  };
349
456
  fileRefSection[`${fileRefUuid}_comment`] = basename;
350
457
  buildFileSection[buildUuid] = {
@@ -376,14 +483,7 @@ function addResourceFileManually(ctx: PbxContext, file: string, lastKnownFileTyp
376
483
  const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
377
484
  const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
378
485
 
379
- fileRefSection[fileRefUuid] = {
380
- isa: "PBXFileReference",
381
- name: `"${basename}"`,
382
- path: `"${file}"`,
383
- sourceTree: '"<group>"',
384
- lastKnownFileType: resourceFileType(file, lastKnownFileType),
385
- includeInIndex: 0,
386
- };
486
+ fileRefSection[fileRefUuid] = topSdkFileReference(file, resourceFileType(file, lastKnownFileType));
387
487
  fileRefSection[`${fileRefUuid}_comment`] = basename;
388
488
  buildFileSection[buildUuid] = {
389
489
  isa: "PBXBuildFile",
@@ -394,7 +494,7 @@ function addResourceFileManually(ctx: PbxContext, file: string, lastKnownFileTyp
394
494
  files.push({ value: buildUuid, comment: `${basename} in Resources` });
395
495
  }
396
496
 
397
- export function setBuildSetting(ctx: PbxContext, key: string, value: string): void {
497
+ export function setBuildSetting(ctx: PbxContext, key: string, value: unknown): void {
398
498
  const uuid = targetUuid(ctx.proj, ctx.targetName);
399
499
  if (!uuid) return;
400
500
  const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  /** Tracks text file originals and pending content for dry-run / apply */
5
5
  export class TextFileStore {
6
6
  private readonly projectRoot: string;
7
- private readonly map = new Map<string, { original: string; current: string }>();
7
+ private readonly map = new Map<string, { original: string; current: string; existed: boolean }>();
8
8
 
9
9
  constructor(projectRoot: string) {
10
10
  this.projectRoot = projectRoot;
@@ -20,7 +20,7 @@ export class TextFileStore {
20
20
  const abs = path.join(this.projectRoot, rel);
21
21
  if (!fs.existsSync(abs)) return "";
22
22
  const content = fs.readFileSync(abs, "utf8");
23
- this.map.set(rel, { original: content, current: content });
23
+ this.map.set(rel, { original: content, current: content, existed: true });
24
24
  return content;
25
25
  }
26
26
 
@@ -32,22 +32,23 @@ export class TextFileStore {
32
32
  return;
33
33
  }
34
34
  let original = "";
35
- if (fs.existsSync(abs)) original = fs.readFileSync(abs, "utf8");
36
- this.map.set(rel, { original, current: content });
35
+ const existed = fs.existsSync(abs);
36
+ if (existed) original = fs.readFileSync(abs, "utf8");
37
+ this.map.set(rel, { original, current: content, existed });
37
38
  }
38
39
 
39
40
  /** Files where current !== original */
40
41
  changedEntries(): Array<{ rel: string; original: string; current: string }> {
41
42
  const out: Array<{ rel: string; original: string; current: string }> = [];
42
43
  for (const [rel, v] of this.map) {
43
- if (v.current !== v.original) out.push({ rel, original: v.original, current: v.current });
44
+ if (!v.existed || v.current !== v.original) out.push({ rel, original: v.original, current: v.current });
44
45
  }
45
46
  return out;
46
47
  }
47
48
 
48
49
  flushToDisk(): void {
49
50
  for (const [rel, v] of this.map) {
50
- if (v.current === v.original) continue;
51
+ if (v.existed && v.current === v.original) continue;
51
52
  const abs = path.join(this.projectRoot, rel);
52
53
  fs.mkdirSync(path.dirname(abs), { recursive: true });
53
54
  fs.writeFileSync(abs, v.current, "utf8");
@@ -53,7 +53,7 @@ describe("meetSdkRemoteConfig", () => {
53
53
  naver: { clientId: "naver-client", secret: "naver-secret", name: "Naver", scheme: "naver-scheme" },
54
54
  apple: {},
55
55
  },
56
- payment: {},
56
+ payment: { appleIap: {} },
57
57
  analytics: {
58
58
  appsflyer: { devKey: "af-key", appleAppId: "1234567890" },
59
59
  firebase: { firebaseUrl: "https://cdn.example/GoogleService-Info.plist", firebaseName: "GoogleService-Info.plist" },
@@ -82,6 +82,7 @@ describe("meetSdkRemoteConfig", () => {
82
82
  devKey: "af-key",
83
83
  appleAppId: "1234567890",
84
84
  });
85
+ expect(parsed!.sdkModules.payment.appleIap).toMatchObject({});
85
86
  });
86
87
 
87
88
  it("does not treat legacy remote field names as valid config", () => {
@@ -50,7 +50,7 @@ function writeNativeSampleRemoteConfig(projectRoot: string): void {
50
50
  apple: {},
51
51
  },
52
52
  payment: {
53
- googleIap: {},
53
+ appleIap: {},
54
54
  },
55
55
  analytics: {
56
56
  appsflyer: {
@@ -156,11 +156,38 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
156
156
  expect(fs.existsSync(path.join(tmp, "topSDK", "TOPCore.framework"))).toBe(true);
157
157
  expect(fs.existsSync(path.join(tmp, "topSDK", "TOPSDK.framework"))).toBe(true);
158
158
  expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGuestSigninPlugin.framework"))).toBe(true);
159
+ expect(fs.existsSync(path.join(tmp, "topSDK", "TOPIAPPayPlugin.framework"))).toBe(true);
160
+ expect(fs.readFileSync(path.join(tmp, "topSDK", "TopSDKInstall.swift"), "utf8")).toBe("");
159
161
  expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
162
+ expect(
163
+ fs.readlinkSync(
164
+ path.join(
165
+ tmp,
166
+ "topSDK",
167
+ "FBSDKLoginKit.xcframework",
168
+ "ios-arm64_x86_64-maccatalyst",
169
+ "FBSDKLoginKit.framework",
170
+ "FBSDKLoginKit"
171
+ )
172
+ )
173
+ ).toBe("Versions/Current/FBSDKLoginKit");
160
174
 
161
175
  const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
162
176
  expect(pbx).toContain("TOPCore.framework");
163
177
  expect(pbx).toContain("TOPGuestSigninPlugin.framework");
178
+ expect(pbx).toContain("TOPIAPPayPlugin.framework in Frameworks");
179
+ expect(pbx).toContain("TOPCoreModel.xcdatamodeld in Sources");
180
+ expect(pbx).not.toContain("TOPCoreModel.xcdatamodeld in Resources");
181
+ expect(pbx).not.toContain("FBSDKLoginKit.xcframework in Resources");
182
+ expect(pbx).toContain(
183
+ 'FBSDKLoginKit.xcframework */ = {isa = PBXFileReference; expectedSignature = "AppleDeveloperProgram:V9WTTPBFK9:Meta Platforms, Inc."; lastKnownFileType = wrapper.xcframework; name = FBSDKLoginKit.xcframework; path = topSDK/FBSDKLoginKit.xcframework; sourceTree = SOURCE_ROOT; };'
184
+ );
185
+ expect(pbx).toContain("FBSDKCoreKit.xcframework");
186
+ expect(pbx).toContain("FBSDKCoreKit_Basics.xcframework");
187
+ expect(pbx).toContain("FBAEMKit.xcframework");
188
+ expect(pbx).not.toContain('path = "topSDK/FBSDKLoginKit.xcframework"; sourceTree = "<group>";');
189
+ expect(pbx).toContain("LIBRARY_SEARCH_PATHS = \"$(SRCROOT)/topSDK\"");
190
+ expect(pbx).toContain("SWIFT_VERSION = 5.0");
164
191
  expect(pbx).toContain('"-ObjC"');
165
192
  expect(pbx).toContain("libc++.tbd");
166
193
  expect(pbx).not.toMatch(/\n\s+-ObjC,/);
@@ -177,6 +204,8 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
177
204
  expect(plist).toContain("<key>APP_ID</key>");
178
205
  expect(plist).toContain("mock-ios-native-sample-app-id");
179
206
  expect(plist).toContain("GuestSignin");
207
+ expect(plist).toContain("IAPPay");
208
+ expect(plist).not.toContain("NSAppTransportSecurity");
180
209
  } finally {
181
210
  fs.rmSync(tmp, { recursive: true, force: true });
182
211
  }
@@ -548,7 +577,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios native-sample parity", () =
548
577
  expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGoogleSigninPlugin.framework"))).toBe(true);
549
578
  expect(fs.existsSync(path.join(tmp, "topSDK", "TOPAppleSigninPlugin.framework"))).toBe(true);
550
579
  expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
551
- expect(fs.existsSync(path.join(tmp, "native-sample", "TopSDKInstall.swift"))).toBe(true);
580
+ expect(fs.readFileSync(path.join(tmp, "topSDK", "TopSDKInstall.swift"), "utf8")).toBe("");
552
581
 
553
582
  const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
554
583
  expect(pbx).toContain("TOPUIPlugin.framework");