@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.
- package/README.md +12 -80
- package/dist/index.js +164 -156
- package/docs/DEVELOPMENT.md +112 -0
- package/package.json +1 -1
- package/src/commands/app-info.ts +32 -34
- package/src/commands/apps.ts +17 -14
- package/src/commands/availability.ts +17 -16
- package/src/commands/beta-review.ts +51 -61
- package/src/commands/builds.ts +37 -29
- package/src/commands/game-center.ts +56 -55
- package/src/commands/iap.ts +47 -53
- package/src/commands/localizations.ts +83 -0
- package/src/commands/metadata.ts +25 -18
- package/src/commands/phased-release.ts +85 -0
- package/src/commands/pricing.ts +34 -30
- package/src/commands/reports.ts +13 -10
- package/src/commands/review-details.ts +79 -0
- package/src/commands/review-submission.ts +143 -0
- package/src/commands/reviews.ts +36 -30
- package/src/commands/screenshots.ts +40 -51
- package/src/commands/signing.ts +80 -100
- package/src/commands/subscriptions.ts +55 -63
- package/src/commands/testflight.ts +34 -23
- package/src/commands/versions.ts +40 -18
- package/src/commands/xcode-cloud.ts +68 -65
- package/src/index.ts +313 -1
|
@@ -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
package/src/commands/app-info.ts
CHANGED
|
@@ -1,34 +1,32 @@
|
|
|
1
|
-
import {
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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(
|
|
25
|
-
for (const info of
|
|
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
|
|
29
|
-
console.log(`Age Rating: ${a
|
|
30
|
-
console.log(`Brazil Rating: ${a
|
|
31
|
-
console.log(`Kids Band: ${a
|
|
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
|
|
49
|
-
|
|
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
|
|
66
|
-
if (opts.platform)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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
|
|
100
|
-
|
|
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
|
})
|
package/src/commands/apps.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
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
|
|
7
|
-
|
|
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
|
|
21
|
-
a.attributes
|
|
22
|
-
a.attributes
|
|
23
|
-
a.attributes
|
|
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
|
|
31
|
-
const
|
|
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
|
|
41
|
-
console.log(`Bundle ID: ${app.attributes
|
|
42
|
-
console.log(`SKU: ${app.attributes
|
|
43
|
-
console.log(`Primary Locale: ${app.attributes
|
|
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 {
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
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
|
|
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
|
|
24
|
-
territories.map(t => [t.id, t.attributes?.
|
|
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
|
|
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
|
-
|
|
45
|
-
data: opts.territories.map(t => ({ type: "
|
|
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 {
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
27
|
-
|
|
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
|
|
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
|
|
49
|
-
|
|
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(
|
|
65
|
-
const a =
|
|
66
|
-
console.log(`Review Detail ID: ${
|
|
67
|
-
console.log(`Contact: ${a
|
|
68
|
-
console.log(`Contact Email: ${a
|
|
69
|
-
console.log(`Contact Phone: ${a
|
|
70
|
-
console.log(`Demo Account: ${a
|
|
71
|
-
if (a
|
|
72
|
-
console.log(`Demo Username: ${a
|
|
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
|
|
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
|
|
90
|
-
const
|
|
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
|
|
93
|
-
|
|
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
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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
|
}
|
package/src/commands/builds.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
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
|
|
12
|
-
"fields[builds]": "version,uploadedDate,processingState,expired",
|
|
13
|
-
|
|
17
|
+
const query: Record<string, unknown> = {
|
|
18
|
+
"fields[builds]": ["version", "uploadedDate", "processingState", "expired"],
|
|
19
|
+
limit: 200,
|
|
14
20
|
}
|
|
15
|
-
if (opts.appId)
|
|
16
|
-
if (opts.version)
|
|
17
|
-
if (opts.platform)
|
|
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
|
|
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
|
|
32
|
-
formatDate(b.attributes
|
|
33
|
-
b.attributes
|
|
34
|
-
b.attributes
|
|
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
|
|
42
|
-
const
|
|
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
|
|
52
|
-
console.log(`Uploaded: ${formatDate(b.attributes
|
|
53
|
-
console.log(`Processing State: ${b.attributes
|
|
54
|
-
console.log(`Expired: ${b.attributes
|
|
55
|
-
console.log(`Min OS: ${b.attributes
|
|
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
|
|
65
|
-
|
|
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 =
|
|
77
|
+
const existing = locs.find((l) => l.attributes?.locale === opts.locale)
|
|
69
78
|
|
|
70
79
|
if (existing) {
|
|
71
|
-
await
|
|
72
|
-
|
|
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
|
|
84
|
-
method: "POST",
|
|
92
|
+
await betaBuildLocalizationsCreateInstance({
|
|
85
93
|
body: {
|
|
86
94
|
data: {
|
|
87
95
|
type: "betaBuildLocalizations",
|