{"version":3,"sources":["../../src/cert/csr.ts","../../src/pem/index.ts"],"names":["join","execSync"],"mappings":"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","file":"index.mjs","sourcesContent":["import * as x509 from \"@peculiar/x509\";\nimport { webcrypto, generateKeyPairSync } from \"node:crypto\";\nimport { execSync } from \"node:child_process\";\nimport { writeFileSync, readFileSync, unlinkSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\n// Register the Node.js crypto provider.\nx509.cryptoProvider.set(webcrypto as unknown as Parameters<typeof x509.cryptoProvider.set>[0]);\n\nfunction toPem(header: string, der: Buffer): string {\n  const b64 = der.toString(\"base64\");\n  const lines: string[] = [];\n  for (let i = 0; i < b64.length; i += 64) lines.push(b64.slice(i, i + 64));\n  return `-----BEGIN ${header}-----\\n${lines.join(\"\\n\")}\\n-----END ${header}-----`;\n}\n\n/**\n * Generate EC P-256 CSR — used by App Store Connect API (JWT auth).\n */\nexport async function generateCSR(): Promise<{ csr: string; privateKey: string }> {\n  const algorithm = { name: \"ECDSA\", namedCurve: \"P-256\" } as const;\n  const keyPair = await webcrypto.subtle.generateKey(algorithm, true, [\"sign\", \"verify\"]);\n\n  const csr = await x509.Pkcs10CertificateRequestGenerator.create({\n    name: \"CN=PEM\",\n    keys: keyPair,\n    signingAlgorithm: { name: \"ECDSA\", hash: \"SHA-256\" },\n  });\n\n  const pkcs8 = await webcrypto.subtle.exportKey(\"pkcs8\", keyPair.privateKey);\n  return {\n    csr: csr.toString(\"pem\"),\n    privateKey: toPem(\"PRIVATE KEY\", Buffer.from(pkcs8)),\n  };\n}\n\n/**\n * Generate RSA 2048 CSR — required by Apple Developer Portal (session auth).\n * Matches Fastlane's create_certificate_signing_request which uses RSA 2048.\n */\nexport function generateRSACSR(): { csr: string; privateKey: string } {\n  const { privateKey, publicKey } = generateKeyPairSync(\"rsa\", {\n    modulusLength: 2048,\n    privateKeyEncoding: { type: \"pkcs8\", format: \"pem\" },\n    publicKeyEncoding: { type: \"spki\", format: \"pem\" },\n  });\n\n  // Generate CSR using openssl (Node crypto doesn't have native CSR generation for RSA)\n  const keyPath = join(tmpdir(), `fastnode-rsa-${Date.now()}.pem`);\n  const csrPath = join(tmpdir(), `fastnode-rsa-${Date.now()}.csr`);\n\n  try {\n    writeFileSync(keyPath, privateKey);\n    execSync(`openssl req -new -key \"${keyPath}\" -out \"${csrPath}\" -subj \"/CN=PEM\" 2>/dev/null`);\n    const csr = readFileSync(csrPath, \"utf-8\");\n    return { csr, privateKey };\n  } finally {\n    try { unlinkSync(keyPath); } catch {}\n    try { unlinkSync(csrPath); } catch {}\n  }\n}\n","import { execFile } from \"node:child_process\";\nimport { promisify } from \"node:util\";\nimport { writeFile, mkdir, unlink } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport type { AppStoreConnectClient } from \"../client/index.js\";\nimport type { CertificateType } from \"../types/index.js\";\nimport { generateCSR } from \"../cert/csr.js\";\n\nconst execFileAsync = promisify(execFile);\n\nexport type PushCertKind =\n  | \"development\"\n  | \"production\"\n  | \"website\"\n  | \"voip\"\n  | \"passbook\";\n\nexport interface PemOptions {\n  client: AppStoreConnectClient;\n  bundleIdentifier: string;\n  development?: boolean;\n  websitePush?: boolean;\n  /** Voice-over-IP push cert (Portal type code `E5D663CMZW`). */\n  voipPush?: boolean;\n  /** Passbook/Wallet cert (Portal type code `Y3B2F3TYSI`). */\n  passbook?: boolean;\n  outputDir?: string;\n  p12Password?: string;\n  activeDaysLimit?: number;\n  force?: boolean;\n  platform?: \"ios\" | \"macos\";\n  generateP12?: boolean;\n  savePrivateKey?: boolean;\n}\n\nexport interface PemResult {\n  pemPath: string;\n  p12Path?: string;\n  keyPath?: string;\n  expiresAt: Date;\n}\n\n/**\n * Resolve the push certificate descriptor for the given options. Returns:\n * - `kind`: semantic certificate family (used in filename prefix)\n * - `portalTypeCode`: Apple Developer Portal type code (matches Fastlane's\n *   `Spaceship::Portal::Certificate` codes in certificate.rb)\n * - `ascCertificateType`: best-fit ASC `CertificateType` for the ASC API\n *   fallback path (note: push certs are really a Portal concept; the ASC\n *   cert types here only approximate the platform distinction).\n */\nexport function getPushCertificateType(options: {\n  development?: boolean;\n  websitePush?: boolean;\n  voipPush?: boolean;\n  passbook?: boolean;\n  platform?: \"ios\" | \"macos\";\n}): {\n  kind: PushCertKind;\n  portalTypeCode: string;\n  ascCertificateType: CertificateType;\n} {\n  if (options.passbook) {\n    return {\n      kind: \"passbook\",\n      portalTypeCode: \"Y3B2F3TYSI\",\n      ascCertificateType: \"IOS_DISTRIBUTION\",\n    };\n  }\n  if (options.voipPush) {\n    return {\n      kind: \"voip\",\n      portalTypeCode: \"E5D663CMZW\",\n      ascCertificateType: \"IOS_DISTRIBUTION\",\n    };\n  }\n  if (options.websitePush) {\n    return {\n      kind: \"website\",\n      portalTypeCode: \"3T2ZP62QW8\",\n      ascCertificateType: \"IOS_DISTRIBUTION\",\n    };\n  }\n  const platform = options.platform ?? \"ios\";\n  if (platform === \"macos\") {\n    return options.development\n      ? {\n          kind: \"development\",\n          portalTypeCode: \"HQ4KP3I34R\", // MacDevelopmentPush\n          ascCertificateType: \"MAC_APP_DEVELOPMENT\",\n        }\n      : {\n          kind: \"production\",\n          portalTypeCode: \"CDZ7EMXIZ1\", // MacProductionPush\n          ascCertificateType: \"MAC_APP_DISTRIBUTION\",\n        };\n  }\n  return options.development\n    ? {\n        kind: \"development\",\n        portalTypeCode: \"JKG5JZ54H7\", // DevelopmentPush\n        ascCertificateType: \"IOS_DEVELOPMENT\",\n      }\n    : {\n        kind: \"production\",\n        portalTypeCode: \"UPV3DW712I\", // ProductionPush\n        ascCertificateType: \"IOS_DISTRIBUTION\",\n      };\n}\n\nexport async function createPushCertificate(\n  options: PemOptions\n): Promise<PemResult> {\n  const outputDir = options.outputDir ?? process.cwd();\n  await mkdir(outputDir, { recursive: true });\n\n  const descriptor = getPushCertificateType(options);\n  const platform = options.platform ?? \"ios\";\n  const activeDaysLimit = options.activeDaysLimit ?? 30;\n  const generateP12 = options.generateP12 !== false;\n  const savePrivateKey = options.savePrivateKey !== false;\n  // Mirror Fastlane pem's filename convention (manager.rb line 68):\n  // `{type}_{app_identifier}_{platform}`.\n  const filenameBase = `${descriptor.kind}_${options.bundleIdentifier}_${platform}`;\n\n  // Check for existing valid push certs\n  if (!options.force) {\n    const existing = await options.client.certificates.list({\n      filter: { certificateType: descriptor.ascCertificateType },\n    });\n\n    const now = new Date();\n    const validCert = existing.find((c) => {\n      const expires = new Date(c.attributes.expirationDate);\n      const daysLeft = (expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n      return daysLeft > activeDaysLimit && c.attributes.displayName?.includes(options.bundleIdentifier);\n    });\n\n    if (validCert) {\n      // Cert exists and is still valid — but we can't re-download the private key\n      // Just return info about the existing cert\n      return {\n        pemPath: join(outputDir, `${filenameBase}.pem`),\n        expiresAt: new Date(validCert.attributes.expirationDate),\n      };\n    }\n  }\n\n  // Generate new CSR and create cert\n  const { csr, privateKey } = await generateCSR();\n\n  const cert = await options.client.certificates.create({\n    certificateType: descriptor.ascCertificateType,\n    csrContent: csr,\n  });\n\n  const certDer = Buffer.from(cert.attributes.certificateContent, \"base64\");\n\n  // Convert DER cert to PEM\n  const certPem =\n    \"-----BEGIN CERTIFICATE-----\\n\" +\n    certDer.toString(\"base64\").match(/.{1,64}/g)!.join(\"\\n\") +\n    \"\\n-----END CERTIFICATE-----\";\n\n  // Write combined PEM (cert + key)\n  const pemPath = join(outputDir, `${filenameBase}.pem`);\n  await writeFile(pemPath, certPem + \"\\n\" + privateKey);\n\n  const result: PemResult = {\n    pemPath,\n    expiresAt: new Date(cert.attributes.expirationDate),\n  };\n\n  // Save private key\n  if (savePrivateKey) {\n    const keyPath = join(outputDir, `${filenameBase}.pkey`);\n    await writeFile(keyPath, privateKey);\n    result.keyPath = keyPath;\n  }\n\n  // Generate PKCS#12\n  if (generateP12) {\n    const p12Path = join(outputDir, `${filenameBase}.p12`);\n    const p12Password = options.p12Password ?? \"\";\n    try {\n      const { execSync } = await import(\"node:child_process\");\n      // Write private key to temp file\n      const keyTmpPath = join(outputDir, `${filenameBase}.tmp.key`);\n      await writeFile(keyTmpPath, privateKey);\n      execSync(\n        `openssl pkcs12 -export -inkey \"${keyTmpPath}\" -in \"${pemPath}\" -out \"${p12Path}\" -passout \"pass:${p12Password}\"`,\n        { stdio: \"pipe\" }\n      );\n      await unlink(keyTmpPath).catch(() => {});\n      result.p12Path = p12Path;\n    } catch {\n      // openssl may not be available — p12 generation is optional\n    }\n  }\n\n  return result;\n}\n\nexport async function renewPushCertificate(\n  options: PemOptions\n): Promise<PemResult> {\n  return createPushCertificate({ ...options, force: true });\n}\n\nexport async function checkExpiration(options: {\n  client: AppStoreConnectClient;\n  bundleIdentifier: string;\n  development?: boolean;\n  activeDaysLimit?: number;\n}): Promise<{ valid: boolean; daysLeft: number; expiresAt: Date | null }> {\n  const certType: CertificateType = options.development ? \"IOS_DEVELOPMENT\" : \"IOS_DISTRIBUTION\";\n  const certs = await options.client.certificates.list({\n    filter: { certificateType: certType },\n  });\n\n  const now = new Date();\n  const limit = options.activeDaysLimit ?? 30;\n\n  for (const cert of certs) {\n    if (cert.attributes.displayName?.includes(options.bundleIdentifier)) {\n      const expires = new Date(cert.attributes.expirationDate);\n      const daysLeft = (expires.getTime() - now.getTime()) / (1000 * 60 * 60 * 24);\n      return { valid: daysLeft > limit, daysLeft: Math.floor(daysLeft), expiresAt: expires };\n    }\n  }\n\n  return { valid: false, daysLeft: 0, expiresAt: null };\n}\n"]}