@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.
Files changed (55) hide show
  1. package/README.md +299 -0
  2. package/bun.lock +231 -0
  3. package/dist/index.js +179 -0
  4. package/docs/API_COVERAGE.md +355 -0
  5. package/docs/openapi/latest.json +230597 -0
  6. package/docs/openapi/paths.txt +1208 -0
  7. package/openapi-ts.config.ts +25 -0
  8. package/package.json +32 -0
  9. package/scripts/gen-paths-index.py +24 -0
  10. package/src/api/client.ts +132 -0
  11. package/src/api/generated/client/client.gen.ts +298 -0
  12. package/src/api/generated/client/index.ts +25 -0
  13. package/src/api/generated/client/types.gen.ts +214 -0
  14. package/src/api/generated/client/utils.gen.ts +316 -0
  15. package/src/api/generated/client.gen.ts +16 -0
  16. package/src/api/generated/core/auth.gen.ts +41 -0
  17. package/src/api/generated/core/bodySerializer.gen.ts +82 -0
  18. package/src/api/generated/core/params.gen.ts +169 -0
  19. package/src/api/generated/core/pathSerializer.gen.ts +171 -0
  20. package/src/api/generated/core/queryKeySerializer.gen.ts +117 -0
  21. package/src/api/generated/core/serverSentEvents.gen.ts +242 -0
  22. package/src/api/generated/core/types.gen.ts +104 -0
  23. package/src/api/generated/core/utils.gen.ts +140 -0
  24. package/src/api/generated/index.ts +4 -0
  25. package/src/api/generated/sdk.gen.ts +11701 -0
  26. package/src/api/generated/types.gen.ts +92035 -0
  27. package/src/api/hey-api-client.ts +20 -0
  28. package/src/api/types.ts +160 -0
  29. package/src/auth/credentials.ts +125 -0
  30. package/src/auth/jwt.ts +118 -0
  31. package/src/commands/app-info.ts +110 -0
  32. package/src/commands/apps.ts +44 -0
  33. package/src/commands/auth.ts +327 -0
  34. package/src/commands/availability.ts +52 -0
  35. package/src/commands/beta-review.ts +145 -0
  36. package/src/commands/builds.ts +97 -0
  37. package/src/commands/game-center.ts +114 -0
  38. package/src/commands/iap.ts +105 -0
  39. package/src/commands/metadata.ts +81 -0
  40. package/src/commands/pricing.ts +110 -0
  41. package/src/commands/reports.ts +116 -0
  42. package/src/commands/reviews.ts +93 -0
  43. package/src/commands/screenshots.ts +139 -0
  44. package/src/commands/signing.ts +214 -0
  45. package/src/commands/subscriptions.ts +144 -0
  46. package/src/commands/testflight.ts +110 -0
  47. package/src/commands/versions.ts +76 -0
  48. package/src/commands/xcode-cloud.ts +207 -0
  49. package/src/index.ts +1661 -0
  50. package/src/utils/help-spec.ts +835 -0
  51. package/src/utils/output.ts +79 -0
  52. package/tests/auth.test.ts +105 -0
  53. package/tests/client.test.ts +22 -0
  54. package/tests/output.test.ts +36 -0
  55. package/tsconfig.json +15 -0
@@ -0,0 +1,79 @@
1
+ import chalk from "chalk"
2
+ import Table from "cli-table3"
3
+ import type { ASCError } from "../api/client.ts"
4
+
5
+ export type OutputFormat = "table" | "json" | "plain"
6
+
7
+ export function detectFormat(override?: string): OutputFormat {
8
+ if (override === "json" || override === "table" || override === "plain") {
9
+ return override
10
+ }
11
+ return process.stdout.isTTY ? "table" : "json"
12
+ }
13
+
14
+ export function printJSON(data: unknown): void {
15
+ console.log(JSON.stringify(data, null, 2))
16
+ }
17
+
18
+ export function printTable(
19
+ headers: string[],
20
+ rows: string[][],
21
+ title?: string,
22
+ ): void {
23
+ if (title) console.log(chalk.bold(title))
24
+ const t = new Table({
25
+ head: headers.map((h) => chalk.cyan(h)),
26
+ style: { head: [], border: [] },
27
+ })
28
+ for (const row of rows) {
29
+ t.push(row)
30
+ }
31
+ console.log(t.toString())
32
+ }
33
+
34
+ export function printPlain(lines: string[]): void {
35
+ for (const l of lines) console.log(l)
36
+ }
37
+
38
+ export function printSuccess(msg: string): void {
39
+ console.log(chalk.green("✓") + " " + msg)
40
+ }
41
+
42
+ export function printWarning(msg: string): void {
43
+ console.log(chalk.yellow("⚠") + " " + msg)
44
+ }
45
+
46
+ export function printError(err: unknown): void {
47
+ if (isASCError(err)) {
48
+ console.error(chalk.red("Error") + ` [${err.status}]: ${err.message}`)
49
+ if (err.errors && err.errors.length > 0) {
50
+ for (const e of err.errors) {
51
+ if (e.detail && e.detail !== err.message) {
52
+ console.error(chalk.dim(` ${e.code}: ${e.detail}`))
53
+ }
54
+ }
55
+ }
56
+ } else if (err instanceof Error) {
57
+ console.error(chalk.red("Error: ") + err.message)
58
+ } else {
59
+ console.error(chalk.red("Error: ") + String(err))
60
+ }
61
+ }
62
+
63
+ function isASCError(err: unknown): err is ASCError {
64
+ return err instanceof Error && err.name === "ASCError"
65
+ }
66
+
67
+ export function truncate(s: string, max: number): string {
68
+ if (s.length <= max) return s
69
+ return s.slice(0, max - 1) + "…"
70
+ }
71
+
72
+ export function formatDate(iso?: string): string {
73
+ if (!iso) return "-"
74
+ return new Date(iso).toLocaleDateString("en-US", {
75
+ year: "numeric",
76
+ month: "short",
77
+ day: "numeric",
78
+ })
79
+ }
@@ -0,0 +1,105 @@
1
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test"
2
+ import { join } from "path"
3
+ import { existsSync, unlinkSync, mkdirSync } from "fs"
4
+ import {
5
+ loadCredentials,
6
+ saveCredentials,
7
+ addOrUpdateProfile,
8
+ removeProfile,
9
+ setActiveProfile,
10
+ type CredentialsFile,
11
+ type Profile,
12
+ } from "../src/auth/credentials.ts"
13
+
14
+ // Use a temp dir for tests to avoid touching real ~/.asc/credentials.json
15
+ const TEST_HOME = join(import.meta.dir, "tmp_home")
16
+ const TEST_ASC_DIR = join(TEST_HOME, ".asc")
17
+ const TEST_CREDS = join(TEST_ASC_DIR, "credentials.json")
18
+
19
+ // Override HOME for credential path
20
+ const origHome = process.env["HOME"]
21
+ beforeEach(() => {
22
+ process.env["HOME"] = TEST_HOME
23
+ mkdirSync(TEST_ASC_DIR, { recursive: true })
24
+ if (existsSync(TEST_CREDS)) unlinkSync(TEST_CREDS)
25
+ })
26
+ afterEach(() => {
27
+ process.env["HOME"] = origHome
28
+ if (existsSync(TEST_CREDS)) unlinkSync(TEST_CREDS)
29
+ })
30
+
31
+ describe("credentials", () => {
32
+ test("loadCredentials returns empty when no file", () => {
33
+ const creds = loadCredentials()
34
+ expect(creds.profiles).toHaveLength(0)
35
+ expect(creds.activeProfile).toBe("default")
36
+ })
37
+
38
+ test("saveCredentials + loadCredentials roundtrip", () => {
39
+ const data: CredentialsFile = {
40
+ activeProfile: "work",
41
+ profiles: [
42
+ {
43
+ name: "work",
44
+ keyId: "ABC123",
45
+ issuerId: "issuer-1",
46
+ privateKeyPath: "~/.asc/work.p8",
47
+ isDefault: true,
48
+ },
49
+ ],
50
+ }
51
+ saveCredentials(data)
52
+ const loaded = loadCredentials()
53
+ expect(loaded.activeProfile).toBe("work")
54
+ expect(loaded.profiles).toHaveLength(1)
55
+ expect(loaded.profiles[0]?.keyId).toBe("ABC123")
56
+ })
57
+
58
+ test("addOrUpdateProfile adds new profile", () => {
59
+ const profile: Profile = {
60
+ name: "personal",
61
+ keyId: "K1",
62
+ issuerId: "I1",
63
+ privateKeyPath: "~/.asc/personal.p8",
64
+ isDefault: false,
65
+ }
66
+ addOrUpdateProfile(profile)
67
+ const loaded = loadCredentials()
68
+ expect(loaded.profiles).toHaveLength(1)
69
+ expect(loaded.profiles[0]?.name).toBe("personal")
70
+ expect(loaded.activeProfile).toBe("personal") // first profile becomes active
71
+ })
72
+
73
+ test("addOrUpdateProfile updates existing profile", () => {
74
+ const p1: Profile = { name: "work", keyId: "K1", issuerId: "I1", privateKeyPath: "~/.asc/w.p8", isDefault: true }
75
+ addOrUpdateProfile(p1)
76
+ addOrUpdateProfile({ ...p1, keyId: "K2" })
77
+ const loaded = loadCredentials()
78
+ expect(loaded.profiles).toHaveLength(1)
79
+ expect(loaded.profiles[0]?.keyId).toBe("K2")
80
+ })
81
+
82
+ test("removeProfile removes by name", () => {
83
+ const p1: Profile = { name: "work", keyId: "K1", issuerId: "I1", privateKeyPath: "~/.asc/w.p8", isDefault: true }
84
+ const p2: Profile = { name: "personal", keyId: "K2", issuerId: "I2", privateKeyPath: "~/.asc/p.p8", isDefault: false }
85
+ addOrUpdateProfile(p1)
86
+ addOrUpdateProfile(p2)
87
+ removeProfile("work")
88
+ const loaded = loadCredentials()
89
+ expect(loaded.profiles).toHaveLength(1)
90
+ expect(loaded.profiles[0]?.name).toBe("personal")
91
+ })
92
+
93
+ test("setActiveProfile switches active", () => {
94
+ addOrUpdateProfile({ name: "a", keyId: "K1", issuerId: "I1", privateKeyPath: "~/.asc/a.p8", isDefault: true })
95
+ addOrUpdateProfile({ name: "b", keyId: "K2", issuerId: "I2", privateKeyPath: "~/.asc/b.p8", isDefault: false })
96
+ const ok = setActiveProfile("b")
97
+ expect(ok).toBe(true)
98
+ expect(loadCredentials().activeProfile).toBe("b")
99
+ })
100
+
101
+ test("setActiveProfile returns false for unknown profile", () => {
102
+ const ok = setActiveProfile("nope")
103
+ expect(ok).toBe(false)
104
+ })
105
+ })
@@ -0,0 +1,22 @@
1
+ import { describe, test, expect, mock } from "bun:test"
2
+ import { ASCError } from "../src/api/client.ts"
3
+
4
+ describe("ASCError", () => {
5
+ test("creates error with status and message", () => {
6
+ const err = new ASCError("Unauthorized", 401)
7
+ expect(err.message).toBe("Unauthorized")
8
+ expect(err.status).toBe(401)
9
+ expect(err.name).toBe("ASCError")
10
+ })
11
+
12
+ test("stores errors array", () => {
13
+ const errors = [{ status: "401", code: "NOT_AUTHORIZED", title: "Unauthorized" }]
14
+ const err = new ASCError("Unauthorized", 401, errors)
15
+ expect(err.errors).toEqual(errors)
16
+ })
17
+
18
+ test("is instance of Error", () => {
19
+ const err = new ASCError("test", 500)
20
+ expect(err instanceof Error).toBe(true)
21
+ })
22
+ })
@@ -0,0 +1,36 @@
1
+ import { describe, test, expect } from "bun:test"
2
+ import { detectFormat, truncate, formatDate } from "../src/utils/output.ts"
3
+
4
+ describe("output", () => {
5
+ test("detectFormat returns json for explicit json", () => {
6
+ expect(detectFormat("json")).toBe("json")
7
+ })
8
+
9
+ test("detectFormat returns table for explicit table", () => {
10
+ expect(detectFormat("table")).toBe("table")
11
+ })
12
+
13
+ test("detectFormat returns plain for explicit plain", () => {
14
+ expect(detectFormat("plain")).toBe("plain")
15
+ })
16
+
17
+ test("truncate returns string as-is when short", () => {
18
+ expect(truncate("hello", 10)).toBe("hello")
19
+ })
20
+
21
+ test("truncate truncates long strings", () => {
22
+ const result = truncate("hello world", 8)
23
+ expect(result.length).toBe(8)
24
+ expect(result.endsWith("…")).toBe(true)
25
+ })
26
+
27
+ test("formatDate formats ISO string", () => {
28
+ const result = formatDate("2024-03-15T00:00:00Z")
29
+ expect(result).toContain("2024")
30
+ expect(result).toContain("Mar")
31
+ })
32
+
33
+ test("formatDate returns dash for undefined", () => {
34
+ expect(formatDate(undefined)).toBe("-")
35
+ })
36
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "allowImportingTsExtensions": true,
7
+ "strict": true,
8
+ "noUncheckedIndexedAccess": true,
9
+ "noEmit": true,
10
+ "lib": ["ESNext", "DOM"],
11
+ "types": ["bun-types"],
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src/**/*", "tests/**/*"]
15
+ }