@leonxin/meetgames 0.1.13 → 0.1.15
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/config/meetsdk-ios.json +1 -1
- 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/config/meetSdkRemoteConfig.d.ts +2 -0
- package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
- package/dist/config/meetSdkRemoteConfig.js +14 -1
- package/dist/config/meetSdkRemoteConfig.js.map +1 -1
- package/dist/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +18 -1
- package/dist/ios/channelConfig.js.map +1 -1
- package/dist/ios/infoPlist.d.ts +0 -1
- package/dist/ios/infoPlist.d.ts.map +1 -1
- package/dist/ios/infoPlist.js +0 -5
- package/dist/ios/infoPlist.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +172 -97
- package/dist/ios/integrate.js.map +1 -1
- package/dist/ios/pbxprojEditor.d.ts +1 -1
- package/dist/ios/pbxprojEditor.d.ts.map +1 -1
- package/dist/ios/pbxprojEditor.js +88 -15
- package/dist/ios/pbxprojEditor.js.map +1 -1
- package/dist/ops/fileStore.d.ts.map +1 -1
- package/dist/ops/fileStore.js +6 -5
- package/dist/ops/fileStore.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/config/meetSdkRemoteConfig.ts +13 -1
- package/src/ios/channelConfig.ts +18 -1
- package/src/ios/infoPlist.ts +0 -7
- package/src/ios/integrate.ts +177 -96
- package/src/ios/pbxprojEditor.ts +98 -16
- package/src/ops/fileStore.ts +7 -6
- package/src/ops/handlers.ts +38 -3
- package/tests/doctor.test.ts +43 -3
- package/tests/meetSdkRemoteConfig.test.ts +3 -2
- package/tests/meetSdkRemoteGradle.test.ts +2 -2
- package/tests/pipeline.android.test.ts +64 -10
- package/tests/pipeline.ios.test.ts +134 -43
- package/tests/platformSelection.test.ts +4 -4
package/src/ios/integrate.ts
CHANGED
|
@@ -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 {
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
addSystemLib,
|
|
32
31
|
addThirdPartyFramework,
|
|
33
32
|
findInfoPlistPathsFromPbx,
|
|
33
|
+
getTargetBuildSettings,
|
|
34
34
|
loadPbxFromStore,
|
|
35
35
|
savePbxToStore,
|
|
36
36
|
setBuildSetting,
|
|
@@ -49,6 +49,34 @@ export interface IosIntegrateOptions {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
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
|
+
};
|
|
57
|
+
|
|
58
|
+
function logValue(value: unknown): string {
|
|
59
|
+
if (value === undefined) return "<missing>";
|
|
60
|
+
if (typeof value === "string") return value;
|
|
61
|
+
return JSON.stringify(value);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function pushValueChangeLog(logs: string[], label: string, before: unknown, after: unknown): void {
|
|
65
|
+
if (valuesEqual(before, after)) {
|
|
66
|
+
logs.push(`SUCCESS: ${label} 未变化 value:${logValue(after)}`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
logs.push(`SUCCESS: ${label} ${logValue(before)} -> ${logValue(after)}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function valuesEqual(before: unknown, after: unknown): boolean {
|
|
73
|
+
return JSON.stringify(before) === JSON.stringify(after);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function cloneValue(value: unknown): unknown {
|
|
77
|
+
if (value === undefined) return undefined;
|
|
78
|
+
return JSON.parse(JSON.stringify(value)) as unknown;
|
|
79
|
+
}
|
|
52
80
|
|
|
53
81
|
function ok(changed: string[], warnings: string[] = [], logs: string[] = []): StepResult {
|
|
54
82
|
return { ok: true, changedFiles: changed, logs, warnings, errors: [] };
|
|
@@ -70,7 +98,6 @@ function applyPlugin(
|
|
|
70
98
|
remoteSources: Map<string, string> = new Map()
|
|
71
99
|
): void {
|
|
72
100
|
const { config, sourceDir } = loaded;
|
|
73
|
-
const srcRoot = pbx.srcRoot;
|
|
74
101
|
|
|
75
102
|
logs.push(`- 插件:${config.name}, 类型:${config.type},版本:${config.pluginVersion}`);
|
|
76
103
|
|
|
@@ -163,7 +190,13 @@ function applyPlugin(
|
|
|
163
190
|
|
|
164
191
|
if (perform.includes("buildSettings") && config.buildSettings) {
|
|
165
192
|
for (const [k, v] of Object.entries(config.buildSettings)) {
|
|
166
|
-
|
|
193
|
+
const beforeValues = [...new Set(getTargetBuildSettings(pbx).map((settings) => settings[k]))];
|
|
194
|
+
if (beforeValues.length === 0 || beforeValues.some((before) => !valuesEqual(before, v))) {
|
|
195
|
+
setBuildSetting(pbx, k, v);
|
|
196
|
+
}
|
|
197
|
+
for (const before of beforeValues.length ? beforeValues : [undefined]) {
|
|
198
|
+
pushValueChangeLog(logs, `BuildSetting设置${k}`, before, v);
|
|
199
|
+
}
|
|
167
200
|
logs.push(`SUCCESS: BuildSetting设置${k}为${v} 完成`);
|
|
168
201
|
}
|
|
169
202
|
}
|
|
@@ -174,8 +207,6 @@ function applyPlugin(
|
|
|
174
207
|
logs.push(`SUCCESS: 添加other link flag:${flag} 完成`);
|
|
175
208
|
}
|
|
176
209
|
}
|
|
177
|
-
|
|
178
|
-
logs.push(`SUCCESS: 插件${config.name}接入完成`);
|
|
179
210
|
}
|
|
180
211
|
|
|
181
212
|
function validateLoadedPluginResourcesForIos(loaded: LoadedPluginConfig): string[] {
|
|
@@ -266,14 +297,27 @@ function ensureTopSdkInstallSwift(
|
|
|
266
297
|
pbx: PbxContext
|
|
267
298
|
): string | null {
|
|
268
299
|
if (hasSwiftSource(pbx.srcRoot)) return null;
|
|
269
|
-
const relFromSrc = path.join(
|
|
300
|
+
const relFromSrc = path.join("topSDK", "TopSDKInstall.swift").split(path.sep).join("/");
|
|
270
301
|
const abs = path.join(pbx.srcRoot, relFromSrc);
|
|
271
302
|
const relFromProject = path.relative(ctx.projectRoot, abs).split(path.sep).join("/");
|
|
272
|
-
store.write(relFromProject, "
|
|
303
|
+
store.write(relFromProject, "");
|
|
273
304
|
addSourceOrResourceFile(pbx, relFromSrc);
|
|
274
305
|
return relFromProject;
|
|
275
306
|
}
|
|
276
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
|
+
|
|
277
321
|
function appDelegateCodeShouldMoveToSceneDelegate(code: CodeConfig): boolean {
|
|
278
322
|
if (code.type === "header") return false;
|
|
279
323
|
const method = code.method ?? "";
|
|
@@ -325,7 +369,8 @@ function mergePlistParams(
|
|
|
325
369
|
loaded: LoadedPluginConfig,
|
|
326
370
|
channelConfig: Record<string, unknown>,
|
|
327
371
|
perform: readonly PerformSetting[],
|
|
328
|
-
logs: string[]
|
|
372
|
+
logs: string[],
|
|
373
|
+
dirtyDocs: Set<PlistDocument>
|
|
329
374
|
): void {
|
|
330
375
|
if (!perform.includes("infoParams") && !perform.includes("urlScheme") && !perform.includes("queriesSchemes")) {
|
|
331
376
|
return;
|
|
@@ -334,20 +379,31 @@ function mergePlistParams(
|
|
|
334
379
|
if (perform.includes("infoParams") && loaded.config.infoParams) {
|
|
335
380
|
for (const [key, raw] of Object.entries(loaded.config.infoParams)) {
|
|
336
381
|
const value = applyChannelTemplateValue(raw, channelConfig);
|
|
337
|
-
|
|
382
|
+
const before = doc.data[key];
|
|
383
|
+
if (!valuesEqual(before, value)) {
|
|
384
|
+
addPlistParam(doc.data, key, value);
|
|
385
|
+
dirtyDocs.add(doc);
|
|
386
|
+
}
|
|
387
|
+
pushValueChangeLog(logs, `info.plist配置key:${key}`, before, value);
|
|
338
388
|
logs.push(`SUCCESS: info.plist添加配置key:${key} value:${String(value)} 完成`);
|
|
339
389
|
}
|
|
340
390
|
}
|
|
341
391
|
if (perform.includes("queriesSchemes") && loaded.config.queriesSchemes?.length) {
|
|
342
392
|
for (const scheme of loaded.config.queriesSchemes) {
|
|
393
|
+
const before = cloneValue(doc.data.LSApplicationQueriesSchemes ?? []);
|
|
343
394
|
addQueriesScheme(doc.data, scheme);
|
|
395
|
+
if (!valuesEqual(before, doc.data.LSApplicationQueriesSchemes)) dirtyDocs.add(doc);
|
|
396
|
+
pushValueChangeLog(logs, `info.plist queriesSchemes ${scheme}`, before, doc.data.LSApplicationQueriesSchemes);
|
|
344
397
|
logs.push(`SUCCESS: 添加queriesSchemes:${scheme} 完成`);
|
|
345
398
|
}
|
|
346
399
|
}
|
|
347
400
|
if (perform.includes("urlScheme") && loaded.config.urlScheme) {
|
|
348
401
|
let scheme = applyChannelTemplate(loaded.config.urlScheme, channelConfig);
|
|
349
402
|
if (scheme.endsWith("://")) scheme = scheme.replace("://", "");
|
|
403
|
+
const before = cloneValue(doc.data.CFBundleURLTypes ?? []);
|
|
350
404
|
addUrlScheme(doc.data, scheme);
|
|
405
|
+
if (!valuesEqual(before, doc.data.CFBundleURLTypes)) dirtyDocs.add(doc);
|
|
406
|
+
pushValueChangeLog(logs, `info.plist URL Scheme ${scheme}`, before, doc.data.CFBundleURLTypes);
|
|
351
407
|
logs.push(`SUCCESS: 设置urlScheme:${scheme}完成`);
|
|
352
408
|
}
|
|
353
409
|
}
|
|
@@ -367,7 +423,9 @@ function buildTopSdkPluginInfo(
|
|
|
367
423
|
for (const [paramKey, rawValue] of Object.entries(loaded.config.pluginParams ?? {})) {
|
|
368
424
|
params[paramKey] = applyChannelTemplate(String(rawValue), channelConfig);
|
|
369
425
|
}
|
|
370
|
-
|
|
426
|
+
if (Object.keys(params).length > 0) {
|
|
427
|
+
info.params = params;
|
|
428
|
+
}
|
|
371
429
|
}
|
|
372
430
|
if (loaded.config.type === 4) {
|
|
373
431
|
dataPluginsInfo.push(info);
|
|
@@ -378,6 +436,89 @@ function buildTopSdkPluginInfo(
|
|
|
378
436
|
return { pluginsInfo, dataPluginsInfo };
|
|
379
437
|
}
|
|
380
438
|
|
|
439
|
+
function injectDelegateCodes(
|
|
440
|
+
loadedConfigs: LoadedPluginConfig[],
|
|
441
|
+
ctx: WorkspaceContext,
|
|
442
|
+
store: TextFileStore,
|
|
443
|
+
pbx: PbxContext,
|
|
444
|
+
warnings: string[],
|
|
445
|
+
changed: Set<string>,
|
|
446
|
+
logs: string[]
|
|
447
|
+
): void {
|
|
448
|
+
const hasAppDelegateCodes = loadedConfigs.some((loaded) => (loaded.config.appDelegateCodes ?? []).length > 0);
|
|
449
|
+
const hasSceneDelegateCodes = loadedConfigs.some((loaded) => (loaded.config.sceneDelegateCodes ?? []).length > 0);
|
|
450
|
+
if (!hasAppDelegateCodes && !hasSceneDelegateCodes) return;
|
|
451
|
+
|
|
452
|
+
const sceneDelegateRelPaths: string[] = [];
|
|
453
|
+
for (const abs of findSceneDelegateFiles(pbx.srcRoot)) {
|
|
454
|
+
sceneDelegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
455
|
+
}
|
|
456
|
+
const hasUniqueSceneDelegate = sceneDelegateRelPaths.length === 1;
|
|
457
|
+
|
|
458
|
+
const delegateRelPaths: string[] = [];
|
|
459
|
+
for (const abs of findDelegateFiles(pbx.srcRoot)) {
|
|
460
|
+
delegateRelPaths.push(path.relative(ctx.projectRoot, abs).split(path.sep).join("/"));
|
|
461
|
+
}
|
|
462
|
+
if (hasAppDelegateCodes && !delegateRelPaths.length) {
|
|
463
|
+
warnings.push("no UIApplicationDelegate .m/.mm found; skipped AppDelegate injection (SwiftUI @main is not supported yet)");
|
|
464
|
+
}
|
|
465
|
+
if (hasAppDelegateCodes && delegateRelPaths.length > 1) {
|
|
466
|
+
warnings.push(`multiple UIApplicationDelegate files found; skipped AppDelegate injection: ${delegateRelPaths.join(", ")}`);
|
|
467
|
+
}
|
|
468
|
+
for (const rel of hasAppDelegateCodes && delegateRelPaths.length === 1 ? delegateRelPaths : []) {
|
|
469
|
+
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
470
|
+
logs.push("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
|
|
471
|
+
let okInject = true;
|
|
472
|
+
for (const loaded of loadedConfigs) {
|
|
473
|
+
for (const code of appDelegateCodesForLifecycle(loaded, hasUniqueSceneDelegate)) {
|
|
474
|
+
const existed = cu.hasCode(code.content);
|
|
475
|
+
if (code.type === "header") {
|
|
476
|
+
if (!cu.addHeader(code.content)) okInject = false;
|
|
477
|
+
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
478
|
+
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
479
|
+
}
|
|
480
|
+
if (okInject) {
|
|
481
|
+
logs.push(
|
|
482
|
+
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (!okInject) warnings.push(`AppDelegate injection incomplete for ${rel}`);
|
|
488
|
+
const before = store.read(rel);
|
|
489
|
+
cu.applyToStore((c) => store.write(rel, c));
|
|
490
|
+
if (store.read(rel) !== before) changed.add(rel);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (hasSceneDelegateCodes && sceneDelegateRelPaths.length > 1) {
|
|
494
|
+
warnings.push(`multiple UIWindowSceneDelegate files found; skipped SceneDelegate injection: ${sceneDelegateRelPaths.join(", ")}`);
|
|
495
|
+
}
|
|
496
|
+
for (const rel of hasSceneDelegateCodes && sceneDelegateRelPaths.length === 1 ? sceneDelegateRelPaths : []) {
|
|
497
|
+
const cu = CodeUtils.fromFile(path.join(ctx.projectRoot, rel));
|
|
498
|
+
logs.push("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
|
|
499
|
+
let okInject = true;
|
|
500
|
+
for (const loaded of loadedConfigs) {
|
|
501
|
+
for (const code of loaded.config.sceneDelegateCodes ?? []) {
|
|
502
|
+
const existed = cu.hasCode(code.content);
|
|
503
|
+
if (code.type === "header") {
|
|
504
|
+
if (!cu.addHeader(code.content)) okInject = false;
|
|
505
|
+
} else if ((code.type === "method" || code.type === "code") && code.method) {
|
|
506
|
+
if (!cu.addCodeToMethod(code.method, code.content, Boolean(code.addToReturn))) okInject = false;
|
|
507
|
+
}
|
|
508
|
+
if (okInject) {
|
|
509
|
+
logs.push(
|
|
510
|
+
`SUCCESS: 代码文件${path.join(ctx.projectRoot, rel)},${existed ? "已存在代码" : "添加代码"}${code.content}${existed ? "" : "完成"}`
|
|
511
|
+
);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (!okInject) warnings.push(`SceneDelegate injection incomplete for ${rel}`);
|
|
516
|
+
const before = store.read(rel);
|
|
517
|
+
cu.applyToStore((c) => store.write(rel, c));
|
|
518
|
+
if (store.read(rel) !== before) changed.add(rel);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
381
522
|
export async function runIosIntegrateTopSdk(
|
|
382
523
|
ctx: WorkspaceContext,
|
|
383
524
|
store: TextFileStore,
|
|
@@ -436,6 +577,9 @@ export async function runIosIntegrateTopSdk(
|
|
|
436
577
|
|
|
437
578
|
const xcodeprojPath = ctx.ios.xcodeprojPath!;
|
|
438
579
|
const pbx = await loadPbxFromStore(store, ctx.projectRoot, xcodeprojPath, targetName);
|
|
580
|
+
if (perform.includes("buildSettings")) {
|
|
581
|
+
applyDefaultIosBuildSettings(pbx, logs);
|
|
582
|
+
}
|
|
439
583
|
const fm = new TopSdkFileManager(pbx.srcRoot);
|
|
440
584
|
|
|
441
585
|
let plistDocs: PlistDocument[];
|
|
@@ -444,6 +588,7 @@ export async function runIosIntegrateTopSdk(
|
|
|
444
588
|
} catch (e) {
|
|
445
589
|
return fail([e instanceof Error ? e.message : String(e)], warnings, logs);
|
|
446
590
|
}
|
|
591
|
+
const dirtyPlistDocs = new Set<PlistDocument>();
|
|
447
592
|
|
|
448
593
|
const firebaseDownload = await downloadIosFirebaseConfig(remote, pbx, dryRun, iosFirebaseConfigRelPath(plistDocs));
|
|
449
594
|
warnings.push(...firebaseDownload.warnings);
|
|
@@ -466,112 +611,48 @@ export async function runIosIntegrateTopSdk(
|
|
|
466
611
|
logs.push("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
|
|
467
612
|
}
|
|
468
613
|
}
|
|
614
|
+
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
|
|
615
|
+
if (perform.includes("infoParams") && loaded.config.name === "AppleSignin") {
|
|
616
|
+
for (const rel of ensureAppleSignInEntitlement(store, ctx.projectRoot, pbx)) {
|
|
617
|
+
changed.add(rel);
|
|
618
|
+
}
|
|
619
|
+
logs.push("SUCCESS: 启用SigninWithApple完成");
|
|
620
|
+
}
|
|
621
|
+
logs.push(`SUCCESS: 插件${loaded.config.name}接入完成`);
|
|
469
622
|
}
|
|
470
623
|
for (const loaded of coreConfigs) {
|
|
471
624
|
applyPlugin(loaded, pbx, fm, channelConfig, perform, binaryCopies, dryRun, logs, firebaseDownload.remoteSources);
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs);
|
|
476
|
-
if (perform.includes("infoParams")) {
|
|
477
|
-
setAppTransportSecurity(plistDocs[0]?.data ?? {}, true);
|
|
625
|
+
mergePlistParams(plistDocs, loaded, channelConfig, perform, logs, dirtyPlistDocs);
|
|
626
|
+
if (executeAppDelegate) {
|
|
627
|
+
injectDelegateCodes([loaded], ctx, store, pbx, warnings, changed, logs);
|
|
478
628
|
}
|
|
629
|
+
logs.push(`SUCCESS: 插件${loaded.config.name}接入完成`);
|
|
479
630
|
}
|
|
480
631
|
for (const doc of plistDocs) {
|
|
481
632
|
if (perform.includes("infoParams")) {
|
|
482
633
|
const { pluginsInfo, dataPluginsInfo } = buildTopSdkPluginInfo(pluginConfigs, channelConfig);
|
|
483
|
-
|
|
634
|
+
const before = doc.data.TOPSDK;
|
|
635
|
+
const nextTopSdk = {
|
|
484
636
|
APP_ID: remote.topsdk.appId,
|
|
485
637
|
Plugins: pluginsInfo,
|
|
486
638
|
dataPlugins: dataPluginsInfo,
|
|
487
|
-
}
|
|
639
|
+
};
|
|
640
|
+
if (!valuesEqual(before, nextTopSdk)) {
|
|
641
|
+
addPlistParam(doc.data, "TOPSDK", nextTopSdk);
|
|
642
|
+
dirtyPlistDocs.add(doc);
|
|
643
|
+
}
|
|
644
|
+
pushValueChangeLog(logs, "info.plist配置key:TOPSDK", before, nextTopSdk);
|
|
488
645
|
}
|
|
489
646
|
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)) {
|
|
647
|
+
if (dirtyPlistDocs.has(doc)) {
|
|
648
|
+
store.write(rel, buildPlistXml(doc.data));
|
|
496
649
|
changed.add(rel);
|
|
497
650
|
}
|
|
498
|
-
logs.push("SUCCESS: 启用SigninWithApple完成");
|
|
499
651
|
}
|
|
500
652
|
|
|
501
653
|
savePbxToStore(store, pbx);
|
|
502
654
|
changed.add(pbx.rel);
|
|
503
655
|
|
|
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
656
|
logs.push("SUCCESS: info.plist配置写入完成");
|
|
576
657
|
logs.push("!! 接入流程已全部结束 !!");
|
|
577
658
|
return ok([...changed], warnings, logs);
|
package/src/ios/pbxprojEditor.ts
CHANGED
|
@@ -103,7 +103,14 @@ function sanitizePbxBuildSettings(proj: XcodeProject): void {
|
|
|
103
103
|
continue;
|
|
104
104
|
}
|
|
105
105
|
if (Array.isArray(raw)) {
|
|
106
|
-
|
|
106
|
+
const normalized = raw.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
|
|
107
|
+
const seen = new Set<string>();
|
|
108
|
+
settings[key] = normalized.filter((value) => {
|
|
109
|
+
const marker = typeof value === "string" ? unquotePbxString(value) : JSON.stringify(value);
|
|
110
|
+
if (seen.has(marker)) return false;
|
|
111
|
+
seen.add(marker);
|
|
112
|
+
return true;
|
|
113
|
+
});
|
|
107
114
|
}
|
|
108
115
|
}
|
|
109
116
|
}
|
|
@@ -162,12 +169,15 @@ function ensureResourcesBuildPhase(proj: XcodeProject, targetName: string): void
|
|
|
162
169
|
export function addThirdPartyFramework(ctx: PbxContext, relPathFromSrcRoot: string, embed: boolean): void {
|
|
163
170
|
const file = relPathFromSrcRoot.split(path.sep).join("/");
|
|
164
171
|
const target = targetKey(ctx.proj, ctx.targetName);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
target,
|
|
170
|
-
|
|
172
|
+
const basename = path.basename(file);
|
|
173
|
+
const fileRefUuid = ensureFileRef(ctx, file, frameworkFileType(file));
|
|
174
|
+
addBuildFileToPhase(ctx, ensureFrameworksBuildPhase(ctx, target), fileRefUuid, basename, "Frameworks", ["Weak"]);
|
|
175
|
+
if (embed) {
|
|
176
|
+
addBuildFileToPhase(ctx, ensureCopyFilesBuildPhase(ctx, target), fileRefUuid, basename, "Embed Frameworks", [
|
|
177
|
+
"CodeSignOnCopy",
|
|
178
|
+
"RemoveHeadersOnCopy",
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
171
181
|
}
|
|
172
182
|
|
|
173
183
|
export function addCopyFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
|
|
@@ -202,13 +212,81 @@ function findFileRefUuid(ctx: PbxContext, file: string): string | null {
|
|
|
202
212
|
return null;
|
|
203
213
|
}
|
|
204
214
|
|
|
215
|
+
function frameworkFileType(file: string): string {
|
|
216
|
+
return file.endsWith(".xcframework") ? "wrapper.xcframework" : "wrapper.framework";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function ensureFileRef(ctx: PbxContext, file: string, lastKnownFileType: string): string {
|
|
220
|
+
const basename = path.basename(file);
|
|
221
|
+
const existing = findFileRefUuid(ctx, file);
|
|
222
|
+
const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
|
|
223
|
+
if (existing) {
|
|
224
|
+
const ref = fileRefSection[existing];
|
|
225
|
+
if (ref && typeof ref === "object") {
|
|
226
|
+
(ref as Record<string, unknown>).lastKnownFileType = lastKnownFileType;
|
|
227
|
+
}
|
|
228
|
+
return existing;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const uuid = ctx.proj.generateUuid();
|
|
232
|
+
fileRefSection[uuid] = {
|
|
233
|
+
isa: "PBXFileReference",
|
|
234
|
+
name: `"${basename}"`,
|
|
235
|
+
path: `"${file}"`,
|
|
236
|
+
sourceTree: '"<group>"',
|
|
237
|
+
lastKnownFileType,
|
|
238
|
+
includeInIndex: 0,
|
|
239
|
+
};
|
|
240
|
+
fileRefSection[`${uuid}_comment`] = basename;
|
|
241
|
+
return uuid;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function ensureFrameworksBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
|
|
245
|
+
try {
|
|
246
|
+
const phase = ctx.proj.pbxFrameworksBuildPhaseObj(target) as Record<string, unknown>;
|
|
247
|
+
phase.files = (phase.files as unknown[]) ?? [];
|
|
248
|
+
return phase;
|
|
249
|
+
} catch {
|
|
250
|
+
ctx.proj.addBuildPhase([], "PBXFrameworksBuildPhase", "Frameworks", target);
|
|
251
|
+
const phase = ctx.proj.pbxFrameworksBuildPhaseObj(target) as Record<string, unknown>;
|
|
252
|
+
phase.files = (phase.files as unknown[]) ?? [];
|
|
253
|
+
return phase;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function addBuildFileToPhase(
|
|
258
|
+
ctx: PbxContext,
|
|
259
|
+
phase: Record<string, unknown>,
|
|
260
|
+
fileRefUuid: string,
|
|
261
|
+
basename: string,
|
|
262
|
+
phaseName: string,
|
|
263
|
+
attributes?: string[]
|
|
264
|
+
): void {
|
|
265
|
+
const files = (phase.files ??= []) as Array<{ value?: string; comment?: string }>;
|
|
266
|
+
if (files.some((file) => file.comment === `${basename} in ${phaseName}`)) return;
|
|
267
|
+
|
|
268
|
+
const buildUuid = ctx.proj.generateUuid();
|
|
269
|
+
const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
|
|
270
|
+
const buildFile: Record<string, unknown> = {
|
|
271
|
+
isa: "PBXBuildFile",
|
|
272
|
+
fileRef: fileRefUuid,
|
|
273
|
+
fileRef_comment: basename,
|
|
274
|
+
};
|
|
275
|
+
if (attributes?.length) {
|
|
276
|
+
buildFile.settings = { ATTRIBUTES: attributes };
|
|
277
|
+
}
|
|
278
|
+
buildFileSection[buildUuid] = buildFile;
|
|
279
|
+
buildFileSection[`${buildUuid}_comment`] = `${basename} in ${phaseName}`;
|
|
280
|
+
files.push({ value: buildUuid, comment: `${basename} in ${phaseName}` });
|
|
281
|
+
}
|
|
282
|
+
|
|
205
283
|
function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
|
|
206
284
|
const section = objectSection(ctx.proj, "PBXCopyFilesBuildPhase");
|
|
207
285
|
for (const [uuid, raw] of Object.entries(section)) {
|
|
208
286
|
if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
|
|
209
287
|
const phase = raw as Record<string, unknown>;
|
|
210
288
|
const name = String(phase.name ?? "").replace(/^"|"$/g, "");
|
|
211
|
-
if (name === "
|
|
289
|
+
if (name === "Embed Frameworks" || section[`${uuid}_comment`] === "Embed Frameworks") {
|
|
212
290
|
phase.files = (phase.files as unknown[]) ?? [];
|
|
213
291
|
return phase;
|
|
214
292
|
}
|
|
@@ -221,15 +299,15 @@ function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<stri
|
|
|
221
299
|
dstPath: '""',
|
|
222
300
|
dstSubfolderSpec: 10,
|
|
223
301
|
files: [],
|
|
224
|
-
name: '"
|
|
302
|
+
name: '"Embed Frameworks"',
|
|
225
303
|
runOnlyForDeploymentPostprocessing: 0,
|
|
226
304
|
};
|
|
227
305
|
section[uuid] = phase;
|
|
228
|
-
section[`${uuid}_comment`] = "
|
|
306
|
+
section[`${uuid}_comment`] = "Embed Frameworks";
|
|
229
307
|
const native = ctx.proj.pbxNativeTargetSection?.()[target] as { buildPhases?: Array<{ value: string; comment: string }> };
|
|
230
308
|
native.buildPhases ??= [];
|
|
231
309
|
if (!native.buildPhases.some((p) => p.value === uuid)) {
|
|
232
|
-
native.buildPhases.push({ value: uuid, comment: "
|
|
310
|
+
native.buildPhases.push({ value: uuid, comment: "Embed Frameworks" });
|
|
233
311
|
}
|
|
234
312
|
return phase;
|
|
235
313
|
}
|
|
@@ -289,9 +367,13 @@ export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: str
|
|
|
289
367
|
const target = targetKey(ctx.proj, ctx.targetName);
|
|
290
368
|
if (file.endsWith(".h")) {
|
|
291
369
|
ctx.proj.addHeaderFile(file, { target });
|
|
292
|
-
} else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift")) {
|
|
370
|
+
} else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift") || file.endsWith(".xcdatamodeld")) {
|
|
293
371
|
try {
|
|
294
|
-
|
|
372
|
+
if (file.endsWith(".xcdatamodeld")) {
|
|
373
|
+
addSourceFileManually(ctx, file, "wrapper.xcdatamodel");
|
|
374
|
+
} else {
|
|
375
|
+
ctx.proj.addSourceFile(file, { target });
|
|
376
|
+
}
|
|
295
377
|
} catch (e) {
|
|
296
378
|
if (!String(e instanceof Error ? e.message : e).includes("path")) throw e;
|
|
297
379
|
addSourceFileManually(ctx, file);
|
|
@@ -327,7 +409,7 @@ function sourceFileType(file: string): string {
|
|
|
327
409
|
return "sourcecode.c.objc";
|
|
328
410
|
}
|
|
329
411
|
|
|
330
|
-
function addSourceFileManually(ctx: PbxContext, file: string): void {
|
|
412
|
+
function addSourceFileManually(ctx: PbxContext, file: string, lastKnownFileType?: string): void {
|
|
331
413
|
const basename = path.basename(file);
|
|
332
414
|
ensureSourcesBuildPhase(ctx.proj, ctx.targetName);
|
|
333
415
|
const sources = ctx.proj.pbxSourcesBuildPhaseObj(targetKey(ctx.proj, ctx.targetName));
|
|
@@ -344,7 +426,7 @@ function addSourceFileManually(ctx: PbxContext, file: string): void {
|
|
|
344
426
|
name: `"${basename}"`,
|
|
345
427
|
path: `"${file}"`,
|
|
346
428
|
sourceTree: '"<group>"',
|
|
347
|
-
lastKnownFileType: sourceFileType(file),
|
|
429
|
+
lastKnownFileType: lastKnownFileType ?? sourceFileType(file),
|
|
348
430
|
};
|
|
349
431
|
fileRefSection[`${fileRefUuid}_comment`] = basename;
|
|
350
432
|
buildFileSection[buildUuid] = {
|
|
@@ -394,7 +476,7 @@ function addResourceFileManually(ctx: PbxContext, file: string, lastKnownFileTyp
|
|
|
394
476
|
files.push({ value: buildUuid, comment: `${basename} in Resources` });
|
|
395
477
|
}
|
|
396
478
|
|
|
397
|
-
export function setBuildSetting(ctx: PbxContext, key: string, value:
|
|
479
|
+
export function setBuildSetting(ctx: PbxContext, key: string, value: unknown): void {
|
|
398
480
|
const uuid = targetUuid(ctx.proj, ctx.targetName);
|
|
399
481
|
if (!uuid) return;
|
|
400
482
|
const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
|
package/src/ops/fileStore.ts
CHANGED
|
@@ -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
|
-
|
|
36
|
-
|
|
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");
|