@leonxin/meetgames 0.1.8 → 0.1.12
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/.agents/skills/meet-sdk-regression/SKILL.md +93 -0
- package/.cursor/mcp.example.json +16 -0
- package/.cursor/mcp.json +11 -0
- package/.cursor/skills/meetgames-mcp/SKILL.md +48 -0
- package/.vite/vitest/results.json +1 -0
- package/README.md +36 -13
- package/{meetsdk-android.json → config/meetsdk-android.json} +2 -1
- package/config/meetsdk-ios.json +15 -0
- package/dist/android/adapter.d.ts.map +1 -1
- package/dist/android/adapter.js +2 -2
- package/dist/android/adapter.js.map +1 -1
- package/dist/android/detect.d.ts +2 -2
- package/dist/android/detect.d.ts.map +1 -1
- package/dist/android/detect.js +36 -8
- package/dist/android/detect.js.map +1 -1
- package/dist/cache.d.ts +44 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +101 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +181 -49
- package/dist/cli.js.map +1 -1
- package/dist/config/meetSdkDefaultConfig.d.ts +19 -2
- package/dist/config/meetSdkDefaultConfig.d.ts.map +1 -1
- package/dist/config/meetSdkDefaultConfig.js +69 -6
- package/dist/config/meetSdkDefaultConfig.js.map +1 -1
- package/dist/config/meetSdkIosConfig.d.ts +21 -0
- package/dist/config/meetSdkIosConfig.d.ts.map +1 -0
- package/dist/config/meetSdkIosConfig.js +68 -0
- package/dist/config/meetSdkIosConfig.js.map +1 -0
- package/dist/config/meetSdkRemoteConfig.d.ts +19 -1
- package/dist/config/meetSdkRemoteConfig.d.ts.map +1 -1
- package/dist/config/meetSdkRemoteConfig.js +64 -25
- package/dist/config/meetSdkRemoteConfig.js.map +1 -1
- package/dist/contracts/types.d.ts +27 -6
- package/dist/contracts/types.d.ts.map +1 -1
- package/dist/core/doctor.d.ts +17 -0
- package/dist/core/doctor.d.ts.map +1 -0
- package/dist/core/doctor.js +444 -0
- package/dist/core/doctor.js.map +1 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +0 -15
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/platform.d.ts +12 -0
- package/dist/core/platform.d.ts.map +1 -0
- package/dist/core/platform.js +40 -0
- package/dist/core/platform.js.map +1 -0
- package/dist/core/previewPatches.d.ts +1 -1
- package/dist/core/previewPatches.js +2 -2
- package/dist/core/previewPatches.js.map +1 -1
- package/dist/core/reporter.js +1 -1
- package/dist/core/reporter.js.map +1 -1
- package/dist/core/workspace.d.ts +2 -2
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +6 -5
- package/dist/core/workspace.js.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/ios/channelConfig.d.ts +1 -0
- package/dist/ios/channelConfig.d.ts.map +1 -1
- package/dist/ios/channelConfig.js +82 -0
- 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 +11 -2
- package/dist/ios/codeUtils.js.map +1 -1
- package/dist/ios/detect.d.ts +2 -2
- package/dist/ios/detect.d.ts.map +1 -1
- package/dist/ios/detect.js +49 -10
- package/dist/ios/detect.js.map +1 -1
- package/dist/ios/entitlements.d.ts +4 -0
- package/dist/ios/entitlements.d.ts.map +1 -0
- package/dist/ios/entitlements.js +53 -0
- package/dist/ios/entitlements.js.map +1 -0
- package/dist/ios/fileManager.d.ts.map +1 -1
- package/dist/ios/fileManager.js +3 -2
- package/dist/ios/fileManager.js.map +1 -1
- package/dist/ios/infoPlist.d.ts +1 -1
- package/dist/ios/infoPlist.d.ts.map +1 -1
- package/dist/ios/infoPlist.js.map +1 -1
- package/dist/ios/integrate.d.ts.map +1 -1
- package/dist/ios/integrate.js +214 -39
- package/dist/ios/integrate.js.map +1 -1
- package/dist/ios/pbxprojEditor.d.ts +2 -0
- package/dist/ios/pbxprojEditor.d.ts.map +1 -1
- package/dist/ios/pbxprojEditor.js +250 -6
- package/dist/ios/pbxprojEditor.js.map +1 -1
- package/dist/ios/pluginConfig.d.ts +1 -0
- package/dist/ios/pluginConfig.d.ts.map +1 -1
- package/dist/ios/pluginConfig.js +36 -4
- package/dist/ios/pluginConfig.js.map +1 -1
- package/dist/ios/sdkBundle.d.ts +1 -6
- package/dist/ios/sdkBundle.d.ts.map +1 -1
- package/dist/ios/sdkBundle.js +47 -17
- package/dist/ios/sdkBundle.js.map +1 -1
- package/dist/ios/template.d.ts +1 -0
- package/dist/ios/template.d.ts.map +1 -1
- package/dist/ios/template.js +14 -1
- package/dist/ios/template.js.map +1 -1
- package/dist/ios/types.d.ts +2 -2
- package/dist/ios/types.d.ts.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +22 -15
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/service.d.ts +11 -6
- package/dist/mcp/service.d.ts.map +1 -1
- package/dist/mcp/service.js +61 -18
- package/dist/mcp/service.js.map +1 -1
- package/dist/ops/handlers.d.ts.map +1 -1
- package/dist/ops/handlers.js +34 -23
- package/dist/ops/handlers.js.map +1 -1
- package/dist/remote/sdkHomeDownload.d.ts +65 -0
- package/dist/remote/sdkHomeDownload.d.ts.map +1 -0
- package/dist/remote/sdkHomeDownload.js +229 -0
- package/dist/remote/sdkHomeDownload.js.map +1 -0
- package/dist/remote/topsdkDownloadSdkConfig.d.ts.map +1 -1
- package/dist/remote/topsdkDownloadSdkConfig.js +11 -1
- package/dist/remote/topsdkDownloadSdkConfig.js.map +1 -1
- package/dist/shared/errors.d.ts +7 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +16 -0
- package/dist/shared/errors.js.map +1 -0
- package/docs/API.md +246 -32
- package/docs/CLI.md +291 -0
- package/docs/INTEGRATION.md +116 -0
- package/docs/MCP.md +86 -0
- package/docs/README.md +18 -10
- package/docs/{api → archive/api}/downloadSDKConfig.md +9 -7
- package/docs/{api → archive/api}/getChannelConfig-meetgames.md +2 -2
- package/docs/archive/ios-migration.md +2139 -0
- package/docs/{ → archive/product/}/346/212/200/346/234/257/346/226/271/346/241/210/350/260/203/347/240/224.md +7 -7
- package/docs/{ → archive/product/}/351/234/200/346/261/202/346/226/207/346/241/243.md +16 -15
- package/package.json +7 -36
- package/recipes/android-default.yaml +0 -5
- package/recipes/integrate-default.yaml +0 -5
- package/src/android/adapter.ts +9 -0
- package/src/android/assembleIntegrationJson.ts +33 -0
- package/src/android/detect.ts +132 -0
- package/src/android/downloadGoogleServicesJson.ts +56 -0
- package/src/android/gradle.ts +116 -0
- package/src/android/manifest.ts +50 -0
- package/src/android/meetSdkRemoteGradle.ts +837 -0
- package/src/cache.ts +164 -0
- package/src/cli.ts +496 -0
- package/src/config/fetchConfigWrite.ts +30 -0
- package/src/config/loadAndroidIntegration.ts +41 -0
- package/src/config/loadManifest.ts +40 -0
- package/src/config/meetSdkDefaultConfig.ts +100 -0
- package/src/config/meetSdkIosConfig.ts +89 -0
- package/src/config/meetSdkRemoteConfig.ts +1215 -0
- package/src/config/topsdkFeatureModules.ts +92 -0
- package/src/contracts/types.ts +129 -0
- package/src/core/doctor.ts +485 -0
- package/src/core/patch.ts +64 -0
- package/src/core/pipeline.ts +107 -0
- package/src/core/platform.ts +47 -0
- package/src/core/previewPatches.ts +23 -0
- package/src/core/reporter.ts +24 -0
- package/src/core/workspace.ts +31 -0
- package/src/entry.ts +7 -0
- package/src/index.ts +140 -0
- package/src/ios/channelConfig.ts +128 -0
- package/src/ios/codeUtils.ts +160 -0
- package/src/ios/detect.ts +105 -0
- package/src/ios/entitlements.ts +61 -0
- package/src/ios/fileManager.ts +48 -0
- package/src/ios/infoPlist.ts +55 -0
- package/src/ios/integrate.ts +517 -0
- package/src/ios/pbxprojEditor.ts +450 -0
- package/src/ios/pluginConfig.ts +97 -0
- package/src/ios/reserved.ts +8 -0
- package/src/ios/sdkBundle.ts +59 -0
- package/src/ios/template.ts +36 -0
- package/src/ios/types.ts +65 -0
- package/src/mcp/server.ts +176 -0
- package/src/mcp/service.ts +253 -0
- package/src/mcp-entry.ts +7 -0
- package/src/ops/fileStore.ts +56 -0
- package/src/ops/handlers.ts +308 -0
- package/src/remote/fetchJson.ts +22 -0
- package/src/remote/sdkHomeDownload.ts +295 -0
- package/src/remote/topsdkDownloadSdkConfig.ts +93 -0
- package/src/remote/topsdkGetSdkConfig.ts +122 -0
- package/src/remote/topsdkSign.ts +10 -0
- package/src/shared/errors.ts +16 -0
- package/tests/assemble.test.ts +12 -0
- package/tests/doctor.test.ts +91 -0
- package/tests/downloadGoogleServicesJson.test.ts +47 -0
- package/tests/fetch-remote.test.ts +23 -0
- package/tests/fetchConfigOverrides.test.ts +28 -0
- package/tests/fetchConfigWrite.test.ts +54 -0
- package/tests/fixtures-hosts.test.ts +78 -0
- package/tests/gradle.test.ts +33 -0
- package/tests/integration-json.test.ts +29 -0
- package/tests/ios.codeUtils.test.ts +23 -0
- package/tests/ios.sdkBundle.test.ts +21 -0
- package/tests/loadManifest.test.ts +15 -0
- package/tests/manifest-xml.test.ts +30 -0
- package/tests/mcp.e2e.ts +214 -0
- package/tests/mcp.service.test.ts +53 -0
- package/tests/meetSdkRemoteConfig.test.ts +481 -0
- package/tests/meetSdkRemoteGradle.test.ts +414 -0
- package/tests/pipeline.android.test.ts +95 -0
- package/tests/pipeline.integration-json.test.ts +58 -0
- package/tests/pipeline.ios.test.ts +392 -0
- package/tests/pipeline.preview.patch.test.ts +85 -0
- package/tests/platformSelection.test.ts +77 -0
- package/tests/sdkHomeDownload.test.ts +124 -0
- package/tests/sdkVersionConfig.test.ts +131 -0
- package/tests/topsdk.test.ts +53 -0
- package/tests/topsdkDownloadSdkConfig.test.ts +81 -0
- package/tests/topsdkFeatureModules.test.ts +116 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +9 -0
- package/vitest.mcp.config.ts +11 -0
- package/bundled/android/sample.txt +0 -1
- package/docs/ANDROID.md +0 -133
- package/docs/CURSOR-MCP-SETUP.md +0 -72
- package/docs/MCP-SKILL.md +0 -63
- package/fixtures/api-samples/getChannelConfig-meetgames.sample.json +0 -123
- package/fixtures/meetsdk-remote-config.download-shape.json +0 -20
- package/fixtures/meetsdk-remote-config.mock.json +0 -69
- package/fixtures/recipes/android-default.fixture.yaml +0 -15
- package/fixtures/recipes/android-integration.fixture.json +0 -29
- package/fixtures/topsdk-config-reference.json +0 -39
- /package/docs/{api → archive/api}/getSDKConfig.md +0 -0
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import type { TextFileStore } from "../ops/fileStore.js";
|
|
6
|
+
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
8
|
+
type XcodeProject = any;
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const xcode = require("xcode") as {
|
|
12
|
+
project: (pbxprojPath: string) => XcodeProject;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export interface PbxContext {
|
|
16
|
+
rel: string;
|
|
17
|
+
proj: XcodeProject;
|
|
18
|
+
srcRoot: string;
|
|
19
|
+
targetName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function parseProjectFromContent(content: string): Promise<XcodeProject> {
|
|
23
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "meet-pbx-"));
|
|
24
|
+
const pbxprojPath = path.join(dir, "project.pbxproj");
|
|
25
|
+
fs.writeFileSync(pbxprojPath, content, "utf8");
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const proj = xcode.project(pbxprojPath);
|
|
28
|
+
proj.parse((err: Error | null) => {
|
|
29
|
+
if (err) reject(err);
|
|
30
|
+
else resolve(proj);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureObjectSection(proj: XcodeProject, sectionName: string): void {
|
|
36
|
+
const objects = proj.hash?.project?.objects as Record<string, unknown> | undefined;
|
|
37
|
+
if (!objects) return;
|
|
38
|
+
if (!objects[sectionName]) objects[sectionName] = {};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function objectSection(proj: XcodeProject, sectionName: string): Record<string, unknown> {
|
|
42
|
+
ensureObjectSection(proj, sectionName);
|
|
43
|
+
return (proj.hash.project.objects as Record<string, Record<string, unknown>>)[sectionName];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function loadPbxFromStore(
|
|
47
|
+
store: TextFileStore,
|
|
48
|
+
projectRoot: string,
|
|
49
|
+
xcodeprojPath: string,
|
|
50
|
+
targetName: string
|
|
51
|
+
): Promise<PbxContext> {
|
|
52
|
+
const rel = path.relative(projectRoot, path.join(xcodeprojPath, "project.pbxproj")).split(path.sep).join("/");
|
|
53
|
+
const content = store.read(rel);
|
|
54
|
+
const proj = await parseProjectFromContent(content);
|
|
55
|
+
// Newer Xcode projects can omit empty sections, while the `xcode` package assumes
|
|
56
|
+
// sections such as PBXBuildFile already exist before adding frameworks/resources.
|
|
57
|
+
ensureObjectSection(proj, "PBXBuildFile");
|
|
58
|
+
ensureObjectSection(proj, "PBXFileReference");
|
|
59
|
+
const xcodeprojDir = path.dirname(xcodeprojPath);
|
|
60
|
+
let srcRoot = xcodeprojDir;
|
|
61
|
+
const first = proj.getFirstProject?.()?.firstProject;
|
|
62
|
+
const projectDirPath = first?.projectDirPath
|
|
63
|
+
? String(first.projectDirPath).replace(/^"|"$/g, "").trim()
|
|
64
|
+
: "";
|
|
65
|
+
if (projectDirPath.length > 0) {
|
|
66
|
+
srcRoot = path.resolve(xcodeprojDir, projectDirPath);
|
|
67
|
+
}
|
|
68
|
+
return { rel, proj, srcRoot, targetName };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function savePbxToStore(store: TextFileStore, ctx: PbxContext): void {
|
|
72
|
+
stripUndefinedValues(ctx.proj.hash?.project?.objects);
|
|
73
|
+
sanitizePbxBuildSettings(ctx.proj);
|
|
74
|
+
store.write(ctx.rel, ctx.proj.writeSync());
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function stripUndefinedValues(value: unknown): void {
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
for (const item of value) stripUndefinedValues(item);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!value || typeof value !== "object") return;
|
|
83
|
+
|
|
84
|
+
const record = value as Record<string, unknown>;
|
|
85
|
+
for (const [key, nested] of Object.entries(record)) {
|
|
86
|
+
if (nested === undefined || nested === "undefined") {
|
|
87
|
+
delete record[key];
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
stripUndefinedValues(nested);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function sanitizePbxBuildSettings(proj: XcodeProject): void {
|
|
95
|
+
const buildConfigs = proj.pbxXCBuildConfigurationSection?.() ?? {};
|
|
96
|
+
for (const [uuid, cfg] of Object.entries(buildConfigs)) {
|
|
97
|
+
if (uuid.endsWith("_comment") || !cfg || typeof cfg !== "object") continue;
|
|
98
|
+
const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings;
|
|
99
|
+
if (!settings) continue;
|
|
100
|
+
for (const [key, raw] of Object.entries(settings)) {
|
|
101
|
+
if (typeof raw === "string") {
|
|
102
|
+
settings[key] = quotePbxStringIfNeeded(raw);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(raw)) {
|
|
106
|
+
settings[key] = raw.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function isPbxQuotedString(value: string): boolean {
|
|
113
|
+
return value.length >= 2 && value.startsWith('"') && value.endsWith('"');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function unquotePbxString(value: string): string {
|
|
117
|
+
if (!isPbxQuotedString(value)) return value;
|
|
118
|
+
return value.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function quotePbxStringIfNeeded(value: string): string {
|
|
122
|
+
if (isPbxQuotedString(value)) return value;
|
|
123
|
+
if (!pbxStringNeedsQuotes(value)) return value;
|
|
124
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function pbxStringNeedsQuotes(value: string): boolean {
|
|
128
|
+
if (value.length === 0 || value.startsWith("-")) return true;
|
|
129
|
+
return /[^A-Za-z0-9_.$/{}()+-]/.test(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function samePbxStringValue(a: unknown, b: string): boolean {
|
|
133
|
+
return typeof a === "string" && unquotePbxString(a) === unquotePbxString(b);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function targetUuid(proj: XcodeProject, targetName: string): string | undefined {
|
|
137
|
+
const targets = proj.pbxNativeTargetSection?.() ?? {};
|
|
138
|
+
for (const [uuid, t] of Object.entries(targets)) {
|
|
139
|
+
if (uuid.endsWith("_comment")) continue;
|
|
140
|
+
const name = (t as { name?: string }).name?.replace(/"/g, "");
|
|
141
|
+
if (name === targetName) return uuid;
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function targetKey(proj: XcodeProject, targetName: string): string {
|
|
147
|
+
const uuid = targetUuid(proj, targetName);
|
|
148
|
+
if (!uuid) throw new Error(`Xcode target not found: ${targetName}`);
|
|
149
|
+
return uuid;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function ensureResourcesBuildPhase(proj: XcodeProject, targetName: string): void {
|
|
153
|
+
const target = targetKey(proj, targetName);
|
|
154
|
+
try {
|
|
155
|
+
proj.pbxResourcesBuildPhaseObj(target);
|
|
156
|
+
return;
|
|
157
|
+
} catch {
|
|
158
|
+
proj.addBuildPhase([], "PBXResourcesBuildPhase", "Resources", target);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function addThirdPartyFramework(ctx: PbxContext, relPathFromSrcRoot: string, embed: boolean): void {
|
|
163
|
+
const file = relPathFromSrcRoot.split(path.sep).join("/");
|
|
164
|
+
const target = targetKey(ctx.proj, ctx.targetName);
|
|
165
|
+
ctx.proj.addFramework(file, {
|
|
166
|
+
customFramework: true,
|
|
167
|
+
embed,
|
|
168
|
+
sign: true,
|
|
169
|
+
target,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function addCopyFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
|
|
174
|
+
const file = relPathFromSrcRoot.split(path.sep).join("/");
|
|
175
|
+
const target = targetKey(ctx.proj, ctx.targetName);
|
|
176
|
+
if (copyFilesPhaseJson(ctx, target).includes(path.basename(file))) return;
|
|
177
|
+
ensureCopyFilesBuildPhase(ctx, target);
|
|
178
|
+
addCopyFileManually(ctx, target, file);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function copyFilesPhaseJson(ctx: PbxContext, target: string): string {
|
|
182
|
+
try {
|
|
183
|
+
return JSON.stringify(ctx.proj.pbxCopyfilesBuildPhaseObj?.(target) ?? {});
|
|
184
|
+
} catch {
|
|
185
|
+
return "";
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function findFileRefUuid(ctx: PbxContext, file: string): string | null {
|
|
190
|
+
const basename = path.basename(file);
|
|
191
|
+
const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
|
|
192
|
+
for (const [uuid, raw] of Object.entries(fileRefSection)) {
|
|
193
|
+
if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
|
|
194
|
+
const ref = raw as Record<string, unknown>;
|
|
195
|
+
const comment = fileRefSection[`${uuid}_comment`];
|
|
196
|
+
const refPath = String(ref.path ?? "").replace(/^"|"$/g, "");
|
|
197
|
+
const refName = String(ref.name ?? "").replace(/^"|"$/g, "");
|
|
198
|
+
if (comment === basename || refPath === file || refPath.endsWith(`/${basename}`) || refName === basename) {
|
|
199
|
+
return uuid;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function ensureCopyFilesBuildPhase(ctx: PbxContext, target: string): Record<string, unknown> {
|
|
206
|
+
const section = objectSection(ctx.proj, "PBXCopyFilesBuildPhase");
|
|
207
|
+
for (const [uuid, raw] of Object.entries(section)) {
|
|
208
|
+
if (uuid.endsWith("_comment") || !raw || typeof raw !== "object") continue;
|
|
209
|
+
const phase = raw as Record<string, unknown>;
|
|
210
|
+
const name = String(phase.name ?? "").replace(/^"|"$/g, "");
|
|
211
|
+
if (name === "Copy Files" || section[`${uuid}_comment`] === "Copy Files") {
|
|
212
|
+
phase.files = (phase.files as unknown[]) ?? [];
|
|
213
|
+
return phase;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const uuid = ctx.proj.generateUuid();
|
|
218
|
+
const phase: Record<string, unknown> = {
|
|
219
|
+
isa: "PBXCopyFilesBuildPhase",
|
|
220
|
+
buildActionMask: 2147483647,
|
|
221
|
+
dstPath: '""',
|
|
222
|
+
dstSubfolderSpec: 10,
|
|
223
|
+
files: [],
|
|
224
|
+
name: '"Copy Files"',
|
|
225
|
+
runOnlyForDeploymentPostprocessing: 0,
|
|
226
|
+
};
|
|
227
|
+
section[uuid] = phase;
|
|
228
|
+
section[`${uuid}_comment`] = "Copy Files";
|
|
229
|
+
const native = ctx.proj.pbxNativeTargetSection?.()[target] as { buildPhases?: Array<{ value: string; comment: string }> };
|
|
230
|
+
native.buildPhases ??= [];
|
|
231
|
+
if (!native.buildPhases.some((p) => p.value === uuid)) {
|
|
232
|
+
native.buildPhases.push({ value: uuid, comment: "Copy Files" });
|
|
233
|
+
}
|
|
234
|
+
return phase;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function addCopyFileManually(ctx: PbxContext, target: string, file: string): void {
|
|
238
|
+
const basename = path.basename(file);
|
|
239
|
+
const phase = ensureCopyFilesBuildPhase(ctx, target);
|
|
240
|
+
const files = (phase.files ??= []) as Array<{ value?: string; comment?: string }>;
|
|
241
|
+
if (JSON.stringify(files).includes(basename)) return;
|
|
242
|
+
|
|
243
|
+
let fileRefUuid: string = findFileRefUuid(ctx, file) ?? "";
|
|
244
|
+
if (!fileRefUuid) {
|
|
245
|
+
fileRefUuid = ctx.proj.generateUuid();
|
|
246
|
+
const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
|
|
247
|
+
fileRefSection[fileRefUuid] = {
|
|
248
|
+
isa: "PBXFileReference",
|
|
249
|
+
name: `"${basename}"`,
|
|
250
|
+
path: `"${file}"`,
|
|
251
|
+
sourceTree: '"<group>"',
|
|
252
|
+
lastKnownFileType: "wrapper.framework",
|
|
253
|
+
includeInIndex: 0,
|
|
254
|
+
};
|
|
255
|
+
fileRefSection[`${fileRefUuid}_comment`] = basename;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const buildUuid = ctx.proj.generateUuid();
|
|
259
|
+
const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
|
|
260
|
+
buildFileSection[buildUuid] = {
|
|
261
|
+
isa: "PBXBuildFile",
|
|
262
|
+
fileRef: fileRefUuid,
|
|
263
|
+
fileRef_comment: basename,
|
|
264
|
+
};
|
|
265
|
+
buildFileSection[`${buildUuid}_comment`] = `${basename} in Copy Files`;
|
|
266
|
+
files.push({ value: buildUuid, comment: `${basename} in Copy Files` });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function addSystemFramework(ctx: PbxContext, frameworkName: string): void {
|
|
270
|
+
const target = targetKey(ctx.proj, ctx.targetName);
|
|
271
|
+
ctx.proj.addFramework(`System/Library/Frameworks/${frameworkName}`, {
|
|
272
|
+
customFramework: false,
|
|
273
|
+
weak: false,
|
|
274
|
+
target,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function addSystemLib(ctx: PbxContext, libName: string): void {
|
|
279
|
+
const target = targetKey(ctx.proj, ctx.targetName);
|
|
280
|
+
ctx.proj.addFramework(`usr/lib/${libName}`, {
|
|
281
|
+
customFramework: false,
|
|
282
|
+
weak: false,
|
|
283
|
+
target,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function addSourceOrResourceFile(ctx: PbxContext, relPathFromSrcRoot: string): void {
|
|
288
|
+
const file = relPathFromSrcRoot.split(path.sep).join("/");
|
|
289
|
+
const target = targetKey(ctx.proj, ctx.targetName);
|
|
290
|
+
if (file.endsWith(".h")) {
|
|
291
|
+
ctx.proj.addHeaderFile(file, { target });
|
|
292
|
+
} else if (file.endsWith(".m") || file.endsWith(".mm") || file.endsWith(".swift")) {
|
|
293
|
+
ctx.proj.addSourceFile(file, { target });
|
|
294
|
+
} else {
|
|
295
|
+
ensureResourcesBuildPhase(ctx.proj, ctx.targetName);
|
|
296
|
+
const opt: Record<string, unknown> = { target };
|
|
297
|
+
if (file.endsWith(".xcdatamodeld")) {
|
|
298
|
+
opt.lastKnownFileType = "wrapper.xcdatamodel";
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
ctx.proj.addResourceFile(file, opt);
|
|
302
|
+
} catch (e) {
|
|
303
|
+
if (!String(e instanceof Error ? e.message : e).includes("path")) throw e;
|
|
304
|
+
addResourceFileManually(ctx, file, typeof opt.lastKnownFileType === "string" ? opt.lastKnownFileType : undefined);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function resourceFileType(file: string, lastKnownFileType?: string): string {
|
|
310
|
+
if (lastKnownFileType) return lastKnownFileType;
|
|
311
|
+
if (file.endsWith(".bundle")) return "wrapper.plug-in";
|
|
312
|
+
if (file.endsWith(".xcdatamodeld")) return "wrapper.xcdatamodel";
|
|
313
|
+
if (file.endsWith(".xcassets")) return "folder.assetcatalog";
|
|
314
|
+
if (file.endsWith(".plist")) return "text.plist.xml";
|
|
315
|
+
return "unknown";
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function addResourceFileManually(ctx: PbxContext, file: string, lastKnownFileType?: string): void {
|
|
319
|
+
const basename = path.basename(file);
|
|
320
|
+
const resources = ctx.proj.pbxResourcesBuildPhaseObj(targetKey(ctx.proj, ctx.targetName));
|
|
321
|
+
const files = (resources.files ??= []) as Array<{ value?: string; comment?: string }>;
|
|
322
|
+
if (JSON.stringify(files).includes(basename)) return;
|
|
323
|
+
|
|
324
|
+
const fileRefUuid = ctx.proj.generateUuid();
|
|
325
|
+
const buildUuid = ctx.proj.generateUuid();
|
|
326
|
+
const fileRefSection = objectSection(ctx.proj, "PBXFileReference");
|
|
327
|
+
const buildFileSection = objectSection(ctx.proj, "PBXBuildFile");
|
|
328
|
+
|
|
329
|
+
fileRefSection[fileRefUuid] = {
|
|
330
|
+
isa: "PBXFileReference",
|
|
331
|
+
name: `"${basename}"`,
|
|
332
|
+
path: `"${file}"`,
|
|
333
|
+
sourceTree: '"<group>"',
|
|
334
|
+
lastKnownFileType: resourceFileType(file, lastKnownFileType),
|
|
335
|
+
includeInIndex: 0,
|
|
336
|
+
};
|
|
337
|
+
fileRefSection[`${fileRefUuid}_comment`] = basename;
|
|
338
|
+
buildFileSection[buildUuid] = {
|
|
339
|
+
isa: "PBXBuildFile",
|
|
340
|
+
fileRef: fileRefUuid,
|
|
341
|
+
fileRef_comment: basename,
|
|
342
|
+
};
|
|
343
|
+
buildFileSection[`${buildUuid}_comment`] = `${basename} in Resources`;
|
|
344
|
+
files.push({ value: buildUuid, comment: `${basename} in Resources` });
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function setBuildSetting(ctx: PbxContext, key: string, value: string): void {
|
|
348
|
+
const uuid = targetUuid(ctx.proj, ctx.targetName);
|
|
349
|
+
if (!uuid) return;
|
|
350
|
+
const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
|
|
351
|
+
const listId = native?.buildConfigurationList;
|
|
352
|
+
const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
|
|
353
|
+
if (!listId || !lists[listId]) return;
|
|
354
|
+
const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
|
|
355
|
+
const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
|
|
356
|
+
for (const entry of list.buildConfigurations ?? []) {
|
|
357
|
+
const cfgId = typeof entry === "string" ? entry : entry.value;
|
|
358
|
+
const cfg = buildConfigs[cfgId];
|
|
359
|
+
if (!cfg || typeof cfg !== "object") continue;
|
|
360
|
+
const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
|
|
361
|
+
settings[key] = value;
|
|
362
|
+
(cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function addOtherLinkerFlag(ctx: PbxContext, flag: string): void {
|
|
367
|
+
const uuid = targetUuid(ctx.proj, ctx.targetName);
|
|
368
|
+
if (!uuid) return;
|
|
369
|
+
const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
|
|
370
|
+
const listId = native?.buildConfigurationList;
|
|
371
|
+
const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
|
|
372
|
+
if (!listId || !lists[listId]) return;
|
|
373
|
+
const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
|
|
374
|
+
const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
|
|
375
|
+
for (const entry of list.buildConfigurations ?? []) {
|
|
376
|
+
const cfgId = typeof entry === "string" ? entry : entry.value;
|
|
377
|
+
const cfg = buildConfigs[cfgId];
|
|
378
|
+
if (!cfg || typeof cfg !== "object") continue;
|
|
379
|
+
const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
|
|
380
|
+
const cur = settings.OTHER_LDFLAGS;
|
|
381
|
+
const normalizedFlag = unquotePbxString(flag);
|
|
382
|
+
const nextFlag = quotePbxStringIfNeeded(normalizedFlag);
|
|
383
|
+
if (typeof cur === "string") {
|
|
384
|
+
if (!unquotePbxString(cur).split(/\s+/).includes(normalizedFlag)) {
|
|
385
|
+
settings.OTHER_LDFLAGS = [quotePbxStringIfNeeded(cur), nextFlag];
|
|
386
|
+
}
|
|
387
|
+
} else if (Array.isArray(cur)) {
|
|
388
|
+
const values = cur.map((value) => (typeof value === "string" ? quotePbxStringIfNeeded(value) : value));
|
|
389
|
+
if (!values.some((value) => samePbxStringValue(value, normalizedFlag))) values.push(nextFlag);
|
|
390
|
+
settings.OTHER_LDFLAGS = values;
|
|
391
|
+
} else {
|
|
392
|
+
settings.OTHER_LDFLAGS = [nextFlag];
|
|
393
|
+
}
|
|
394
|
+
(cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export function getTargetBuildSettings(ctx: PbxContext): Array<Record<string, unknown>> {
|
|
399
|
+
const uuid = targetUuid(ctx.proj, ctx.targetName);
|
|
400
|
+
if (!uuid) return [];
|
|
401
|
+
const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
|
|
402
|
+
const listId = native?.buildConfigurationList;
|
|
403
|
+
const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
|
|
404
|
+
if (!listId || !lists[listId]) return [];
|
|
405
|
+
const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
|
|
406
|
+
const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
|
|
407
|
+
const out: Array<Record<string, unknown>> = [];
|
|
408
|
+
for (const entry of list.buildConfigurations ?? []) {
|
|
409
|
+
const cfgId = typeof entry === "string" ? entry : entry.value;
|
|
410
|
+
const cfg = buildConfigs[cfgId];
|
|
411
|
+
if (!cfg || typeof cfg !== "object") continue;
|
|
412
|
+
const settings = (cfg as { buildSettings?: Record<string, unknown> }).buildSettings ?? {};
|
|
413
|
+
(cfg as { buildSettings: Record<string, unknown> }).buildSettings = settings;
|
|
414
|
+
out.push(settings);
|
|
415
|
+
}
|
|
416
|
+
return out;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function findInfoPlistPathsFromPbx(ctx: PbxContext): string[] {
|
|
420
|
+
const paths = new Set<string>();
|
|
421
|
+
const uuid = targetUuid(ctx.proj, ctx.targetName);
|
|
422
|
+
if (!uuid) return [];
|
|
423
|
+
const native = ctx.proj.pbxNativeTargetSection?.()[uuid] as { buildConfigurationList?: string };
|
|
424
|
+
const listId = native?.buildConfigurationList;
|
|
425
|
+
const lists = ctx.proj.pbxXCConfigurationList?.() ?? {};
|
|
426
|
+
if (!listId || !lists[listId]) return [];
|
|
427
|
+
const list = lists[listId] as { buildConfigurations?: Array<string | { value: string }> };
|
|
428
|
+
const buildConfigs = ctx.proj.pbxXCBuildConfigurationSection?.() ?? {};
|
|
429
|
+
for (const entry of list.buildConfigurations ?? []) {
|
|
430
|
+
const cfgId = typeof entry === "string" ? entry : entry.value;
|
|
431
|
+
const cfg = buildConfigs[cfgId];
|
|
432
|
+
const settings = (cfg as { buildSettings?: Record<string, string> })?.buildSettings;
|
|
433
|
+
const info = settings?.INFOPLIST_FILE;
|
|
434
|
+
if (!info) continue;
|
|
435
|
+
let resolved = info.replace(/"/g, "");
|
|
436
|
+
if (resolved.includes("$(SRCROOT)")) {
|
|
437
|
+
resolved = resolved.replace(/\$\(SRCROOT\)/g, ctx.srcRoot);
|
|
438
|
+
} else if (!path.isAbsolute(resolved)) {
|
|
439
|
+
resolved = path.join(ctx.srcRoot, resolved);
|
|
440
|
+
}
|
|
441
|
+
resolved = path.normalize(resolved);
|
|
442
|
+
paths.add(resolved);
|
|
443
|
+
}
|
|
444
|
+
return [...paths];
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
export function ensureInfoPlistInProject(ctx: PbxContext, _targetName: string, relPlist: string): void {
|
|
448
|
+
setBuildSetting(ctx, "GENERATE_INFOPLIST_FILE", "NO");
|
|
449
|
+
setBuildSetting(ctx, "INFOPLIST_FILE", relPlist);
|
|
450
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import type { LoadedPluginConfig, PluginConfigJson } from "./types.js";
|
|
4
|
+
|
|
5
|
+
const CONFIG_FILENAMES = ["config.t.json", "config.json"] as const;
|
|
6
|
+
|
|
7
|
+
function findConfigFileInDir(dir: string): string | null {
|
|
8
|
+
for (const name of CONFIG_FILENAMES) {
|
|
9
|
+
const p = path.join(dir, name);
|
|
10
|
+
if (fs.existsSync(p)) return p;
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizePluginConfig(raw: Record<string, unknown>): PluginConfigJson {
|
|
16
|
+
const flags =
|
|
17
|
+
(raw.OtherLinkerFlags as string[] | undefined) ??
|
|
18
|
+
(raw.otherLinkerFlags as string[] | undefined) ??
|
|
19
|
+
[];
|
|
20
|
+
return {
|
|
21
|
+
...(raw as unknown as PluginConfigJson),
|
|
22
|
+
OtherLinkerFlags: flags,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseConfigJson(filePath: string): PluginConfigJson {
|
|
27
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
28
|
+
const config = normalizePluginConfig(raw);
|
|
29
|
+
if (!config.name || typeof config.type !== "number") {
|
|
30
|
+
throw new Error(`invalid plugin config (missing name/type): ${filePath}`);
|
|
31
|
+
}
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveSourceDir(sdkRoot: string, config: PluginConfigJson, pluginDir: string): string {
|
|
36
|
+
const sdkDir = path.join(sdkRoot, "sdk");
|
|
37
|
+
if (config.type === 0) {
|
|
38
|
+
return path.join(sdkDir, "source");
|
|
39
|
+
}
|
|
40
|
+
return path.join(pluginDir, "source");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function loadPluginConfig(configPath: string, sdkRoot: string): LoadedPluginConfig {
|
|
44
|
+
const abs = path.resolve(configPath);
|
|
45
|
+
const pluginDir = path.dirname(abs);
|
|
46
|
+
const config = parseConfigJson(abs);
|
|
47
|
+
const sourceDir = resolveSourceDir(sdkRoot, config, pluginDir);
|
|
48
|
+
return { configPath: abs, pluginDir, sourceDir, config };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Core SDK manifests: `sdk/*.json` (e.g. topsdkConfig.t.json). */
|
|
52
|
+
export function listSdkCoreConfigs(sdkRoot: string): LoadedPluginConfig[] {
|
|
53
|
+
const sdkDir = path.join(sdkRoot, "sdk");
|
|
54
|
+
if (!fs.existsSync(sdkDir)) return [];
|
|
55
|
+
const out: LoadedPluginConfig[] = [];
|
|
56
|
+
for (const name of fs.readdirSync(sdkDir)) {
|
|
57
|
+
if (!name.endsWith(".json") || name.startsWith(".")) continue;
|
|
58
|
+
out.push(loadPluginConfig(path.join(sdkDir, name), sdkRoot));
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function findPluginByFolder(sdkRoot: string, folderName: string): LoadedPluginConfig | null {
|
|
64
|
+
const pluginDir = path.join(sdkRoot, "plugins", folderName);
|
|
65
|
+
const cfg = findConfigFileInDir(pluginDir);
|
|
66
|
+
if (!cfg) return null;
|
|
67
|
+
return loadPluginConfig(cfg, sdkRoot);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function validateLoadedPluginResources(loaded: LoadedPluginConfig): string[] {
|
|
71
|
+
const errors: string[] = [];
|
|
72
|
+
const { config, sourceDir, pluginDir, configPath } = loaded;
|
|
73
|
+
const dirName = path.basename(pluginDir);
|
|
74
|
+
const isKnownDataManagerAlias = config.type === 4 && dirName.endsWith("Manager");
|
|
75
|
+
if (config.type !== 0 && dirName !== config.name && !isKnownDataManagerAlias) {
|
|
76
|
+
errors.push(`plugin config name mismatch: ${configPath} name=${config.name} dir=${path.basename(pluginDir)}`);
|
|
77
|
+
}
|
|
78
|
+
if (!fs.existsSync(sourceDir)) {
|
|
79
|
+
errors.push(`plugin source directory not found: ${sourceDir}`);
|
|
80
|
+
return errors;
|
|
81
|
+
}
|
|
82
|
+
for (const source of config.sources ?? []) {
|
|
83
|
+
const p = path.join(sourceDir, source);
|
|
84
|
+
if (!fs.existsSync(p)) errors.push(`missing source resource for ${config.name}: ${source}`);
|
|
85
|
+
}
|
|
86
|
+
for (const fw of config.frameworks ?? []) {
|
|
87
|
+
if (fw.system) continue;
|
|
88
|
+
const p = path.join(sourceDir, fw.name);
|
|
89
|
+
if (!fs.existsSync(p)) errors.push(`missing framework resource for ${config.name}: ${fw.name}`);
|
|
90
|
+
}
|
|
91
|
+
for (const lib of config.libs ?? []) {
|
|
92
|
+
if (lib.system) continue;
|
|
93
|
+
const p = path.join(sourceDir, lib.name);
|
|
94
|
+
if (!fs.existsSync(p)) errors.push(`missing lib resource for ${config.name}: ${lib.name}`);
|
|
95
|
+
}
|
|
96
|
+
return errors;
|
|
97
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IosToolingReserved } from "../contracts/types.js";
|
|
2
|
+
|
|
3
|
+
export const iosToolingReserved: IosToolingReserved = {
|
|
4
|
+
status: "implemented",
|
|
5
|
+
plan: ["framework-copy", "pbxproj", "plist", "app-delegate"],
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export { detectIOS as detectIOSPlaceholder } from "./detect.js";
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { MEET_SDK_TOOL_CACHE_ROOT } from "../cache.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* TopSDK iOS package root from cache.
|
|
7
|
+
* Native iOS only.
|
|
8
|
+
*/
|
|
9
|
+
function findIosSdkRoot(container: string): string | null {
|
|
10
|
+
if (!fs.existsSync(container)) return null;
|
|
11
|
+
const children = fs
|
|
12
|
+
.readdirSync(container, { withFileTypes: true })
|
|
13
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith("."))
|
|
14
|
+
.map((d) => path.join(container, d.name))
|
|
15
|
+
.sort()
|
|
16
|
+
.reverse();
|
|
17
|
+
for (const dir of children) {
|
|
18
|
+
if (fs.existsSync(path.join(dir, "sdk")) && fs.existsSync(path.join(dir, "plugins"))) {
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (fs.existsSync(path.join(container, "sdk")) && fs.existsSync(path.join(container, "plugins"))) {
|
|
23
|
+
return container;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function findLatestCachedIosSdkRoot(): string | null {
|
|
29
|
+
const iosCache = path.join(MEET_SDK_TOOL_CACHE_ROOT, "sdk", "ios");
|
|
30
|
+
if (!fs.existsSync(iosCache)) return null;
|
|
31
|
+
const candidates: string[] = [];
|
|
32
|
+
const walk = (dir: string): void => {
|
|
33
|
+
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
34
|
+
if (!ent.isDirectory() || ent.name.startsWith(".")) continue;
|
|
35
|
+
const abs = path.join(dir, ent.name);
|
|
36
|
+
if (ent.name === "extracted") {
|
|
37
|
+
candidates.push(abs);
|
|
38
|
+
} else {
|
|
39
|
+
walk(abs);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
walk(iosCache);
|
|
44
|
+
candidates.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs);
|
|
45
|
+
for (const candidate of candidates) {
|
|
46
|
+
const found = findIosSdkRoot(candidate);
|
|
47
|
+
if (found) return found;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function resolveIosSdkRoot(_packageRoot: string): string {
|
|
53
|
+
const cached = findLatestCachedIosSdkRoot();
|
|
54
|
+
if (cached) return cached;
|
|
55
|
+
|
|
56
|
+
throw new Error(
|
|
57
|
+
`iOS SDK not found in ${path.join(MEET_SDK_TOOL_CACHE_ROOT, "sdk", "ios")}; run download-ios-sdk or setup first.`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Substitute `{path.to.value}` placeholders using channel config (topsdk-tool-ios `checkStr`). */
|
|
2
|
+
export function applyChannelTemplate(value: string, channelConfig: Record<string, unknown>): string {
|
|
3
|
+
let result = value;
|
|
4
|
+
const re = /\{([^}]+)\}/g;
|
|
5
|
+
let m: RegExpExecArray | null;
|
|
6
|
+
while ((m = re.exec(value)) !== null) {
|
|
7
|
+
const token = m[1].startsWith("$C.") ? m[1].slice(3) : m[1];
|
|
8
|
+
const parts = token.split(".");
|
|
9
|
+
let cur: unknown = channelConfig;
|
|
10
|
+
for (const p of parts) {
|
|
11
|
+
if (cur && typeof cur === "object" && p in (cur as Record<string, unknown>)) {
|
|
12
|
+
cur = (cur as Record<string, unknown>)[p];
|
|
13
|
+
} else {
|
|
14
|
+
cur = "";
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const replacement = cur == null ? "" : String(cur);
|
|
19
|
+
result = result.replace(m[0], replacement);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function applyChannelTemplateValue(value: unknown, channelConfig: Record<string, unknown>): unknown {
|
|
25
|
+
if (typeof value === "string") return applyChannelTemplate(value, channelConfig);
|
|
26
|
+
if (Array.isArray(value)) return value.map((item) => applyChannelTemplateValue(item, channelConfig));
|
|
27
|
+
if (value && typeof value === "object") {
|
|
28
|
+
return Object.fromEntries(
|
|
29
|
+
Object.entries(value as Record<string, unknown>).map(([key, item]) => [
|
|
30
|
+
key,
|
|
31
|
+
applyChannelTemplateValue(item, channelConfig),
|
|
32
|
+
])
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return value;
|
|
36
|
+
}
|