async function run()

in Tasks/AppStorePromote/app-store-promote.ts [52:251]


async function run() {
    const appSpecificPasswordEnvVar: string = 'FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD';
    const fastlaneSessionEnvVar: string = 'FASTLANE_SESSION';
    let apiKeyFilePath: string;
    let isTwoFactorAuthEnabled: boolean = false;
    let isUsingApiKey: boolean = false;
    try {
        tl.setResourcePath(path.join(__dirname, 'task.json'));

        //check if this is running on Mac and fail the task if not
        if (os.platform() !== 'darwin') {
            throw new Error(tl.loc('DarwinOnly'));
        }

        tl.debug('Reading all inputs...');

        // Get input variables
        let authType: string = tl.getInput('authType', false);
        let credentials: UserCredentials = new UserCredentials();
        let apiKey: ApiKey;

        const createapiKeyFilePath = (apiKeyId: string) => {
            const tempPath = tl.getVariable('Agent.TempDirectory') || tl.getVariable('Agent.BuildDirectory');
            return path.join(tempPath, `api_key${apiKeyId}.json`);
        };

        if (authType === 'ServiceEndpoint') {
            let serviceEndpoint: tl.EndpointAuthorization = tl.getEndpointAuthorization(tl.getInput('serviceEndpoint', true), false);

            if (serviceEndpoint.scheme === 'Token') {
                // Using App Store Connect API Key
                isUsingApiKey = true;
                apiKeyFilePath = createapiKeyFilePath(serviceEndpoint.parameters['apiKeyId']);
                apiKey = {
                    key_id: serviceEndpoint.parameters['apiKeyId'],
                    issuer_id: serviceEndpoint.parameters['apiKeyIssuerId'],
                    key: serviceEndpoint.parameters['apitoken'],
                    in_house: serviceEndpoint.parameters['apiKeyInHouse'] === 'apiKeyInHouse_true',
                    is_key_content_base64: true
                };
            } else {
                credentials.username = serviceEndpoint.parameters['username'];
                credentials.password = serviceEndpoint.parameters['password'];
                credentials.appSpecificPassword = serviceEndpoint.parameters['appSpecificPassword'];
                if (credentials.appSpecificPassword) {
                    isTwoFactorAuthEnabled = true;
                    let fastlaneSession: string = serviceEndpoint.parameters['fastlaneSession'];
                    if (!fastlaneSession) {
                        throw Error(tl.loc('FastlaneSessionEmpty'));
                    }
                    credentials.fastlaneSession = fastlaneSession;
                }
            }
        } else if (authType === 'UserAndPass') {
            credentials.username = tl.getInput('username', true);
            credentials.password = tl.getInput('password', true);
            isTwoFactorAuthEnabled = tl.getBoolInput('isTwoFactorAuth');
            if (isTwoFactorAuthEnabled) {
                credentials.appSpecificPassword = tl.getInput('appSpecificPassword', true);
                credentials.fastlaneSession = tl.getInput('fastlaneSession', true);
            }
        } else if (authType === 'ApiKey') {
            isUsingApiKey = true;
            apiKeyFilePath = createapiKeyFilePath(tl.getInput('apiKeyId', true));
            apiKey = {
                key_id: tl.getInput('apiKeyId', true),
                issuer_id: tl.getInput('apiKeyIssuerId', true),
                key: tl.getInput('apitoken', true),
                in_house: tl.getBoolInput('apiKeyInHouse', false),
                is_key_content_base64: true
            };
        }

        let appIdentifier: string = tl.getInput('appIdentifier', true);
        let chooseBuild: string = tl.getInput('chooseBuild', true);
        let buildNumber: string = tl.getInput('buildNumber', false);
        let shouldAutoRelease: boolean = tl.getBoolInput('shouldAutoRelease', false);
        let teamId: string = tl.getInput('teamId', false);
        let teamName: string = tl.getInput('teamName', false);

        let installFastlane: boolean = tl.getBoolInput('installFastlane', false);
        let fastlaneVersionChoice: string = tl.getInput('fastlaneToolsVersion', false);
        let fastlaneVersionToInstall: string;  //defaults to 'LatestVersion'
        if (fastlaneVersionChoice === 'SpecificVersion') {
            fastlaneVersionToInstall = tl.getInput('fastlaneToolsSpecificVersion', true);
        }
        let fastlaneArguments: string = tl.getInput('fastlaneArguments');
        tl.debug('Read all inputs.');

        // Set up environment
        tl.debug(`GEM_CACHE=${process.env['GEM_CACHE']}`);
        let gemCache: string = process.env['GEM_CACHE'] || path.join(process.env['HOME'], '.gem-cache');
        tl.debug(`gemCache=${gemCache}`);
        process.env['GEM_HOME'] = gemCache;
        process.env['FASTLANE_PASSWORD'] = credentials.password;
        process.env['FASTLANE_DONT_STORE_PASSWORD'] = 'true';

        if (isTwoFactorAuthEnabled) {
            // Properties required for two-factor authentication:
            // 1) Account username and password
            // 2) App-specific password (Apple account->Security where two factor authentication is set)
            // 3) FASTLANE_SESSION, which is essentially a cookie granting access to Apple accounts
            // To get a FASTLANE_SESSION, run 'fastlane spaceauth -u [email]' interactively (requires PIN)
            // See: https://github.com/fastlane/fastlane/blob/master/spaceship/README.md
            tl.debug('Using two-factor authentication');
            process.env[fastlaneSessionEnvVar] = credentials.fastlaneSession;
            process.env[appSpecificPasswordEnvVar] = credentials.appSpecificPassword;
        }

        // Add bin of new gem home so we don't have to resolve it later
        process.env['PATH'] = process.env['PATH'] + ':' + gemCache + path.sep + 'bin';

        // Install the ruby gem for fastlane
        tl.debug('Checking for ruby install...');
        tl.which('ruby', true);

        //Whenever a specific version of fastlane is requested, we're going to attempt to uninstall any installed
        //versions of fastlane.  Note that this doesn't uninstall dependencies of fastlane.
        if (installFastlane && fastlaneVersionToInstall) {
            try {
                let gemRunner: ToolRunner = tl.tool(tl.which('gem', true));
                gemRunner.arg(['uninstall', 'fastlane']);
                tl.debug(`Uninstalling all fastlane versions...`);
                gemRunner.arg(['-a', '-I']);  //uninstall all versions
                await gemRunner.exec();
            } catch (err) {
                tl.warning(tl.loc('UninstallFastlaneFailed', err));
            }
        }
        // If desired, install the fastlane tools (if they're already present, should be a no-op)
        if (installFastlane) {
            tl.debug('Installing fastlane...');
            let gemRunner: ToolRunner = tl.tool(tl.which('gem', true));
            gemRunner.arg(['install', 'fastlane']);
            if (fastlaneVersionToInstall) {
                tl.debug(`Installing specific version of fastlane: ${fastlaneVersionToInstall}`);
                gemRunner.arg(['-v', fastlaneVersionToInstall]);
            }
            await gemRunner.exec();

            // If desired, update fastlane (if already latest, should be a no-op)
            if (!fastlaneVersionToInstall) {
                tl.debug('Updating fastlane...');
                gemRunner = tl.tool(tl.which('gem', true));
                gemRunner.arg(['update', 'fastlane', '-i', gemCache]);
                await gemRunner.exec();
            }
        } else {
            tl.debug('Skipped fastlane installation.');
        }

        //Run the deliver command 
        // See https://github.com/fastlane/fastlane/blob/master/deliver/lib/deliver/options.rb for more information on these arguments
        let deliverCommand: ToolRunner = tl.tool('fastlane');
        if (isUsingApiKey) {
            if (fs.existsSync(apiKeyFilePath)) {
                fs.unlinkSync(apiKeyFilePath);
            }
            let apiKeyJsonData = JSON.stringify(apiKey);
            fs.writeFileSync(apiKeyFilePath, apiKeyJsonData);

            // Prechecking in-app purchases is not supported with API key authorization
            console.log(tl.loc('PrecheckInAppPurchasesDisabled'));
            deliverCommand.arg(['deliver', 'submit_build', '--precheck_include_in_app_purchases', 'false', '--api_key_path', apiKeyFilePath, '-a', appIdentifier]);
        } else {
            deliverCommand.arg(['deliver', 'submit_build', '-u', credentials.username, '-a', appIdentifier]);
        }
        if (chooseBuild.toLowerCase() === 'specify') {
            deliverCommand.arg(['-n', buildNumber]);
        }
        deliverCommand.arg(['--skip_binary_upload', 'true', '--skip_metadata', 'true', '--skip_screenshots', 'true']);
        deliverCommand.argIf(shouldAutoRelease, '--automatic_release');
        deliverCommand.argIf(teamId, ['-k', teamId]);
        deliverCommand.argIf(teamName, ['--team_name', teamName]);
        deliverCommand.arg('--force');

        //use .line instead of arg/argif to support mulitple parameters input by user
        if (fastlaneArguments) {
            deliverCommand.line(fastlaneArguments);
        }

        await deliverCommand.exec();

        tl.setResult(tl.TaskResult.Succeeded, tl.loc('SuccessfullySubmitted'));
    } catch (err) {
        tl.setResult(tl.TaskResult.Failed, err);
    } finally {
        if (isTwoFactorAuthEnabled) {
            tl.debug('Clearing two-factor authentication environment variables');
            process.env[fastlaneSessionEnvVar] = '';
            process.env[appSpecificPasswordEnvVar] = '';
        }
        if (isUsingApiKey && apiKeyFilePath && process.env['DEBUG_API_KEY_FILE'] !== 'true') {
            tl.debug('Clearing API Key file');
            if (fs.existsSync(apiKeyFilePath)) {
                fs.unlinkSync(apiKeyFilePath);
            }
        }
    }
}