@onmyway133/asc-cli 1.0.1
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 +299 -0
- package/bun.lock +231 -0
- package/dist/index.js +179 -0
- package/docs/API_COVERAGE.md +355 -0
- package/docs/openapi/latest.json +230597 -0
- package/docs/openapi/paths.txt +1208 -0
- package/openapi-ts.config.ts +25 -0
- package/package.json +32 -0
- package/scripts/gen-paths-index.py +24 -0
- package/src/api/client.ts +132 -0
- package/src/api/generated/client/client.gen.ts +298 -0
- package/src/api/generated/client/index.ts +25 -0
- package/src/api/generated/client/types.gen.ts +214 -0
- package/src/api/generated/client/utils.gen.ts +316 -0
- package/src/api/generated/client.gen.ts +16 -0
- package/src/api/generated/core/auth.gen.ts +41 -0
- package/src/api/generated/core/bodySerializer.gen.ts +82 -0
- package/src/api/generated/core/params.gen.ts +169 -0
- package/src/api/generated/core/pathSerializer.gen.ts +171 -0
- package/src/api/generated/core/queryKeySerializer.gen.ts +117 -0
- package/src/api/generated/core/serverSentEvents.gen.ts +242 -0
- package/src/api/generated/core/types.gen.ts +104 -0
- package/src/api/generated/core/utils.gen.ts +140 -0
- package/src/api/generated/index.ts +4 -0
- package/src/api/generated/sdk.gen.ts +11701 -0
- package/src/api/generated/types.gen.ts +92035 -0
- package/src/api/hey-api-client.ts +20 -0
- package/src/api/types.ts +160 -0
- package/src/auth/credentials.ts +125 -0
- package/src/auth/jwt.ts +118 -0
- package/src/commands/app-info.ts +110 -0
- package/src/commands/apps.ts +44 -0
- package/src/commands/auth.ts +327 -0
- package/src/commands/availability.ts +52 -0
- package/src/commands/beta-review.ts +145 -0
- package/src/commands/builds.ts +97 -0
- package/src/commands/game-center.ts +114 -0
- package/src/commands/iap.ts +105 -0
- package/src/commands/metadata.ts +81 -0
- package/src/commands/pricing.ts +110 -0
- package/src/commands/reports.ts +116 -0
- package/src/commands/reviews.ts +93 -0
- package/src/commands/screenshots.ts +139 -0
- package/src/commands/signing.ts +214 -0
- package/src/commands/subscriptions.ts +144 -0
- package/src/commands/testflight.ts +110 -0
- package/src/commands/versions.ts +76 -0
- package/src/commands/xcode-cloud.ts +207 -0
- package/src/index.ts +1661 -0
- package/src/utils/help-spec.ts +835 -0
- package/src/utils/output.ts +79 -0
- package/tests/auth.test.ts +105 -0
- package/tests/client.test.ts +22 -0
- package/tests/output.test.ts +36 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ascFetch, ascFetchAll } from "../api/client.ts"
|
|
2
|
+
import { readFileSync } from "fs"
|
|
3
|
+
import { detectFormat, printJSON, printSuccess, printTable } from "../utils/output.ts"
|
|
4
|
+
|
|
5
|
+
// ---- App Screenshots ----
|
|
6
|
+
|
|
7
|
+
export async function screenshotSetsList(opts: {
|
|
8
|
+
versionLocalizationId: string
|
|
9
|
+
output?: string
|
|
10
|
+
}): Promise<void> {
|
|
11
|
+
const items = await ascFetchAll<{
|
|
12
|
+
id: string
|
|
13
|
+
attributes: {
|
|
14
|
+
screenshotDisplayType?: string
|
|
15
|
+
appScreenshots?: { meta?: { total?: number } }
|
|
16
|
+
}
|
|
17
|
+
}>(`/v1/appStoreVersionLocalizations/${opts.versionLocalizationId}/appScreenshotSets`, {
|
|
18
|
+
params: { "fields[appScreenshotSets]": "screenshotDisplayType,appScreenshots" },
|
|
19
|
+
})
|
|
20
|
+
const fmt = detectFormat(opts.output)
|
|
21
|
+
if (fmt === "json") { printJSON(items); return }
|
|
22
|
+
printTable(
|
|
23
|
+
["Set ID", "Display Type"],
|
|
24
|
+
items.map(s => [s.id, s.attributes.screenshotDisplayType ?? "-"]),
|
|
25
|
+
`Screenshot Sets (${items.length})`,
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function screenshotsList(opts: {
|
|
30
|
+
setId: string
|
|
31
|
+
output?: string
|
|
32
|
+
}): Promise<void> {
|
|
33
|
+
const items = await ascFetchAll<{
|
|
34
|
+
id: string
|
|
35
|
+
attributes: {
|
|
36
|
+
fileName?: string
|
|
37
|
+
fileSize?: number
|
|
38
|
+
sourceFileChecksum?: string
|
|
39
|
+
assetDeliveryState?: { state?: string }
|
|
40
|
+
imageAsset?: { templateUrl?: string; width?: number; height?: number }
|
|
41
|
+
}
|
|
42
|
+
}>(`/v1/appScreenshotSets/${opts.setId}/appScreenshots`)
|
|
43
|
+
const fmt = detectFormat(opts.output)
|
|
44
|
+
if (fmt === "json") { printJSON(items); return }
|
|
45
|
+
printTable(
|
|
46
|
+
["ID", "File Name", "State"],
|
|
47
|
+
items.map(s => [
|
|
48
|
+
s.id,
|
|
49
|
+
s.attributes.fileName ?? "-",
|
|
50
|
+
s.attributes.assetDeliveryState?.state ?? "-",
|
|
51
|
+
]),
|
|
52
|
+
`Screenshots (${items.length})`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function screenshotCreate(opts: {
|
|
57
|
+
setId: string
|
|
58
|
+
filePath: string
|
|
59
|
+
}): Promise<void> {
|
|
60
|
+
const fileData = readFileSync(opts.filePath)
|
|
61
|
+
const fileName = opts.filePath.split("/").pop() ?? "screenshot.png"
|
|
62
|
+
const fileSize = fileData.length
|
|
63
|
+
|
|
64
|
+
// Step 1: Reserve asset
|
|
65
|
+
const reserve = await ascFetch<{
|
|
66
|
+
data: {
|
|
67
|
+
id: string
|
|
68
|
+
attributes: {
|
|
69
|
+
uploadOperations?: Array<{
|
|
70
|
+
url: string
|
|
71
|
+
method: string
|
|
72
|
+
offset: number
|
|
73
|
+
length: number
|
|
74
|
+
requestHeaders?: Array<{ name: string; value: string }>
|
|
75
|
+
}>
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}>("/v1/appScreenshots", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
body: {
|
|
81
|
+
data: {
|
|
82
|
+
type: "appScreenshots",
|
|
83
|
+
attributes: { fileName, fileSize },
|
|
84
|
+
relationships: {
|
|
85
|
+
appScreenshotSet: { data: { type: "appScreenshotSets", id: opts.setId } },
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
const screenshotId = reserve.data.data.id
|
|
92
|
+
const uploads = reserve.data.data.attributes.uploadOperations ?? []
|
|
93
|
+
|
|
94
|
+
// Step 2: Upload parts
|
|
95
|
+
for (const op of uploads) {
|
|
96
|
+
const chunk = fileData.subarray(op.offset, op.offset + op.length)
|
|
97
|
+
const headers: Record<string, string> = { "Content-Type": "image/png" }
|
|
98
|
+
for (const h of op.requestHeaders ?? []) headers[h.name] = h.value
|
|
99
|
+
await fetch(op.url, { method: op.method, headers, body: chunk })
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Step 3: Commit
|
|
103
|
+
await ascFetch(`/v1/appScreenshots/${screenshotId}`, {
|
|
104
|
+
method: "PATCH",
|
|
105
|
+
body: {
|
|
106
|
+
data: {
|
|
107
|
+
type: "appScreenshots",
|
|
108
|
+
id: screenshotId,
|
|
109
|
+
attributes: { uploaded: true },
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
printSuccess(`Screenshot uploaded: ${screenshotId}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function screenshotDelete(screenshotId: string): Promise<void> {
|
|
118
|
+
await ascFetch(`/v1/appScreenshots/${screenshotId}`, { method: "DELETE" })
|
|
119
|
+
printSuccess(`Screenshot ${screenshotId} deleted`)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---- App Preview Sets ----
|
|
123
|
+
|
|
124
|
+
export async function previewSetsList(opts: {
|
|
125
|
+
versionLocalizationId: string
|
|
126
|
+
output?: string
|
|
127
|
+
}): Promise<void> {
|
|
128
|
+
const items = await ascFetchAll<{
|
|
129
|
+
id: string
|
|
130
|
+
attributes: { previewType?: string }
|
|
131
|
+
}>(`/v1/appStoreVersionLocalizations/${opts.versionLocalizationId}/appPreviewSets`)
|
|
132
|
+
const fmt = detectFormat(opts.output)
|
|
133
|
+
if (fmt === "json") { printJSON(items); return }
|
|
134
|
+
printTable(
|
|
135
|
+
["Set ID", "Preview Type"],
|
|
136
|
+
items.map(s => [s.id, s.attributes.previewType ?? "-"]),
|
|
137
|
+
`Preview Sets (${items.length})`,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ascFetch, ascFetchAll } from "../api/client.ts"
|
|
2
|
+
import { detectFormat, formatDate, printJSON, printSuccess, printTable } from "../utils/output.ts"
|
|
3
|
+
|
|
4
|
+
// ---- Certificates ----
|
|
5
|
+
|
|
6
|
+
export async function certificatesList(opts: {
|
|
7
|
+
type?: string
|
|
8
|
+
output?: string
|
|
9
|
+
} = {}): Promise<void> {
|
|
10
|
+
const params: Record<string, string> = {
|
|
11
|
+
"fields[certificates]": "certificateType,displayName,expirationDate,serialNumber,platform",
|
|
12
|
+
}
|
|
13
|
+
if (opts.type) params["filter[certificateType]"] = opts.type
|
|
14
|
+
const items = await ascFetchAll<{
|
|
15
|
+
id: string
|
|
16
|
+
attributes: {
|
|
17
|
+
certificateType?: string
|
|
18
|
+
displayName?: string
|
|
19
|
+
expirationDate?: string
|
|
20
|
+
serialNumber?: string
|
|
21
|
+
platform?: string
|
|
22
|
+
}
|
|
23
|
+
}>("/v1/certificates", { params })
|
|
24
|
+
const fmt = detectFormat(opts.output)
|
|
25
|
+
if (fmt === "json") { printJSON(items); return }
|
|
26
|
+
printTable(
|
|
27
|
+
["ID", "Name", "Type", "Expires", "Serial"],
|
|
28
|
+
items.map(c => [
|
|
29
|
+
c.id,
|
|
30
|
+
c.attributes.displayName ?? "-",
|
|
31
|
+
c.attributes.certificateType ?? "-",
|
|
32
|
+
formatDate(c.attributes.expirationDate ?? ""),
|
|
33
|
+
c.attributes.serialNumber ?? "-",
|
|
34
|
+
]),
|
|
35
|
+
`Certificates (${items.length})`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function certificateGet(certId: string, opts: { output?: string } = {}): Promise<void> {
|
|
40
|
+
const result = await ascFetch<{
|
|
41
|
+
data: {
|
|
42
|
+
id: string
|
|
43
|
+
attributes: {
|
|
44
|
+
certificateType?: string
|
|
45
|
+
displayName?: string
|
|
46
|
+
expirationDate?: string
|
|
47
|
+
serialNumber?: string
|
|
48
|
+
platform?: string
|
|
49
|
+
certificateContent?: string
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}>(`/v1/certificates/${certId}`)
|
|
53
|
+
const fmt = detectFormat(opts.output)
|
|
54
|
+
if (fmt === "json") { printJSON(result.data.data); return }
|
|
55
|
+
const a = result.data.data.attributes
|
|
56
|
+
console.log(`ID: ${result.data.data.id}`)
|
|
57
|
+
console.log(`Name: ${a.displayName ?? "-"}`)
|
|
58
|
+
console.log(`Type: ${a.certificateType ?? "-"}`)
|
|
59
|
+
console.log(`Expires: ${formatDate(a.expirationDate ?? "")}`)
|
|
60
|
+
console.log(`Serial: ${a.serialNumber ?? "-"}`)
|
|
61
|
+
console.log(`Platform: ${a.platform ?? "-"}`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function certificateRevoke(certId: string): Promise<void> {
|
|
65
|
+
await ascFetch(`/v1/certificates/${certId}`, { method: "DELETE" })
|
|
66
|
+
printSuccess(`Certificate ${certId} revoked`)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---- Devices ----
|
|
70
|
+
|
|
71
|
+
export async function devicesList(opts: {
|
|
72
|
+
platform?: string
|
|
73
|
+
status?: string
|
|
74
|
+
output?: string
|
|
75
|
+
} = {}): Promise<void> {
|
|
76
|
+
const params: Record<string, string> = {
|
|
77
|
+
"fields[devices]": "name,deviceClass,model,udid,platform,status,addedDate",
|
|
78
|
+
}
|
|
79
|
+
if (opts.platform) params["filter[platform]"] = opts.platform
|
|
80
|
+
if (opts.status) params["filter[status]"] = opts.status
|
|
81
|
+
const items = await ascFetchAll<{
|
|
82
|
+
id: string
|
|
83
|
+
attributes: {
|
|
84
|
+
name?: string
|
|
85
|
+
deviceClass?: string
|
|
86
|
+
model?: string
|
|
87
|
+
udid?: string
|
|
88
|
+
platform?: string
|
|
89
|
+
status?: string
|
|
90
|
+
addedDate?: string
|
|
91
|
+
}
|
|
92
|
+
}>("/v1/devices", { params })
|
|
93
|
+
const fmt = detectFormat(opts.output)
|
|
94
|
+
if (fmt === "json") { printJSON(items); return }
|
|
95
|
+
printTable(
|
|
96
|
+
["ID", "Name", "Model", "Platform", "Status", "UDID"],
|
|
97
|
+
items.map(d => [
|
|
98
|
+
d.id,
|
|
99
|
+
d.attributes.name ?? "-",
|
|
100
|
+
d.attributes.model ?? d.attributes.deviceClass ?? "-",
|
|
101
|
+
d.attributes.platform ?? "-",
|
|
102
|
+
d.attributes.status ?? "-",
|
|
103
|
+
d.attributes.udid ?? "-",
|
|
104
|
+
]),
|
|
105
|
+
`Devices (${items.length})`,
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function deviceRegister(opts: {
|
|
110
|
+
name: string
|
|
111
|
+
udid: string
|
|
112
|
+
platform: string
|
|
113
|
+
}): Promise<void> {
|
|
114
|
+
const result = await ascFetch<{ data: { id: string } }>("/v1/devices", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
body: {
|
|
117
|
+
data: {
|
|
118
|
+
type: "devices",
|
|
119
|
+
attributes: { name: opts.name, udid: opts.udid, platform: opts.platform },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
printSuccess(`Device registered: ${result.data.data.id} (${opts.udid})`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---- Bundle IDs ----
|
|
127
|
+
|
|
128
|
+
export async function bundleIdsList(opts: {
|
|
129
|
+
platform?: string
|
|
130
|
+
identifier?: string
|
|
131
|
+
output?: string
|
|
132
|
+
} = {}): Promise<void> {
|
|
133
|
+
const params: Record<string, string> = {
|
|
134
|
+
"fields[bundleIds]": "name,identifier,platform,seedId",
|
|
135
|
+
}
|
|
136
|
+
if (opts.platform) params["filter[platform]"] = opts.platform
|
|
137
|
+
if (opts.identifier) params["filter[identifier]"] = opts.identifier
|
|
138
|
+
const items = await ascFetchAll<{
|
|
139
|
+
id: string
|
|
140
|
+
attributes: { name?: string; identifier?: string; platform?: string; seedId?: string }
|
|
141
|
+
}>("/v1/bundleIds", { params })
|
|
142
|
+
const fmt = detectFormat(opts.output)
|
|
143
|
+
if (fmt === "json") { printJSON(items); return }
|
|
144
|
+
printTable(
|
|
145
|
+
["ID", "Name", "Identifier", "Platform", "Seed ID"],
|
|
146
|
+
items.map(b => [
|
|
147
|
+
b.id,
|
|
148
|
+
b.attributes.name ?? "-",
|
|
149
|
+
b.attributes.identifier ?? "-",
|
|
150
|
+
b.attributes.platform ?? "-",
|
|
151
|
+
b.attributes.seedId ?? "-",
|
|
152
|
+
]),
|
|
153
|
+
`Bundle IDs (${items.length})`,
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export async function bundleIdRegister(opts: {
|
|
158
|
+
name: string
|
|
159
|
+
identifier: string
|
|
160
|
+
platform: string
|
|
161
|
+
}): Promise<void> {
|
|
162
|
+
const result = await ascFetch<{ data: { id: string } }>("/v1/bundleIds", {
|
|
163
|
+
method: "POST",
|
|
164
|
+
body: {
|
|
165
|
+
data: {
|
|
166
|
+
type: "bundleIds",
|
|
167
|
+
attributes: { name: opts.name, identifier: opts.identifier, platform: opts.platform },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
})
|
|
171
|
+
printSuccess(`Bundle ID registered: ${result.data.data.id} (${opts.identifier})`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---- Provisioning Profiles ----
|
|
175
|
+
|
|
176
|
+
export async function profilesList(opts: {
|
|
177
|
+
type?: string
|
|
178
|
+
name?: string
|
|
179
|
+
output?: string
|
|
180
|
+
} = {}): Promise<void> {
|
|
181
|
+
const params: Record<string, string> = {
|
|
182
|
+
"fields[profiles]": "name,profileType,profileState,expirationDate,uuid",
|
|
183
|
+
}
|
|
184
|
+
if (opts.type) params["filter[profileType]"] = opts.type
|
|
185
|
+
if (opts.name) params["filter[name]"] = opts.name
|
|
186
|
+
const items = await ascFetchAll<{
|
|
187
|
+
id: string
|
|
188
|
+
attributes: {
|
|
189
|
+
name?: string
|
|
190
|
+
profileType?: string
|
|
191
|
+
profileState?: string
|
|
192
|
+
expirationDate?: string
|
|
193
|
+
uuid?: string
|
|
194
|
+
}
|
|
195
|
+
}>("/v1/profiles", { params })
|
|
196
|
+
const fmt = detectFormat(opts.output)
|
|
197
|
+
if (fmt === "json") { printJSON(items); return }
|
|
198
|
+
printTable(
|
|
199
|
+
["ID", "Name", "Type", "State", "Expires"],
|
|
200
|
+
items.map(p => [
|
|
201
|
+
p.id,
|
|
202
|
+
p.attributes.name ?? "-",
|
|
203
|
+
p.attributes.profileType ?? "-",
|
|
204
|
+
p.attributes.profileState ?? "-",
|
|
205
|
+
formatDate(p.attributes.expirationDate ?? ""),
|
|
206
|
+
]),
|
|
207
|
+
`Provisioning Profiles (${items.length})`,
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function profileDelete(profileId: string): Promise<void> {
|
|
212
|
+
await ascFetch(`/v1/profiles/${profileId}`, { method: "DELETE" })
|
|
213
|
+
printSuccess(`Profile ${profileId} deleted`)
|
|
214
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ascFetch, ascFetchAll } from "../api/client.ts"
|
|
2
|
+
import { detectFormat, printJSON, printSuccess, printTable, truncate } from "../utils/output.ts"
|
|
3
|
+
|
|
4
|
+
// ---- Subscription Groups ----
|
|
5
|
+
|
|
6
|
+
export async function subscriptionGroupsList(opts: { appId: string; output?: string }): Promise<void> {
|
|
7
|
+
const items = await ascFetchAll<{
|
|
8
|
+
id: string
|
|
9
|
+
attributes: { referenceName?: string; subscriptions?: number }
|
|
10
|
+
}>(`/v1/apps/${opts.appId}/subscriptionGroups`, {
|
|
11
|
+
params: { "fields[subscriptionGroups]": "referenceName,subscriptions" },
|
|
12
|
+
})
|
|
13
|
+
const fmt = detectFormat(opts.output)
|
|
14
|
+
if (fmt === "json") { printJSON(items); return }
|
|
15
|
+
printTable(
|
|
16
|
+
["Group ID", "Reference Name"],
|
|
17
|
+
items.map(g => [g.id, g.attributes.referenceName ?? "-"]),
|
|
18
|
+
`Subscription Groups (${items.length})`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function subscriptionGroupCreate(opts: {
|
|
23
|
+
appId: string
|
|
24
|
+
referenceName: string
|
|
25
|
+
}): Promise<void> {
|
|
26
|
+
const result = await ascFetch<{ data: { id: string } }>("/v1/subscriptionGroups", {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: {
|
|
29
|
+
data: {
|
|
30
|
+
type: "subscriptionGroups",
|
|
31
|
+
attributes: { referenceName: opts.referenceName },
|
|
32
|
+
relationships: { app: { data: { type: "apps", id: opts.appId } } },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
printSuccess(`Created subscription group: ${result.data.data.id}`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---- Subscriptions ----
|
|
40
|
+
|
|
41
|
+
export async function subscriptionsList(opts: { groupId: string; output?: string }): Promise<void> {
|
|
42
|
+
const items = await ascFetchAll<{
|
|
43
|
+
id: string
|
|
44
|
+
attributes: {
|
|
45
|
+
name?: string
|
|
46
|
+
productId?: string
|
|
47
|
+
subscriptionPeriod?: string
|
|
48
|
+
state?: string
|
|
49
|
+
familySharable?: boolean
|
|
50
|
+
}
|
|
51
|
+
}>(`/v1/subscriptionGroups/${opts.groupId}/subscriptions`, {
|
|
52
|
+
params: { "fields[subscriptions]": "name,productId,subscriptionPeriod,state,familySharable" },
|
|
53
|
+
})
|
|
54
|
+
const fmt = detectFormat(opts.output)
|
|
55
|
+
if (fmt === "json") { printJSON(items); return }
|
|
56
|
+
printTable(
|
|
57
|
+
["ID", "Name", "Product ID", "Period", "State"],
|
|
58
|
+
items.map(s => [
|
|
59
|
+
s.id,
|
|
60
|
+
truncate(s.attributes.name ?? "-", 30),
|
|
61
|
+
s.attributes.productId ?? "-",
|
|
62
|
+
s.attributes.subscriptionPeriod ?? "-",
|
|
63
|
+
s.attributes.state ?? "-",
|
|
64
|
+
]),
|
|
65
|
+
`Subscriptions (${items.length})`,
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function subscriptionGet(subId: string, opts: { output?: string } = {}): Promise<void> {
|
|
70
|
+
const result = await ascFetch<{
|
|
71
|
+
data: {
|
|
72
|
+
id: string
|
|
73
|
+
attributes: {
|
|
74
|
+
name?: string
|
|
75
|
+
productId?: string
|
|
76
|
+
subscriptionPeriod?: string
|
|
77
|
+
state?: string
|
|
78
|
+
familySharable?: boolean
|
|
79
|
+
reviewNote?: string
|
|
80
|
+
availableInAllTerritories?: boolean
|
|
81
|
+
groupLevel?: number
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}>(`/v1/subscriptions/${subId}`)
|
|
85
|
+
const fmt = detectFormat(opts.output)
|
|
86
|
+
if (fmt === "json") { printJSON(result.data.data); return }
|
|
87
|
+
const a = result.data.data.attributes
|
|
88
|
+
console.log(`ID: ${result.data.data.id}`)
|
|
89
|
+
console.log(`Name: ${a.name ?? "-"}`)
|
|
90
|
+
console.log(`Product ID: ${a.productId ?? "-"}`)
|
|
91
|
+
console.log(`Period: ${a.subscriptionPeriod ?? "-"}`)
|
|
92
|
+
console.log(`State: ${a.state ?? "-"}`)
|
|
93
|
+
console.log(`Group Level: ${a.groupLevel ?? "-"}`)
|
|
94
|
+
console.log(`Family Share: ${a.familySharable ?? false}`)
|
|
95
|
+
console.log(`All Territ.: ${a.availableInAllTerritories ?? false}`)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export async function subscriptionCreate(opts: {
|
|
99
|
+
groupId: string
|
|
100
|
+
name: string
|
|
101
|
+
productId: string
|
|
102
|
+
period: string
|
|
103
|
+
}): Promise<void> {
|
|
104
|
+
const result = await ascFetch<{ data: { id: string } }>("/v1/subscriptions", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: {
|
|
107
|
+
data: {
|
|
108
|
+
type: "subscriptions",
|
|
109
|
+
attributes: {
|
|
110
|
+
name: opts.name,
|
|
111
|
+
productId: opts.productId,
|
|
112
|
+
subscriptionPeriod: opts.period,
|
|
113
|
+
},
|
|
114
|
+
relationships: {
|
|
115
|
+
group: { data: { type: "subscriptionGroups", id: opts.groupId } },
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
})
|
|
120
|
+
printSuccess(`Created subscription: ${result.data.data.id} (${opts.productId})`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function subscriptionPricesList(opts: {
|
|
124
|
+
subId: string
|
|
125
|
+
territory?: string
|
|
126
|
+
output?: string
|
|
127
|
+
}): Promise<void> {
|
|
128
|
+
const params: Record<string, string> = {
|
|
129
|
+
"fields[subscriptionPrices]": "startDate,territory",
|
|
130
|
+
include: "subscriptionPricePoint",
|
|
131
|
+
}
|
|
132
|
+
if (opts.territory) params["filter[territory]"] = opts.territory
|
|
133
|
+
const items = await ascFetchAll<{
|
|
134
|
+
id: string
|
|
135
|
+
attributes: { startDate?: string; territory?: unknown }
|
|
136
|
+
}>(`/v1/subscriptions/${opts.subId}/prices`, { params })
|
|
137
|
+
const fmt = detectFormat(opts.output)
|
|
138
|
+
if (fmt === "json") { printJSON(items); return }
|
|
139
|
+
printTable(
|
|
140
|
+
["Price ID", "Territory", "Start Date"],
|
|
141
|
+
items.map(p => [p.id, String(p.attributes.territory ?? "-"), p.attributes.startDate ?? "ongoing"]),
|
|
142
|
+
`Subscription Prices (${items.length})`,
|
|
143
|
+
)
|
|
144
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import * as p from "@clack/prompts"
|
|
2
|
+
import { ascFetch, ascFetchAll } from "../api/client.ts"
|
|
3
|
+
import type { BetaGroup, BetaTester } from "../api/types.ts"
|
|
4
|
+
import { detectFormat, formatDate, printJSON, printSuccess, printTable, truncate } from "../utils/output.ts"
|
|
5
|
+
|
|
6
|
+
export async function testflightGroupsList(opts: {
|
|
7
|
+
appId?: string
|
|
8
|
+
output?: string
|
|
9
|
+
} = {}): Promise<void> {
|
|
10
|
+
let groups: BetaGroup[]
|
|
11
|
+
if (opts.appId) {
|
|
12
|
+
groups = await ascFetchAll<BetaGroup>(`/v1/apps/${opts.appId}/betaGroups`)
|
|
13
|
+
} else {
|
|
14
|
+
groups = await ascFetchAll<BetaGroup>("/v1/betaGroups")
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fmt = detectFormat(opts.output)
|
|
18
|
+
if (fmt === "json") {
|
|
19
|
+
printJSON(groups.map((g) => ({ id: g.id, ...g.attributes })))
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
printTable(
|
|
24
|
+
["ID", "Name", "Internal", "Public Link"],
|
|
25
|
+
groups.map((g) => [
|
|
26
|
+
g.id,
|
|
27
|
+
g.attributes.name,
|
|
28
|
+
g.attributes.isInternalGroup ? "yes" : "no",
|
|
29
|
+
g.attributes.publicLink ?? "-",
|
|
30
|
+
]),
|
|
31
|
+
`Beta Groups (${groups.length})`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function testflightTestersList(opts: {
|
|
36
|
+
groupId: string
|
|
37
|
+
output?: string
|
|
38
|
+
}): Promise<void> {
|
|
39
|
+
const testers = await ascFetchAll<BetaTester>(
|
|
40
|
+
`/v1/betaGroups/${opts.groupId}/betaTesters`,
|
|
41
|
+
{
|
|
42
|
+
params: { "fields[betaTesters]": "firstName,lastName,email,inviteType,state" },
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const fmt = detectFormat(opts.output)
|
|
47
|
+
if (fmt === "json") {
|
|
48
|
+
printJSON(testers.map((t) => ({ id: t.id, ...t.attributes })))
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
printTable(
|
|
53
|
+
["ID", "Name", "Email", "State"],
|
|
54
|
+
testers.map((t) => [
|
|
55
|
+
t.id,
|
|
56
|
+
[t.attributes.firstName, t.attributes.lastName].filter(Boolean).join(" ") || "-",
|
|
57
|
+
t.attributes.email,
|
|
58
|
+
t.attributes.state ?? "-",
|
|
59
|
+
]),
|
|
60
|
+
`Testers (${testers.length})`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function testflightTestersAdd(opts: {
|
|
65
|
+
groupId: string
|
|
66
|
+
email?: string
|
|
67
|
+
firstName?: string
|
|
68
|
+
lastName?: string
|
|
69
|
+
}): Promise<void> {
|
|
70
|
+
let email = opts.email
|
|
71
|
+
if (!email) {
|
|
72
|
+
const val = await p.text({ message: "Tester email:" })
|
|
73
|
+
if (p.isCancel(val)) { p.cancel("Cancelled"); process.exit(0) }
|
|
74
|
+
email = val as string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Create tester + add to group
|
|
78
|
+
const res = await ascFetch<BetaTester>("/v1/betaTesters", {
|
|
79
|
+
method: "POST",
|
|
80
|
+
body: {
|
|
81
|
+
data: {
|
|
82
|
+
type: "betaTesters",
|
|
83
|
+
attributes: {
|
|
84
|
+
email,
|
|
85
|
+
firstName: opts.firstName,
|
|
86
|
+
lastName: opts.lastName,
|
|
87
|
+
},
|
|
88
|
+
relationships: {
|
|
89
|
+
betaGroups: {
|
|
90
|
+
data: [{ type: "betaGroups", id: opts.groupId }],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
printSuccess(`Tester ${email} added (ID: ${res.data.id})`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function testflightTestersRemove(opts: {
|
|
100
|
+
groupId: string
|
|
101
|
+
testerId: string
|
|
102
|
+
}): Promise<void> {
|
|
103
|
+
await ascFetch(`/v1/betaGroups/${opts.groupId}/relationships/betaTesters`, {
|
|
104
|
+
method: "DELETE",
|
|
105
|
+
body: {
|
|
106
|
+
data: [{ type: "betaTesters", id: opts.testerId }],
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
printSuccess(`Tester ${opts.testerId} removed from group`)
|
|
110
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { ascFetch, ascFetchAll } from "../api/client.ts"
|
|
2
|
+
import type { AppStoreVersion, Platform } from "../api/types.ts"
|
|
3
|
+
import { detectFormat, formatDate, printJSON, printSuccess, printTable } from "../utils/output.ts"
|
|
4
|
+
|
|
5
|
+
export async function versionsList(opts: {
|
|
6
|
+
appId: string
|
|
7
|
+
platform?: string
|
|
8
|
+
output?: string
|
|
9
|
+
}): Promise<void> {
|
|
10
|
+
const params: Record<string, string> = {
|
|
11
|
+
"fields[appStoreVersions]": "platform,versionString,appStoreState,createdDate",
|
|
12
|
+
}
|
|
13
|
+
if (opts.platform) params["filter[platform]"] = opts.platform.toUpperCase()
|
|
14
|
+
|
|
15
|
+
const versions = await ascFetchAll<AppStoreVersion>(
|
|
16
|
+
`/v1/apps/${opts.appId}/appStoreVersions`,
|
|
17
|
+
{ params },
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
const fmt = detectFormat(opts.output)
|
|
21
|
+
if (fmt === "json") {
|
|
22
|
+
printJSON(versions.map((v) => ({ id: v.id, ...v.attributes })))
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
printTable(
|
|
27
|
+
["ID", "Version", "Platform", "State", "Created"],
|
|
28
|
+
versions.map((v) => [
|
|
29
|
+
v.id,
|
|
30
|
+
v.attributes.versionString,
|
|
31
|
+
v.attributes.platform,
|
|
32
|
+
v.attributes.appStoreState,
|
|
33
|
+
formatDate(v.attributes.createdDate),
|
|
34
|
+
]),
|
|
35
|
+
`Versions (${versions.length})`,
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function versionsCreate(opts: {
|
|
40
|
+
appId: string
|
|
41
|
+
version: string
|
|
42
|
+
platform: string
|
|
43
|
+
}): Promise<void> {
|
|
44
|
+
const platform = opts.platform.toUpperCase() as Platform
|
|
45
|
+
const res = await ascFetch<AppStoreVersion>("/v1/appStoreVersions", {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: {
|
|
48
|
+
data: {
|
|
49
|
+
type: "appStoreVersions",
|
|
50
|
+
attributes: {
|
|
51
|
+
platform,
|
|
52
|
+
versionString: opts.version,
|
|
53
|
+
},
|
|
54
|
+
relationships: {
|
|
55
|
+
app: { data: { type: "apps", id: opts.appId } },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
printSuccess(`Created version ${opts.version} (${platform}) — ID: ${res.data.id}`)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function versionsSubmit(versionId: string): Promise<void> {
|
|
64
|
+
await ascFetch("/v1/appStoreVersionSubmissions", {
|
|
65
|
+
method: "POST",
|
|
66
|
+
body: {
|
|
67
|
+
data: {
|
|
68
|
+
type: "appStoreVersionSubmissions",
|
|
69
|
+
relationships: {
|
|
70
|
+
appStoreVersion: { data: { type: "appStoreVersions", id: versionId } },
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
printSuccess(`Version ${versionId} submitted for review`)
|
|
76
|
+
}
|