From 2c86d1a09732986c4e7b726ba929507093b68a91 Mon Sep 17 00:00:00 2001 From: Darryl Pogue Date: Fri, 14 Apr 2023 02:52:03 -0700 Subject: [PATCH] feat: Support Apple Cloud Distribution signing (#1276) --- lib/build.js | 68 ++++++++++++++++++++++++++--------- tests/spec/unit/build.spec.js | 56 +++++++++++++++++++---------- 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/lib/build.js b/lib/build.js index 35fe1c9c6..a41057ae9 100644 --- a/lib/build.js +++ b/lib/build.js @@ -28,6 +28,20 @@ const util = require('util'); const check_reqs = require('./check_reqs'); const projectFile = require('./projectFile'); +const buildConfigProperties = [ + 'codeSignIdentity', + 'codeSignResourceRules', + 'provisioningProfile', + 'developmentTeam', + 'packageType', + 'buildFlag', + 'iCloudContainerEnvironment', + 'automaticProvisioning', + 'authenticationKeyPath', + 'authenticationKeyID', + 'authenticationKeyIssuerID' +]; + // These are regular expressions to detect if the user is changing any of the built-in xcodebuildArgs /* eslint-disable no-useless-escape */ const buildFlagMatchers = { @@ -111,10 +125,9 @@ module.exports.run = function (buildOpts) { const buildType = buildOpts.release ? 'release' : 'debug'; const config = buildConfig.ios[buildType]; if (config) { - ['codeSignIdentity', 'codeSignResourceRules', 'provisioningProfile', 'developmentTeam', 'packageType', 'buildFlag', 'iCloudContainerEnvironment', 'automaticProvisioning'].forEach( - key => { - buildOpts[key] = buildOpts[key] || config[key]; - }); + buildConfigProperties.forEach(key => { + buildOpts[key] = buildOpts[key] || config[key]; + }); } } } @@ -211,7 +224,7 @@ module.exports.run = function (buildOpts) { // remove the build/device folder before building fs.removeSync(buildOutputDir); - const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, buildOpts.device, buildOpts.buildFlag, emulatorTarget, buildOpts.automaticProvisioning); + const xcodebuildArgs = getXcodeBuildArgs(projectName, projectPath, configuration, emulatorTarget, buildOpts); return execa('xcodebuild', xcodebuildArgs, { cwd: projectPath, stdio: 'inherit' }); }).then(() => { if (!buildOpts.device || buildOpts.noSign) { @@ -264,7 +277,7 @@ module.exports.run = function (buildOpts) { } function packageArchive () { - const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts.automaticProvisioning); + const xcodearchiveArgs = getXcodeArchiveArgs(projectName, projectPath, buildOutputDir, exportOptionsPath, buildOpts); return execa('xcodebuild', xcodearchiveArgs, { cwd: projectPath, stdio: 'inherit' }); } @@ -302,16 +315,15 @@ module.exports.findXCodeProjectIn = findXCodeProjectIn; * @param {String} projectName Name of xcode project * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild * @param {String} configuration Configuration name: debug|release - * @param {Boolean} isDevice Flag that specify target for package (device/emulator) - * @param {Array} buildFlags * @param {String} emulatorTarget Target for emulator (rather than default) - * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning + * @param {Object} buildConfig The build configuration options * @return {Array} Array of arguments that could be passed directly to spawn method */ -function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, buildFlags, emulatorTarget, autoProvisioning) { +function getXcodeBuildArgs (projectName, projectPath, configuration, emulatorTarget, buildConfig = {}) { let options; let buildActions; let settings; + const buildFlags = buildConfig.buildFlag; const customArgs = {}; customArgs.otherFlags = []; @@ -325,7 +337,7 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b } } - if (isDevice) { + if (buildConfig.device) { options = [ '-workspace', customArgs.workspace || `${projectName}.xcworkspace`, '-scheme', customArgs.scheme || projectName, @@ -344,8 +356,17 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b customArgs.otherFlags = customArgs.otherFlags.concat(['-sdk', customArgs.sdk]); } - if (autoProvisioning) { - options = options.concat(['-allowProvisioningUpdates']); + if (buildConfig.automaticProvisioning) { + options.push('-allowProvisioningUpdates'); + } + if (buildConfig.authenticationKeyPath) { + options.push('-authenticationKeyPath', buildConfig.authenticationKeyPath); + } + if (buildConfig.authenticationKeyID) { + options.push('-authenticationKeyID', buildConfig.authenticationKeyID); + } + if (buildConfig.authenticationKeyIssuerID) { + options.push('-authenticationKeyIssuerID', buildConfig.authenticationKeyIssuerID); } } else { // emulator options = [ @@ -376,16 +397,31 @@ function getXcodeBuildArgs (projectName, projectPath, configuration, isDevice, b * @param {String} projectPath Path to project file. Will be used to set CWD for xcodebuild * @param {String} outputPath Output directory to contain the IPA * @param {String} exportOptionsPath Path to the exportOptions.plist file - * @param {Boolean} autoProvisioning Whether to allow Xcode to automatically update provisioning + * @param {Object} buildConfig Build configuration options * @return {Array} Array of arguments that could be passed directly to spawn method */ -function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, autoProvisioning) { +function getXcodeArchiveArgs (projectName, projectPath, outputPath, exportOptionsPath, buildConfig = {}) { + const options = []; + + if (buildConfig.automaticProvisioning) { + options.push('-allowProvisioningUpdates'); + } + if (buildConfig.authenticationKeyPath) { + options.push('-authenticationKeyPath', buildConfig.authenticationKeyPath); + } + if (buildConfig.authenticationKeyID) { + options.push('-authenticationKeyID', buildConfig.authenticationKeyID); + } + if (buildConfig.authenticationKeyIssuerID) { + options.push('-authenticationKeyIssuerID', buildConfig.authenticationKeyIssuerID); + } + return [ '-exportArchive', '-archivePath', `${projectName}.xcarchive`, '-exportOptionsPlist', exportOptionsPath, '-exportPath', outputPath - ].concat(autoProvisioning ? ['-allowProvisioningUpdates'] : []); + ].concat(options); } function parseBuildFlag (buildFlag, args) { diff --git a/tests/spec/unit/build.spec.js b/tests/spec/unit/build.spec.js index cc9dfe35d..1f855b167 100644 --- a/tests/spec/unit/build.spec.js +++ b/tests/spec/unit/build.spec.js @@ -31,10 +31,7 @@ describe('build', () => { build.__set__('__dirname', path.join('/test', 'dir')); it('should generate appropriate args if a single buildFlag is passed in', () => { - const isDevice = true; - const buildFlags = ''; - - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: '' }); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -54,7 +51,6 @@ describe('build', () => { }); it('should generate appropriate args if buildFlags are passed in', () => { - const isDevice = true; const buildFlags = [ '-workspace TestWorkspaceFlag', '-scheme TestSchemeFlag', @@ -65,7 +61,7 @@ describe('build', () => { 'SHARED_PRECOMPS_DIR=TestSharedPrecompsDirFlag' ]; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: buildFlags }); expect(args).toEqual([ '-workspace', 'TestWorkspaceFlag', @@ -85,8 +81,7 @@ describe('build', () => { }); it('should generate appropriate args for device', () => { - const isDevice = true; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true }); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -106,8 +101,7 @@ describe('build', () => { }); it('should generate appropriate args for simulator', () => { - const isDevice = false; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null, 'iPhone 5s'); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', 'iPhone 5s', { device: false }); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -127,10 +121,9 @@ describe('build', () => { }); it('should add matched flags that are not overriding for device', () => { - const isDevice = true; const buildFlags = '-sdk TestSdkFlag'; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', { device: true, buildFlag: buildFlags }); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -152,10 +145,9 @@ describe('build', () => { }); it('should add matched flags that are not overriding for simulator', () => { - const isDevice = false; const buildFlags = '-archivePath TestArchivePathFlag'; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, buildFlags, 'iPhone 5s'); + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', 'iPhone 5s', { device: false, buildFlag: buildFlags }); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -177,8 +169,15 @@ describe('build', () => { }); it('should generate appropriate args for automatic provisioning', () => { - const isDevice = true; - const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', isDevice, null, null, true); + const buildOpts = { + device: true, + automaticProvisioning: true, + authenticationKeyPath: '/tmp/asc-key.p8', + authenticationKeyID: '12345', + authenticationKeyIssuerID: '00000000-0000-0000-0000-000000000000' + }; + + const args = getXcodeBuildArgs('TestProjectName', testProjectPath, 'TestConfiguration', '', buildOpts); expect(args).toEqual([ '-workspace', 'TestProjectName.xcworkspace', @@ -191,11 +190,17 @@ describe('build', () => { '-archivePath', 'TestProjectName.xcarchive', '-allowProvisioningUpdates', + '-authenticationKeyPath', + '/tmp/asc-key.p8', + '-authenticationKeyID', + '12345', + '-authenticationKeyIssuerID', + '00000000-0000-0000-0000-000000000000', 'archive', `CONFIGURATION_BUILD_DIR=${path.join(testProjectPath, 'build', 'device')}`, `SHARED_PRECOMPS_DIR=${path.join(testProjectPath, 'build', 'sharedpch')}` ]); - expect(args.length).toEqual(14); + expect(args.length).toEqual(20); }); }); @@ -215,7 +220,14 @@ describe('build', () => { }); it('should generate the appropriate arguments for automatic provisioning', () => { - const archiveArgs = getXcodeArchiveArgs('TestProjectName', testProjectPath, '/test/output/path', '/test/export/options/path', true); + const buildOpts = { + automaticProvisioning: true, + authenticationKeyPath: '/tmp/asc-key.p8', + authenticationKeyID: '12345', + authenticationKeyIssuerID: '00000000-0000-0000-0000-000000000000' + }; + + const archiveArgs = getXcodeArchiveArgs('TestProjectName', testProjectPath, '/test/output/path', '/test/export/options/path', buildOpts); expect(archiveArgs[0]).toEqual('-exportArchive'); expect(archiveArgs[1]).toEqual('-archivePath'); expect(archiveArgs[2]).toEqual('TestProjectName.xcarchive'); @@ -224,7 +236,13 @@ describe('build', () => { expect(archiveArgs[5]).toEqual('-exportPath'); expect(archiveArgs[6]).toEqual('/test/output/path'); expect(archiveArgs[7]).toEqual('-allowProvisioningUpdates'); - expect(archiveArgs.length).toEqual(8); + expect(archiveArgs[8]).toEqual('-authenticationKeyPath'); + expect(archiveArgs[9]).toEqual('/tmp/asc-key.p8'); + expect(archiveArgs[10]).toEqual('-authenticationKeyID'); + expect(archiveArgs[11]).toEqual('12345'); + expect(archiveArgs[12]).toEqual('-authenticationKeyIssuerID'); + expect(archiveArgs[13]).toEqual('00000000-0000-0000-0000-000000000000'); + expect(archiveArgs.length).toEqual(14); }); });