UNPKG

ios-uploader

Version:

Easy to use, cross-platform tool to upload an iOS app to itunes-connect.

200 lines (174 loc) 5.42 kB
const fs = require('fs'); const os = require('os'); const path = require('path'); const crypto = require('crypto'); const stream = require('stream'); const zlib = require('zlib'); const axios = require('axios'); const yauzl = require('yauzl'); const plist = require('simple-plist'); const prettyBytes = require('pretty-bytes'); const concat = require('concat-stream'); const { promisify } = require('util'); const INFO_PLIST_FILE_PATTERN = /^Payload\/[^/]*.app\/Info\.plist$/; exports.generateIDString = function () { // YYYYMMDDHHmmss-sss return new Date().toISOString().replace(/-|:|T|Z/g, '').replace('.', '-'); }; exports.makeSessionDigest = function (sessionId, requestChecksum, requestId, sharedSecret) { return crypto.createHash('md5') .update(sessionId) .update(requestChecksum) .update(requestId) .update(sharedSecret) .digest('hex'); }; exports.openFile = function (path, flags = 'r') { return new Promise((resolve, reject) => { fs.open(path, flags, (err, fd) => { if (err) return reject(err); resolve(fd); }); }); }; exports.closeFile = function (fd) { return new Promise((resolve, reject) => { fs.close(fd, (err) => { if (err) return reject(err); resolve(); }); }); }; exports.readFileDataFromZip = function (fd, fileNamePattern) { return new Promise((resolve, reject) => { yauzl.fromFd(fd, { autoClose: false, lazyEntries: true }, (err, zipFile) => { if (err) return reject(err); zipFile.on('error', reject); zipFile.on('entry', (entry) => { if (fileNamePattern.test(entry.fileName)) { zipFile.openReadStream(entry, (err, stream) => { if (err) throw err; stream.pipe(concat(resolve)); }); } else { zipFile.readEntry(); } }); zipFile.on('end', () => { resolve(null); }); zipFile.readEntry(); }); }); }; exports.extractBundleIdAndVersion = async function (fd) { let data; try { data = await exports.readFileDataFromZip(fd, INFO_PLIST_FILE_PATTERN); } catch { // Ignore this error, handled below. } if (!data || data.length === 0) { throw new Error('Info.plist not found'); } let infoPlist; try { infoPlist = plist.parse(data, 'Info.plist'); } catch { throw new Error('Failed to parse Info.plist'); } if (infoPlist && infoPlist.CFBundleIdentifier && infoPlist.CFBundleVersion && infoPlist.CFBundleShortVersionString) { return { bundleId: infoPlist.CFBundleIdentifier, bundleVersion: infoPlist.CFBundleVersion, bundleShortVersion: infoPlist.CFBundleShortVersionString, }; } throw new Error('Bundle info not found in Info.plist'); }; exports.ensureTempDir = async function () { const tempDir = path.join(os.tmpdir(), 'ios-uploader'); await fs.promises.mkdir(tempDir, { recursive: true }); return tempDir; }; exports.downloadTempFile = async function (fileUrl, onProgress = () => { }) { const res = await axios.get(fileUrl, { responseType: 'stream', }); let newFilePath = path.join( await exports.ensureTempDir(), Math.random().toString(16).substr(2, 8) + '.ipa', ); const writer = fs.createWriteStream(newFilePath); const contentLength = Number(res.headers['content-length'] || 0); let downloaded = 0; if (contentLength > 0) { onProgress(0, contentLength); res.data.on('data', (chunk) => onProgress(downloaded += chunk.length, contentLength)); } res.data.pipe(writer); await promisify(stream.finished)(writer); return newFilePath; }; exports.removeTempFile = async function (filePath) { await fs.promises.unlink(filePath); }; exports.getFileStats = function (fd) { return new Promise((resolve, reject) => { fs.fstat(fd, (err, stats) => { if (err) return reject(err); resolve(stats); }); }); }; exports.readFile = function (path, encoding = 'utf-8') { return new Promise((resolve, reject) => { fs.readFile(path, encoding, (err, f) => { if (err) return reject(err); resolve(f); }); }); }; exports.getFileMD5 = function (fd) { return new Promise((resolve, reject) => { const output = crypto.createHash('md5'); const input = fs.createReadStream('', { fd, start: 0, autoClose: false }); input.on('error', (err) => reject(err)); output.once('readable', () => { resolve(output.read().toString('hex')); }); input.pipe(output); }); }; exports.getFilePart = function (fd, offset, length) { return new Promise((resolve, reject) => { let buffer = Buffer.allocUnsafe(length); fs.read(fd, buffer, 0, length, offset, (err) => { if (err) return reject(err); resolve(buffer); }); }); }; exports.getStringMD5 = function (text) { return crypto.createHash('md5').update(text).digest('hex'); }; exports.getStringMD5Buffer = function (text) { return crypto.createHash('md5').update(text).digest(); }; exports.bufferToGZBase64 = function (buf) { return new Promise((resolve, reject) => { zlib.gzip(buf, (err, res) => { if (err) return reject(err); resolve(res.toString('base64')); }); }); }; exports.formatSpeedAndEta = function (bytes, total, duration) { return { speed: prettyBytes(Math.round((bytes / duration) * 1000)) + '/s', eta: Math.round(((total - bytes) / (bytes / duration)) / 1000) + 's', }; };