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