@onmyway133/asc-cli 1.0.2 → 1.0.3

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.
@@ -0,0 +1,112 @@
1
+ # Development
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/onmyway133/asc-cli
7
+ cd asc-cli
8
+ bun install
9
+ ```
10
+
11
+ ## Run during development
12
+
13
+ ```bash
14
+ bun run src/index.ts <command>
15
+ bun run src/index.ts auth list
16
+ bun run src/index.ts apps list --output json
17
+ ```
18
+
19
+ ## Type check
20
+
21
+ ```bash
22
+ bun run typecheck
23
+ ```
24
+
25
+ ## Tests
26
+
27
+ ```bash
28
+ bun test
29
+ ```
30
+
31
+ Tests use a temp `tests/tmp_home` directory — they never touch `~/.asc/credentials.json`. Always clean up test artifacts; don't run the CLI manually against real profiles to test behavior.
32
+
33
+ ## Build
34
+
35
+ ```bash
36
+ bun run build # outputs dist/index.js
37
+ ./dist/asc --help
38
+ ```
39
+
40
+ ## Regenerate API client
41
+
42
+ When Apple updates their OpenAPI spec:
43
+
44
+ ```bash
45
+ bun run generate
46
+ ```
47
+
48
+ This fetches the latest spec to `docs/openapi/latest.json`, regenerates `docs/openapi/paths.txt`, and re-runs `openapi-ts` to update `src/api/generated/`.
49
+
50
+ The snapshot is committed to the repo so the client can be built offline.
51
+
52
+ ## Browse API endpoints
53
+
54
+ ```bash
55
+ grep "apps" docs/openapi/paths.txt
56
+ grep "GET.*builds" docs/openapi/paths.txt
57
+ ```
58
+
59
+ ## Adding a new command
60
+
61
+ 1. Check `docs/API_COVERAGE.md` for uncovered endpoints
62
+ 2. Find the relevant function in `src/api/generated/sdk.gen.ts`
63
+ 3. Add a handler in `src/commands/<area>.ts` using the generated SDK function
64
+ 4. Wire up the command in `src/index.ts`
65
+ 5. Mark it covered in `docs/API_COVERAGE.md`
66
+
67
+ ## Project structure
68
+
69
+ ```
70
+ src/
71
+ index.ts # CLI entry point — all command definitions
72
+ auth/
73
+ credentials.ts # Load/save ~/.asc/credentials.json
74
+ jwt.ts # JWT generation + macOS Keychain (security CLI)
75
+ api/
76
+ client.ts # throwOnError / ASCError / ascFetch (fallback only)
77
+ hey-api-client.ts # hey-api client with auth interceptor
78
+ generated/ # Auto-generated SDK (do not edit)
79
+ commands/ # Business logic for each command group
80
+ utils/
81
+ output.ts # Table / JSON / plain formatters
82
+ help-spec.ts # Structured JSON help for AI agents
83
+ docs/
84
+ API_COVERAGE.md # Checklist of covered vs uncovered endpoints
85
+ openapi/
86
+ latest.json # Offline OpenAPI snapshot (committed)
87
+ paths.txt # Quick-grep index of all endpoints
88
+ ```
89
+
90
+ ## Known SDK gaps
91
+
92
+ Three endpoints have no generated SDK function and fall back to `ascFetch`/`ascFetchAll`. If Apple updates the OpenAPI spec to include them, run `bun run generate` and migrate:
93
+
94
+ | Endpoint | File | Reason |
95
+ |----------|------|--------|
96
+ | `POST /v1/appStoreVersionSubmissions` | `versions.ts` | Deprecated submission API — SDK only includes the `DELETE` variant |
97
+ | `PATCH /v1/customerReviewResponses/{id}` | `reviews.ts` | SDK has create/delete for review responses but no update function |
98
+ | `GET /v1/ciBuildRuns` | `xcode-cloud.ts` | Unfiltered collection; SDK only exposes filtered variants (`ciWorkflowsBuildRunsGetToManyRelated`) |
99
+ | `GET /v1/ciBuildRuns/{id}/artifacts` | `xcode-cloud.ts` | Sub-resource not present in generated SDK |
100
+ | `GET /v1/ciBuildRuns/{id}/testResults` | `xcode-cloud.ts` | Sub-resource not present in generated SDK |
101
+
102
+ ## Publish
103
+
104
+ ```bash
105
+ # Bump version in package.json, then:
106
+ bun run build
107
+ git add package.json
108
+ git commit -m "Release X.X.X"
109
+ git tag X.X.X
110
+ git push && git push --tags
111
+ npm publish --access public --otp=<your-otp>
112
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onmyway133/asc-cli",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "description": "App Store Connect CLI — manage apps, builds, TestFlight, metadata and more from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,34 +1,32 @@
1
- import { ascFetch, ascFetchAll } from "../api/client.ts"
1
+ import {
2
+ appsAppInfosGetToManyRelated,
3
+ appInfosUpdateInstance,
4
+ appCategoriesGetCollection,
5
+ ageRatingDeclarationsUpdateInstance,
6
+ } from "../api/generated/sdk.gen.ts"
7
+ import { throwOnError } from "../api/client.ts"
2
8
  import { detectFormat, printJSON, printTable } from "../utils/output.ts"
3
9
 
4
10
  // ---- App Info (content ratings, age ratings, primary/secondary categories) ----
5
11
 
6
12
  export async function appInfoGet(appId: string, opts: { output?: string } = {}): Promise<void> {
7
- const result = await ascFetch<{
8
- data: Array<{
9
- id: string
10
- attributes: {
11
- appStoreState?: string
12
- appStoreAgeRating?: string
13
- brazilAgeRating?: string
14
- kidsAgeBand?: string
15
- }
16
- }>
17
- }>(`/v1/apps/${appId}/appInfos`, {
18
- params: {
19
- include: "primaryCategory,secondaryCategory",
20
- "fields[appInfos]": "appStoreState,appStoreAgeRating,brazilAgeRating,kidsAgeBand",
13
+ const result = await appsAppInfosGetToManyRelated({
14
+ path: { id: appId },
15
+ query: {
16
+ include: ["primaryCategory", "secondaryCategory"],
17
+ "fields[appInfos]": ["appStoreState", "appStoreAgeRating", "brazilAgeRating", "kidsAgeBand"],
21
18
  },
22
19
  })
20
+ const data = throwOnError(result)
23
21
  const fmt = detectFormat(opts.output)
24
- if (fmt === "json") { printJSON(result.data); return }
25
- for (const info of result.data.data) {
22
+ if (fmt === "json") { printJSON(data); return }
23
+ for (const info of data.data) {
26
24
  const a = info.attributes
27
25
  console.log(`Info ID: ${info.id}`)
28
- console.log(`State: ${a.appStoreState ?? "-"}`)
29
- console.log(`Age Rating: ${a.appStoreAgeRating ?? "-"}`)
30
- console.log(`Brazil Rating: ${a.brazilAgeRating ?? "-"}`)
31
- console.log(`Kids Band: ${a.kidsAgeBand ?? "-"}`)
26
+ console.log(`State: ${a?.appStoreState ?? "-"}`)
27
+ console.log(`Age Rating: ${a?.appStoreAgeRating ?? "-"}`)
28
+ console.log(`Brazil Rating: ${a?.brazilAgeRating ?? "-"}`)
29
+ console.log(`Kids Band: ${a?.kidsAgeBand ?? "-"}`)
32
30
  console.log("")
33
31
  }
34
32
  }
@@ -45,13 +43,13 @@ export async function appInfoUpdate(opts: {
45
43
  if (opts.secondaryCategoryId) {
46
44
  relationships["secondaryCategory"] = { data: { type: "appCategories", id: opts.secondaryCategoryId } }
47
45
  }
48
- await ascFetch(`/v1/appInfos/${opts.infoId}`, {
49
- method: "PATCH",
46
+ await appInfosUpdateInstance({
47
+ path: { id: opts.infoId },
50
48
  body: {
51
49
  data: {
52
50
  type: "appInfos",
53
51
  id: opts.infoId,
54
- relationships,
52
+ relationships: relationships as never,
55
53
  },
56
54
  },
57
55
  })
@@ -62,17 +60,17 @@ export async function appCategoriesList(opts: {
62
60
  platform?: string
63
61
  output?: string
64
62
  } = {}): Promise<void> {
65
- const params: Record<string, string> = {}
66
- if (opts.platform) params["filter[platforms]"] = opts.platform
67
- const items = await ascFetchAll<{
68
- id: string
69
- attributes: { platforms?: string[] }
70
- }>("/v1/appCategories", { params })
63
+ const query: Record<string, unknown> = {}
64
+ if (opts.platform) query["filter[platforms]"] = [opts.platform]
65
+
66
+ const result = await appCategoriesGetCollection({ query: query as never })
67
+ const data = throwOnError(result)
68
+ const items = data.data
71
69
  const fmt = detectFormat(opts.output)
72
70
  if (fmt === "json") { printJSON(items); return }
73
71
  printTable(
74
72
  ["Category ID", "Platforms"],
75
- items.map(c => [c.id, (c.attributes.platforms ?? []).join(", ")]),
73
+ items.map(c => [c.id, (c.attributes?.platforms ?? []).join(", ")]),
76
74
  `App Categories (${items.length})`,
77
75
  )
78
76
  }
@@ -96,13 +94,13 @@ export async function ageRatingDeclarationUpdate(opts: {
96
94
  violenceRealistic?: string
97
95
  }): Promise<void> {
98
96
  const { declarationId, ...attributes } = opts
99
- await ascFetch(`/v1/ageRatingDeclarations/${declarationId}`, {
100
- method: "PATCH",
97
+ await ageRatingDeclarationsUpdateInstance({
98
+ path: { id: declarationId },
101
99
  body: {
102
100
  data: {
103
101
  type: "ageRatingDeclarations",
104
102
  id: declarationId,
105
- attributes,
103
+ attributes: attributes as never,
106
104
  },
107
105
  },
108
106
  })
@@ -1,11 +1,13 @@
1
- import { ascFetch, ascFetchAll } from "../api/client.ts"
2
- import type { App } from "../api/types.ts"
1
+ import { appsGetCollection, appsGetInstance } from "../api/generated/sdk.gen.ts"
2
+ import { throwOnError } from "../api/client.ts"
3
3
  import { detectFormat, formatDate, printJSON, printTable, truncate } from "../utils/output.ts"
4
4
 
5
5
  export async function appsList(opts: { limit?: number; output?: string } = {}): Promise<void> {
6
- const apps = await ascFetchAll<App>("/v1/apps", {
7
- params: { "fields[apps]": "name,bundleId,sku,primaryLocale" },
6
+ const result = await appsGetCollection({
7
+ query: { "fields[apps]": ["name", "bundleId", "sku", "primaryLocale"], limit: 200 },
8
8
  })
9
+ const data = throwOnError(result)
10
+ const apps = data.data
9
11
 
10
12
  const fmt = detectFormat(opts.output)
11
13
  if (fmt === "json") {
@@ -17,18 +19,19 @@ export async function appsList(opts: { limit?: number; output?: string } = {}):
17
19
  ["ID", "Name", "Bundle ID", "SKU", "Locale"],
18
20
  apps.map((a) => [
19
21
  a.id,
20
- truncate(a.attributes.name, 40),
21
- a.attributes.bundleId,
22
- a.attributes.sku ?? "-",
23
- a.attributes.primaryLocale,
22
+ truncate(a.attributes?.name ?? "-", 40),
23
+ a.attributes?.bundleId ?? "-",
24
+ a.attributes?.sku ?? "-",
25
+ a.attributes?.primaryLocale ?? "-",
24
26
  ]),
25
27
  `Apps (${apps.length})`,
26
28
  )
27
29
  }
28
30
 
29
31
  export async function appsGet(appId: string, opts: { output?: string } = {}): Promise<void> {
30
- const res = await ascFetch<App>(`/v1/apps/${appId}`)
31
- const app = res.data
32
+ const result = await appsGetInstance({ path: { id: appId } })
33
+ const data = throwOnError(result)
34
+ const app = data.data
32
35
 
33
36
  const fmt = detectFormat(opts.output)
34
37
  if (fmt === "json") {
@@ -37,8 +40,8 @@ export async function appsGet(appId: string, opts: { output?: string } = {}): Pr
37
40
  }
38
41
 
39
42
  console.log(`ID: ${app.id}`)
40
- console.log(`Name: ${app.attributes.name}`)
41
- console.log(`Bundle ID: ${app.attributes.bundleId}`)
42
- console.log(`SKU: ${app.attributes.sku ?? "-"}`)
43
- console.log(`Primary Locale: ${app.attributes.primaryLocale}`)
43
+ console.log(`Name: ${app.attributes?.name ?? "-"}`)
44
+ console.log(`Bundle ID: ${app.attributes?.bundleId ?? "-"}`)
45
+ console.log(`SKU: ${app.attributes?.sku ?? "-"}`)
46
+ console.log(`Primary Locale: ${app.attributes?.primaryLocale ?? "-"}`)
44
47
  }
@@ -1,27 +1,29 @@
1
- import { ascFetch, ascFetchAll } from "../api/client.ts"
1
+ import {
2
+ appsAppAvailabilityV2GetToOneRelated,
3
+ appAvailabilitiesV2CreateInstance,
4
+ } from "../api/generated/sdk.gen.ts"
5
+ import { throwOnError } from "../api/client.ts"
2
6
  import { detectFormat, printJSON, printTable } from "../utils/output.ts"
3
7
 
4
8
  export async function availabilityGet(appId: string, opts: { output?: string } = {}): Promise<void> {
5
- const result = await ascFetch<{
6
- data: { id: string; attributes: { availableInNewTerritories?: boolean } }
7
- included?: Array<{ type: string; id: string; attributes?: { currency?: string; name?: string } }>
8
- }>(`/v1/apps/${appId}/appAvailabilityV2`, {
9
- params: {
10
- include: "availableTerritories",
11
- "fields[territories]": "currency,name",
9
+ const result = await appsAppAvailabilityV2GetToOneRelated({
10
+ path: { id: appId },
11
+ query: {
12
+ include: ["territoryAvailabilities"],
12
13
  },
13
14
  })
14
- const body = result.data
15
+ const body = throwOnError(result)
15
16
  const fmt = detectFormat(opts.output)
16
17
  if (fmt === "json") { printJSON(body); return }
17
18
  console.log(`App ID: ${appId}`)
18
19
  console.log(`Available in new territories: ${body.data.attributes?.availableInNewTerritories ?? "-"}`)
19
- const territories = (body.included ?? []).filter(r => r.type === "territories")
20
+ const included = (body as { data: unknown; included?: Array<{ type: string; id: string; attributes?: { territory?: string | null } }> }).included ?? []
21
+ const territories = included.filter(r => r.type === "territoryAvailabilities")
20
22
  if (territories.length) {
21
23
  console.log(`\nEnabled Territories (${territories.length}):`)
22
24
  printTable(
23
- ["Territory Code", "Name", "Currency"],
24
- territories.map(t => [t.id, t.attributes?.name ?? "-", t.attributes?.currency ?? "-"]),
25
+ ["Territory Availability ID", "Territory"],
26
+ territories.map(t => [t.id, t.attributes?.territory ?? "-"]),
25
27
  )
26
28
  }
27
29
  }
@@ -31,8 +33,7 @@ export async function availabilityTerritoriesSet(opts: {
31
33
  territories: string[]
32
34
  availableInNewTerritories?: boolean
33
35
  }): Promise<void> {
34
- await ascFetch(`/v1/appAvailabilities`, {
35
- method: "POST",
36
+ await appAvailabilitiesV2CreateInstance({
36
37
  body: {
37
38
  data: {
38
39
  type: "appAvailabilities",
@@ -41,8 +42,8 @@ export async function availabilityTerritoriesSet(opts: {
41
42
  },
42
43
  relationships: {
43
44
  app: { data: { type: "apps", id: opts.appId } },
44
- availableTerritories: {
45
- data: opts.territories.map(t => ({ type: "territories", id: t })),
45
+ territoryAvailabilities: {
46
+ data: opts.territories.map(t => ({ type: "territoryAvailabilities" as const, id: t })),
46
47
  },
47
48
  },
48
49
  },
@@ -1,35 +1,39 @@
1
- import { ascFetch, ascFetchAll } from "../api/client.ts"
1
+ import {
2
+ betaAppReviewSubmissionsCreateInstance,
3
+ betaAppReviewSubmissionsGetCollection,
4
+ appsBetaAppReviewDetailGetToOneRelated,
5
+ betaAppReviewDetailsUpdateInstance,
6
+ buildsBuildBetaDetailGetToOneRelated,
7
+ buildBetaDetailsUpdateInstance,
8
+ buildsBetaBuildLocalizationsGetToManyRelated,
9
+ } from "../api/generated/sdk.gen.ts"
10
+ import { throwOnError } from "../api/client.ts"
2
11
  import { detectFormat, formatDate, printJSON, printSuccess, printTable, truncate } from "../utils/output.ts"
3
12
 
4
13
  // ---- Beta App Review Submissions ----
5
14
 
6
15
  export async function betaReviewSubmissionCreate(buildId: string): Promise<void> {
7
- const result = await ascFetch<{ data: { id: string; attributes: { betaReviewState?: string } } }>(
8
- "/v1/betaAppReviewSubmissions",
9
- {
10
- method: "POST",
11
- body: {
12
- data: {
13
- type: "betaAppReviewSubmissions",
14
- relationships: {
15
- build: { data: { type: "builds", id: buildId } },
16
- },
16
+ const result = await betaAppReviewSubmissionsCreateInstance({
17
+ body: {
18
+ data: {
19
+ type: "betaAppReviewSubmissions",
20
+ relationships: {
21
+ build: { data: { type: "builds", id: buildId } },
17
22
  },
18
23
  },
19
24
  },
20
- )
21
- const state = result.data.data.attributes.betaReviewState
25
+ })
26
+ const data = throwOnError(result)
27
+ const state = data.data.attributes?.betaReviewState
22
28
  printSuccess(`Beta review submitted for build ${buildId} (state: ${state ?? "WAITING_FOR_REVIEW"})`)
23
29
  }
24
30
 
25
31
  export async function betaReviewSubmissionGet(buildId: string, opts: { output?: string } = {}): Promise<void> {
26
- const items = await ascFetchAll<{
27
- id: string
28
- attributes: { betaReviewState?: string }
29
- relationships?: { build?: { data?: { id: string } } }
30
- }>("/v1/betaAppReviewSubmissions", {
31
- params: { "filter[build]": buildId },
32
+ const result = await betaAppReviewSubmissionsGetCollection({
33
+ query: { "filter[build]": [buildId] },
32
34
  })
35
+ const data = throwOnError(result)
36
+ const items = data.data
33
37
  const fmt = detectFormat(opts.output)
34
38
  if (fmt === "json") { printJSON(items); return }
35
39
  if (items.length === 0) {
@@ -38,40 +42,27 @@ export async function betaReviewSubmissionGet(buildId: string, opts: { output?:
38
42
  }
39
43
  for (const s of items) {
40
44
  console.log(`Submission ID: ${s.id}`)
41
- console.log(`State: ${s.attributes.betaReviewState ?? "-"}`)
45
+ console.log(`State: ${s.attributes?.betaReviewState ?? "-"}`)
42
46
  }
43
47
  }
44
48
 
45
49
  // ---- Beta App Review Details ----
46
50
 
47
51
  export async function betaReviewDetailGet(appId: string, opts: { output?: string } = {}): Promise<void> {
48
- const result = await ascFetch<{
49
- data: {
50
- id: string
51
- attributes: {
52
- contactFirstName?: string
53
- contactLastName?: string
54
- contactPhone?: string
55
- contactEmail?: string
56
- demoAccountName?: string
57
- demoAccountPassword?: string
58
- demoAccountRequired?: boolean
59
- notes?: string
60
- }
61
- }
62
- }>(`/v1/apps/${appId}/betaAppReviewDetail`)
52
+ const result = await appsBetaAppReviewDetailGetToOneRelated({ path: { id: appId } })
53
+ const data = throwOnError(result)
63
54
  const fmt = detectFormat(opts.output)
64
- if (fmt === "json") { printJSON(result.data.data); return }
65
- const a = result.data.data.attributes
66
- console.log(`Review Detail ID: ${result.data.data.id}`)
67
- console.log(`Contact: ${a.contactFirstName ?? ""} ${a.contactLastName ?? ""}`)
68
- console.log(`Contact Email: ${a.contactEmail ?? "-"}`)
69
- console.log(`Contact Phone: ${a.contactPhone ?? "-"}`)
70
- console.log(`Demo Account: ${a.demoAccountRequired ? "Required" : "Not required"}`)
71
- if (a.demoAccountRequired) {
72
- console.log(`Demo Username: ${a.demoAccountName ?? "-"}`)
55
+ if (fmt === "json") { printJSON(data.data); return }
56
+ const a = data.data.attributes
57
+ console.log(`Review Detail ID: ${data.data.id}`)
58
+ console.log(`Contact: ${a?.contactFirstName ?? ""} ${a?.contactLastName ?? ""}`)
59
+ console.log(`Contact Email: ${a?.contactEmail ?? "-"}`)
60
+ console.log(`Contact Phone: ${a?.contactPhone ?? "-"}`)
61
+ console.log(`Demo Account: ${a?.demoAccountRequired ? "Required" : "Not required"}`)
62
+ if (a?.demoAccountRequired) {
63
+ console.log(`Demo Username: ${a?.demoAccountName ?? "-"}`)
73
64
  }
74
- if (a.notes) console.log(`Notes: ${a.notes}`)
65
+ if (a?.notes) console.log(`Notes: ${a.notes}`)
75
66
  }
76
67
 
77
68
  export async function betaReviewDetailUpdate(opts: {
@@ -86,16 +77,17 @@ export async function betaReviewDetailUpdate(opts: {
86
77
  notes?: string
87
78
  }): Promise<void> {
88
79
  // First get the detail ID
89
- const current = await ascFetch<{ data: { id: string } }>(`/v1/apps/${opts.appId}/betaAppReviewDetail`)
90
- const detailId = current.data.data.id
80
+ const current = await appsBetaAppReviewDetailGetToOneRelated({ path: { id: opts.appId } })
81
+ const currentData = throwOnError(current)
82
+ const detailId = currentData.data.id
91
83
  const { appId, ...attributes } = opts
92
- await ascFetch(`/v1/betaAppReviewDetails/${detailId}`, {
93
- method: "PATCH",
84
+ await betaAppReviewDetailsUpdateInstance({
85
+ path: { id: detailId },
94
86
  body: {
95
87
  data: {
96
88
  type: "betaAppReviewDetails",
97
89
  id: detailId,
98
- attributes,
90
+ attributes: attributes as never,
99
91
  },
100
92
  },
101
93
  })
@@ -108,12 +100,11 @@ export async function buildBetaDetailUpdate(opts: {
108
100
  buildId: string
109
101
  autoNotifyEnabled?: boolean
110
102
  }): Promise<void> {
111
- const current = await ascFetch<{
112
- data: { id: string }
113
- }>(`/v1/builds/${opts.buildId}/buildBetaDetail`)
114
- const detailId = current.data.data.id
115
- await ascFetch(`/v1/buildBetaDetails/${detailId}`, {
116
- method: "PATCH",
103
+ const current = await buildsBuildBetaDetailGetToOneRelated({ path: { id: opts.buildId } })
104
+ const currentData = throwOnError(current)
105
+ const detailId = currentData.data.id
106
+ await buildBetaDetailsUpdateInstance({
107
+ path: { id: detailId },
117
108
  body: {
118
109
  data: {
119
110
  type: "buildBetaDetails",
@@ -131,15 +122,14 @@ export async function betaBuildLocalizationsList(opts: {
131
122
  buildId: string
132
123
  output?: string
133
124
  }): Promise<void> {
134
- const items = await ascFetchAll<{
135
- id: string
136
- attributes: { locale?: string; whatsNew?: string }
137
- }>(`/v1/builds/${opts.buildId}/betaBuildLocalizations`)
125
+ const result = await buildsBetaBuildLocalizationsGetToManyRelated({ path: { id: opts.buildId } })
126
+ const data = throwOnError(result)
127
+ const items = data.data
138
128
  const fmt = detectFormat(opts.output)
139
129
  if (fmt === "json") { printJSON(items); return }
140
130
  printTable(
141
131
  ["ID", "Locale", "What's New"],
142
- items.map(l => [l.id, l.attributes.locale ?? "-", truncate(l.attributes.whatsNew ?? "-", 60)]),
132
+ items.map(l => [l.id, l.attributes?.locale ?? "-", truncate(l.attributes?.whatsNew ?? "-", 60)]),
143
133
  `Beta Build Localizations (${items.length})`,
144
134
  )
145
135
  }
@@ -1,6 +1,12 @@
1
- import { ascFetch, ascFetchAll } from "../api/client.ts"
2
- import type { Build } from "../api/types.ts"
3
- import { detectFormat, formatDate, printJSON, printSuccess, printTable, truncate } from "../utils/output.ts"
1
+ import {
2
+ buildsGetCollection,
3
+ buildsGetInstance,
4
+ buildsBetaBuildLocalizationsGetToManyRelated,
5
+ betaBuildLocalizationsUpdateInstance,
6
+ betaBuildLocalizationsCreateInstance,
7
+ } from "../api/generated/sdk.gen.ts"
8
+ import { throwOnError } from "../api/client.ts"
9
+ import { detectFormat, formatDate, printJSON, printSuccess, printTable } from "../utils/output.ts"
4
10
 
5
11
  export async function buildsList(opts: {
6
12
  appId?: string
@@ -8,15 +14,17 @@ export async function buildsList(opts: {
8
14
  platform?: string
9
15
  output?: string
10
16
  } = {}): Promise<void> {
11
- const params: Record<string, string> = {
12
- "fields[builds]": "version,uploadedDate,processingState,expired",
13
- "limit": "200",
17
+ const query: Record<string, unknown> = {
18
+ "fields[builds]": ["version", "uploadedDate", "processingState", "expired"],
19
+ limit: 200,
14
20
  }
15
- if (opts.appId) params["filter[app]"] = opts.appId
16
- if (opts.version) params["filter[version]"] = opts.version
17
- if (opts.platform) params["filter[platform]"] = opts.platform.toUpperCase()
21
+ if (opts.appId) query["filter[app]"] = [opts.appId]
22
+ if (opts.version) query["filter[version]"] = [opts.version]
23
+ if (opts.platform) query["filter[preReleaseVersion.platform]"] = [opts.platform.toUpperCase()]
18
24
 
19
- const builds = await ascFetchAll<Build>("/v1/builds", { params })
25
+ const result = await buildsGetCollection({ query } as Parameters<typeof buildsGetCollection>[0])
26
+ const data = throwOnError(result)
27
+ const builds = data.data
20
28
 
21
29
  const fmt = detectFormat(opts.output)
22
30
  if (fmt === "json") {
@@ -28,18 +36,19 @@ export async function buildsList(opts: {
28
36
  ["ID", "Version", "Uploaded", "State", "Expired"],
29
37
  builds.map((b) => [
30
38
  b.id,
31
- b.attributes.version,
32
- formatDate(b.attributes.uploadedDate),
33
- b.attributes.processingState ?? "-",
34
- b.attributes.expired ? "yes" : "no",
39
+ b.attributes?.version ?? "-",
40
+ formatDate(b.attributes?.uploadedDate ?? ""),
41
+ b.attributes?.processingState ?? "-",
42
+ b.attributes?.expired ? "yes" : "no",
35
43
  ]),
36
44
  `Builds (${builds.length})`,
37
45
  )
38
46
  }
39
47
 
40
48
  export async function buildsGet(buildId: string, opts: { output?: string } = {}): Promise<void> {
41
- const res = await ascFetch<Build>(`/v1/builds/${buildId}`)
42
- const b = res.data
49
+ const result = await buildsGetInstance({ path: { id: buildId } })
50
+ const data = throwOnError(result)
51
+ const b = data.data
43
52
 
44
53
  const fmt = detectFormat(opts.output)
45
54
  if (fmt === "json") {
@@ -48,11 +57,11 @@ export async function buildsGet(buildId: string, opts: { output?: string } = {})
48
57
  }
49
58
 
50
59
  console.log(`ID: ${b.id}`)
51
- console.log(`Version: ${b.attributes.version}`)
52
- console.log(`Uploaded: ${formatDate(b.attributes.uploadedDate)}`)
53
- console.log(`Processing State: ${b.attributes.processingState ?? "-"}`)
54
- console.log(`Expired: ${b.attributes.expired ? "yes" : "no"}`)
55
- console.log(`Min OS: ${b.attributes.minOsVersion ?? "-"}`)
60
+ console.log(`Version: ${b.attributes?.version ?? "-"}`)
61
+ console.log(`Uploaded: ${formatDate(b.attributes?.uploadedDate ?? "")}`)
62
+ console.log(`Processing State: ${b.attributes?.processingState ?? "-"}`)
63
+ console.log(`Expired: ${b.attributes?.expired ? "yes" : "no"}`)
64
+ console.log(`Min OS: ${b.attributes?.minOsVersion ?? "-"}`)
56
65
  }
57
66
 
58
67
  export async function buildsUpdateBetaNotes(opts: {
@@ -61,15 +70,15 @@ export async function buildsUpdateBetaNotes(opts: {
61
70
  notes: string
62
71
  }): Promise<void> {
63
72
  // First get existing localizations
64
- const res = await ascFetchAll<{ type: string; id: string; attributes: { locale: string; whatsNew?: string } }>(
65
- `/v1/builds/${opts.buildId}/betaBuildLocalizations`,
66
- )
73
+ const listResult = await buildsBetaBuildLocalizationsGetToManyRelated({ path: { id: opts.buildId } })
74
+ const listData = throwOnError(listResult)
75
+ const locs = listData.data
67
76
 
68
- const existing = res.find((l) => l.attributes.locale === opts.locale)
77
+ const existing = locs.find((l) => l.attributes?.locale === opts.locale)
69
78
 
70
79
  if (existing) {
71
- await ascFetch(`/v1/betaBuildLocalizations/${existing.id}`, {
72
- method: "PATCH",
80
+ await betaBuildLocalizationsUpdateInstance({
81
+ path: { id: existing.id },
73
82
  body: {
74
83
  data: {
75
84
  type: "betaBuildLocalizations",
@@ -80,8 +89,7 @@ export async function buildsUpdateBetaNotes(opts: {
80
89
  })
81
90
  printSuccess(`Beta notes updated for locale "${opts.locale}"`)
82
91
  } else {
83
- await ascFetch("/v1/betaBuildLocalizations", {
84
- method: "POST",
92
+ await betaBuildLocalizationsCreateInstance({
85
93
  body: {
86
94
  data: {
87
95
  type: "betaBuildLocalizations",