Skip to content

Commit 3898127

Browse files
unity-xcode-builder@v1.2.1 (#17)
- cleanup duplicate code path when obtaining app id - fix SemVer when processing info.plist - enhancing Xcode version selection logic - bundle version auto-increment logic improvements
1 parent 04c0c19 commit 3898127

File tree

10 files changed

+459
-283
lines changed

10 files changed

+459
-283
lines changed

.github/workflows/validate.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ jobs:
4747
version-file: 'None'
4848
build-targets: ${{ matrix.build-target }}
4949
unity-version: ${{ matrix.unity-version }}
50-
architecture: 'arm64'
5150
- name: Find Unity Template Path and Version
5251
run: |
5352
$rootPath = $env:UNITY_EDITOR_PATH -replace "Editor.*", ""

dist/index.js

Lines changed: 221 additions & 129 deletions
Large diffs are not rendered by default.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 14 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "unity-xcode-builder",
3-
"version": "1.2.0",
3+
"version": "1.2.1",
44
"description": "A GitHub Action to build, archive, and upload Unity exported xcode projects.",
55
"author": "buildalon",
66
"license": "MIT",
@@ -25,18 +25,18 @@
2525
"uuid": "^10.0.0"
2626
},
2727
"devDependencies": {
28-
"@types/node": "^22.13.14",
28+
"@types/node": "^22.15.3",
2929
"@types/plist": "^3.0.5",
3030
"@types/semver": "^7.7.0",
3131
"@types/uuid": "^10.0.0",
3232
"@vercel/ncc": "^0.34.0",
3333
"shx": "^0.3.4",
34-
"typescript": "^5.8.2"
34+
"typescript": "^5.8.3"
3535
},
3636
"scripts": {
3737
"build": "npm run clean && npm run bundle",
3838
"bundle": "ncc build src/index.ts -o dist --source-map --license licenses.txt",
3939
"watch": "ncc build src/index.ts -o dist --source-map --license licenses.txt --watch",
4040
"clean": "npm install && shx rm -rf dist/ out/ node_modules/ && npm ci"
4141
}
42-
}
42+
}

src/AppStoreConnectClient.ts

Lines changed: 64 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ function checkAuthError(error: any) {
4848
}
4949
}
5050

51-
export async function GetAppId(project: XcodeProject): Promise<XcodeProject> {
52-
if (project.appId) { return project; }
51+
export async function GetAppId(project: XcodeProject): Promise<string> {
5352
await getOrCreateClient(project);
5453
const { data: response, error } = await appStoreConnectClient.api.AppsService.appsGetCollection({
5554
query: { 'filter[bundleId]': [project.bundleId] }
@@ -58,27 +57,32 @@ export async function GetAppId(project: XcodeProject): Promise<XcodeProject> {
5857
checkAuthError(error);
5958
throw new Error(`Error fetching apps: ${JSON.stringify(error)}`);
6059
}
60+
log(`GET /appsGetCollection\n${JSON.stringify(response, null, 2)}`);
6161
if (!response) {
6262
throw new Error(`No apps found for bundle id ${project.bundleId}`);
6363
}
6464
if (response.data.length === 0) {
6565
throw new Error(`No apps found for bundle id ${project.bundleId}`);
6666
}
67-
project.appId = response.data[0].id;
68-
return project;
67+
if (response.data.length > 1) {
68+
log(`Multiple apps found for bundle id ${project.bundleId}!`);
69+
for (const app of response.data) {
70+
log(`[${app.id}] ${app.attributes?.bundleId}`);
71+
if (project.bundleId === app.attributes?.bundleId) {
72+
return app.id;
73+
}
74+
}
75+
}
76+
return response.data[0].id;
6977
}
7078

71-
export async function GetLatestBundleVersion(project: XcodeProject): Promise<number> {
79+
export async function GetLatestBundleVersion(project: XcodeProject): Promise<string | null> {
7280
await getOrCreateClient(project);
7381
let { preReleaseVersion, build } = await getLastPreReleaseVersionAndBuild(project);
7482
if (!build) {
7583
build = await getLastPrereleaseBuild(preReleaseVersion);
7684
}
77-
const buildVersion = build.attributes.version;
78-
if (!buildVersion) {
79-
throw new Error(`No build version found!\n${JSON.stringify(build, null, 2)}`);
80-
}
81-
return Number(buildVersion);
85+
return build?.attributes?.version;
8286
}
8387

8488
function reMapPlatform(project: XcodeProject): ('IOS' | 'MAC_OS' | 'TV_OS' | 'VISION_OS') {
@@ -97,7 +101,7 @@ function reMapPlatform(project: XcodeProject): ('IOS' | 'MAC_OS' | 'TV_OS' | 'VI
97101
}
98102

99103
async function getLastPreReleaseVersionAndBuild(project: XcodeProject): Promise<PreReleaseVersionWithBuild> {
100-
if (!project.appId) { project = await GetAppId(project); }
104+
if (!project.appId) { project.appId = await GetAppId(project); }
101105
const preReleaseVersionRequest: PreReleaseVersionsGetCollectionData = {
102106
query: {
103107
'filter[app]': [project.appId],
@@ -109,7 +113,7 @@ async function getLastPreReleaseVersionAndBuild(project: XcodeProject): Promise<
109113
limit: 1,
110114
}
111115
};
112-
log(`/preReleaseVersions?${JSON.stringify(preReleaseVersionRequest.query)}`);
116+
log(`GET /preReleaseVersions?${JSON.stringify(preReleaseVersionRequest.query)}`);
113117
const { data: preReleaseResponse, error: preReleaseError } = await appStoreConnectClient.api.PreReleaseVersionsService.preReleaseVersionsGetCollection(preReleaseVersionRequest);
114118
const responseJson = JSON.stringify(preReleaseResponse, null, 2);
115119
if (preReleaseError) {
@@ -151,7 +155,7 @@ async function getLastPrereleaseBuild(prereleaseVersion: PrereleaseVersion): Pro
151155
limit: 1
152156
}
153157
};
154-
log(`/builds?${JSON.stringify(buildsRequest.query)}`);
158+
log(`GET /builds?${JSON.stringify(buildsRequest.query)}`);
155159
const { data: buildsResponse, error: buildsError } = await appStoreConnectClient.api.BuildsService.buildsGetCollection(buildsRequest);
156160
const responseJson = JSON.stringify(buildsResponse, null, 2);
157161
if (buildsError) {
@@ -173,7 +177,7 @@ async function getBetaBuildLocalization(build: Build): Promise<BetaBuildLocaliza
173177
'fields[betaBuildLocalizations]': ['whatsNew']
174178
}
175179
};
176-
log(`/betaBuildLocalizations?${JSON.stringify(betaBuildLocalizationRequest.query)}`);
180+
log(`GET /betaBuildLocalizations?${JSON.stringify(betaBuildLocalizationRequest.query)}`);
177181
const { data: betaBuildLocalizationResponse, error: betaBuildLocalizationError } = await appStoreConnectClient.api.BetaBuildLocalizationsService.betaBuildLocalizationsGetCollection(betaBuildLocalizationRequest);
178182
const responseJson = JSON.stringify(betaBuildLocalizationResponse, null, 2);
179183
if (betaBuildLocalizationError) {
@@ -205,7 +209,7 @@ async function createBetaBuildLocalization(build: Build, whatsNew: string): Prom
205209
}
206210
}
207211
}
208-
log(`/betaBuildLocalizations\n${JSON.stringify(betaBuildLocalizationRequest, null, 2)}`);
212+
log(`POST /betaBuildLocalizations\n${JSON.stringify(betaBuildLocalizationRequest, null, 2)}`);
209213
const { data: response, error: responseError } = await appStoreConnectClient.api.BetaBuildLocalizationsService.betaBuildLocalizationsCreateInstance({
210214
body: betaBuildLocalizationRequest
211215
});
@@ -228,63 +232,73 @@ async function updateBetaBuildLocalization(betaBuildLocalization: BetaBuildLocal
228232
}
229233
}
230234
};
231-
log(`/betaBuildLocalizations/${betaBuildLocalization.id}\n${JSON.stringify(updateBuildLocalization, null, 2)}`);
235+
log(`POST /betaBuildLocalizations/${betaBuildLocalization.id}\n${JSON.stringify(updateBuildLocalization, null, 2)}`);
232236
const { error: updateError } = await appStoreConnectClient.api.BetaBuildLocalizationsService.betaBuildLocalizationsUpdateInstance({
233237
path: { id: betaBuildLocalization.id },
234238
body: updateBuildLocalization
235239
});
236-
const responseJson = JSON.stringify(updateBuildLocalization, null, 2);
237240
if (updateError) {
238241
checkAuthError(updateError);
239242
throw new Error(`Error updating beta build localization: ${JSON.stringify(updateError, null, 2)}`);
240243
}
241-
log(responseJson);
242244
return betaBuildLocalization;
243245
}
244246

245-
async function pollForValidBuild(project: XcodeProject, buildVersion: number, whatsNew: string, maxRetries: number = 60, interval: number = 30): Promise<BetaBuildLocalization> {
247+
async function pollForValidBuild(project: XcodeProject, maxRetries: number = 60, interval: number = 30): Promise<Build> {
248+
log(`Polling build validation...`);
249+
await new Promise(resolve => setTimeout(resolve, interval * 1000));
246250
let retries = 0;
247-
while (retries < maxRetries) {
248-
if (core.isDebug()) {
249-
core.startGroup(`Polling for build... Attempt ${++retries}/${maxRetries}`);
250-
}
251-
try {
252-
let { preReleaseVersion, build } = await getLastPreReleaseVersionAndBuild(project);
253-
if (!preReleaseVersion) {
254-
throw new Error('No pre-release version found!');
255-
}
251+
while (++retries < maxRetries) {
252+
core.info(`Polling for build... Attempt ${retries}/${maxRetries}`);
253+
let { preReleaseVersion, build } = await getLastPreReleaseVersionAndBuild(project);
254+
if (preReleaseVersion) {
256255
if (!build) {
257256
build = await getLastPrereleaseBuild(preReleaseVersion);
258257
}
259-
if (build.attributes?.version !== buildVersion.toString()) {
260-
throw new Error(`Build version ${build.attributes?.version} does not match expected version ${buildVersion}`);
261-
}
262-
if (build.attributes?.processingState !== 'VALID') {
263-
throw new Error(`Build ${buildVersion} is not valid yet!`);
264-
}
265-
const betaBuildLocalization = await getBetaBuildLocalization(build);
266-
try {
267-
if (!betaBuildLocalization) {
268-
return await createBetaBuildLocalization(build, whatsNew);
258+
if (build) {
259+
const normalizedBuildVersion = normalizeVersion(build.attributes?.version);
260+
const normalizedProjectVersion = normalizeVersion(project.bundleVersion);
261+
switch (build.attributes?.processingState) {
262+
case 'VALID':
263+
if (normalizedBuildVersion === normalizedProjectVersion) {
264+
core.info(`Build ${build.attributes.version} is VALID`);
265+
return build;
266+
} else {
267+
core.info(`Waiting for ${project.bundleVersion}...`);
268+
}
269+
break;
270+
case 'FAILED':
271+
case 'INVALID':
272+
throw new Error(`Build ${build.attributes.version} === ${build.attributes.processingState}!`);
273+
default:
274+
core.info(`Build ${build.attributes.version} is ${build.attributes.processingState}...`);
275+
break;
269276
}
270-
} catch (error) {
271-
log(error, core.isDebug() ? 'warning' : 'info');
272-
}
273-
return await updateBetaBuildLocalization(betaBuildLocalization, whatsNew);
274-
} catch (error) {
275-
log(error, core.isDebug() ? 'error' : 'info');
276-
}
277-
finally {
278-
if (core.isDebug()) {
279-
core.endGroup();
277+
} else {
278+
core.info(`Waiting for build ${preReleaseVersion.attributes?.version}...`);
280279
}
280+
} else {
281+
core.info(`Waiting for pre-release build ${project.versionString}...`);
281282
}
282283
await new Promise(resolve => setTimeout(resolve, interval * 1000));
283284
}
284285
throw new Error('Timed out waiting for valid build!');
285286
}
286287

287-
export async function UpdateTestDetails(project: XcodeProject, buildVersion: number, whatsNew: string): Promise<void> {
288+
export async function UpdateTestDetails(project: XcodeProject, whatsNew: string): Promise<void> {
289+
core.info(`Updating test details...`);
288290
await getOrCreateClient(project);
289-
await pollForValidBuild(project, buildVersion, whatsNew);
291+
const build = await pollForValidBuild(project);
292+
const betaBuildLocalization = await getBetaBuildLocalization(build);
293+
if (!betaBuildLocalization) {
294+
core.info(`Creating beta build localization...`);
295+
await createBetaBuildLocalization(build, whatsNew);
296+
} else {
297+
core.info(`Updating beta build localization...`);
298+
await updateBetaBuildLocalization(betaBuildLocalization, whatsNew);
299+
}
290300
}
301+
302+
function normalizeVersion(version: string): string {
303+
return version.split('.').map(part => parseInt(part, 10).toString()).join('.');
304+
}

src/AppleCredential.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ class AppleCredential {
3939
signingIdentity?: string;
4040
provisioningProfileUUID?: string;
4141
bearerToken?: string;
42-
appleId?: string;
4342
ascPublicId?: string;
4443
}
4544

src/XcodeProject.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export class XcodeProject {
1010
bundleId: string,
1111
projectDirectory: string,
1212
versionString: string,
13-
bundleVersion: number,
13+
bundleVersion: string,
1414
scheme: string,
1515
credential: AppleCredential,
1616
xcodeVersion: SemVer
@@ -30,6 +30,7 @@ export class XcodeProject {
3030
projectPath: string;
3131
projectName: string;
3232
bundleId: string;
33+
appId: string;
3334
projectDirectory: string;
3435
credential: AppleCredential;
3536
platform: string;
@@ -40,11 +41,11 @@ export class XcodeProject {
4041
exportOption: string;
4142
exportOptionsPath: string;
4243
entitlementsPath: string;
43-
appId: string;
4444
versionString: string;
45-
bundleVersion: number;
45+
bundleVersion: string;
4646
scheme: string;
4747
xcodeVersion: SemVer;
48+
autoIncrementBuildNumber: boolean;
4849
isAppStoreUpload(): boolean {
4950
return this.exportOption === 'app-store' || this.exportOption === 'app-store-connect';
5051
}

0 commit comments

Comments
 (0)