2022-09-30 05:39:11 +00:00

179 lines
6.3 KiB
JavaScript

/**
* @module util-provisioning-profiles
*/
'use strict'
const path = require('path')
const fs = require("fs-extra")
const os = require('os')
const { executeAppBuilderAsJson } = require("../out/util/appBuilder")
const util = require('./util')
const debuglog = util.debuglog
const debugwarn = util.debugwarn
const getAppContentsPath = util.getAppContentsPath
const copyFileAsync = util.copyFileAsync
const execFileAsync = util.execFileAsync
/**
* @constructor
* @param {string} filePath - Path to provisioning profile.
* @param {Object} message - Decoded message in provisioning profile.
*/
let ProvisioningProfile = module.exports.ProvisioningProfile = function (filePath, message) {
this.filePath = filePath
this.message = message
}
Object.defineProperty(ProvisioningProfile.prototype, 'name', {
get: function () {
return this.message['Name']
}
})
Object.defineProperty(ProvisioningProfile.prototype, 'platforms', {
get: function () {
if ('ProvisionsAllDevices' in this.message) return ['darwin'] // Developer ID
else if (this.type === 'distribution') return ['mas'] // Mac App Store
else return ['darwin', 'mas'] // Mac App Development
}
})
Object.defineProperty(ProvisioningProfile.prototype, 'type', {
get: function () {
if ('ProvisionedDevices' in this.message) return 'development' // Mac App Development
else return 'distribution' // Developer ID or Mac App Store
}
})
/**
* Returns a promise resolving to a ProvisioningProfile instance based on file.
* @function
* @param {string} filePath - Path to provisioning profile.
* @param {string} keychain - Keychain to use when unlocking provisioning profile.
* @returns {Promise} Promise.
*/
function getProvisioningProfileAsync(filePath, keychain) {
const securityArgs = [
'cms',
'-D', // Decode a CMS message
'-i', filePath // Use infile as source of data
]
if (keychain != null) {
securityArgs.push('-k', keychain)
}
return util.execFileAsync('security', securityArgs)
.then(async function (result) {
// make filename unique so it doesn't create issues with parallel method calls
const timestamp = process.hrtime.bigint
? process.hrtime.bigint().toString()
: process.hrtime().join('')
// todo read directly
const tempFile = path.join(os.tmpdir(), `${require('crypto').createHash('sha1').update(filePath).update(timestamp).digest('hex')}.plist`)
await fs.outputFile(tempFile, result)
const plistContent = await executeAppBuilderAsJson(["decode-plist", "-f", tempFile])
await fs.unlink(tempFile)
const provisioningProfile = new ProvisioningProfile(filePath, plistContent[0])
debuglog('Provisioning profile:', '\n',
'> Name:', provisioningProfile.name, '\n',
'> Platforms:', provisioningProfile.platforms, '\n',
'> Type:', provisioningProfile.type, '\n',
'> Path:', provisioningProfile.filePath, '\n',
'> Message:', provisioningProfile.message)
return provisioningProfile
})
}
/**
* Returns a promise resolving to a list of suitable provisioning profile within the current working directory.
* @function
* @param {Object} opts - Options.
* @returns {Promise} Promise.
*/
async function findProvisioningProfilesAsync(opts) {
const dirPath = process.cwd()
const dirContent = await Promise.all((await fs.readdir(dirPath))
.filter(it => it.endsWith(".provisionprofile"))
.map(async function (name) {
const filePath = path.join(dirPath, name)
const stat = await fs.lstat(filePath)
return stat.isFile() ? filePath : undefined
}))
return util.flatList(await Promise.all(util.flatList(dirContent).map(filePath => {
return getProvisioningProfileAsync(filePath)
.then((provisioningProfile) => {
if (provisioningProfile.platforms.indexOf(opts.platform) >= 0 && provisioningProfile.type === opts.type) {
return provisioningProfile
}
debugwarn('Provisioning profile above ignored, not for ' + opts.platform + ' ' + opts.type + '.')
return undefined
})
})))
}
/**
* Returns a promise embedding the provisioning profile in the app Contents folder.
* @function
* @param {Object} opts - Options.
* @returns {Promise} Promise.
*/
module.exports.preEmbedProvisioningProfile = function (opts) {
async function embedProvisioningProfile () {
if (!opts['provisioning-profile']) {
return
}
debuglog('Looking for existing provisioning profile...')
let embeddedFilePath = path.join(getAppContentsPath(opts), 'embedded.provisionprofile')
try {
await fs.lstat(embeddedFilePath)
debuglog('Found embedded provisioning profile:', '\n',
'* Please manually remove the existing file if not wanted.', '\n',
'* Current file at:', embeddedFilePath)
} catch (err) {
if (err.code === 'ENOENT') {
// File does not exist
debuglog('Embedding provisioning profile...')
return copyFileAsync(opts['provisioning-profile'].filePath, embeddedFilePath)
} else throw err
}
}
if (opts['provisioning-profile']) {
// User input provisioning profile
debuglog('`provisioning-profile` passed in arguments.')
if (opts['provisioning-profile'] instanceof ProvisioningProfile) {
return embedProvisioningProfile()
} else {
return getProvisioningProfileAsync(opts['provisioning-profile'], opts['keychain'])
.then(function (provisioningProfile) {
opts['provisioning-profile'] = provisioningProfile
})
.then(embedProvisioningProfile)
}
} else {
// Discover provisioning profile
debuglog('No `provisioning-profile` passed in arguments, will find in current working directory and in user library...')
return findProvisioningProfilesAsync(opts)
.then(function (provisioningProfiles) {
if (provisioningProfiles.length > 0) {
// Provisioning profile(s) found
if (provisioningProfiles.length > 1) {
debuglog('Multiple provisioning profiles found, will use the first discovered.')
} else {
debuglog('Found 1 provisioning profile.')
}
opts['provisioning-profile'] = provisioningProfiles[0]
} else {
// No provisioning profile found
debuglog('No provisioning profile found, will not embed profile in app contents.')
}
})
.then(embedProvisioningProfile)
}
}