Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ko.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<p align="center">
<img src="https://img.shields.io/badge/store%20API-205%20endpoints-8957e5?logo=apple&logoColor=white" alt="205 App Store Connect &amp; Google Play API operations" />
<img src="https://img.shields.io/badge/CRUD-full%20lifecycle-1f6feb" alt="Full create / read / update / delete coverage across the store APIs" />
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2909%20passing-3fb950?logo=vitest&logoColor=white" alt="2909 tests passing" /></a>
<a href="https://github.com/YosefHayim/launch-store/actions/workflows/ci.yml"><img src="https://img.shields.io/badge/tests-2917%20passing-3fb950?logo=vitest&logoColor=white" alt="2917 tests passing" /></a>
</p>

<!-- stats-badges:end -->
Expand Down
2 changes: 1 addition & 1 deletion llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Each is declared in `launch.config.ts` (or a `*.config.json` sidecar) and reconc

## Commands

All 65 `launch` commands (205 store-API operations underneath, 2909 tests):
All 65 `launch` commands (205 store-API operations underneath, 2917 tests):

- `launch init` - scaffold launch.config.ts (and .env.example) into the current repo
- `launch adopt` - onboard an app that already ships: import its App Store Connect setup into config
Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Each is declared in `launch.config.ts` (or a `*.config.json` sidecar) and reconc

## Commands

All 65 `launch` commands (205 store-API operations underneath, 2909 tests):
All 65 `launch` commands (205 store-API operations underneath, 2917 tests):

- `launch init` - scaffold launch.config.ts (and .env.example) into the current repo
- `launch adopt` - onboard an app that already ships: import its App Store Connect setup into config
Expand Down
31 changes: 31 additions & 0 deletions src/apple/ascClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3362,3 +3362,34 @@ describe('findBundleId - exact identifier, not substring (#291)', () => {
expect(await client.findBundleId('com.example.sampleapp')).toBeNull();
});
});

describe('AppStoreConnectClient - app store versions', () => {
it('reads the attached build id from the build relationship, and asks Apple for it', async () => {
fetchMock.mockResolvedValueOnce(
fakeResponse(
200,
JSON.stringify({
data: [
{
id: 'v-edit',
attributes: { versionString: '1.2.0', appStoreState: 'PREPARE_FOR_SUBMISSION' },
relationships: { build: { data: { type: 'builds', id: 'b-42' } } },
},
{
id: 'v-empty',
attributes: { versionString: '1.1.0', appStoreState: 'READY_FOR_SALE' },
relationships: { build: { data: null } },
},
],
}),
),
);
const versions = await client.listAppStoreVersions('app1', 'IOS');
const [requestUrl] = expectArrayElement(fetchMock.mock.calls, 0, 'mock call');
expect(String(requestUrl)).toContain(
'fields[appStoreVersions]=versionString,appStoreState,releaseType,build',
);
expect(expectArrayElement(versions, 0, 'version').attachedBuildId).toBe('b-42');
expect(expectArrayElement(versions, 1, 'version').attachedBuildId).toBeUndefined();
});
});
48 changes: 23 additions & 25 deletions src/apple/ascClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -378,15 +378,21 @@ type ResourceSingle<A> = {
attributes: A;
};
};
/**
* One App Store Connect resource. `relationships` holds the `build` linkage only when the request named
* `build` in `fields`; Apple then returns its `data` linkage (null when no build is attached).
*/
type AppleResource<Attributes> = {
id: string;
attributes: Attributes;
relationships?: { build?: { data?: { id: string } | null } };
};
/**
* One page of a paginated collection: the page's `data` plus Apple's `links.next` - an absolute URL
* to the following page, present only while more pages remain. Consumed by {@link AppStoreConnectClient.requestAll}.
*/
type PagedList<A> = {
data: {
id: string;
attributes: A;
}[];
data: AppleResource<A>[];
links?: {
next?: string;
};
Expand Down Expand Up @@ -891,16 +897,8 @@ export class AppStoreConnectClient {
*/
// A is the caller-specified attributes shape of the returned rows; no argument infers it, so the param is required.
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
private async requestAll<A>(path: string): Promise<
{
id: string;
attributes: A;
}[]
> {
const all: {
id: string;
attributes: A;
}[] = [];
private async requestAll<A>(path: string): Promise<AppleResource<A>[]> {
const all: AppleResource<A>[] = [];
let next: string | undefined = path;
while (next) {
// biome-ignore lint/performance/noAwaitInLoops: cursor pagination - each page’s `next` link comes from the prior response, so the reads are inherently sequential
Expand Down Expand Up @@ -4043,7 +4041,7 @@ export class AppStoreConnectClient {
appStoreState?: string;
releaseType?: string;
}>(
`/apps/${appId}/appStoreVersions?filter[platform]=${platform}&limit=200&fields[appStoreVersions]=versionString,appStoreState,releaseType`,
`/apps/${appId}/appStoreVersions?filter[platform]=${platform}&limit=200&fields[appStoreVersions]=versionString,appStoreState,releaseType,build`,
);
return appleResources.map((entry) => toVersionResource(entry));
}
Expand Down Expand Up @@ -4974,27 +4972,27 @@ const toBuildResources = ({ data: builds, included = [] }: BuildPage): BuildReso
builds.map((entry) => toBuildResource(entry, included));
/** Map a raw version row onto an {@link AppStoreVersionResource}, keeping a known versionString fallback. */
const toVersionResource = (
entry: {
id: string;
attributes: {
versionString?: string;
appStoreState?: string;
releaseType?: string;
};
},
entry: AppleResource<{
versionString?: string;
appStoreState?: string;
releaseType?: string;
}>,
fallbackVersion = '',
): AppStoreVersionResource => {
let versionString = entry.attributes.versionString;
if (versionString === undefined) versionString = fallbackVersion;
let appStoreState = entry.attributes.appStoreState;
if (appStoreState === undefined) appStoreState = '';
const releaseType = entry.attributes.releaseType;
const attachedBuildId = entry.relationships?.build?.data?.id;
const isBuildAttached = attachedBuildId !== undefined;
const version: AppStoreVersionResource = {
id: entry.id,
versionString,
appStoreState,
...(releaseType !== undefined && { releaseType }),
...(isBuildAttached && { attachedBuildId }),
};
if (entry.attributes.releaseType !== undefined)
Object.assign(version, { releaseType: entry.attributes.releaseType });
return version;
};
/** Map a raw phased-release row onto a {@link PhasedReleaseResource}. */
Expand Down
5 changes: 5 additions & 0 deletions src/cli/commands/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ export const registerReleaseCommand = (program: Command): void => {
.option('--manual', 'iOS only - hold the approved build for manual release', false)
.option('--scheduled <iso>', 'iOS only - schedule the go-live at an ISO-8601 instant')
.option('--phased', "iOS only - opt into Apple's 7-day phased rollout", false)
.option(
'--attach-only',
'iOS only - attach the --build to the editable App Store version without submitting for review',
false,
)
.option('--dry-run', 'print the release plan (touches nothing) and exit', false)
.option(
'--create-app',
Expand Down
Loading
Loading