@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.
@@ -209,8 +209,8 @@ async function revokeExpiredCertificates(options) {
209
209
  return count;
210
210
  }
211
211
  async function importToKeychain(certData, keychainPath, keychainPassword) {
212
- const { tmpdir: tmpdir7 } = await import('os');
213
- const tempCertPath = path.join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
212
+ const { tmpdir: tmpdir8 } = await import('os');
213
+ const tempCertPath = path.join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
214
214
  await promises.writeFile(tempCertPath, certData);
215
215
  try {
216
216
  const args = [
@@ -1642,14 +1642,14 @@ var init_building = __esm({
1642
1642
  name: "clean_build_artifacts",
1643
1643
  description: "Remove build artifacts (IPA, dSYM, etc.)",
1644
1644
  async run(params, ctx) {
1645
- const { rm: rm2 } = await import('fs/promises');
1645
+ const { rm: rm4 } = await import('fs/promises');
1646
1646
  const paths = [
1647
1647
  ctx.get("IPA_OUTPUT_PATH"),
1648
1648
  ctx.get("PKG_OUTPUT_PATH"),
1649
1649
  ctx.get("DSYM_OUTPUT_PATH")
1650
1650
  ].filter(Boolean);
1651
1651
  for (const p of paths) {
1652
- await rm2(p, { force: true }).catch(() => {
1652
+ await rm4(p, { force: true }).catch(() => {
1653
1653
  });
1654
1654
  }
1655
1655
  return { cleaned: paths };
@@ -2296,23 +2296,13 @@ var init_request = __esm({
2296
2296
  };
2297
2297
  }
2298
2298
  });
2299
-
2300
- // src/pilot/index.ts
2301
- var pilot_exports = {};
2302
- __export(pilot_exports, {
2303
- distributeToGroups: () => distributeToGroups,
2304
- expireBuild: () => expireBuild,
2305
- setChangelog: () => setChangelog,
2306
- uploadToTestFlight: () => uploadToTestFlight,
2307
- waitForProcessing: () => waitForProcessing
2308
- });
2309
- async function uploadToTestFlight(options) {
2299
+ async function uploadWithAltool(options) {
2310
2300
  if (!fs.existsSync(options.ipaPath)) {
2311
2301
  throw new Error(`File not found: ${options.ipaPath}`);
2312
2302
  }
2313
2303
  const platform = options.platform ?? "ios";
2314
2304
  const timeout = options.timeout ?? 18e5;
2315
- const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-key-"));
2305
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-altool-"));
2316
2306
  const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2317
2307
  await promises.writeFile(keyPath, options.apiPrivateKey);
2318
2308
  return new Promise((resolve4) => {
@@ -2336,24 +2326,228 @@ async function uploadToTestFlight(options) {
2336
2326
  const timer = setTimeout(() => {
2337
2327
  child.kill("SIGTERM");
2338
2328
  }, timeout);
2339
- child.stdout.on("data", (data) => {
2329
+ const onData = (data) => {
2340
2330
  const chunk = data.toString();
2341
2331
  output += chunk;
2342
2332
  options.onOutput?.(chunk);
2333
+ };
2334
+ child.stdout.on("data", onData);
2335
+ child.stderr.on("data", onData);
2336
+ child.on("close", async (code) => {
2337
+ clearTimeout(timer);
2338
+ await promises.unlink(keyPath).catch(() => {
2339
+ });
2340
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2341
+ });
2342
+ resolve4({ success: code === 0, output, backend: "altool" });
2343
2343
  });
2344
- child.stderr.on("data", (data) => {
2344
+ });
2345
+ }
2346
+ var init_upload_altool = __esm({
2347
+ "src/pilot/upload-altool.ts"() {
2348
+ }
2349
+ });
2350
+ async function resolveTransporter() {
2351
+ const override = process.env.ITMSTRANSPORTER_BIN;
2352
+ if (override) {
2353
+ if (!fs.existsSync(override)) {
2354
+ throw new Error(
2355
+ `ITMSTRANSPORTER_BIN is set to "${override}" but that path does not exist.`
2356
+ );
2357
+ }
2358
+ return override;
2359
+ }
2360
+ const candidates = process.platform === "darwin" ? [
2361
+ // Xcode 15+: Transporter.app bundled with Xcode
2362
+ "/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter",
2363
+ // Standalone Transporter.app from the Mac App Store
2364
+ "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
2365
+ ] : [
2366
+ "/usr/local/itms/bin/iTMSTransporter",
2367
+ "/opt/itms/bin/iTMSTransporter",
2368
+ "/usr/local/bin/iTMSTransporter"
2369
+ ];
2370
+ for (const path of candidates) {
2371
+ if (fs.existsSync(path)) return path;
2372
+ }
2373
+ throw new Error(
2374
+ [
2375
+ "iTMSTransporter not found.",
2376
+ "",
2377
+ "Install options:",
2378
+ process.platform === "darwin" ? " - Install Xcode (includes Transporter)" : " - Download Apple's standalone iTMSTransporter package for Linux",
2379
+ " - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary",
2380
+ "",
2381
+ `Searched: ${candidates.join(", ")}`
2382
+ ].join("\n")
2383
+ );
2384
+ }
2385
+ async function verifyJava() {
2386
+ return new Promise((resolve4, reject) => {
2387
+ const child = child_process.spawn("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
2388
+ let stderr = "";
2389
+ child.stderr.on("data", (d) => {
2390
+ stderr += d.toString();
2391
+ });
2392
+ child.on("error", (err) => {
2393
+ reject(
2394
+ new Error(
2395
+ `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`
2396
+ )
2397
+ );
2398
+ });
2399
+ child.on("close", (code) => {
2400
+ if (code !== 0) {
2401
+ reject(new Error(`java -version exited ${code}: ${stderr}`));
2402
+ return;
2403
+ }
2404
+ const match = stderr.match(/version "([^"]+)"/);
2405
+ resolve4({ version: match?.[1] ?? "unknown" });
2406
+ });
2407
+ });
2408
+ }
2409
+ var init_transporter_install = __esm({
2410
+ "src/pilot/transporter-install.ts"() {
2411
+ }
2412
+ });
2413
+ async function uploadWithTransporter(options) {
2414
+ if (!fs.existsSync(options.ipaPath)) {
2415
+ throw new Error(`File not found: ${options.ipaPath}`);
2416
+ }
2417
+ await verifyJava();
2418
+ const transporterBin = await resolveTransporter();
2419
+ const platform = options.platform ?? "ios";
2420
+ const timeout = options.timeout ?? 18e5;
2421
+ const keyDir = await promises.mkdtemp(path.join(os.tmpdir(), "fastnode-itms-"));
2422
+ const keyPath = path.join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2423
+ await promises.writeFile(keyPath, options.apiPrivateKey);
2424
+ return new Promise((resolve4) => {
2425
+ const args = [
2426
+ "-m",
2427
+ "upload",
2428
+ "-assetFile",
2429
+ options.ipaPath,
2430
+ "-apiKey",
2431
+ options.apiKeyId,
2432
+ "-apiIssuer",
2433
+ options.apiIssuerId,
2434
+ // WONoPause prevents the JVM from waiting on stdin in non-TTY
2435
+ // environments (CI, LaunchAgent) where it would hang forever.
2436
+ "-WONoPause",
2437
+ "true"
2438
+ ];
2439
+ if (platform !== "ios") {
2440
+ args.push("-bundleType", platform);
2441
+ }
2442
+ const child = child_process.spawn(transporterBin, args, {
2443
+ env: {
2444
+ ...process.env,
2445
+ API_PRIVATE_KEYS_DIR: keyDir
2446
+ },
2447
+ stdio: ["ignore", "pipe", "pipe"]
2448
+ });
2449
+ let output = "";
2450
+ const timer = setTimeout(() => {
2451
+ child.kill("SIGTERM");
2452
+ }, timeout);
2453
+ const onData = (data) => {
2345
2454
  const chunk = data.toString();
2346
2455
  output += chunk;
2347
2456
  options.onOutput?.(chunk);
2348
- });
2457
+ };
2458
+ child.stdout.on("data", onData);
2459
+ child.stderr.on("data", onData);
2349
2460
  child.on("close", async (code) => {
2350
2461
  clearTimeout(timer);
2351
- await promises.unlink(keyPath).catch(() => {
2462
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2463
+ });
2464
+ resolve4({ success: code === 0, output, backend: "transporter" });
2465
+ });
2466
+ child.on("error", async (err) => {
2467
+ clearTimeout(timer);
2468
+ await promises.rm(keyDir, { recursive: true, force: true }).catch(() => {
2469
+ });
2470
+ resolve4({
2471
+ success: false,
2472
+ output: output + `
2473
+ Process error: ${err.message}`,
2474
+ backend: "transporter"
2352
2475
  });
2353
- resolve4({ success: code === 0, output });
2354
2476
  });
2355
2477
  });
2356
2478
  }
2479
+ var init_upload_transporter = __esm({
2480
+ "src/pilot/upload-transporter.ts"() {
2481
+ init_transporter_install();
2482
+ }
2483
+ });
2484
+
2485
+ // src/pilot/classify.ts
2486
+ function classifyUploadError(output) {
2487
+ for (const { pattern, kind } of RULES) {
2488
+ if (pattern.test(output)) return kind;
2489
+ }
2490
+ return null;
2491
+ }
2492
+ function isTransientUploadError(output) {
2493
+ return classifyUploadError(output) !== null;
2494
+ }
2495
+ var RULES;
2496
+ var init_classify = __esm({
2497
+ "src/pilot/classify.ts"() {
2498
+ RULES = [
2499
+ // Apple's finalize-upload call returning 5xx — the most common transient
2500
+ // failure we see in practice. Example:
2501
+ // "CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):
2502
+ // received status code 500; internal server error."
2503
+ { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: "finalize_failed" },
2504
+ // Generic 5xx from ASC backend services
2505
+ { pattern: /status code 5\d\d/i, kind: "apple_5xx" },
2506
+ { pattern: /internal server error/i, kind: "apple_5xx" },
2507
+ { pattern: /Service Unavailable/i, kind: "apple_5xx" },
2508
+ { pattern: /Bad Gateway/i, kind: "apple_5xx" },
2509
+ // Network-level interruptions during the upload body transfer
2510
+ { pattern: /Connection reset/i, kind: "connection_reset" },
2511
+ { pattern: /SocketException/i, kind: "connection_reset" },
2512
+ { pattern: /Broken pipe/i, kind: "connection_reset" },
2513
+ // Timeouts — distinct from resets because the other side may have
2514
+ // actually processed the request, so dedupe logic may matter.
2515
+ { pattern: /Read timed out/i, kind: "network_timeout" },
2516
+ { pattern: /SocketTimeoutException/i, kind: "network_timeout" },
2517
+ { pattern: /Operation timed out/i, kind: "network_timeout" },
2518
+ // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)
2519
+ { pattern: /java\.lang\.OutOfMemoryError/i, kind: "transporter_jvm_error" },
2520
+ { pattern: /EOFException/i, kind: "transporter_jvm_error" }
2521
+ ];
2522
+ }
2523
+ });
2524
+
2525
+ // src/pilot/index.ts
2526
+ var pilot_exports = {};
2527
+ __export(pilot_exports, {
2528
+ classifyUploadError: () => classifyUploadError,
2529
+ distributeToGroups: () => distributeToGroups,
2530
+ expireBuild: () => expireBuild,
2531
+ isTransientUploadError: () => isTransientUploadError,
2532
+ resolveTransporter: () => resolveTransporter,
2533
+ setChangelog: () => setChangelog,
2534
+ uploadToTestFlight: () => uploadToTestFlight,
2535
+ uploadWithAltool: () => uploadWithAltool,
2536
+ uploadWithTransporter: () => uploadWithTransporter,
2537
+ verifyJava: () => verifyJava,
2538
+ waitForProcessing: () => waitForProcessing
2539
+ });
2540
+ async function uploadToTestFlight(options) {
2541
+ const backend = selectBackend(options.backend ?? "auto");
2542
+ if (backend === "altool") {
2543
+ return uploadWithAltool(options);
2544
+ }
2545
+ return uploadWithTransporter(options);
2546
+ }
2547
+ function selectBackend(pref) {
2548
+ if (pref !== "auto") return pref;
2549
+ return process.platform === "darwin" ? "altool" : "transporter";
2550
+ }
2357
2551
  async function waitForProcessing(client, params) {
2358
2552
  const timeout = params.timeout ?? 18e5;
2359
2553
  const pollInterval = params.pollInterval ?? 3e4;
@@ -2474,6 +2668,12 @@ function sleep2(ms) {
2474
2668
  var init_pilot = __esm({
2475
2669
  "src/pilot/index.ts"() {
2476
2670
  init_request();
2671
+ init_upload_altool();
2672
+ init_upload_transporter();
2673
+ init_upload_altool();
2674
+ init_upload_transporter();
2675
+ init_classify();
2676
+ init_transporter_install();
2477
2677
  }
2478
2678
  });
2479
2679
  async function loadMetadata(metadataPath) {
@@ -2561,9 +2761,9 @@ var init_metadata = __esm({
2561
2761
  // src/xcode/upload.ts
2562
2762
  var upload_exports = {};
2563
2763
  __export(upload_exports, {
2564
- uploadWithAltool: () => uploadWithAltool
2764
+ uploadWithAltool: () => uploadWithAltool2
2565
2765
  });
2566
- async function uploadWithAltool(options) {
2766
+ async function uploadWithAltool2(options) {
2567
2767
  requireMacOS("altool upload");
2568
2768
  let keyDir;
2569
2769
  if (options.apiPrivateKey && !options.apiKeyPath) {
@@ -2672,8 +2872,8 @@ async function uploadToAppStore(options) {
2672
2872
  if (!options.skipBinaryUpload) {
2673
2873
  const binaryPath = options.ipaPath ?? options.pkgPath;
2674
2874
  if (binaryPath && options.apiKeyId && options.apiIssuerId) {
2675
- const { uploadWithAltool: uploadWithAltool2 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2676
- await uploadWithAltool2({
2875
+ const { uploadWithAltool: uploadWithAltool3 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2876
+ await uploadWithAltool3({
2677
2877
  filePath: binaryPath,
2678
2878
  apiKeyId: options.apiKeyId,
2679
2879
  apiIssuerId: options.apiIssuerId,
@@ -3459,8 +3659,8 @@ async function captureScreenshots(options) {
3459
3659
  const outputDir = options.outputDirectory ?? path.join(process.cwd(), "screenshots");
3460
3660
  const retries = options.numberOfRetries ?? 1;
3461
3661
  if (options.clearPreviousScreenshots && fs.existsSync(outputDir)) {
3462
- const { rm: rm2 } = await import('fs/promises');
3463
- await rm2(outputDir, { recursive: true, force: true });
3662
+ const { rm: rm4 } = await import('fs/promises');
3663
+ await rm4(outputDir, { recursive: true, force: true });
3464
3664
  }
3465
3665
  fs.mkdirSync(outputDir, { recursive: true });
3466
3666
  const screenshots = {};