@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.
@@ -178,8 +178,8 @@ async function revokeExpiredCertificates(options) {
178
178
  return count;
179
179
  }
180
180
  async function importToKeychain(certData, keychainPath, keychainPassword) {
181
- const { tmpdir: tmpdir7 } = await import('os');
182
- const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
181
+ const { tmpdir: tmpdir8 } = await import('os');
182
+ const tempCertPath = path.join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
183
183
  await promises.writeFile(tempCertPath, certData);
184
184
  try {
185
185
  const args = [
@@ -1611,14 +1611,14 @@ var init_building = __esm({
1611
1611
  name: "clean_build_artifacts",
1612
1612
  description: "Remove build artifacts (IPA, dSYM, etc.)",
1613
1613
  async run(params, ctx) {
1614
- const { rm: rm2 } = await import('fs/promises');
1614
+ const { rm: rm4 } = await import('fs/promises');
1615
1615
  const paths = [
1616
1616
  ctx.get("IPA_OUTPUT_PATH"),
1617
1617
  ctx.get("PKG_OUTPUT_PATH"),
1618
1618
  ctx.get("DSYM_OUTPUT_PATH")
1619
1619
  ].filter(Boolean);
1620
1620
  for (const p of paths) {
1621
- await rm2(p, { force: true }).catch(() => {
1621
+ await rm4(p, { force: true }).catch(() => {
1622
1622
  });
1623
1623
  }
1624
1624
  return { cleaned: paths };
@@ -2265,23 +2265,13 @@ var init_request = __esm({
2265
2265
  };
2266
2266
  }
2267
2267
  });
2268
-
2269
- // src/pilot/index.ts
2270
- var pilot_exports = {};
2271
- __export(pilot_exports, {
2272
- distributeToGroups: () => distributeToGroups,
2273
- expireBuild: () => expireBuild,
2274
- setChangelog: () => setChangelog,
2275
- uploadToTestFlight: () => uploadToTestFlight,
2276
- waitForProcessing: () => waitForProcessing
2277
- });
2278
- async function uploadToTestFlight(options) {
2268
+ async function uploadWithAltool(options) {
2279
2269
  if (!fs.existsSync(options.ipaPath)) {
2280
2270
  throw new Error(`File not found: ${options.ipaPath}`);
2281
2271
  }
2282
2272
  const platform = options.platform ?? "ios";
2283
2273
  const timeout = options.timeout ?? 18e5;
2284
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-key-"));
2274
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-altool-"));
2285
2275
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2286
2276
  await promises.writeFile(keyPath, options.apiPrivateKey);
2287
2277
  return new Promise((resolve3) => {
@@ -2305,24 +2295,228 @@ async function uploadToTestFlight(options) {
2305
2295
  const timer = setTimeout(() => {
2306
2296
  child.kill("SIGTERM");
2307
2297
  }, timeout);
2308
- child.stdout.on("data", (data) => {
2298
+ const onData = (data) => {
2309
2299
  const chunk = data.toString();
2310
2300
  output += chunk;
2311
2301
  options.onOutput?.(chunk);
2302
+ };
2303
+ child.stdout.on("data", onData);
2304
+ child.stderr.on("data", onData);
2305
+ child.on("close", async (code) => {
2306
+ clearTimeout(timer);
2307
+ await promises.unlink(keyPath).catch(() => {
2308
+ });
2309
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2310
+ });
2311
+ resolve3({ success: code === 0, output, backend: "altool" });
2312
2312
  });
2313
- child.stderr.on("data", (data) => {
2313
+ });
2314
+ }
2315
+ var init_upload_altool = __esm({
2316
+ "src/pilot/upload-altool.ts"() {
2317
+ }
2318
+ });
2319
+ async function resolveTransporter() {
2320
+ const override = process.env.ITMSTRANSPORTER_BIN;
2321
+ if (override) {
2322
+ if (!fs.existsSync(override)) {
2323
+ throw new Error(
2324
+ `ITMSTRANSPORTER_BIN is set to "${override}" but that path does not exist.`
2325
+ );
2326
+ }
2327
+ return override;
2328
+ }
2329
+ const candidates = process.platform === "darwin" ? [
2330
+ // Xcode 15+: Transporter.app bundled with Xcode
2331
+ "/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter",
2332
+ // Standalone Transporter.app from the Mac App Store
2333
+ "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
2334
+ ] : [
2335
+ "/usr/local/itms/bin/iTMSTransporter",
2336
+ "/opt/itms/bin/iTMSTransporter",
2337
+ "/usr/local/bin/iTMSTransporter"
2338
+ ];
2339
+ for (const path of candidates) {
2340
+ if (fs.existsSync(path)) return path;
2341
+ }
2342
+ throw new Error(
2343
+ [
2344
+ "iTMSTransporter not found.",
2345
+ "",
2346
+ "Install options:",
2347
+ process.platform === "darwin" ? " - Install Xcode (includes Transporter)" : " - Download Apple's standalone iTMSTransporter package for Linux",
2348
+ " - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary",
2349
+ "",
2350
+ `Searched: ${candidates.join(", ")}`
2351
+ ].join("\n")
2352
+ );
2353
+ }
2354
+ async function verifyJava() {
2355
+ return new Promise((resolve3, reject) => {
2356
+ const child = child_process.spawn("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
2357
+ let stderr = "";
2358
+ child.stderr.on("data", (d) => {
2359
+ stderr += d.toString();
2360
+ });
2361
+ child.on("error", (err) => {
2362
+ reject(
2363
+ new Error(
2364
+ `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`
2365
+ )
2366
+ );
2367
+ });
2368
+ child.on("close", (code) => {
2369
+ if (code !== 0) {
2370
+ reject(new Error(`java -version exited ${code}: ${stderr}`));
2371
+ return;
2372
+ }
2373
+ const match = stderr.match(/version "([^"]+)"/);
2374
+ resolve3({ version: match?.[1] ?? "unknown" });
2375
+ });
2376
+ });
2377
+ }
2378
+ var init_transporter_install = __esm({
2379
+ "src/pilot/transporter-install.ts"() {
2380
+ }
2381
+ });
2382
+ async function uploadWithTransporter(options) {
2383
+ if (!fs.existsSync(options.ipaPath)) {
2384
+ throw new Error(`File not found: ${options.ipaPath}`);
2385
+ }
2386
+ await verifyJava();
2387
+ const transporterBin = await resolveTransporter();
2388
+ const platform = options.platform ?? "ios";
2389
+ const timeout = options.timeout ?? 18e5;
2390
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
2391
+ const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2392
+ await promises.writeFile(keyPath, options.apiPrivateKey);
2393
+ return new Promise((resolve3) => {
2394
+ const args = [
2395
+ "-m",
2396
+ "upload",
2397
+ "-assetFile",
2398
+ options.ipaPath,
2399
+ "-apiKey",
2400
+ options.apiKeyId,
2401
+ "-apiIssuer",
2402
+ options.apiIssuerId,
2403
+ // WONoPause prevents the JVM from waiting on stdin in non-TTY
2404
+ // environments (CI, LaunchAgent) where it would hang forever.
2405
+ "-WONoPause",
2406
+ "true"
2407
+ ];
2408
+ if (platform !== "ios") {
2409
+ args.push("-bundleType", platform);
2410
+ }
2411
+ const child = child_process.spawn(transporterBin, args, {
2412
+ env: {
2413
+ ...process.env,
2414
+ API_PRIVATE_KEYS_DIR: keyDir
2415
+ },
2416
+ stdio: ["ignore", "pipe", "pipe"]
2417
+ });
2418
+ let output = "";
2419
+ const timer = setTimeout(() => {
2420
+ child.kill("SIGTERM");
2421
+ }, timeout);
2422
+ const onData = (data) => {
2314
2423
  const chunk = data.toString();
2315
2424
  output += chunk;
2316
2425
  options.onOutput?.(chunk);
2317
- });
2426
+ };
2427
+ child.stdout.on("data", onData);
2428
+ child.stderr.on("data", onData);
2318
2429
  child.on("close", async (code) => {
2319
2430
  clearTimeout(timer);
2320
- await promises.unlink(keyPath).catch(() => {
2431
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2432
+ });
2433
+ resolve3({ success: code === 0, output, backend: "transporter" });
2434
+ });
2435
+ child.on("error", async (err) => {
2436
+ clearTimeout(timer);
2437
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2438
+ });
2439
+ resolve3({
2440
+ success: false,
2441
+ output: output + `
2442
+ Process error: ${err.message}`,
2443
+ backend: "transporter"
2321
2444
  });
2322
- resolve3({ success: code === 0, output });
2323
2445
  });
2324
2446
  });
2325
2447
  }
2448
+ var init_upload_transporter = __esm({
2449
+ "src/pilot/upload-transporter.ts"() {
2450
+ init_transporter_install();
2451
+ }
2452
+ });
2453
+
2454
+ // src/pilot/classify.ts
2455
+ function classifyUploadError(output) {
2456
+ for (const { pattern, kind } of RULES) {
2457
+ if (pattern.test(output)) return kind;
2458
+ }
2459
+ return null;
2460
+ }
2461
+ function isTransientUploadError(output) {
2462
+ return classifyUploadError(output) !== null;
2463
+ }
2464
+ var RULES;
2465
+ var init_classify = __esm({
2466
+ "src/pilot/classify.ts"() {
2467
+ RULES = [
2468
+ // Apple's finalize-upload call returning 5xx — the most common transient
2469
+ // failure we see in practice. Example:
2470
+ // "CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):
2471
+ // received status code 500; internal server error."
2472
+ { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: "finalize_failed" },
2473
+ // Generic 5xx from ASC backend services
2474
+ { pattern: /status code 5\d\d/i, kind: "apple_5xx" },
2475
+ { pattern: /internal server error/i, kind: "apple_5xx" },
2476
+ { pattern: /Service Unavailable/i, kind: "apple_5xx" },
2477
+ { pattern: /Bad Gateway/i, kind: "apple_5xx" },
2478
+ // Network-level interruptions during the upload body transfer
2479
+ { pattern: /Connection reset/i, kind: "connection_reset" },
2480
+ { pattern: /SocketException/i, kind: "connection_reset" },
2481
+ { pattern: /Broken pipe/i, kind: "connection_reset" },
2482
+ // Timeouts — distinct from resets because the other side may have
2483
+ // actually processed the request, so dedupe logic may matter.
2484
+ { pattern: /Read timed out/i, kind: "network_timeout" },
2485
+ { pattern: /SocketTimeoutException/i, kind: "network_timeout" },
2486
+ { pattern: /Operation timed out/i, kind: "network_timeout" },
2487
+ // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)
2488
+ { pattern: /java\.lang\.OutOfMemoryError/i, kind: "transporter_jvm_error" },
2489
+ { pattern: /EOFException/i, kind: "transporter_jvm_error" }
2490
+ ];
2491
+ }
2492
+ });
2493
+
2494
+ // src/pilot/index.ts
2495
+ var pilot_exports = {};
2496
+ __export(pilot_exports, {
2497
+ classifyUploadError: () => classifyUploadError,
2498
+ distributeToGroups: () => distributeToGroups,
2499
+ expireBuild: () => expireBuild,
2500
+ isTransientUploadError: () => isTransientUploadError,
2501
+ resolveTransporter: () => resolveTransporter,
2502
+ setChangelog: () => setChangelog,
2503
+ uploadToTestFlight: () => uploadToTestFlight,
2504
+ uploadWithAltool: () => uploadWithAltool,
2505
+ uploadWithTransporter: () => uploadWithTransporter,
2506
+ verifyJava: () => verifyJava,
2507
+ waitForProcessing: () => waitForProcessing
2508
+ });
2509
+ async function uploadToTestFlight(options) {
2510
+ const backend = selectBackend(options.backend ?? "auto");
2511
+ if (backend === "altool") {
2512
+ return uploadWithAltool(options);
2513
+ }
2514
+ return uploadWithTransporter(options);
2515
+ }
2516
+ function selectBackend(pref) {
2517
+ if (pref !== "auto") return pref;
2518
+ return process.platform === "darwin" ? "altool" : "transporter";
2519
+ }
2326
2520
  async function waitForProcessing(client, params) {
2327
2521
  const timeout = params.timeout ?? 18e5;
2328
2522
  const pollInterval = params.pollInterval ?? 3e4;
@@ -2443,6 +2637,12 @@ function sleep2(ms) {
2443
2637
  var init_pilot = __esm({
2444
2638
  "src/pilot/index.ts"() {
2445
2639
  init_request();
2640
+ init_upload_altool();
2641
+ init_upload_transporter();
2642
+ init_upload_altool();
2643
+ init_upload_transporter();
2644
+ init_classify();
2645
+ init_transporter_install();
2446
2646
  }
2447
2647
  });
2448
2648
  async function loadMetadata(metadataPath) {
@@ -2530,9 +2730,9 @@ var init_metadata = __esm({
2530
2730
  // src/xcode/upload.ts
2531
2731
  var upload_exports = {};
2532
2732
  __export(upload_exports, {
2533
- uploadWithAltool: () => uploadWithAltool
2733
+ uploadWithAltool: () => uploadWithAltool2
2534
2734
  });
2535
- async function uploadWithAltool(options) {
2735
+ async function uploadWithAltool2(options) {
2536
2736
  requireMacOS("altool upload");
2537
2737
  let keyDir;
2538
2738
  if (options.apiPrivateKey && !options.apiKeyPath) {
@@ -2641,8 +2841,8 @@ async function uploadToAppStore(options) {
2641
2841
  if (!options.skipBinaryUpload) {
2642
2842
  const binaryPath = options.ipaPath ?? options.pkgPath;
2643
2843
  if (binaryPath && options.apiKeyId && options.apiIssuerId) {
2644
- const { uploadWithAltool: uploadWithAltool2 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2645
- await uploadWithAltool2({
2844
+ const { uploadWithAltool: uploadWithAltool3 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2845
+ await uploadWithAltool3({
2646
2846
  filePath: binaryPath,
2647
2847
  apiKeyId: options.apiKeyId,
2648
2848
  apiIssuerId: options.apiIssuerId,
@@ -3428,8 +3628,8 @@ async function captureScreenshots(options) {
3428
3628
  const outputDir = options.outputDirectory ?? path.join(process.cwd(), "screenshots");
3429
3629
  const retries = options.numberOfRetries ?? 1;
3430
3630
  if (options.clearPreviousScreenshots && fs.existsSync(outputDir)) {
3431
- const { rm: rm2 } = await import('fs/promises');
3432
- await rm2(outputDir, { recursive: true, force: true });
3631
+ const { rm: rm4 } = await import('fs/promises');
3632
+ await rm4(outputDir, { recursive: true, force: true });
3433
3633
  }
3434
3634
  fs.mkdirSync(outputDir, { recursive: true });
3435
3635
  const screenshots = {};