@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.
@@ -153,8 +153,8 @@ async function revokeExpiredCertificates(options) {
153
153
  return count;
154
154
  }
155
155
  async function importToKeychain(certData, keychainPath, keychainPassword) {
156
- const { tmpdir: tmpdir7 } = await import('os');
157
- const tempCertPath = join(tmpdir7(), `fastnode-cert-${Date.now()}.cer`);
156
+ const { tmpdir: tmpdir8 } = await import('os');
157
+ const tempCertPath = join(tmpdir8(), `fastnode-cert-${Date.now()}.cer`);
158
158
  await writeFile(tempCertPath, certData);
159
159
  try {
160
160
  const args = [
@@ -1586,14 +1586,14 @@ var init_building = __esm({
1586
1586
  name: "clean_build_artifacts",
1587
1587
  description: "Remove build artifacts (IPA, dSYM, etc.)",
1588
1588
  async run(params, ctx) {
1589
- const { rm: rm2 } = await import('fs/promises');
1589
+ const { rm: rm4 } = await import('fs/promises');
1590
1590
  const paths = [
1591
1591
  ctx.get("IPA_OUTPUT_PATH"),
1592
1592
  ctx.get("PKG_OUTPUT_PATH"),
1593
1593
  ctx.get("DSYM_OUTPUT_PATH")
1594
1594
  ].filter(Boolean);
1595
1595
  for (const p of paths) {
1596
- await rm2(p, { force: true }).catch(() => {
1596
+ await rm4(p, { force: true }).catch(() => {
1597
1597
  });
1598
1598
  }
1599
1599
  return { cleaned: paths };
@@ -2240,23 +2240,13 @@ var init_request = __esm({
2240
2240
  };
2241
2241
  }
2242
2242
  });
2243
-
2244
- // src/pilot/index.ts
2245
- var pilot_exports = {};
2246
- __export(pilot_exports, {
2247
- distributeToGroups: () => distributeToGroups,
2248
- expireBuild: () => expireBuild,
2249
- setChangelog: () => setChangelog,
2250
- uploadToTestFlight: () => uploadToTestFlight,
2251
- waitForProcessing: () => waitForProcessing
2252
- });
2253
- async function uploadToTestFlight(options) {
2243
+ async function uploadWithAltool(options) {
2254
2244
  if (!existsSync(options.ipaPath)) {
2255
2245
  throw new Error(`File not found: ${options.ipaPath}`);
2256
2246
  }
2257
2247
  const platform = options.platform ?? "ios";
2258
2248
  const timeout = options.timeout ?? 18e5;
2259
- const keyDir = await mkdtemp(join(tmpdir(), "fastnode-key-"));
2249
+ const keyDir = await mkdtemp(join(tmpdir(), "fastnode-altool-"));
2260
2250
  const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2261
2251
  await writeFile(keyPath, options.apiPrivateKey);
2262
2252
  return new Promise((resolve4) => {
@@ -2280,24 +2270,228 @@ async function uploadToTestFlight(options) {
2280
2270
  const timer = setTimeout(() => {
2281
2271
  child.kill("SIGTERM");
2282
2272
  }, timeout);
2283
- child.stdout.on("data", (data) => {
2273
+ const onData = (data) => {
2284
2274
  const chunk = data.toString();
2285
2275
  output += chunk;
2286
2276
  options.onOutput?.(chunk);
2277
+ };
2278
+ child.stdout.on("data", onData);
2279
+ child.stderr.on("data", onData);
2280
+ child.on("close", async (code) => {
2281
+ clearTimeout(timer);
2282
+ await unlink(keyPath).catch(() => {
2283
+ });
2284
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
2285
+ });
2286
+ resolve4({ success: code === 0, output, backend: "altool" });
2287
2287
  });
2288
- child.stderr.on("data", (data) => {
2288
+ });
2289
+ }
2290
+ var init_upload_altool = __esm({
2291
+ "src/pilot/upload-altool.ts"() {
2292
+ }
2293
+ });
2294
+ async function resolveTransporter() {
2295
+ const override = process.env.ITMSTRANSPORTER_BIN;
2296
+ if (override) {
2297
+ if (!existsSync(override)) {
2298
+ throw new Error(
2299
+ `ITMSTRANSPORTER_BIN is set to "${override}" but that path does not exist.`
2300
+ );
2301
+ }
2302
+ return override;
2303
+ }
2304
+ const candidates = process.platform === "darwin" ? [
2305
+ // Xcode 15+: Transporter.app bundled with Xcode
2306
+ "/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/itms/bin/iTMSTransporter",
2307
+ // Standalone Transporter.app from the Mac App Store
2308
+ "/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter"
2309
+ ] : [
2310
+ "/usr/local/itms/bin/iTMSTransporter",
2311
+ "/opt/itms/bin/iTMSTransporter",
2312
+ "/usr/local/bin/iTMSTransporter"
2313
+ ];
2314
+ for (const path of candidates) {
2315
+ if (existsSync(path)) return path;
2316
+ }
2317
+ throw new Error(
2318
+ [
2319
+ "iTMSTransporter not found.",
2320
+ "",
2321
+ "Install options:",
2322
+ process.platform === "darwin" ? " - Install Xcode (includes Transporter)" : " - Download Apple's standalone iTMSTransporter package for Linux",
2323
+ " - Or set ITMSTRANSPORTER_BIN to the absolute path of the binary",
2324
+ "",
2325
+ `Searched: ${candidates.join(", ")}`
2326
+ ].join("\n")
2327
+ );
2328
+ }
2329
+ async function verifyJava() {
2330
+ return new Promise((resolve4, reject) => {
2331
+ const child = spawn("java", ["-version"], { stdio: ["ignore", "pipe", "pipe"] });
2332
+ let stderr = "";
2333
+ child.stderr.on("data", (d) => {
2334
+ stderr += d.toString();
2335
+ });
2336
+ child.on("error", (err) => {
2337
+ reject(
2338
+ new Error(
2339
+ `java not found on PATH (${err.message}). Install Java 17 LTS and ensure it is on PATH.`
2340
+ )
2341
+ );
2342
+ });
2343
+ child.on("close", (code) => {
2344
+ if (code !== 0) {
2345
+ reject(new Error(`java -version exited ${code}: ${stderr}`));
2346
+ return;
2347
+ }
2348
+ const match = stderr.match(/version "([^"]+)"/);
2349
+ resolve4({ version: match?.[1] ?? "unknown" });
2350
+ });
2351
+ });
2352
+ }
2353
+ var init_transporter_install = __esm({
2354
+ "src/pilot/transporter-install.ts"() {
2355
+ }
2356
+ });
2357
+ async function uploadWithTransporter(options) {
2358
+ if (!existsSync(options.ipaPath)) {
2359
+ throw new Error(`File not found: ${options.ipaPath}`);
2360
+ }
2361
+ await verifyJava();
2362
+ const transporterBin = await resolveTransporter();
2363
+ const platform = options.platform ?? "ios";
2364
+ const timeout = options.timeout ?? 18e5;
2365
+ const keyDir = await mkdtemp(join(tmpdir(), "fastnode-itms-"));
2366
+ const keyPath = join(keyDir, `AuthKey_${options.apiKeyId}.p8`);
2367
+ await writeFile(keyPath, options.apiPrivateKey);
2368
+ return new Promise((resolve4) => {
2369
+ const args = [
2370
+ "-m",
2371
+ "upload",
2372
+ "-assetFile",
2373
+ options.ipaPath,
2374
+ "-apiKey",
2375
+ options.apiKeyId,
2376
+ "-apiIssuer",
2377
+ options.apiIssuerId,
2378
+ // WONoPause prevents the JVM from waiting on stdin in non-TTY
2379
+ // environments (CI, LaunchAgent) where it would hang forever.
2380
+ "-WONoPause",
2381
+ "true"
2382
+ ];
2383
+ if (platform !== "ios") {
2384
+ args.push("-bundleType", platform);
2385
+ }
2386
+ const child = spawn(transporterBin, args, {
2387
+ env: {
2388
+ ...process.env,
2389
+ API_PRIVATE_KEYS_DIR: keyDir
2390
+ },
2391
+ stdio: ["ignore", "pipe", "pipe"]
2392
+ });
2393
+ let output = "";
2394
+ const timer = setTimeout(() => {
2395
+ child.kill("SIGTERM");
2396
+ }, timeout);
2397
+ const onData = (data) => {
2289
2398
  const chunk = data.toString();
2290
2399
  output += chunk;
2291
2400
  options.onOutput?.(chunk);
2292
- });
2401
+ };
2402
+ child.stdout.on("data", onData);
2403
+ child.stderr.on("data", onData);
2293
2404
  child.on("close", async (code) => {
2294
2405
  clearTimeout(timer);
2295
- await unlink(keyPath).catch(() => {
2406
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
2407
+ });
2408
+ resolve4({ success: code === 0, output, backend: "transporter" });
2409
+ });
2410
+ child.on("error", async (err) => {
2411
+ clearTimeout(timer);
2412
+ await rm(keyDir, { recursive: true, force: true }).catch(() => {
2413
+ });
2414
+ resolve4({
2415
+ success: false,
2416
+ output: output + `
2417
+ Process error: ${err.message}`,
2418
+ backend: "transporter"
2296
2419
  });
2297
- resolve4({ success: code === 0, output });
2298
2420
  });
2299
2421
  });
2300
2422
  }
2423
+ var init_upload_transporter = __esm({
2424
+ "src/pilot/upload-transporter.ts"() {
2425
+ init_transporter_install();
2426
+ }
2427
+ });
2428
+
2429
+ // src/pilot/classify.ts
2430
+ function classifyUploadError(output) {
2431
+ for (const { pattern, kind } of RULES) {
2432
+ if (pattern.test(output)) return kind;
2433
+ }
2434
+ return null;
2435
+ }
2436
+ function isTransientUploadError(output) {
2437
+ return classifyUploadError(output) !== null;
2438
+ }
2439
+ var RULES;
2440
+ var init_classify = __esm({
2441
+ "src/pilot/classify.ts"() {
2442
+ RULES = [
2443
+ // Apple's finalize-upload call returning 5xx — the most common transient
2444
+ // failure we see in practice. Example:
2445
+ // "CHANGE UPLOAD STATE TO COMPLETE (UPLOADING ASSET DESCRIPTION):
2446
+ // received status code 500; internal server error."
2447
+ { pattern: /CHANGE UPLOAD STATE TO COMPLETE/i, kind: "finalize_failed" },
2448
+ // Generic 5xx from ASC backend services
2449
+ { pattern: /status code 5\d\d/i, kind: "apple_5xx" },
2450
+ { pattern: /internal server error/i, kind: "apple_5xx" },
2451
+ { pattern: /Service Unavailable/i, kind: "apple_5xx" },
2452
+ { pattern: /Bad Gateway/i, kind: "apple_5xx" },
2453
+ // Network-level interruptions during the upload body transfer
2454
+ { pattern: /Connection reset/i, kind: "connection_reset" },
2455
+ { pattern: /SocketException/i, kind: "connection_reset" },
2456
+ { pattern: /Broken pipe/i, kind: "connection_reset" },
2457
+ // Timeouts — distinct from resets because the other side may have
2458
+ // actually processed the request, so dedupe logic may matter.
2459
+ { pattern: /Read timed out/i, kind: "network_timeout" },
2460
+ { pattern: /SocketTimeoutException/i, kind: "network_timeout" },
2461
+ { pattern: /Operation timed out/i, kind: "network_timeout" },
2462
+ // JVM-internal errors from iTMSTransporter (OOM, thread interruption, etc.)
2463
+ { pattern: /java\.lang\.OutOfMemoryError/i, kind: "transporter_jvm_error" },
2464
+ { pattern: /EOFException/i, kind: "transporter_jvm_error" }
2465
+ ];
2466
+ }
2467
+ });
2468
+
2469
+ // src/pilot/index.ts
2470
+ var pilot_exports = {};
2471
+ __export(pilot_exports, {
2472
+ classifyUploadError: () => classifyUploadError,
2473
+ distributeToGroups: () => distributeToGroups,
2474
+ expireBuild: () => expireBuild,
2475
+ isTransientUploadError: () => isTransientUploadError,
2476
+ resolveTransporter: () => resolveTransporter,
2477
+ setChangelog: () => setChangelog,
2478
+ uploadToTestFlight: () => uploadToTestFlight,
2479
+ uploadWithAltool: () => uploadWithAltool,
2480
+ uploadWithTransporter: () => uploadWithTransporter,
2481
+ verifyJava: () => verifyJava,
2482
+ waitForProcessing: () => waitForProcessing
2483
+ });
2484
+ async function uploadToTestFlight(options) {
2485
+ const backend = selectBackend(options.backend ?? "auto");
2486
+ if (backend === "altool") {
2487
+ return uploadWithAltool(options);
2488
+ }
2489
+ return uploadWithTransporter(options);
2490
+ }
2491
+ function selectBackend(pref) {
2492
+ if (pref !== "auto") return pref;
2493
+ return process.platform === "darwin" ? "altool" : "transporter";
2494
+ }
2301
2495
  async function waitForProcessing(client, params) {
2302
2496
  const timeout = params.timeout ?? 18e5;
2303
2497
  const pollInterval = params.pollInterval ?? 3e4;
@@ -2418,6 +2612,12 @@ function sleep2(ms) {
2418
2612
  var init_pilot = __esm({
2419
2613
  "src/pilot/index.ts"() {
2420
2614
  init_request();
2615
+ init_upload_altool();
2616
+ init_upload_transporter();
2617
+ init_upload_altool();
2618
+ init_upload_transporter();
2619
+ init_classify();
2620
+ init_transporter_install();
2421
2621
  }
2422
2622
  });
2423
2623
  async function loadMetadata(metadataPath) {
@@ -2505,9 +2705,9 @@ var init_metadata = __esm({
2505
2705
  // src/xcode/upload.ts
2506
2706
  var upload_exports = {};
2507
2707
  __export(upload_exports, {
2508
- uploadWithAltool: () => uploadWithAltool
2708
+ uploadWithAltool: () => uploadWithAltool2
2509
2709
  });
2510
- async function uploadWithAltool(options) {
2710
+ async function uploadWithAltool2(options) {
2511
2711
  requireMacOS("altool upload");
2512
2712
  let keyDir;
2513
2713
  if (options.apiPrivateKey && !options.apiKeyPath) {
@@ -2616,8 +2816,8 @@ async function uploadToAppStore(options) {
2616
2816
  if (!options.skipBinaryUpload) {
2617
2817
  const binaryPath = options.ipaPath ?? options.pkgPath;
2618
2818
  if (binaryPath && options.apiKeyId && options.apiIssuerId) {
2619
- const { uploadWithAltool: uploadWithAltool2 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2620
- await uploadWithAltool2({
2819
+ const { uploadWithAltool: uploadWithAltool3 } = await Promise.resolve().then(() => (init_upload(), upload_exports));
2820
+ await uploadWithAltool3({
2621
2821
  filePath: binaryPath,
2622
2822
  apiKeyId: options.apiKeyId,
2623
2823
  apiIssuerId: options.apiIssuerId,
@@ -3403,8 +3603,8 @@ async function captureScreenshots(options) {
3403
3603
  const outputDir = options.outputDirectory ?? join(process.cwd(), "screenshots");
3404
3604
  const retries = options.numberOfRetries ?? 1;
3405
3605
  if (options.clearPreviousScreenshots && existsSync(outputDir)) {
3406
- const { rm: rm2 } = await import('fs/promises');
3407
- await rm2(outputDir, { recursive: true, force: true });
3606
+ const { rm: rm4 } = await import('fs/promises');
3607
+ await rm4(outputDir, { recursive: true, force: true });
3408
3608
  }
3409
3609
  mkdirSync(outputDir, { recursive: true });
3410
3610
  const screenshots = {};