util-provisioning-profiles.js
6.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/**
* @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)
}
}