@leonxin/meetgames 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/android/manifest.d.ts.map +1 -1
- package/dist/android/manifest.js +9 -0
- package/dist/android/manifest.js.map +1 -1
- package/dist/android/meetSdkRemoteGradle.d.ts.map +1 -1
- package/dist/android/meetSdkRemoteGradle.js +126 -6
- package/dist/android/meetSdkRemoteGradle.js.map +1 -1
- package/dist/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.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +18 -2
- 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 +242 -97
- 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/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 +48 -1
- package/package.json +1 -1
- package/src/android/manifest.ts +11 -0
- package/src/android/meetSdkRemoteGradle.ts +125 -7
- 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 +18 -2
- package/src/ios/codeUtils.ts +4 -0
- package/src/ios/integrate.ts +253 -96
- package/src/ios/pbxprojEditor.ts +51 -1
- package/src/ops/handlers.ts +38 -3
- package/tests/doctor.test.ts +43 -3
- package/tests/meetSdkRemoteConfig.test.ts +1 -1
- package/tests/meetSdkRemoteGradle.test.ts +2 -2
- package/tests/pipeline.android.test.ts +64 -10
- package/tests/pipeline.ios.test.ts +219 -32
- package/tests/platformSelection.test.ts +4 -4
package/tests/doctor.test.ts
CHANGED
|
@@ -11,15 +11,55 @@ import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js
|
|
|
11
11
|
|
|
12
12
|
const pkgRoot = path.resolve(__dirname, "..");
|
|
13
13
|
const androidLatestRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
|
|
14
|
-
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "
|
|
15
|
-
const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
|
|
14
|
+
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "native-sample");
|
|
16
15
|
const hasIosProjectFixture = fs.existsSync(iosProjectRoot);
|
|
17
16
|
const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
|
|
18
17
|
|
|
19
18
|
function copyIosProjectToTemp(): string {
|
|
20
19
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-doctor-"));
|
|
21
20
|
fs.cpSync(iosProjectRoot, tmp, { recursive: true });
|
|
22
|
-
fs.
|
|
21
|
+
fs.writeFileSync(
|
|
22
|
+
path.join(tmp, "meetsdk-remote-config.json"),
|
|
23
|
+
JSON.stringify(
|
|
24
|
+
{
|
|
25
|
+
packageName: "com.meetgames.topsdk.demo",
|
|
26
|
+
channel: "APPLE",
|
|
27
|
+
devicePlatform: "ios",
|
|
28
|
+
topsdk: {
|
|
29
|
+
appId: "mock-ios-native-sample-app-id",
|
|
30
|
+
appSecret: "mock-ios-native-sample-app-secret-do-not-use-in-production",
|
|
31
|
+
},
|
|
32
|
+
sdkModules: {
|
|
33
|
+
login: {
|
|
34
|
+
guest: {},
|
|
35
|
+
facebook: {
|
|
36
|
+
clientId: "883695101201170",
|
|
37
|
+
scheme: "fb883695101201170",
|
|
38
|
+
secret: "f840b8663b1351ddcb8f6a640cee18c6",
|
|
39
|
+
name: "top-demo",
|
|
40
|
+
openMessenger: "0",
|
|
41
|
+
},
|
|
42
|
+
google: {
|
|
43
|
+
clientId: "396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql.apps.googleusercontent.com",
|
|
44
|
+
scheme: "com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql",
|
|
45
|
+
},
|
|
46
|
+
apple: {},
|
|
47
|
+
},
|
|
48
|
+
payment: { googleIap: {} },
|
|
49
|
+
analytics: {
|
|
50
|
+
appsflyer: {
|
|
51
|
+
devKey: "af-dev-key",
|
|
52
|
+
appleAppId: "123456789",
|
|
53
|
+
enableDebugLog: true,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
null,
|
|
59
|
+
2
|
|
60
|
+
),
|
|
61
|
+
"utf8"
|
|
62
|
+
);
|
|
23
63
|
return tmp;
|
|
24
64
|
}
|
|
25
65
|
|
|
@@ -14,7 +14,7 @@ import { loadBuiltInMeetSdkDefaultConfig } from "../src/config/meetSdkDefaultCon
|
|
|
14
14
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
const refPath = path.join(here, "..", "fixtures", "topsdk-config-reference.json");
|
|
16
16
|
const offlineMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.mock.json");
|
|
17
|
-
const offlineIosMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.ios-
|
|
17
|
+
const offlineIosMockPath = path.join(here, "..", "fixtures", "meetsdk-remote-config.ios-native-sample.json");
|
|
18
18
|
const downloadShapePath = path.join(here, "..", "fixtures", "meetsdk-remote-config.download-shape.json");
|
|
19
19
|
|
|
20
20
|
const mapCtx = {
|
|
@@ -150,7 +150,7 @@ android {
|
|
|
150
150
|
expect(modOut.content.indexOf(TOPSDK_PLUGIN_AUTO_START)).toBeLessThan(modOut.content.indexOf("dependencies"));
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
it("
|
|
153
|
+
it("keeps an existing apply plugin with the same id instead of duplicating or rewriting", () => {
|
|
154
154
|
const modIn = `
|
|
155
155
|
apply plugin: 'com.android.application'
|
|
156
156
|
apply plugin: 'com.google.gms.google-services'
|
|
@@ -173,7 +173,7 @@ dependencies { }
|
|
|
173
173
|
.split("\n")
|
|
174
174
|
.filter((l) => l.includes("com.google.gms.google-services"));
|
|
175
175
|
expect(pluginLines.length).toBe(1);
|
|
176
|
-
expect(modOut.content).toContain(TOPSDK_PLUGIN_AUTO_START);
|
|
176
|
+
expect(modOut.content).not.toContain(TOPSDK_PLUGIN_AUTO_START);
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it("updates classpath by group:artifact instead of duplicating", () => {
|
|
@@ -40,7 +40,10 @@ function testProjectRoot(...names: string[]): string {
|
|
|
40
40
|
return path.join(androidFixtureRoot, names[0] ?? "");
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
async function runAndroidPipelineDryRun(
|
|
43
|
+
async function runAndroidPipelineDryRun(
|
|
44
|
+
projectRoot: string,
|
|
45
|
+
opts?: { requirePackageNameInPatch?: boolean; requireRemoteValuesInPatch?: boolean; allowEmptyPatch?: boolean }
|
|
46
|
+
) {
|
|
44
47
|
const manifest = loadManifestFile(path.join(recipeFixtureRoot, "android-default.fixture.yaml"));
|
|
45
48
|
const cfg = JSON.parse(fs.readFileSync(path.join(projectRoot, "meetsdk-remote-config.json"), "utf8")) as {
|
|
46
49
|
packageName: string;
|
|
@@ -51,20 +54,32 @@ async function runAndroidPipelineDryRun(projectRoot: string, opts?: { requirePac
|
|
|
51
54
|
expect(ctx.android?.ok).toBe(true);
|
|
52
55
|
const { report, patch, binaryCopies } = await runPipeline(ctx, manifest, { dryRun: true });
|
|
53
56
|
expect(report.errors).toEqual([]);
|
|
54
|
-
|
|
57
|
+
if (!opts?.allowEmptyPatch) {
|
|
58
|
+
expect(patch.length).toBeGreaterThan(0);
|
|
59
|
+
}
|
|
55
60
|
expect(binaryCopies).toEqual([]);
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
if (!opts?.allowEmptyPatch || patch.length > 0) {
|
|
62
|
+
expect(patch).toContain("TOPSDK REPO AUTO");
|
|
63
|
+
expect(patch).toContain("TOPSDK AUTO");
|
|
64
|
+
}
|
|
58
65
|
if (opts?.requirePackageNameInPatch !== false) {
|
|
59
66
|
expect(patch).toContain(cfg.packageName);
|
|
60
67
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
if (opts?.requireRemoteValuesInPatch !== false) {
|
|
69
|
+
expect(patch).toContain(cfg.topsdk.appId);
|
|
70
|
+
expect(patch).toContain(cfg.channel);
|
|
71
|
+
expect(patch).toContain("facebook_app_id");
|
|
72
|
+
expect(patch).toContain("top_app_id");
|
|
73
|
+
}
|
|
65
74
|
return { ctx, patch };
|
|
66
75
|
}
|
|
67
76
|
|
|
77
|
+
function copyAndroidLatestToTemp(): string {
|
|
78
|
+
const tmp = fs.mkdtempSync(path.join(fs.realpathSync("/tmp"), "meet-android-idempotent-"));
|
|
79
|
+
fs.cpSync(androidLatestRoot, tmp, { recursive: true });
|
|
80
|
+
return tmp;
|
|
81
|
+
}
|
|
82
|
+
|
|
68
83
|
describe("pipeline android fixture", () => {
|
|
69
84
|
beforeEach(() => {
|
|
70
85
|
stubSdkHomeVersion();
|
|
@@ -85,11 +100,50 @@ describe("pipeline android fixture", () => {
|
|
|
85
100
|
const hasPowerRaidConfig = fs.existsSync(path.join(powerRaidRoot, "meetsdk-remote-config.json"));
|
|
86
101
|
|
|
87
102
|
it.skipIf(!hasPowerRaidConfig)("dry-run on power-raid (:launcher)", async () => {
|
|
88
|
-
const { ctx, patch } = await runAndroidPipelineDryRun(powerRaidRoot, {
|
|
103
|
+
const { ctx, patch } = await runAndroidPipelineDryRun(powerRaidRoot, {
|
|
104
|
+
requirePackageNameInPatch: false,
|
|
105
|
+
requireRemoteValuesInPatch: false,
|
|
106
|
+
allowEmptyPatch: true,
|
|
107
|
+
});
|
|
89
108
|
expect(ctx.android?.ok && ctx.android.moduleName).toBe(":launcher");
|
|
90
|
-
expect(patch).toContain("launcher/build.gradle");
|
|
91
109
|
const addedLines = patch.split("\n").filter((line) => line.startsWith("+")).join("\n");
|
|
92
110
|
expect(addedLines).not.toContain("//Firebase");
|
|
93
111
|
expect(addedLines).not.toContain("//Appsflyer");
|
|
94
112
|
});
|
|
113
|
+
|
|
114
|
+
it("apply is idempotent and logs Android value updates", async () => {
|
|
115
|
+
const tmp = copyAndroidLatestToTemp();
|
|
116
|
+
try {
|
|
117
|
+
const manifest = loadManifestFile(path.join(recipeFixtureRoot, "android-default.fixture.yaml"));
|
|
118
|
+
const buildGradlePath = path.join(tmp, "app", "build.gradle");
|
|
119
|
+
const beforeGradle = fs.readFileSync(buildGradlePath, "utf8");
|
|
120
|
+
fs.writeFileSync(
|
|
121
|
+
buildGradlePath,
|
|
122
|
+
beforeGradle.replace(
|
|
123
|
+
'versionName "1.0"',
|
|
124
|
+
`versionName "1.0"
|
|
125
|
+
resValue('string', 'top_app_id', 'old-app-id')
|
|
126
|
+
resValue('string', 'facebook_app_id', 'old-facebook-id')`
|
|
127
|
+
),
|
|
128
|
+
"utf8"
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const ctx = buildWorkspaceContext(tmp, pkgRoot);
|
|
132
|
+
const first = await runPipeline(ctx, manifest, { dryRun: false });
|
|
133
|
+
expect(first.report.errors).toEqual([]);
|
|
134
|
+
const logs = (first.report.logs ?? []).join("\n");
|
|
135
|
+
expect(logs).toContain("Android applicationId com.example.myapplication -> com.meet.integrate.androidsample");
|
|
136
|
+
expect(logs).toContain("Android resValue top_app_id string:old-app-id -> string:mock-topsdk-app-id");
|
|
137
|
+
expect(logs).toContain("Android resValue facebook_app_id string:old-facebook-id -> string:0000000000000000");
|
|
138
|
+
|
|
139
|
+
const second = await runPipeline(buildWorkspaceContext(tmp, pkgRoot), manifest, { dryRun: false });
|
|
140
|
+
expect(second.report.errors).toEqual([]);
|
|
141
|
+
const after = fs.readFileSync(buildGradlePath, "utf8");
|
|
142
|
+
expect((after.match(/resValue\('string', 'top_app_id'/g) ?? []).length).toBe(1);
|
|
143
|
+
expect((after.match(/resValue\('string', 'facebook_app_id'/g) ?? []).length).toBe(1);
|
|
144
|
+
expect((after.match(/applicationId "com.meet.integrate.androidsample"/g) ?? []).length).toBe(1);
|
|
145
|
+
} finally {
|
|
146
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
147
|
+
}
|
|
148
|
+
});
|
|
95
149
|
});
|
|
@@ -7,27 +7,63 @@ import { loadManifestFile } from "../src/config/loadManifest.js";
|
|
|
7
7
|
import { buildWorkspaceContext } from "../src/core/workspace.js";
|
|
8
8
|
import { runPipeline } from "../src/core/pipeline.js";
|
|
9
9
|
import { detectIOS } from "../src/ios/detect.js";
|
|
10
|
+
import { parsePlistXml } from "../src/ios/infoPlist.js";
|
|
10
11
|
import { resolveIosSdkRootFromDirectory } from "../src/remote/sdkHomeDownload.js";
|
|
11
12
|
|
|
12
13
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
13
14
|
const pkgRoot = path.resolve(here, "..");
|
|
14
|
-
const iosRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "
|
|
15
|
-
const iosRemoteConfigFixture = path.join(pkgRoot, "fixtures", "meetsdk-remote-config.ios-tooltest.json");
|
|
15
|
+
const iosRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "native-sample");
|
|
16
16
|
const hasIosProjectFixture = fs.existsSync(iosRoot);
|
|
17
17
|
const manifest = () => loadManifestFile(path.join(pkgRoot, "recipes", "ios-default.yaml"));
|
|
18
18
|
const fixtureIosSdkRoot = resolveIosSdkRootFromDirectory(path.join(pkgRoot, "fixtures", "ios-sdk", "topSDK-ios--V1.6.0.5"));
|
|
19
19
|
|
|
20
|
-
function writeOfflineRemoteConfig(projectRoot: string): void {
|
|
21
|
-
fs.copyFileSync(iosRemoteConfigFixture, path.join(projectRoot, "meetsdk-remote-config.json"));
|
|
22
|
-
}
|
|
23
|
-
|
|
24
20
|
function copyProjectToTemp(): string {
|
|
25
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-
|
|
21
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "meet-ios-native-sample-"));
|
|
26
22
|
fs.cpSync(iosRoot, tmp, { recursive: true });
|
|
27
|
-
|
|
23
|
+
writeNativeSampleRemoteConfig(tmp);
|
|
28
24
|
return tmp;
|
|
29
25
|
}
|
|
30
26
|
|
|
27
|
+
function writeNativeSampleRemoteConfig(projectRoot: string): void {
|
|
28
|
+
const remote = {
|
|
29
|
+
packageName: "com.meetgames.topsdk.demo",
|
|
30
|
+
channel: "APPLE",
|
|
31
|
+
devicePlatform: "ios",
|
|
32
|
+
topsdk: {
|
|
33
|
+
appId: "mock-ios-native-sample-app-id",
|
|
34
|
+
appSecret: "mock-ios-native-sample-app-secret-do-not-use-in-production",
|
|
35
|
+
},
|
|
36
|
+
sdkModules: {
|
|
37
|
+
login: {
|
|
38
|
+
guest: {},
|
|
39
|
+
facebook: {
|
|
40
|
+
clientId: "883695101201170",
|
|
41
|
+
scheme: "fb883695101201170",
|
|
42
|
+
secret: "f840b8663b1351ddcb8f6a640cee18c6",
|
|
43
|
+
name: "top-demo",
|
|
44
|
+
openMessenger: "0",
|
|
45
|
+
},
|
|
46
|
+
google: {
|
|
47
|
+
clientId: "396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql.apps.googleusercontent.com",
|
|
48
|
+
scheme: "com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql",
|
|
49
|
+
},
|
|
50
|
+
apple: {},
|
|
51
|
+
},
|
|
52
|
+
payment: {
|
|
53
|
+
googleIap: {},
|
|
54
|
+
},
|
|
55
|
+
analytics: {
|
|
56
|
+
appsflyer: {
|
|
57
|
+
devKey: "af-dev-key",
|
|
58
|
+
appleAppId: "123456789",
|
|
59
|
+
enableDebugLog: true,
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
fs.writeFileSync(path.join(projectRoot, "meetsdk-remote-config.json"), JSON.stringify(remote, null, 2), "utf8");
|
|
65
|
+
}
|
|
66
|
+
|
|
31
67
|
function pbxBuildFileRefErrors(pbx: string): string[] {
|
|
32
68
|
const fileRefs = new Set<string>();
|
|
33
69
|
for (const match of pbx.matchAll(/^\s*([A-F0-9]{24}) \/\* .* \*\/ = \{isa = PBXFileReference;/gm)) {
|
|
@@ -48,6 +84,14 @@ function plannedIosAsset(patch: string, binaryCopies: Array<{ relTo: string }>,
|
|
|
48
84
|
return patch.includes(name) || binaryCopies.some((c) => c.relTo.includes(name));
|
|
49
85
|
}
|
|
50
86
|
|
|
87
|
+
function occurrenceCount(content: string, needle: string): number {
|
|
88
|
+
return content.split(needle).length - 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function regexCount(content: string, pattern: RegExp): number {
|
|
92
|
+
return [...content.matchAll(pattern)].length;
|
|
93
|
+
}
|
|
94
|
+
|
|
51
95
|
function testIosContext(projectRoot: string) {
|
|
52
96
|
return buildWorkspaceContext(projectRoot, pkgRoot, { iosSdkRoot: fixtureIosSdkRoot });
|
|
53
97
|
}
|
|
@@ -60,29 +104,29 @@ describe("pipeline ios fixture SDK", () => {
|
|
|
60
104
|
});
|
|
61
105
|
|
|
62
106
|
describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
63
|
-
it("detects
|
|
107
|
+
it("detects native-sample xcodeproj with AppDelegate target", () => {
|
|
64
108
|
const d = detectIOS(iosRoot);
|
|
65
109
|
expect(d.ok).toBe(true);
|
|
66
110
|
if (d.ok) {
|
|
67
|
-
expect(d.targetName).toBe("
|
|
68
|
-
expect(fs.existsSync(path.join(iosRoot, "
|
|
111
|
+
expect(d.targetName).toBe("native-sample");
|
|
112
|
+
expect(fs.existsSync(path.join(iosRoot, "native-sample", "AppDelegate.m"))).toBe(true);
|
|
69
113
|
}
|
|
70
114
|
});
|
|
71
115
|
|
|
72
116
|
it("detects when project root is the xcodeproj directory itself", () => {
|
|
73
|
-
const xcodeprojRoot = path.join(iosRoot, "
|
|
117
|
+
const xcodeprojRoot = path.join(iosRoot, "native-sample.xcodeproj");
|
|
74
118
|
const d = detectIOS(xcodeprojRoot);
|
|
75
119
|
expect(d.ok).toBe(true);
|
|
76
120
|
if (d.ok) {
|
|
77
121
|
expect(d.xcodeprojPath).toBe(xcodeprojRoot);
|
|
78
|
-
expect(d.targetName).toBe("
|
|
122
|
+
expect(d.targetName).toBe("native-sample");
|
|
79
123
|
}
|
|
80
124
|
});
|
|
81
125
|
|
|
82
126
|
it("builds workspace context when project root is the xcodeproj directory", () => {
|
|
83
|
-
const ctx = testIosContext(path.join(iosRoot, "
|
|
127
|
+
const ctx = testIosContext(path.join(iosRoot, "native-sample.xcodeproj"));
|
|
84
128
|
expect(ctx.ios?.ok).toBe(true);
|
|
85
|
-
expect(ctx.ios?.ok && ctx.ios.xcodeprojPath).toBe(path.join(iosRoot, "
|
|
129
|
+
expect(ctx.ios?.ok && ctx.ios.xcodeprojPath).toBe(path.join(iosRoot, "native-sample.xcodeproj"));
|
|
86
130
|
});
|
|
87
131
|
|
|
88
132
|
it("dry-run: plans TOPCore + GuestSignin frameworks and pbxproj edits", async () => {
|
|
@@ -114,31 +158,85 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
114
158
|
expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGuestSigninPlugin.framework"))).toBe(true);
|
|
115
159
|
expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
|
|
116
160
|
|
|
117
|
-
const pbx = fs.readFileSync(path.join(tmp, "
|
|
161
|
+
const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
|
|
118
162
|
expect(pbx).toContain("TOPCore.framework");
|
|
119
163
|
expect(pbx).toContain("TOPGuestSigninPlugin.framework");
|
|
120
164
|
expect(pbx).toContain('"-ObjC"');
|
|
121
|
-
expect(pbx).toContain(
|
|
165
|
+
expect(pbx).toContain("libc++.tbd");
|
|
122
166
|
expect(pbx).not.toMatch(/\n\s+-ObjC,/);
|
|
123
|
-
expect(pbx).
|
|
124
|
-
expect(pbxBuildFileRefErrors(pbx)).toEqual([]);
|
|
167
|
+
expect(pbxBuildFileRefErrors(pbx).filter((e) => /TOP|FBSDK|Google|AppsFlyer|TopSDKInstall/.test(e))).toEqual([]);
|
|
125
168
|
expect(pbx).not.toMatch(/\bundefined;/);
|
|
126
169
|
|
|
127
|
-
const delegate = fs.readFileSync(path.join(tmp, "
|
|
170
|
+
const delegate = fs.readFileSync(path.join(tmp, "native-sample", "AppDelegate.m"), "utf8");
|
|
128
171
|
expect(delegate).toContain("#import <TOPSDK/TopSDK.h>");
|
|
129
172
|
expect(delegate).toContain("TopSDK.sharedInstance");
|
|
130
173
|
expect(delegate).toContain("TOPDataSDK");
|
|
131
174
|
|
|
132
|
-
const plist = fs.readFileSync(path.join(tmp, "
|
|
175
|
+
const plist = fs.readFileSync(path.join(tmp, "native-sample", "Info.plist"), "utf8");
|
|
133
176
|
expect(plist).toContain("<key>TOPSDK</key>");
|
|
134
177
|
expect(plist).toContain("<key>APP_ID</key>");
|
|
135
|
-
expect(plist).toContain("mock-ios-
|
|
178
|
+
expect(plist).toContain("mock-ios-native-sample-app-id");
|
|
136
179
|
expect(plist).toContain("GuestSignin");
|
|
137
180
|
} finally {
|
|
138
181
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
139
182
|
}
|
|
140
183
|
});
|
|
141
184
|
|
|
185
|
+
it("apply: is idempotent and logs iOS value updates", async () => {
|
|
186
|
+
const tmp = copyProjectToTemp();
|
|
187
|
+
try {
|
|
188
|
+
const plistPath = path.join(tmp, "native-sample", "Info.plist");
|
|
189
|
+
const beforePlist = fs.readFileSync(plistPath, "utf8");
|
|
190
|
+
fs.writeFileSync(
|
|
191
|
+
plistPath,
|
|
192
|
+
beforePlist
|
|
193
|
+
.replace("<key>FacebookAppID</key>\n\t<string>883695101201170</string>", "<key>FacebookAppID</key>\n\t<string>old-facebook-app-id</string>")
|
|
194
|
+
.replace(
|
|
195
|
+
"</dict>\n</plist>",
|
|
196
|
+
"\t<key>LSApplicationQueriesSchemes</key>\n\t<array>\n\t\t<string>fbapi</string>\n\t</array>\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleTypeRole</key>\n\t\t\t<string>Editor</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>fb883695101201170</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>"
|
|
197
|
+
),
|
|
198
|
+
"utf8"
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const first = await runPipeline(testIosContext(tmp), manifest(), { dryRun: false });
|
|
202
|
+
expect(first.report.errors).toEqual([]);
|
|
203
|
+
const firstLogs = (first.report.logs ?? []).join("\n");
|
|
204
|
+
expect(firstLogs).toContain("SUCCESS: info.plist配置key:FacebookAppID old-facebook-app-id -> 883695101201170");
|
|
205
|
+
|
|
206
|
+
const second = await runPipeline(testIosContext(tmp), manifest(), { dryRun: false });
|
|
207
|
+
expect(second.report.errors).toEqual([]);
|
|
208
|
+
const secondLogs = (second.report.logs ?? []).join("\n");
|
|
209
|
+
expect(secondLogs).toContain("SUCCESS: info.plist配置key:FacebookAppID 未变化 value:883695101201170");
|
|
210
|
+
expect(secondLogs).toContain("SUCCESS: BuildSetting设置VALIDATE_WORKSPACE 未变化 value:YES");
|
|
211
|
+
|
|
212
|
+
const plist = fs.readFileSync(plistPath, "utf8");
|
|
213
|
+
const plistData = parsePlistXml(plist);
|
|
214
|
+
const queries = Array.isArray(plistData.LSApplicationQueriesSchemes)
|
|
215
|
+
? (plistData.LSApplicationQueriesSchemes as string[])
|
|
216
|
+
: [];
|
|
217
|
+
const urlTypes = Array.isArray(plistData.CFBundleURLTypes)
|
|
218
|
+
? (plistData.CFBundleURLTypes as Array<Record<string, unknown>>)
|
|
219
|
+
: [];
|
|
220
|
+
const urlSchemes = urlTypes.flatMap((entry) =>
|
|
221
|
+
Array.isArray(entry.CFBundleURLSchemes) ? (entry.CFBundleURLSchemes as string[]) : []
|
|
222
|
+
);
|
|
223
|
+
expect(occurrenceCount(plist, "<key>FacebookAppID</key>")).toBe(1);
|
|
224
|
+
expect(occurrenceCount(plist, "<key>FacebookClientToken</key>")).toBe(1);
|
|
225
|
+
expect(queries.filter((scheme) => scheme === "fbapi")).toHaveLength(1);
|
|
226
|
+
expect(urlSchemes.filter((scheme) => scheme === "fb883695101201170")).toHaveLength(1);
|
|
227
|
+
expect(urlSchemes.filter((scheme) => scheme === "com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql")).toHaveLength(1);
|
|
228
|
+
expect(occurrenceCount(plist, "<key>TOPSDK</key>")).toBe(1);
|
|
229
|
+
|
|
230
|
+
const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
|
|
231
|
+
expect(regexCount(pbx, /\/\* TopSDKInstall\.swift in Sources \*\/ = \{isa = PBXBuildFile;/g)).toBe(1);
|
|
232
|
+
expect(regexCount(pbx, /\/\* TOPFacebookSigninPlugin\.framework in Frameworks \*\/ = \{isa = PBXBuildFile;/g)).toBe(1);
|
|
233
|
+
expect(regexCount(pbx, /\/\* TOPGoogleSigninPlugin\.framework in Frameworks \*\/ = \{isa = PBXBuildFile;/g)).toBe(1);
|
|
234
|
+
expect(occurrenceCount(pbx, "VALIDATE_WORKSPACE = YES")).toBe(2);
|
|
235
|
+
} finally {
|
|
236
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
142
240
|
it("apply: enables Apple Sign In entitlement when AppleSignin plugin is enabled", async () => {
|
|
143
241
|
const tmp = copyProjectToTemp();
|
|
144
242
|
try {
|
|
@@ -155,15 +253,15 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
155
253
|
const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
|
|
156
254
|
expect(report.errors).toEqual([]);
|
|
157
255
|
|
|
158
|
-
const entitlementsPath = path.join(tmp, "
|
|
256
|
+
const entitlementsPath = path.join(tmp, "native-sample", "native-sample.entitlements");
|
|
159
257
|
expect(fs.existsSync(entitlementsPath)).toBe(true);
|
|
160
258
|
const entitlements = fs.readFileSync(entitlementsPath, "utf8");
|
|
161
259
|
expect(entitlements).toContain("com.apple.developer.applesignin");
|
|
162
260
|
expect(entitlements).toContain("Default");
|
|
163
261
|
|
|
164
|
-
const pbx = fs.readFileSync(path.join(tmp, "
|
|
262
|
+
const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
|
|
165
263
|
expect(pbx).toContain("CODE_SIGN_ENTITLEMENTS");
|
|
166
|
-
expect(pbx).toContain("
|
|
264
|
+
expect(pbx).toContain("native-sample/native-sample.entitlements");
|
|
167
265
|
expect(pbx).toContain("TOPAppleSigninPlugin.framework");
|
|
168
266
|
} finally {
|
|
169
267
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
@@ -296,7 +394,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
296
394
|
const { report, patch, binaryCopies } = await runPipeline(ctx, manifest(), { dryRun: true });
|
|
297
395
|
|
|
298
396
|
expect(report.errors).toEqual([]);
|
|
299
|
-
expect(report.warnings.join("\n")).toContain("would download GoogleService-Info.plist to
|
|
397
|
+
expect(report.warnings.join("\n")).toContain("would download GoogleService-Info.plist to native-sample/GoogleService-Info.plist");
|
|
300
398
|
expect(plannedIosAsset(patch, binaryCopies, "TOPDataFirebasePlugin.framework")).toBe(true);
|
|
301
399
|
} finally {
|
|
302
400
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
@@ -307,7 +405,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
307
405
|
const tmp = copyProjectToTemp();
|
|
308
406
|
try {
|
|
309
407
|
fs.writeFileSync(
|
|
310
|
-
path.join(tmp, "
|
|
408
|
+
path.join(tmp, "native-sample", "AppDelegate.m"),
|
|
311
409
|
`#import "AppDelegate.h"
|
|
312
410
|
|
|
313
411
|
@implementation AppDelegate
|
|
@@ -321,7 +419,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
321
419
|
"utf8"
|
|
322
420
|
);
|
|
323
421
|
fs.writeFileSync(
|
|
324
|
-
path.join(tmp, "
|
|
422
|
+
path.join(tmp, "native-sample", "SceneDelegate.m"),
|
|
325
423
|
`#import "SceneDelegate.h"
|
|
326
424
|
|
|
327
425
|
@implementation SceneDelegate
|
|
@@ -335,7 +433,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
335
433
|
const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
|
|
336
434
|
expect(report.errors).toEqual([]);
|
|
337
435
|
|
|
338
|
-
const appDelegate = fs.readFileSync(path.join(tmp, "
|
|
436
|
+
const appDelegate = fs.readFileSync(path.join(tmp, "native-sample", "AppDelegate.m"), "utf8");
|
|
339
437
|
expect(appDelegate).toContain("[TopSDK.sharedInstance application:application didFinishLaunchingWithOptions:launchOptions]");
|
|
340
438
|
expect(appDelegate).toContain("[TOPDataSDK application:application didFinishLaunchingWithOptions:launchOptions]");
|
|
341
439
|
expect(appDelegate).not.toContain("openURL:url options");
|
|
@@ -343,7 +441,7 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
343
441
|
expect(appDelegate).not.toContain("applicationDidEnterBackground");
|
|
344
442
|
expect(appDelegate).not.toContain("applicationWillEnterForeground");
|
|
345
443
|
|
|
346
|
-
const sceneDelegate = fs.readFileSync(path.join(tmp, "
|
|
444
|
+
const sceneDelegate = fs.readFileSync(path.join(tmp, "native-sample", "SceneDelegate.m"), "utf8");
|
|
347
445
|
expect(sceneDelegate).toContain("[TopSDK.sharedInstance scene:scene openURLContexts:URLContexts]");
|
|
348
446
|
expect(sceneDelegate).toContain("[TopSDK.sharedInstance scene:scene continueUserActivity:userActivity]");
|
|
349
447
|
expect(sceneDelegate).toContain("[TopSDK.sharedInstance sceneWillEnterForeground:scene]");
|
|
@@ -356,12 +454,12 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
356
454
|
it("fails when the configured Info.plist file is missing", async () => {
|
|
357
455
|
const tmp = copyProjectToTemp();
|
|
358
456
|
try {
|
|
359
|
-
fs.rmSync(path.join(tmp, "
|
|
457
|
+
fs.rmSync(path.join(tmp, "native-sample", "Info.plist"));
|
|
360
458
|
|
|
361
459
|
const ctx = testIosContext(tmp);
|
|
362
460
|
const { report } = await runPipeline(ctx, manifest(), { dryRun: true });
|
|
363
461
|
|
|
364
|
-
expect(report.errors.join("\n")).toContain("Info.plist not found for iOS target
|
|
462
|
+
expect(report.errors.join("\n")).toContain("Info.plist not found for iOS target native-sample");
|
|
365
463
|
} finally {
|
|
366
464
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
367
465
|
}
|
|
@@ -390,3 +488,92 @@ describe.skipIf(!hasIosProjectFixture)("pipeline ios fixture", () => {
|
|
|
390
488
|
}
|
|
391
489
|
});
|
|
392
490
|
});
|
|
491
|
+
|
|
492
|
+
describe.skipIf(!hasIosProjectFixture)("pipeline ios native-sample parity", () => {
|
|
493
|
+
it("apply: mirrors topsdk-tool-ios logs and integration points for native-sample", async () => {
|
|
494
|
+
const tmp = copyProjectToTemp();
|
|
495
|
+
try {
|
|
496
|
+
const ctx = testIosContext(tmp);
|
|
497
|
+
const { report } = await runPipeline(ctx, manifest(), { dryRun: false });
|
|
498
|
+
expect(report.errors).toEqual([]);
|
|
499
|
+
|
|
500
|
+
const logs = (report.logs ?? []).join("\n");
|
|
501
|
+
expect(logs).toContain(`*** 项目根目录:${tmp} ***`);
|
|
502
|
+
expect(logs).toContain("- 插件:GuestSignin, 类型:1,版本:1.6.0.3");
|
|
503
|
+
expect(logs).toContain("SUCCESS: 插件UI接入完成");
|
|
504
|
+
expect(logs).toContain("SUCCESS: 导入三方依赖framework TOPFacebookSigninPlugin.framework 完成");
|
|
505
|
+
expect(logs).toContain("SUCCESS: BuildSetting设置VALIDATE_WORKSPACE为YES 完成");
|
|
506
|
+
expect(logs).toContain("SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成");
|
|
507
|
+
expect(logs).toContain("SUCCESS: 启用SigninWithApple完成");
|
|
508
|
+
expect(logs).toContain("检测到工程UIApplicationDelegate实现类,将自动接入对应接口");
|
|
509
|
+
expect(logs).toContain("已存在代码#import <TOPSDK/TopSDK.h>");
|
|
510
|
+
expect(logs).toContain("检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口");
|
|
511
|
+
expect(logs).toContain("SUCCESS: info.plist配置写入完成");
|
|
512
|
+
expect(logs).toContain("!! 接入流程已全部结束 !!");
|
|
513
|
+
const orderedLogMarkers = [
|
|
514
|
+
"- 插件:GuestSignin, 类型:1,版本:1.6.0.3",
|
|
515
|
+
"SUCCESS: 插件GuestSignin接入完成",
|
|
516
|
+
"- 插件:UI, 类型:3,版本:1.6.0.3",
|
|
517
|
+
"SUCCESS: 插件UI接入完成",
|
|
518
|
+
"- 插件:IAPPay, 类型:2,版本:1.6.0.3",
|
|
519
|
+
"SUCCESS: 插件IAPPay接入完成",
|
|
520
|
+
"- 插件:AppsFlyerManager, 类型:4,版本:1.6.0.3",
|
|
521
|
+
"SUCCESS: info.plist添加配置key:NSAdvertisingAttributionReportEndpoint value:https://appsflyer-skadnetwork.com/ 完成",
|
|
522
|
+
"SUCCESS: 插件AppsFlyerManager接入完成",
|
|
523
|
+
"- 插件:FacebookSignin, 类型:1,版本:1.6.0.3",
|
|
524
|
+
"SUCCESS: 未检测到Swift代码文件,自动添加TopSDKInstall.swift完成",
|
|
525
|
+
"SUCCESS: 添加queriesSchemes:fbapi 完成",
|
|
526
|
+
"SUCCESS: 设置urlScheme:fb883695101201170完成",
|
|
527
|
+
"SUCCESS: 插件FacebookSignin接入完成",
|
|
528
|
+
"- 插件:GoogleSignin, 类型:1,版本:1.6.0.3",
|
|
529
|
+
"SUCCESS: 插件GoogleSignin接入完成",
|
|
530
|
+
"- 插件:AppleSignin, 类型:1,版本:1.6.0.3",
|
|
531
|
+
"SUCCESS: 启用SigninWithApple完成",
|
|
532
|
+
"SUCCESS: 插件AppleSignin接入完成",
|
|
533
|
+
"- 插件:TOPSDK, 类型:0,版本:1.6.0.3",
|
|
534
|
+
"检测到工程UIApplicationDelegate实现类,将自动接入对应接口",
|
|
535
|
+
"检测到工程存在UIWindowSceneDelegate实现类,将自动接入对应接口",
|
|
536
|
+
"SUCCESS: 插件TOPSDK接入完成",
|
|
537
|
+
"SUCCESS: info.plist配置写入完成",
|
|
538
|
+
];
|
|
539
|
+
let lastLogIndex = -1;
|
|
540
|
+
for (const marker of orderedLogMarkers) {
|
|
541
|
+
const index = logs.indexOf(marker);
|
|
542
|
+
expect(index, marker).toBeGreaterThan(lastLogIndex);
|
|
543
|
+
lastLogIndex = index;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
expect(fs.existsSync(path.join(tmp, "topSDK", "TOPUIPlugin.framework"))).toBe(true);
|
|
547
|
+
expect(fs.existsSync(path.join(tmp, "topSDK", "TOPFacebookSigninPlugin.framework"))).toBe(true);
|
|
548
|
+
expect(fs.existsSync(path.join(tmp, "topSDK", "TOPGoogleSigninPlugin.framework"))).toBe(true);
|
|
549
|
+
expect(fs.existsSync(path.join(tmp, "topSDK", "TOPAppleSigninPlugin.framework"))).toBe(true);
|
|
550
|
+
expect(fs.existsSync(path.join(tmp, "topSDK", "TopSDKSource.bundle"))).toBe(true);
|
|
551
|
+
expect(fs.existsSync(path.join(tmp, "native-sample", "TopSDKInstall.swift"))).toBe(true);
|
|
552
|
+
|
|
553
|
+
const pbx = fs.readFileSync(path.join(tmp, "native-sample.xcodeproj", "project.pbxproj"), "utf8");
|
|
554
|
+
expect(pbx).toContain("TOPUIPlugin.framework");
|
|
555
|
+
expect(pbx).toContain("TOPFacebookSigninPlugin.framework");
|
|
556
|
+
expect(pbx).toContain("TopSDKInstall.swift");
|
|
557
|
+
expect(pbx).toContain("VALIDATE_WORKSPACE = YES");
|
|
558
|
+
expect(pbx).toContain("ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES");
|
|
559
|
+
expect(pbx).toContain("CODE_SIGN_ENTITLEMENTS");
|
|
560
|
+
expect(pbxBuildFileRefErrors(pbx).filter((e) => /TOP|FBSDK|Google|AppsFlyer|TopSDKInstall/.test(e))).toEqual([]);
|
|
561
|
+
|
|
562
|
+
const plist = fs.readFileSync(path.join(tmp, "native-sample", "Info.plist"), "utf8");
|
|
563
|
+
expect(plist).toContain("<key>FacebookAppID</key>");
|
|
564
|
+
expect(plist).toContain("883695101201170");
|
|
565
|
+
expect(plist).toContain("<key>FacebookDisplayName</key>");
|
|
566
|
+
expect(plist).toContain("top-demo");
|
|
567
|
+
expect(plist).toContain("<key>FacebookClientToken</key>");
|
|
568
|
+
expect(plist).toContain("fbapi20130214");
|
|
569
|
+
expect(plist).toContain("com.googleusercontent.apps.396842465987-8sg3ngohnl5f2r8no5etu401ruv6snql");
|
|
570
|
+
expect(plist).toContain("<key>TOPSDK</key>");
|
|
571
|
+
|
|
572
|
+
const entitlements = fs.readFileSync(path.join(tmp, "native-sample", "native-sample.entitlements"), "utf8");
|
|
573
|
+
expect(entitlements).toContain("com.apple.developer.applesignin");
|
|
574
|
+
expect(entitlements).toContain("Default");
|
|
575
|
+
} finally {
|
|
576
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
577
|
+
}
|
|
578
|
+
});
|
|
579
|
+
});
|
|
@@ -11,7 +11,7 @@ import type { Manifest } from "../src/contracts/types.js";
|
|
|
11
11
|
|
|
12
12
|
const pkgRoot = path.resolve(__dirname, "..");
|
|
13
13
|
const androidLatestRoot = path.join(pkgRoot, "fixtures", "android-test-project", "android-latest-project");
|
|
14
|
-
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "
|
|
14
|
+
const iosProjectRoot = path.join(pkgRoot, "fixtures", "ios-test-project", "native-sample");
|
|
15
15
|
const hasIosProjectFixture = fs.existsSync(iosProjectRoot);
|
|
16
16
|
|
|
17
17
|
describe("platform selection", () => {
|
|
@@ -58,11 +58,11 @@ describe("platform selection", () => {
|
|
|
58
58
|
|
|
59
59
|
it.skipIf(!hasIosProjectFixture)("overrides detected iOS target name when explicitly provided", () => {
|
|
60
60
|
const ctx = buildWorkspaceContext(iosProjectRoot, pkgRoot, {
|
|
61
|
-
appTarget: "
|
|
61
|
+
appTarget: "native-sample",
|
|
62
62
|
});
|
|
63
63
|
|
|
64
|
-
expect(ctx.ios?.ok && ctx.ios.targetName).toBe("
|
|
65
|
-
expect(ctx.ios?.ok && ctx.ios.targetNames).toContain("
|
|
64
|
+
expect(ctx.ios?.ok && ctx.ios.targetName).toBe("native-sample");
|
|
65
|
+
expect(ctx.ios?.ok && ctx.ios.targetNames).toContain("native-sample");
|
|
66
66
|
});
|
|
67
67
|
|
|
68
68
|
it.skipIf(!hasIosProjectFixture)("rejects missing iOS app target names during detection", () => {
|