UNPKG

@wellsite/version-generator

Version:
269 lines 11.5 kB
"use strict"; /** * iOS build number generation utilities * Handles interaction with App Store Connect API to get and increment build numbers */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultIosExecutor = void 0; exports.getIosBuildNumberInfo = getIosBuildNumberInfo; exports.encodeCommitHash = encodeCommitHash; exports.composeIosBuildVersion = composeIosBuildVersion; exports.processBuilds = processBuilds; exports.getAppStoreVersionInfo = getAppStoreVersionInfo; const jwt = __importStar(require("jsonwebtoken")); const https = __importStar(require("https")); const base64_1 = require("./base64"); /** * Gets detailed iOS build number information * * @param options - Options for getting the iOS build number * @param versionInfoProvider - Function to get version info from App Store Connect * @param jwtGenerator - JWT token generator * @param apiClient - App Store Connect API client * @param buildProcessor - Build data processor * @returns Promise resolving to the iOS build number info */ /** * Default implementation of IosExecutor */ class DefaultIosExecutor { /** * Generates a JWT token for App Store Connect API authentication * * @param keyId - The App Store Connect API Key ID * @param issuerId - The App Store Connect API Issuer ID * @param privateKey - The App Store Connect API Private Key * @returns The JWT token */ generateToken(keyId, issuerId, privateKey) { const now = Math.floor(Date.now() / 1000); const payload = { iss: issuerId, exp: now + 20 * 60, // Token expires in 20 minutes aud: 'appstoreconnect-v1', }; const signOptions = { algorithm: 'ES256', header: { alg: 'ES256', kid: keyId, typ: 'JWT', }, }; return jwt.sign(payload, privateKey, signOptions); } /** * Queries the App Store Connect API for builds * * @param bundleId - The bundle ID of the iOS app * @param appReleaseVersion - The app release version (CFBundleShortVersionString) * @param token - The JWT token for authentication * @returns Promise resolving to the builds data */ async queryApi(bundleId, appReleaseVersion, token) { // Encode the filter parameters for the URL const encodedBundleId = encodeURIComponent(bundleId); const encodedVersion = encodeURIComponent(appReleaseVersion); // First, we need to find the app ID using the bundle ID const appUrl = `https://api.appstoreconnect.apple.com/v1/apps?filter[bundleId]=${encodedBundleId}`; // Get the app info first const appInfo = await this.makeApiRequest(appUrl, token); // Check if we found the app if (!appInfo.data || appInfo.data.length === 0) { throw new Error(`No app found with bundle ID: ${bundleId}`); } // Get the app ID from the response const appId = appInfo.data[0].id; // Now construct the URL for builds with the app ID and version const url = `https://api.appstoreconnect.apple.com/v1/builds?filter[app]=${appId}&filter[version]=${encodedVersion}`; return this.makeApiRequest(url, token); } /** * Makes a request to the App Store Connect API * * @param url - The API URL to request * @param token - The JWT token for authentication * @returns Promise resolving to the API response data */ makeApiRequest(url, token) { return new Promise((resolve, reject) => { const req = https.get(url, { headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json', 'User-Agent': 'Version Generator', }, }, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) { try { const jsonData = JSON.parse(data); resolve(jsonData); } catch (error) { reject(new Error(`Failed to parse App Store Connect API response: ${error instanceof Error ? error.message : String(error)}`)); } } else { reject(new Error(`App Store Connect API request failed with status ${res.statusCode}: ${data}`)); } }); }); req.on('error', (error) => { reject(new Error(`App Store Connect API request failed: ${error.message}`)); }); req.end(); }); } } exports.DefaultIosExecutor = DefaultIosExecutor; async function getIosBuildNumberInfo(options, executor = new DefaultIosExecutor()) { try { if (!options.enabled) { return { buildNumber: 0, buildVersion: '0' }; } if (!options.apiKeyId || !options.apiIssuerId || !options.apiPrivateKey || !options.bundleId || !options.appReleaseVersion) { throw new Error('iOS build number generation requires apiKeyId, apiIssuerId, apiPrivateKey, bundleId, and appReleaseVersion'); } // Get version info from App Store Connect API const versionInfo = await getAppStoreVersionInfo(options.bundleId, options.appReleaseVersion, options.apiKeyId, options.apiIssuerId, options.apiPrivateKey, executor); // If no version info found, start with build number 1 if (!versionInfo) { return composeIosBuildVersion(1, options.commitHash); } // Increment the highest build number found return composeIosBuildVersion(versionInfo.highestBuildNumber + 1, options.commitHash); } catch (error) { throw new Error(`Failed to get iOS build number: ${error instanceof Error ? error.message : String(error)}`); } } /** * Encodes a commit hash into an integer for use in iOS build numbers * * @param commitHash - The commit hash to encode * @returns The encoded commit hash as an integer */ function encodeCommitHash(commitHash) { // Take the first 8 characters of the commit hash (this is commonly used as a short hash) const shortHash = commitHash.substring(0, 8); // Convert the hex string to a decimal number // This could be very large, so we'll take modulo to ensure it fits in a 32-bit integer // (iOS build numbers are typically expected to be 32-bit integers) return parseInt(shortHash, 16) % 2147483647; // Max 32-bit signed integer } /** * Gets the iOS build number with dependency injection for testing * * @param options - Options for getting the iOS build number * @param versionInfoProvider - Function to get version info from App Store Connect * @returns Promise resolving to the next iOS build number to use */ /** * Composes an iOS build version object from a build number and optional commit hash * * @param buildNumber - The build number * @param commitHash - Optional commit hash to encode in the build version * @returns The composed iOS build number info */ function composeIosBuildVersion(buildNumber, commitHash) { // Create the build number info object const buildNumberInfo = { buildNumber, buildVersion: buildNumber.toString(), }; // If commit hash is provided, encode it and add to the build number if (commitHash) { buildNumberInfo.encodedCommitHash = encodeCommitHash(commitHash); buildNumberInfo.buildVersion = `${buildNumber}.${buildNumberInfo.encodedCommitHash}`; } return buildNumberInfo; } /** * Processes the builds data to find the highest build number * * @param builds - The builds data from the App Store Connect API * @param appReleaseVersion - The app release version (CFBundleShortVersionString) * @returns The version info with the highest build number */ function processBuilds(builds, appReleaseVersion) { // Check if there are any builds if (!builds || !builds.data || !Array.isArray(builds.data) || builds.data.length === 0) { return undefined; } let highestBuildNumber = 0; // Process each build to find the highest build number for (const build of builds.data) { if (build.attributes && build.attributes.version === appReleaseVersion) { const buildNumber = parseInt(build.attributes.buildNumber, 10); if (!isNaN(buildNumber) && buildNumber > highestBuildNumber) { highestBuildNumber = buildNumber; } } } // If no valid build number was found, return undefined if (highestBuildNumber === 0) { return undefined; } return { highestBuildNumber }; } async function getAppStoreVersionInfo(bundleId, appReleaseVersion, apiKeyId, apiIssuerId, apiPrivateKey, executor = new DefaultIosExecutor()) { try { // Decode the private key if it's base64-encoded // Private key is not expected to be JSON, so we pass false for expectJson const privateKey = (0, base64_1.isBase64)(apiPrivateKey, false) ? Buffer.from(apiPrivateKey, 'base64').toString('utf8') : apiPrivateKey; // Generate JWT token for authentication const token = executor.generateToken(apiKeyId, apiIssuerId, privateKey); // Query App Store Connect API for builds const builds = await executor.queryApi(bundleId, appReleaseVersion, token); // Process the builds to find the highest build number return processBuilds(builds, appReleaseVersion); } catch (error) { throw new Error(`Failed to get App Store version info: ${error instanceof Error ? error.message : String(error)}`); } } //# sourceMappingURL=ios-version.js.map