Skip to content

Commit 0d3c56f

Browse files
authored
[build-tools] Improve ASC app ID error guidance (#3512)
## Summary - surface example visible App Store Connect apps for invalid bundle-id upload failures - reuse shared ASC app summary formatting for not-found and invalid-bundle-id errors - add unit coverage for visible app summaries and message contents Should help debug https://discord.com/channels/695411232856997968/1483454279204343910/1483492265312780340 ## Testing - ./node_modules/.bin/jest --config packages/build-tools/jest/unit-config.ts packages/build-tools/src/steps/utils/ios/__tests__/AscApiUtils.test.ts packages/build-tools/src/steps/functions/__tests__/uploadToAsc.test.ts - ./node_modules/.bin/tsc -p packages/build-tools/tsconfig.json --noEmit
1 parent bc8e60d commit 0d3c56f

3 files changed

Lines changed: 64 additions & 17 deletions

File tree

packages/build-tools/src/steps/functions/uploadToAsc.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,27 @@ export function createUploadToAscBuildFunction(): BuildFunction {
283283
? ipaInfoResult.value.bundleIdentifier
284284
: null;
285285

286+
let visibleAppsSummary: string | null = null;
287+
try {
288+
const apps = await AscApiUtils.getAppsAsync({ client, limit: 5 });
289+
visibleAppsSummary = AscApiUtils.formatAppsList(apps);
290+
} catch {
291+
// Ok to fail, this is just trying to be helpful.
292+
}
293+
286294
throw new UserError(
287295
'EAS_UPLOAD_TO_ASC_INVALID_BUNDLE_ID',
288296
`Build upload was rejected by App Store Connect because the app bundle identifier in the IPA does not match the selected App Store Connect app.\n\n` +
289297
`IPA bundle identifier: ${ipaBundleIdentifier ?? '(unavailable)'}\n` +
290298
`App Store Connect app bundle identifier: ${ascAppBundleIdentifier}\n\n` +
291299
'Bundle identifier cannot be changed for an existing App Store Connect app. ' +
292300
'If you selected the wrong app, change the Apple app identifier in the submit profile. ' +
293-
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).'
301+
'If you selected the right app, you may want to select a different build to upload (or rebuild with a different profile).' +
302+
'\n\nOther App Store Connect apps visible to this API key:\n' +
303+
(visibleAppsSummary ?? ' (unable to retrieve)'),
304+
{
305+
docsUrl: 'https://expo.fyi/asc-app-id',
306+
}
294307
);
295308
}
296309
if (isMissingPurposeStringError(errors)) {

packages/build-tools/src/steps/utils/ios/AscApiUtils.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ export namespace AscApiUtils {
3434

3535
let visibleAppsSummary: string | null = null;
3636
try {
37-
visibleAppsSummary = await getVisibleAppsSummaryAsync(client);
37+
const apps = await AscApiUtils.getAppsAsync({ client, limit: 10 });
38+
visibleAppsSummary = AscApiUtils.formatAppsList(apps);
3839
} catch {
3940
// Don't hide the original NOT_FOUND error with a secondary lookup failure.
4041
throw error;
4142
}
43+
4244
throw new UserError(
4345
'EAS_UPLOAD_TO_ASC_APP_NOT_FOUND',
4446
`App Store Connect app for application identifier ${appleAppIdentifier} was not found. ` +
@@ -108,19 +110,26 @@ export namespace AscApiUtils {
108110
throw error;
109111
}
110112
}
111-
}
112113

113-
async function getVisibleAppsSummaryAsync(
114-
client: Pick<AscApiClient, 'getAsync'>
115-
): Promise<string | null> {
116-
const appsResponse = await client.getAsync('/v1/apps', {
117-
'fields[apps]': ['bundleId', 'name'],
118-
limit: 10,
119-
});
120-
if (appsResponse.data.length === 0) {
121-
return ' (none)';
114+
export async function getAppsAsync({
115+
client,
116+
limit,
117+
}: {
118+
client: Pick<AscApiClient, 'getAsync'>;
119+
limit?: number;
120+
}): Promise<AscApiClientGetApi['/v1/apps']['response']['data']> {
121+
const appsResponse = await client.getAsync('/v1/apps', {
122+
'fields[apps]': ['bundleId', 'name'],
123+
limit: limit ?? 10,
124+
});
125+
return appsResponse.data;
126+
}
127+
128+
export function formatAppsList(apps: AscApiClientGetApi['/v1/apps']['response']['data']): string {
129+
return (
130+
apps
131+
.map(app => `- ${app.attributes.name} (${app.attributes.bundleId}) (ID: ${app.id})`)
132+
.join('\n') || ' (none)'
133+
);
122134
}
123-
return appsResponse.data
124-
.map(app => `- ${app.attributes.name} (${app.attributes.bundleId}) (ID: ${app.id})`)
125-
.join('\n');
126135
}

packages/build-tools/src/steps/utils/ios/__tests__/AscApiUtils.test.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ describe('AscApiUtils', () => {
6060
expect.objectContaining({
6161
errorCode: 'EAS_UPLOAD_TO_ASC_APP_NOT_FOUND',
6262
docsUrl: 'https://expo.fyi/asc-app-id',
63-
message: expect.stringContaining(
64-
'App Store Connect app for application identifier 1234567890 was not found'
63+
message: expect.stringMatching(
64+
/App Store Connect app for application identifier 1234567890 was not found[\s\S]*- Visible App \(com\.visible\.app\) \(ID: 1111111111\)/
6565
),
6666
})
6767
);
@@ -96,6 +96,31 @@ describe('AscApiUtils', () => {
9696
});
9797
});
9898

99+
describe('formatAppsList', () => {
100+
it('formats visible apps', () => {
101+
expect(
102+
AscApiUtils.formatAppsList([
103+
{
104+
type: 'apps',
105+
id: '1111111111',
106+
attributes: { name: 'Visible App', bundleId: 'com.visible.app' },
107+
},
108+
{
109+
type: 'apps',
110+
id: '2222222222',
111+
attributes: { name: 'Second App', bundleId: 'com.second.app' },
112+
},
113+
])
114+
).toBe(
115+
'- Visible App (com.visible.app) (ID: 1111111111)\n- Second App (com.second.app) (ID: 2222222222)'
116+
);
117+
});
118+
119+
it('returns a placeholder when no visible apps are found', () => {
120+
expect(AscApiUtils.formatAppsList([])).toBe(' (none)');
121+
});
122+
});
123+
99124
describe('createBuildUploadAsync', () => {
100125
it('throws UserError when ASC duplicate version error is returned', async () => {
101126
const payload = {

0 commit comments

Comments
 (0)