@sitepong/fastnode 0.1.0 → 0.1.2

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,158 @@
1
+ import { A as AppStoreConnectClient, j as Build } from './index-BLubSlgK.cjs';
2
+
3
+ /**
4
+ * Shared types for the pilot module's upload backends.
5
+ */
6
+ type UploadBackend = "altool" | "transporter";
7
+ interface PilotUploadOptions {
8
+ ipaPath: string;
9
+ apiKeyId: string;
10
+ apiIssuerId: string;
11
+ apiPrivateKey: string;
12
+ /** Apple target platform. Defaults to "ios". */
13
+ platform?: "ios" | "osx" | "appletvos";
14
+ /** Upload timeout in ms. Defaults to 30 min. */
15
+ timeout?: number;
16
+ /** Stream callback for upload progress / log lines. */
17
+ onOutput?: (chunk: string) => void;
18
+ /**
19
+ * Which backend to use. Defaults to "auto":
20
+ * - altool on macOS (requires Xcode CLI)
21
+ * - transporter on Linux (requires Java + iTMSTransporter)
22
+ */
23
+ backend?: UploadBackend | "auto";
24
+ }
25
+ interface UploadResult {
26
+ success: boolean;
27
+ output: string;
28
+ /** Which backend actually ran. Useful for logs/metrics. */
29
+ backend: UploadBackend;
30
+ }
31
+
32
+ /**
33
+ * Upload a build to TestFlight via Apple's `altool` (macOS only).
34
+ *
35
+ * altool ships with Xcode command-line tools. It provides richer error
36
+ * output than iTMSTransporter but is only available on macOS.
37
+ */
38
+ declare function uploadWithAltool(options: PilotUploadOptions): Promise<UploadResult>;
39
+
40
+ /**
41
+ * Upload a build to TestFlight via Apple's iTMSTransporter (Java).
42
+ *
43
+ * This path works on Linux (and macOS) and does NOT require Xcode.
44
+ * It is the primary upload path for the ship worker LXC.
45
+ *
46
+ * iTMSTransporter reads the ASC API private key from a directory
47
+ * named by API_PRIVATE_KEYS_DIR, looking for the file `AuthKey_<keyId>.p8`.
48
+ */
49
+ declare function uploadWithTransporter(options: PilotUploadOptions): Promise<UploadResult>;
50
+
51
+ /**
52
+ * Classify upload failures into transient vs. permanent buckets.
53
+ *
54
+ * Apple's App Store Connect / iTMSTransporter backend is flaky:
55
+ * the finalize step ("CHANGE UPLOAD STATE TO COMPLETE") periodically
56
+ * returns 500, and the Java transporter surfaces various connection
57
+ * errors during long uploads. These are all safe to retry — the IPA
58
+ * is deterministic and Apple deduplicates on build number.
59
+ *
60
+ * Non-transient errors (invalid binary, duplicate build number, auth
61
+ * failure) should NOT be retried — they will never succeed without
62
+ * the caller taking some action.
63
+ */
64
+ type TransientUploadError = "apple_5xx" | "finalize_failed" | "network_timeout" | "connection_reset" | "transporter_jvm_error";
65
+ /**
66
+ * Returns the transient-error kind if the output matches any known
67
+ * retry-worthy pattern, or `null` if the error is non-transient.
68
+ */
69
+ declare function classifyUploadError(output: string): TransientUploadError | null;
70
+ /**
71
+ * Convenience: `true` if the upload should be retried.
72
+ */
73
+ declare function isTransientUploadError(output: string): boolean;
74
+
75
+ /**
76
+ * Locate the iTMSTransporter binary on this host.
77
+ *
78
+ * Apple ships iTMSTransporter as a Java-based tool bundled with Xcode
79
+ * on macOS, and as a standalone Linux distribution used by media
80
+ * publishers for CI. Either works for uploading IPAs — same protocol,
81
+ * same ASC API key format.
82
+ *
83
+ * Resolution order:
84
+ * 1. ITMSTRANSPORTER_BIN env var (pinning for prod deployments)
85
+ * 2. Well-known system paths for the host OS
86
+ * 3. Throw with a clear install hint
87
+ *
88
+ * We deliberately do NOT auto-download Transporter. Apple's download
89
+ * URL is not versioned and the license/redistribution story is fuzzy.
90
+ * Prod hosts should install it once via apt/brew/manual, then pin via
91
+ * env var so CI is reproducible.
92
+ */
93
+ declare function resolveTransporter(): Promise<string>;
94
+ /**
95
+ * Verify that Java is available on PATH. iTMSTransporter is a Java
96
+ * application and won't run without a JRE.
97
+ */
98
+ declare function verifyJava(): Promise<{
99
+ version: string;
100
+ }>;
101
+
102
+ interface PilotDistributeOptions {
103
+ client: AppStoreConnectClient;
104
+ buildId: string;
105
+ groupNames?: string[];
106
+ changelog?: string;
107
+ locale?: string;
108
+ expirePreviousBuilds?: boolean;
109
+ submitForReview?: boolean;
110
+ }
111
+ /**
112
+ * Upload a build to TestFlight.
113
+ *
114
+ * Selects the upload backend based on the host OS:
115
+ * - macOS → altool (ships with Xcode CLI)
116
+ * - Linux → iTMSTransporter (standalone Java tool from Apple)
117
+ *
118
+ * Pass `backend` to force a specific path, e.g. for testing the
119
+ * Linux path on a mac dev machine.
120
+ */
121
+ declare function uploadToTestFlight(options: PilotUploadOptions): Promise<UploadResult>;
122
+ declare function waitForProcessing(client: AppStoreConnectClient, params: {
123
+ bundleId: string;
124
+ buildNumber: string;
125
+ appVersion: string;
126
+ timeout?: number;
127
+ pollInterval?: number;
128
+ }): Promise<Build>;
129
+ declare function distributeToGroups(options: PilotDistributeOptions): Promise<string[]>;
130
+ declare function setChangelog(options: {
131
+ client: AppStoreConnectClient;
132
+ buildId: string;
133
+ changelog: string;
134
+ locale?: string;
135
+ }): Promise<void>;
136
+ declare function expireBuild(client: AppStoreConnectClient, buildId: string): Promise<void>;
137
+
138
+ type index_PilotDistributeOptions = PilotDistributeOptions;
139
+ type index_PilotUploadOptions = PilotUploadOptions;
140
+ type index_TransientUploadError = TransientUploadError;
141
+ type index_UploadBackend = UploadBackend;
142
+ type index_UploadResult = UploadResult;
143
+ declare const index_classifyUploadError: typeof classifyUploadError;
144
+ declare const index_distributeToGroups: typeof distributeToGroups;
145
+ declare const index_expireBuild: typeof expireBuild;
146
+ declare const index_isTransientUploadError: typeof isTransientUploadError;
147
+ declare const index_resolveTransporter: typeof resolveTransporter;
148
+ declare const index_setChangelog: typeof setChangelog;
149
+ declare const index_uploadToTestFlight: typeof uploadToTestFlight;
150
+ declare const index_uploadWithAltool: typeof uploadWithAltool;
151
+ declare const index_uploadWithTransporter: typeof uploadWithTransporter;
152
+ declare const index_verifyJava: typeof verifyJava;
153
+ declare const index_waitForProcessing: typeof waitForProcessing;
154
+ declare namespace index {
155
+ export { type index_PilotDistributeOptions as PilotDistributeOptions, type index_PilotUploadOptions as PilotUploadOptions, type index_TransientUploadError as TransientUploadError, type index_UploadBackend as UploadBackend, type index_UploadResult as UploadResult, index_classifyUploadError as classifyUploadError, index_distributeToGroups as distributeToGroups, index_expireBuild as expireBuild, index_isTransientUploadError as isTransientUploadError, index_resolveTransporter as resolveTransporter, index_setChangelog as setChangelog, index_uploadToTestFlight as uploadToTestFlight, index_uploadWithAltool as uploadWithAltool, index_uploadWithTransporter as uploadWithTransporter, index_verifyJava as verifyJava, index_waitForProcessing as waitForProcessing };
156
+ }
157
+
158
+ export { type PilotDistributeOptions as P, type TransientUploadError as T, type UploadBackend as U, type PilotUploadOptions as a, type UploadResult as b, uploadWithAltool as c, distributeToGroups as d, expireBuild as e, uploadWithTransporter as f, classifyUploadError as g, isTransientUploadError as h, index as i, resolveTransporter as r, setChangelog as s, uploadToTestFlight as u, verifyJava as v, waitForProcessing as w };
@@ -0,0 +1,158 @@
1
+ import { A as AppStoreConnectClient, j as Build } from './index-D1Izm7qt.js';
2
+
3
+ /**
4
+ * Shared types for the pilot module's upload backends.
5
+ */
6
+ type UploadBackend = "altool" | "transporter";
7
+ interface PilotUploadOptions {
8
+ ipaPath: string;
9
+ apiKeyId: string;
10
+ apiIssuerId: string;
11
+ apiPrivateKey: string;
12
+ /** Apple target platform. Defaults to "ios". */
13
+ platform?: "ios" | "osx" | "appletvos";
14
+ /** Upload timeout in ms. Defaults to 30 min. */
15
+ timeout?: number;
16
+ /** Stream callback for upload progress / log lines. */
17
+ onOutput?: (chunk: string) => void;
18
+ /**
19
+ * Which backend to use. Defaults to "auto":
20
+ * - altool on macOS (requires Xcode CLI)
21
+ * - transporter on Linux (requires Java + iTMSTransporter)
22
+ */
23
+ backend?: UploadBackend | "auto";
24
+ }
25
+ interface UploadResult {
26
+ success: boolean;
27
+ output: string;
28
+ /** Which backend actually ran. Useful for logs/metrics. */
29
+ backend: UploadBackend;
30
+ }
31
+
32
+ /**
33
+ * Upload a build to TestFlight via Apple's `altool` (macOS only).
34
+ *
35
+ * altool ships with Xcode command-line tools. It provides richer error
36
+ * output than iTMSTransporter but is only available on macOS.
37
+ */
38
+ declare function uploadWithAltool(options: PilotUploadOptions): Promise<UploadResult>;
39
+
40
+ /**
41
+ * Upload a build to TestFlight via Apple's iTMSTransporter (Java).
42
+ *
43
+ * This path works on Linux (and macOS) and does NOT require Xcode.
44
+ * It is the primary upload path for the ship worker LXC.
45
+ *
46
+ * iTMSTransporter reads the ASC API private key from a directory
47
+ * named by API_PRIVATE_KEYS_DIR, looking for the file `AuthKey_<keyId>.p8`.
48
+ */
49
+ declare function uploadWithTransporter(options: PilotUploadOptions): Promise<UploadResult>;
50
+
51
+ /**
52
+ * Classify upload failures into transient vs. permanent buckets.
53
+ *
54
+ * Apple's App Store Connect / iTMSTransporter backend is flaky:
55
+ * the finalize step ("CHANGE UPLOAD STATE TO COMPLETE") periodically
56
+ * returns 500, and the Java transporter surfaces various connection
57
+ * errors during long uploads. These are all safe to retry — the IPA
58
+ * is deterministic and Apple deduplicates on build number.
59
+ *
60
+ * Non-transient errors (invalid binary, duplicate build number, auth
61
+ * failure) should NOT be retried — they will never succeed without
62
+ * the caller taking some action.
63
+ */
64
+ type TransientUploadError = "apple_5xx" | "finalize_failed" | "network_timeout" | "connection_reset" | "transporter_jvm_error";
65
+ /**
66
+ * Returns the transient-error kind if the output matches any known
67
+ * retry-worthy pattern, or `null` if the error is non-transient.
68
+ */
69
+ declare function classifyUploadError(output: string): TransientUploadError | null;
70
+ /**
71
+ * Convenience: `true` if the upload should be retried.
72
+ */
73
+ declare function isTransientUploadError(output: string): boolean;
74
+
75
+ /**
76
+ * Locate the iTMSTransporter binary on this host.
77
+ *
78
+ * Apple ships iTMSTransporter as a Java-based tool bundled with Xcode
79
+ * on macOS, and as a standalone Linux distribution used by media
80
+ * publishers for CI. Either works for uploading IPAs — same protocol,
81
+ * same ASC API key format.
82
+ *
83
+ * Resolution order:
84
+ * 1. ITMSTRANSPORTER_BIN env var (pinning for prod deployments)
85
+ * 2. Well-known system paths for the host OS
86
+ * 3. Throw with a clear install hint
87
+ *
88
+ * We deliberately do NOT auto-download Transporter. Apple's download
89
+ * URL is not versioned and the license/redistribution story is fuzzy.
90
+ * Prod hosts should install it once via apt/brew/manual, then pin via
91
+ * env var so CI is reproducible.
92
+ */
93
+ declare function resolveTransporter(): Promise<string>;
94
+ /**
95
+ * Verify that Java is available on PATH. iTMSTransporter is a Java
96
+ * application and won't run without a JRE.
97
+ */
98
+ declare function verifyJava(): Promise<{
99
+ version: string;
100
+ }>;
101
+
102
+ interface PilotDistributeOptions {
103
+ client: AppStoreConnectClient;
104
+ buildId: string;
105
+ groupNames?: string[];
106
+ changelog?: string;
107
+ locale?: string;
108
+ expirePreviousBuilds?: boolean;
109
+ submitForReview?: boolean;
110
+ }
111
+ /**
112
+ * Upload a build to TestFlight.
113
+ *
114
+ * Selects the upload backend based on the host OS:
115
+ * - macOS → altool (ships with Xcode CLI)
116
+ * - Linux → iTMSTransporter (standalone Java tool from Apple)
117
+ *
118
+ * Pass `backend` to force a specific path, e.g. for testing the
119
+ * Linux path on a mac dev machine.
120
+ */
121
+ declare function uploadToTestFlight(options: PilotUploadOptions): Promise<UploadResult>;
122
+ declare function waitForProcessing(client: AppStoreConnectClient, params: {
123
+ bundleId: string;
124
+ buildNumber: string;
125
+ appVersion: string;
126
+ timeout?: number;
127
+ pollInterval?: number;
128
+ }): Promise<Build>;
129
+ declare function distributeToGroups(options: PilotDistributeOptions): Promise<string[]>;
130
+ declare function setChangelog(options: {
131
+ client: AppStoreConnectClient;
132
+ buildId: string;
133
+ changelog: string;
134
+ locale?: string;
135
+ }): Promise<void>;
136
+ declare function expireBuild(client: AppStoreConnectClient, buildId: string): Promise<void>;
137
+
138
+ type index_PilotDistributeOptions = PilotDistributeOptions;
139
+ type index_PilotUploadOptions = PilotUploadOptions;
140
+ type index_TransientUploadError = TransientUploadError;
141
+ type index_UploadBackend = UploadBackend;
142
+ type index_UploadResult = UploadResult;
143
+ declare const index_classifyUploadError: typeof classifyUploadError;
144
+ declare const index_distributeToGroups: typeof distributeToGroups;
145
+ declare const index_expireBuild: typeof expireBuild;
146
+ declare const index_isTransientUploadError: typeof isTransientUploadError;
147
+ declare const index_resolveTransporter: typeof resolveTransporter;
148
+ declare const index_setChangelog: typeof setChangelog;
149
+ declare const index_uploadToTestFlight: typeof uploadToTestFlight;
150
+ declare const index_uploadWithAltool: typeof uploadWithAltool;
151
+ declare const index_uploadWithTransporter: typeof uploadWithTransporter;
152
+ declare const index_verifyJava: typeof verifyJava;
153
+ declare const index_waitForProcessing: typeof waitForProcessing;
154
+ declare namespace index {
155
+ export { type index_PilotDistributeOptions as PilotDistributeOptions, type index_PilotUploadOptions as PilotUploadOptions, type index_TransientUploadError as TransientUploadError, type index_UploadBackend as UploadBackend, type index_UploadResult as UploadResult, index_classifyUploadError as classifyUploadError, index_distributeToGroups as distributeToGroups, index_expireBuild as expireBuild, index_isTransientUploadError as isTransientUploadError, index_resolveTransporter as resolveTransporter, index_setChangelog as setChangelog, index_uploadToTestFlight as uploadToTestFlight, index_uploadWithAltool as uploadWithAltool, index_uploadWithTransporter as uploadWithTransporter, index_verifyJava as verifyJava, index_waitForProcessing as waitForProcessing };
156
+ }
157
+
158
+ export { type PilotDistributeOptions as P, type TransientUploadError as T, type UploadBackend as U, type PilotUploadOptions as a, type UploadResult as b, uploadWithAltool as c, distributeToGroups as d, expireBuild as e, uploadWithTransporter as f, classifyUploadError as g, isTransientUploadError as h, index as i, resolveTransporter as r, setChangelog as s, uploadToTestFlight as u, verifyJava as v, waitForProcessing as w };
package/dist/index.cjs CHANGED
@@ -3029,8 +3029,8 @@ async function revokeExpiredCertificates(options) {
3029
3029
  return count;
3030
3030
  }
3031
3031
  async function importToKeychain(certData, keychainPath, keychainPassword) {
3032
- const { tmpdir: tmpdir9 } = await import('os');
3033
- const tempCertPath = path.join(tmpdir9(), `fastnode-cert-${Date.now()}.cer`);
3032
+ const { tmpdir: tmpdir10 } = await import('os');
3033
+ const tempCertPath = path.join(tmpdir10(), `fastnode-cert-${Date.now()}.cer`);
3034
3034
  await promises.writeFile(tempCertPath, certData);
3035
3035
  try {
3036
3036
  const args = [
@@ -4019,23 +4019,13 @@ var init_produce = __esm({
4019
4019
  };
4020
4020
  }
4021
4021
  });
4022
-
4023
- // src/pilot/index.ts
4024
- var pilot_exports = {};
4025
- __export(pilot_exports, {
4026
- distributeToGroups: () => distributeToGroups,
4027
- expireBuild: () => expireBuild,
4028
- setChangelog: () => setChangelog,
4029
- uploadToTestFlight: () => uploadToTestFlight,
4030
- waitForProcessing: () => waitForProcessing
4031
- });
4032
- async function uploadToTestFlight(options) {
4022
+ async function uploadWithAltool(options) {
4033
4023
  if (!fs.existsSync(options.ipaPath)) {
4034
4024
  throw new Error(`File not found: ${options.ipaPath}`);
4035
4025
  }
4036
4026
  const platform = options.platform ?? "ios";
4037
4027
  const timeout = options.timeout ?? 18e5;
4038
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-key-"));
4028
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-altool-"));
4039
4029
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
4040
4030
  await promises.writeFile(keyPath, options.apiPrivateKey);
4041
4031
  return new Promise((resolve4) => {
@@ -4059,24 +4049,228 @@ async function uploadToTestFlight(options) {
4059
4049
  const timer = setTimeout(() => {
4060
4050
  child.kill("SIGTERM");
4061
4051
  }, timeout);
4062
- child.stdout.on("data", (data) => {
4052
+ const onData = (data) => {
4063
4053
  const chunk = data.toString();
4064
4054
  output += chunk;
4065
4055
  options.onOutput?.(chunk);
4056
+ };
4057
+ child.stdout.on("data", onData);
4058
+ child.stderr.on("data", onData);
4059
+ child.on("close", async (code) => {
4060
+ clearTimeout(timer);
4061
+ await promises.unlink(keyPath).catch(() => {
4062
+ });
4063
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
4064
+ });
4065
+ resolve4({ success: code === 0, output, backend: "altool" });
4066
4066
  });
4067
- child.stderr.on("data", (data) => {
4067
+ });
4068
+ }
4069
+ var init_upload_altool = __esm({
4070
+ "src/pilot/upload-altool.ts"() {
4071
+ }
4072
+ });
4073
+ async function resolveTransporter() {
4074
+ const override = process.env.ITMSTRANSPORTER_BIN;
4075
+ if (override) {
4076
+ if (!fs.existsSync(override)) {
4077
+ throw new Error(
4078
+ `ITMSTRANSPORTER_BIN is set to "${override}" but that path does not exist.`
4079
+ );
4080
+ }
4081
+ return override;
4082
+ }
4083
+ const candidates = process.platform === "darwin" ? [
4084
+ // Xcode 15+: Transporter.app bundled with Xcode
4085
+ "/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter",
4086
+ // Standalone Transporter.app from the Mac App Store
4087
+ "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
4088
+ ] : [
4089
+ "/usr/local/itms/bin/iTMSTransporter",
4090
+ "/opt/itms/bin/iTMSTransporter",
4091
+ "/usr/local/bin/iTMSTransporter"
4092
+ ];
4093
+ for (const path of candidates) {
4094
+ if (fs.existsSync(path)) return path;
4095
+ }
4096
+ throw new Error(
4097
+ [
4098
+ "iTMSTransporter not found.",
4099
+ "",
4100
+ "Install options:",
4101
+ process.platform === "darwin" ? " - Install Xcode (includes Transporter)" : " - Download Apple's standalone iTMSTransporter package for Linux",
4102
+ " - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary",
4103
+ "",
4104
+ `Searched: ${candidates.join(", ")}`
4105
+ ].join("\n")
4106
+ );
4107
+ }
4108
+ async function verifyJava() {
4109
+ return new Promise((resolve4, reject) => {
4110
+ const child = child_process.spawn("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
4111
+ let stderr = "";
4112
+ child.stderr.on("data", (d) => {
4113
+ stderr += d.toString();
4114
+ });
4115
+ child.on("error", (err) => {
4116
+ reject(
4117
+ new Error(
4118
+ `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`
4119
+ )
4120
+ );
4121
+ });
4122
+ child.on("close", (code) => {
4123
+ if (code !== 0) {
4124
+ reject(new Error(`java -version exited ${code}: ${stderr}`));
4125
+ return;
4126
+ }
4127
+ const match = stderr.match(/version "([^"]+)"/);
4128
+ resolve4({ version: match?.[1] ?? "unknown" });
4129
+ });
4130
+ });
4131
+ }
4132
+ var init_transporter_install = __esm({
4133
+ "src/pilot/transporter-install.ts"() {
4134
+ }
4135
+ });
4136
+ async function uploadWithTransporter(options) {
4137
+ if (!fs.existsSync(options.ipaPath)) {
4138
+ throw new Error(`File not found: ${options.ipaPath}`);
4139
+ }
4140
+ await verifyJava();
4141
+ const transporterBin = await resolveTransporter();
4142
+ const platform = options.platform ?? "ios";
4143
+ const timeout = options.timeout ?? 18e5;
4144
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
4145
+ const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
4146
+ await promises.writeFile(keyPath, options.apiPrivateKey);
4147
+ return new Promise((resolve4) => {
4148
+ const args = [
4149
+ "-m",
4150
+ "upload",
4151
+ "-assetFile",
4152
+ options.ipaPath,
4153
+ "-apiKey",
4154
+ options.apiKeyId,
4155
+ "-apiIssuer",
4156
+ options.apiIssuerId,
4157
+ // WONoPause prevents the JVM from waiting on stdin in non-TTY
4158
+ // environments (CI, LaunchAgent) where it would hang forever.
4159
+ "-WONoPause",
4160
+ "true"
4161
+ ];
4162
+ if (platform !== "ios") {
4163
+ args.push("-bundleType", platform);
4164
+ }
4165
+ const child = child_process.spawn(transporterBin, args, {
4166
+ env: {
4167
+ ...process.env,
4168
+ API_PRIVATE_KEYS_DIR: keyDir
4169
+ },
4170
+ stdio: ["ignore", "pipe", "pipe"]
4171
+ });
4172
+ let output = "";
4173
+ const timer = setTimeout(() => {
4174
+ child.kill("SIGTERM");
4175
+ }, timeout);
4176
+ const onData = (data) => {
4068
4177
  const chunk = data.toString();
4069
4178
  output += chunk;
4070
4179
  options.onOutput?.(chunk);
4071
- });
4180
+ };
4181
+ child.stdout.on("data", onData);
4182
+ child.stderr.on("data", onData);
4072
4183
  child.on("close", async (code) => {
4073
4184
  clearTimeout(timer);
4074
- await promises.unlink(keyPath).catch(() => {
4185
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
4186
+ });
4187
+ resolve4({ success: code === 0, output, backend: "transporter" });
4188
+ });
4189
+ child.on("error", async (err) => {
4190
+ clearTimeout(timer);
4191
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
4192
+ });
4193
+ resolve4({
4194
+ success: false,
4195
+ output: output + `
4196
+ Process error: ${err.message}`,
4197
+ backend: "transporter"
4075
4198
  });
4076
- resolve4({ success: code === 0, output });
4077
4199
  });
4078
4200
  });
4079
4201
  }
4202
+ var init_upload_transporter = __esm({
4203
+ "src/pilot/upload-transporter.ts"() {
4204
+ init_transporter_install();
4205
+ }
4206
+ });
4207
+
4208
+ // src/pilot/classify.ts
4209
+ function classifyUploadError(output) {
4210
+ for (const { pattern, kind } of RULES) {
4211
+ if (pattern.test(output)) return kind;
4212
+ }
4213
+ return null;
4214
+ }
4215
+ function isTransientUploadError(output) {
4216
+ return classifyUploadError(output) !== null;
4217
+ }
4218
+ var RULES;
4219
+ var init_classify = __esm({
4220
+ "src/pilot/classify.ts"() {
4221
+ RULES = [
4222
+ // Apple's finalize-upload call returning 5xx — the most common transient
4223
+ // failure we see in practice. Example:
4224
+ // "CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):
4225
+ // received status code 500; internal server error."
4226
+ { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: "finalize_failed" },
4227
+ // Generic 5xx from ASC backend services
4228
+ { pattern: /status code 5\d\d/i, kind: "apple_5xx" },
4229
+ { pattern: /internal server error/i, kind: "apple_5xx" },
4230
+ { pattern: /Service Unavailable/i, kind: "apple_5xx" },
4231
+ { pattern: /Bad Gateway/i, kind: "apple_5xx" },
4232
+ // Network-level interruptions during the upload body transfer
4233
+ { pattern: /Connection reset/i, kind: "connection_reset" },
4234
+ { pattern: /SocketException/i, kind: "connection_reset" },
4235
+ { pattern: /Broken pipe/i, kind: "connection_reset" },
4236
+ // Timeouts — distinct from resets because the other side may have
4237
+ // actually processed the request, so dedupe logic may matter.
4238
+ { pattern: /Read timed out/i, kind: "network_timeout" },
4239
+ { pattern: /SocketTimeoutException/i, kind: "network_timeout" },
4240
+ { pattern: /Operation timed out/i, kind: "network_timeout" },
4241
+ // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)
4242
+ { pattern: /java\.lang\.OutOfMemoryError/i, kind: "transporter_jvm_error" },
4243
+ { pattern: /EOFException/i, kind: "transporter_jvm_error" }
4244
+ ];
4245
+ }
4246
+ });
4247
+
4248
+ // src/pilot/index.ts
4249
+ var pilot_exports = {};
4250
+ __export(pilot_exports, {
4251
+ classifyUploadError: () => classifyUploadError,
4252
+ distributeToGroups: () => distributeToGroups,
4253
+ expireBuild: () => expireBuild,
4254
+ isTransientUploadError: () => isTransientUploadError,
4255
+ resolveTransporter: () => resolveTransporter,
4256
+ setChangelog: () => setChangelog,
4257
+ uploadToTestFlight: () => uploadToTestFlight,
4258
+ uploadWithAltool: () => uploadWithAltool,
4259
+ uploadWithTransporter: () => uploadWithTransporter,
4260
+ verifyJava: () => verifyJava,
4261
+ waitForProcessing: () => waitForProcessing
4262
+ });
4263
+ async function uploadToTestFlight(options) {
4264
+ const backend = selectBackend(options.backend ?? "auto");
4265
+ if (backend === "altool") {
4266
+ return uploadWithAltool(options);
4267
+ }
4268
+ return uploadWithTransporter(options);
4269
+ }
4270
+ function selectBackend(pref) {
4271
+ if (pref !== "auto") return pref;
4272
+ return process.platform === "darwin" ? "altool" : "transporter";
4273
+ }
4080
4274
  async function waitForProcessing(client, params) {
4081
4275
  const timeout = params.timeout ?? 18e5;
4082
4276
  const pollInterval = params.pollInterval ?? 3e4;
@@ -4197,6 +4391,12 @@ function sleep2(ms) {
4197
4391
  var init_pilot = __esm({
4198
4392
  "src/pilot/index.ts"() {
4199
4393
  init_request();
4394
+ init_upload_altool();
4395
+ init_upload_transporter();
4396
+ init_upload_altool();
4397
+ init_upload_transporter();
4398
+ init_classify();
4399
+ init_transporter_install();
4200
4400
  }
4201
4401
  });
4202
4402
  async function loadMetadata(metadataPath) {
@@ -4300,9 +4500,9 @@ var init_platform = __esm({
4300
4500
  // src/xcode/upload.ts
4301
4501
  var upload_exports = {};
4302
4502
  __export(upload_exports, {
4303
- uploadWithAltool: () => uploadWithAltool
4503
+ uploadWithAltool: () => uploadWithAltool2
4304
4504
  });
4305
- async function uploadWithAltool(options) {
4505
+ async function uploadWithAltool2(options) {
4306
4506
  requireMacOS("altool upload");
4307
4507
  let keyDir;
4308
4508
  if (options.apiPrivateKey && !options.apiKeyPath) {
@@ -4411,8 +4611,8 @@ async function uploadToAppStore(options) {
4411
4611
  if (!options.skipBinaryUpload) {
4412
4612
  const binaryPath = options.ipaPath ?? options.pkgPath;
4413
4613
  if (binaryPath && options.apiKeyId && options.apiIssuerId) {
4414
- const { uploadWithAltool: uploadWithAltool2 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
4415
- await uploadWithAltool2({
4614
+ const { uploadWithAltool: uploadWithAltool3 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
4615
+ await uploadWithAltool3({
4416
4616
  filePath: binaryPath,
4417
4617
  apiKeyId: options.apiKeyId,
4418
4618
  apiIssuerId: options.apiIssuerId,
@@ -5388,8 +5588,8 @@ async function captureScreenshots(options) {
5388
5588
  const outputDir = options.outputDirectory ?? path.join(process.cwd(), "screenshots");
5389
5589
  const retries = options.numberOfRetries ?? 1;
5390
5590
  if (options.clearPreviousScreenshots && fs.existsSync(outputDir)) {
5391
- const { rm: rm3 } = await import('fs/promises');
5392
- await rm3(outputDir, { recursive: true, force: true });
5591
+ const { rm: rm5 } = await import('fs/promises');
5592
+ await rm5(outputDir, { recursive: true, force: true });
5393
5593
  }
5394
5594
  fs.mkdirSync(outputDir, { recursive: true });
5395
5595
  const screenshots = {};
@@ -6768,14 +6968,14 @@ var init_building = __esm({
6768
6968
  name: "clean_build_artifacts",
6769
6969
  description: "Remove build artifacts (IPA, dSYM, etc.)",
6770
6970
  async run(params, ctx) {
6771
- const { rm: rm3 } = await import('fs/promises');
6971
+ const { rm: rm5 } = await import('fs/promises');
6772
6972
  const paths = [
6773
6973
  ctx.get("IPA_OUTPUT_PATH"),
6774
6974
  ctx.get("PKG_OUTPUT_PATH"),
6775
6975
  ctx.get("DSYM_OUTPUT_PATH")
6776
6976
  ].filter(Boolean);
6777
6977
  for (const p of paths) {
6778
- await rm3(p, { force: true }).catch(() => {
6978
+ await rm5(p, { force: true }).catch(() => {
6779
6979
  });
6780
6980
  }
6781
6981
  return { cleaned: paths };
@@ -7812,7 +8012,7 @@ __export(xcode_exports, {
7812
8012
  setBuildNumber: () => setBuildNumber,
7813
8013
  setVersionNumber: () => setVersionNumber,
7814
8014
  updateSigningSettings: () => updateSigningSettings,
7815
- uploadWithAltool: () => uploadWithAltool
8015
+ uploadWithAltool: () => uploadWithAltool2
7816
8016
  });
7817
8017
  init_platform();
7818
8018