@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.
- 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.js +1 -1
- 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 +95 -21
- 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/docs/INTEGRATION.md +19 -0
- package/package.json +1 -1
- 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 +1 -1
- package/src/ios/codeUtils.ts +4 -0
- package/src/ios/integrate.ts +107 -25
- package/src/ios/pbxprojEditor.ts +51 -1
- package/tests/pipeline.ios.test.ts +107 -1
package/src/ios/integrate.ts
CHANGED
|
@@ -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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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
|
-
|
|
575
|
+
logs.push("SUCCESS: info.plist配置写入完成");
|
|
576
|
+
logs.push("!! 接入流程已全部结束 !!");
|
|
577
|
+
return ok([...changed], warnings, logs);
|
|
496
578
|
}
|
|
497
579
|
|
|
498
580
|
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";
|
|
@@ -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
|
+
});
|