def package(self):
        """Build the App package for deployment to ThreatConnect Exchange."""
        # create build directory
        tmp_path = os.path.join(self.app_path, self.args.outdir, 'build')
        if not os.path.isdir(tmp_path):
            os.makedirs(tmp_path)

        # temp path and cleanup
        template_app_path = os.path.join(tmp_path, 'template')
        if os.access(template_app_path, os.W_OK):
            # cleanup any previous failed builds
            shutil.rmtree(template_app_path)
        # update package data
        self.package_data['package'].append(
            {'action': 'Template Directory:', 'output': template_app_path}
        )

        # build exclude file/directory list
        excludes = [
            'tcex.json',
            self.args.outdir,
            '__pycache__',
            '.c9',  # C9 IDE
            '.git',  # git directory
            '.gitmodules',  # git modules
            '.idea',  # PyCharm
            '*.iml',  # PyCharm files
            '*.pyc',  # any pyc file
            '.python-version',  # pyenv
            '.vscode',  # Visual Studio Code
            'log',  # log directory
        ]
        excludes.extend(self.args.exclude)
        excludes.extend(self.tcex_json.get('package', {}).get('excludes', []))
        # update package data
        self.package_data['package'].append({'action': 'Excluded Files:', 'output': excludes})

        # copy project directory to temp location to use as template for multiple builds
        ignore_patterns = shutil.ignore_patterns(*excludes)
        shutil.copytree(self.app_path, template_app_path, False, ignore_patterns)

        # build list of app json files
        if self.args.install_json is not None:
            contents = [self.args.install_json]
        else:
            contents = os.listdir(self.app_path)

        # package app
        for install_json in sorted(contents):
            # skip files that are not install.json files
            if 'install.json' not in install_json:
                continue

            # get App Name from config, install.json prefix or directory name.
            if install_json == 'install.json':
                app_name = self.tcex_json.get('package', {}).get(
                    'app_name', os.path.basename(self.app_path)
                )
            else:
                app_name = install_json.split('.')[0]

            # update package data
            self.package_data['package'].append({'action': 'App Name:', 'output': app_name})

            # load install json
            ij = self.load_install_json(install_json)

            # automatically update install.json for feature sets supported by the SDK
            ij, ij_modified = self._update_install_json(ij)

            # write update install.json
            if ij_modified:
                self._write_install_json(install_json, ij)

            # find a usable app version
            program_version = ij.get('programVersion', '1.0.0').split('.')
            major_version = program_version[0]
            try:
                minor_version = program_version[1]
            except IndexError:
                minor_version = 0
            app_version = '{}'.format(
                self.tcex_json.get('package', {}).get(
                    'app_version', 'v{}.{}'.format(major_version, minor_version)
                )
            )

            # update package data
            self.package_data['package'].append(
                {'action': 'App Version:', 'output': 'v{}.{}'.format(major_version, minor_version)}
            )

            # !!! The name of the folder in the zip is the *key* for an App. This value must
            # !!! remain consistent for the App to upgrade successfully.
            app_name_version = '{}_{}'.format(app_name, app_version)

            # build app directory
            tmp_app_path = os.path.join(tmp_path, app_name_version)
            if os.access(tmp_app_path, os.W_OK):
                # cleanup any previous failed builds
                shutil.rmtree(tmp_app_path)
            shutil.copytree(template_app_path, tmp_app_path)

            # Copy install.json
            # TODO: do we need copy if writing the data in the next step?
            shutil.copy(install_json, os.path.join(tmp_app_path, 'install.json'))

            # Update commit hash after install.json has been copied.
            if self.commit_hash is not None:
                ij.setdefault('commitHash', self.commit_hash)
            self._write_install_json(os.path.join(tmp_app_path, 'install.json'), ij)

            # update package data
            self.package_data['package'].append(
                {'action': 'Commit Hash:', 'output': self.commit_hash}
            )

            # zip file
            self.zip_file(self.app_path, app_name_version, tmp_path)

            # cleanup build directory
            shutil.rmtree(tmp_app_path)

        # bundle zips (must have more than 1 app)
        if len(self._app_packages) > 1:
            self.bundle(self.tcex_json.get('package', {}).get('bundle_name', app_name))
Build the App package for deployment to ThreatConnect Exchange.