Example #1
0
def create(app_name, app_path):
    auth = Auth()

    # Create the app server-side
    siphon = Siphon(auth.auth_token)
    obj = siphon.create(app_name)
    app_id = obj['id']  # server gives us back our internal app ID

    # Populate our new directory with template files
    copy_app_template(app_name, app_path)

    # Write out a .siphon configuration to the new direcotry
    conf = Config(directory=app_name)
    conf.app_id = app_id

    # Copy our .siphonignore file over
    siphon_ignore = os.path.join(CLI_RESOURCES, '.siphonignore')
    shutil.copyfile(siphon_ignore, os.path.join(app_path, '.siphonignore'))
    puts(colored.green('Siphon app created at %s' % app_path))

    # Register Mixpanel event
    username = auth.username
    mixpanel_event(MIXPANEL_EVENT_CREATE, username, {'app_id': app_id,
                                                     'existing_app': False})

    # Write out the Siphonfile
    with open(os.path.join(app_name, SIPHON_USER_CONFIG), 'w') as fp:
        json.dump({'base_version': obj['base_version']}, fp, indent=2)

    # Implicitly do a push too
    with cd(app_path):
        from siphon.cli.commands.push import push
        push(track_event=False)
    puts(colored.green('Done.'))
Example #2
0
def run(args):
    # Generate/retrieve the user's auth token
    credentials = request_login()
    username = credentials['username']
    auth_token = credentials['auth_token']

    # Update our .auth file
    auth = Auth()
    auth.username = username
    auth.auth_token = auth_token

    mixpanel_event(MIXPANEL_EVENT_LOGIN)
Example #3
0
def run(args):
    # Generate/retrieve the user's auth token
    credentials = request_login()
    username = credentials['username']
    auth_token = credentials['auth_token']

    # Update our .auth file
    auth = Auth()
    auth.username = username
    auth.auth_token = auth_token

    mixpanel_event(MIXPANEL_EVENT_LOGIN)
Example #4
0
def share_with_team_member(email):
    auth = Auth()
    siphon = Siphon(auth.auth_token)
    conf = Config()

    # Prompt the user with implications before we do anything.
    puts(colored.yellow('Checking the sharing status for this app...'))
    sharing_status = siphon.get_sharing_status(conf.app_id)
    shared_with = sharing_status.get('shared_with', [])
    if not prompt_team_share(shared_with):
        return

    siphon.share(conf.app_id, 'team-member', email)
    puts(colored.green('Your app was shared successfully.'))
    mixpanel_event(MIXPANEL_SHARE_TEAM_MEMBER)
Example #5
0
def share_with_team_member(email):
    auth = Auth()
    siphon = Siphon(auth.auth_token)
    conf = Config()

    # Prompt the user with implications before we do anything.
    puts(colored.yellow('Checking the sharing status for this app...'))
    sharing_status = siphon.get_sharing_status(conf.app_id)
    shared_with = sharing_status.get('shared_with', [])
    if not prompt_team_share(shared_with):
        return

    siphon.share(conf.app_id, 'team-member', email)
    puts(colored.green('Your app was shared successfully.'))
    mixpanel_event(MIXPANEL_SHARE_TEAM_MEMBER)
Example #6
0
def push(track_event=True, watch=False, app_id=None):
    # Request the correct bundler URL from siphon-web
    puts(colored.green('Preparing to push your local files...'))
    conf = Config()
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    if app_id is None:
        app_id = conf.app_id

    if track_event:
        event_properties = {'app_id': app_id, 'watch': watch}
        mixpanel_event(MIXPANEL_EVENT_PUSH, properties=event_properties)

    bundler_url = siphon.get_bundler_url(app_id, action='push')
    server_hashes = get_hashes(bundler_url)
    return post_archive(bundler_url, server_hashes)
Example #7
0
def stream_logs():
    conf = Config()
    # Request the correct streamer URL from siphon-web
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Track
    mixpanel_event(MIXPANEL_EVENT_LOGS, properties={'app_id': conf.app_id})

    streamer_url = siphon.get_streamer_url(conf.app_id, 'log_reader')

    puts(colored.yellow('Connecting...'))
    ws = websocket.create_connection(streamer_url)
    puts(colored.green('Streaming logs and errors... (ctrl-c to stop)\n'))
    try:
        for line in ws:
            print(line)
    except KeyboardInterrupt:
        puts(colored.yellow('\nClosing the connection.'))
        ws.close()
Example #8
0
def stream_logs():
    conf = Config()
    # Request the correct streamer URL from siphon-web
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Track
    mixpanel_event(MIXPANEL_EVENT_LOGS, properties={'app_id': conf.app_id})

    streamer_url = siphon.get_streamer_url(conf.app_id, 'log_reader')

    puts(colored.yellow('Connecting...'))
    ws = websocket.create_connection(streamer_url)
    puts(colored.green('Streaming logs and errors... (ctrl-c to stop)\n'))
    try:
        for line in ws:
            print(line)
    except KeyboardInterrupt:
        puts(colored.yellow('\nClosing the connection.'))
        ws.close()
Example #9
0
def share_with_beta_tester(email=None):
    auth = Auth()
    siphon = Siphon(auth.auth_token)
    conf = Config()

    # Prompt the user with implications before we do anything.
    puts(colored.yellow('Checking the sharing status for this app...'))
    sharing_status = siphon.get_sharing_status(conf.app_id)
    shared_with = sharing_status.get('shared_with', [])
    if email:
        if not prompt_beta_share_for_specific_email(shared_with):
            return
    else:
        if len(shared_with) < 1:
            msg = 'Running the "siphon share" command without specifying ' \
                'an email address is only valid if you have one-or-more ' \
                'beta testers who already accepted an invitation.'
            puts(colored.red(msg))
            return
        if not prompt_beta_share_for_all(shared_with):
            return

    # Do a push to the *aliased* beta testing app.
    puts(colored.green('Pushing your local files for beta testing...'))
    aliased_app_id = sharing_status['aliased_app']['id']
    if not push(app_id=aliased_app_id, track_event=False):
        puts(colored.red('\nThe push failed so your beta testers were not ' \
            'notified.'))
        return

    # We only need to add a sharing permission if an email was specified,
    # because the push itself triggers notifications.
    if email is not None:
        siphon.share(conf.app_id, 'beta-tester', email)
    puts(colored.green('\nYour app was shared successfully.'))
    mixpanel_event(MIXPANEL_SHARE_BETA_TESTER)
Example #10
0
def share_with_beta_tester(email=None):
    auth = Auth()
    siphon = Siphon(auth.auth_token)
    conf = Config()

    # Prompt the user with implications before we do anything.
    puts(colored.yellow('Checking the sharing status for this app...'))
    sharing_status = siphon.get_sharing_status(conf.app_id)
    shared_with = sharing_status.get('shared_with', [])
    if email:
        if not prompt_beta_share_for_specific_email(shared_with):
            return
    else:
        if len(shared_with) < 1:
            msg = 'Running the "siphon share" command without specifying ' \
                'an email address is only valid if you have one-or-more ' \
                'beta testers who already accepted an invitation.'
            puts(colored.red(msg))
            return
        if not prompt_beta_share_for_all(shared_with):
            return

    # Do a push to the *aliased* beta testing app.
    puts(colored.green('Pushing your local files for beta testing...'))
    aliased_app_id = sharing_status['aliased_app']['id']
    if not push(app_id=aliased_app_id, track_event=False):
        puts(colored.red('\nThe push failed so your beta testers were not ' \
            'notified.'))
        return

    # We only need to add a sharing permission if an email was specified,
    # because the push itself triggers notifications.
    if email is not None:
        siphon.share(conf.app_id, 'beta-tester', email)
    puts(colored.green('\nYour app was shared successfully.'))
    mixpanel_event(MIXPANEL_SHARE_BETA_TESTER)
Example #11
0
def develop(sim, default_sim=True, global_watchman=False):
    """
    Takes a simulator object and runs the simulator
    """
    conf = Config()
    user_conf = UserConfig()
    app_id = conf.app_id

    mixpanel_props = {
        'app_id': app_id,
        'simulator': sim.formatted_name,
        'default_sim': default_sim
    }
    mixpanel_event(MIXPANEL_EVENT_DEVELOP, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Here we use the version in the Siphonfile since we are not pushing
    # the app.
    version = user_conf.get('base_version')
    # Ensure that node exists in our develop directory
    ensure_node(version)

    # Create a Build instance for the app
    build = Build(conf.app_id, version)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # Get/set the display name
    display_name = user_conf.get('display_name')
    if not display_name:
        print('A display name for this app has not been set. ' \
              'Please enter a display name. This can be changed by ' \
              'modifying the "display_name" value in the app ' \
              'Siphonfile.')
        new_display_name = get_input('Display name: ')
        user_conf.set('display_name', new_display_name)
        display_name = new_display_name

    # Get the facebook_app_id
    facebook_app_id = user_conf.get('facebook_app_id')

    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id,
                                                 display_name, facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # Make sure our develop directory is populated with the latest node modules
    # and is generally Initialized
    dev_dir = DevelopDir(version)
    dev_dir.clean_old()
    dev_dir.ensure_develop_dir()

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name, bundle_id,
                           facebook_app_id=facebook_app_id,
                           app_transport_exception='localhost')
    base = BaseProject(build.project_dir(sim.platform))

    platform_version = sim.platform_version
    arch_dir = build.archive_dir_path(sim.formatted_name, platform_version)

    if not build.archived(sim.formatted_name, platform_version):
        with base.configure(base_conf):
            app = archive(sim, arch_dir, build.project_dir())
    else:
        app = sim.app_path(arch_dir)

    # Update the cached app_info & run the app
    cache.set_build_info(build.build_id, display_name, facebook_app_id)
    start_processes(dev_dir, sim, app, bundle_id, global_watchman)
Example #12
0
def print_apps():
    auth = Auth()
    mixpanel_event(MIXPANEL_EVENT_LIST)
    siphon = Siphon(auth.auth_token)
    for app in siphon.list_apps():
        print(app['name'])
Example #13
0
def publish(platform):
    # Ensure that a publish directory exists
    ensure_publish_dir()
    ensure_platform_keys()

    # Load the app config and wrapper
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)

    mixpanel_props = {
        'upgrade_required': False,
        'platform': platform
    }

    # We first check that the user has a valid subscription, because we're
    # going to do an implicit push before submitting, and it would otherwise
    # be confusing.
    puts(colored.yellow('Checking your account...'))
    if not siphon.user_can_publish():
        mixpanel_props['upgrade_required'] = True
        mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
        prompt_for_upgrade()
        sys.exit(1)
    else:
        puts(colored.green('Your account is ready for publishing.'))

    mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
    # It looks like the user can publish, so we do an implicit push
    # and validate the app.
    app_valid = validate_app([platform])
    if not app_valid:
        # The last push before publishing should be a successful one
        sys.exit(1)

    # Check if this submission requires a hard update, and if so then
    # prompt the user before going ahead with the submit so they understand.
    hard_update = siphon.app_requires_hard_update(conf.app_id, platform)
    if hard_update:
        if not prompt_for_hard_update(platform):
            return
    else:
        puts(colored.green('This update can be published without changing ' \
            'the app binary. It will be available straight away.'))

    # Prompt for platform info and do the actual submission
    user, password = None, None
    if hard_update:
        user, password = prompt_for_platform_info(platform)

    if hard_update:
        if not yn('Your app is about to be processed and submitted to the ' \
        'store. Are you sure you want to continue? [Y/n]: '):
            sys.exit(0)
    else:
        if not yn('Your app update is about to be submitted and the users ' \
        'of your app will receive it as an over-the-air update. Are you ' \
        'sure you want to continue? [Y/n]: '):
            sys.exit(0)

    puts(colored.yellow('Submitting...'))
    siphon.submit(conf.app_id, platform, username=user, password=password)

    puts(colored.green('\nThanks! Your app is in the queue. We will send ' \
        'status updates to your registered email address.'))
    print('Please note that after this submission has been released, you ' \
        'can push instant updates to your users by running the ' \
        '"siphon publish" command again.')
Example #14
0
def play(device, dev_mode=False, platform='ios'):
    """
    Takes a device object and runs an app
    """
    conf = Config()
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Mixpanel tracking
    current_app_data = siphon.get_app(conf.app_id)
    mixpanel_props = {
        'app_id': conf.app_id,
        'app_name': current_app_data['name'],
    }
    mixpanel_event(MIXPANEL_EVENT_PLAY, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #   4. An archive exists and the provisioning profile used to create it
    #      is not installed or is not compatible with the connected device.
    #      (device.profile_synchronised() is False.)
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Push the app and fetch the updated settings if successful
    push(track_event=False)
    updated_app_data = siphon.get_app(conf.app_id)
    version = updated_app_data['base_version']

    # Create a Build instance for the app
    build = Build(conf.app_id, version, dev_mode)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # The new display name defaults to the app name if it is not set
    display_name = updated_app_data.get('display_name')
    if not display_name:
        display_name = updated_app_data.get('name')

    facebook_app_id = updated_app_data.get('facebook_app_id')

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name, bundle_id,
                           facebook_app_id=facebook_app_id)

    # Get the cached build info for the previous build
    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id,
                                                display_name, facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # If the provisioning profile needs updating, then we clear the archive
    # of the device build only. (The profile doesn't affect
    # other builds for the simulator).

    # Is the installed profile compatible with the connected device?
    profile_synchronised = device.profile_synchronised()
    if not profile_synchronised:
        build.clean_archive(device.formatted_name)
        # Sort out a new provisioning profile that includes the device
        device.update_requirements()

    # Was previous build performed with the installed profile? If we're
    # here then a compatible profile is installed.
    build_profile_ok = device.build_profile_ok(build.build_id)
    if not build_profile_ok:
        build.clean_archive(device.formatted_name)

    # If an archive doesn't exist at this point (perhaps it never existed,
    # or was deleted when invalidated above) we need to rebuild.
    if not build.archived(device.formatted_name):
        archive = True
    else:
        archive = False

    arch_dir = build.archive_dir_path(device.formatted_name)

    # Archiving takes a while, so prompt the user
    if archive:
        proceed = yn('A build is required for this app. ' \
                     'This step is needed if you haven\'t run this ' \
                     'app on the device before, ' \
                     'you have changed the name or base version, or ' \
                     'if an update has recently been performed. ' \
                     'This may take a few minutes. Proceed? ' \
                     '[Y/n]: ')
        if proceed:
            try:
                provisioning_profile = cache.get_provisioning_profile_id()
                base = BaseProject(build.project_dir(platform))

                with base.configure(base_conf):
                    app = device.archive(
                        base.directory,
                        arch_dir,
                        conf.app_id,
                        auth.auth_token,
                        provisioning_profile,
                        dev_mode
                    )

                # Update the cached build info
                cache.set_build_info(build.build_id, display_name,
                                     facebook_app_id)
                # Record the provisioning profile used to archive this app
                cache.set_build_profile(build.build_id, provisioning_profile)
            except SiphonDeviceException:
                cleanup_dir(arch_dir)
                sys.exit(1)
            except KeyboardInterrupt:
                puts(colored.red('\nArchiving interrupted'))
                cleanup_dir(arch_dir)
                sys.exit(1)
        else:
            sys.exit(1)
    else:
        app = device.app_path(arch_dir)

    device.run(app)
Example #15
0
def develop(sim, default_sim=True, global_watchman=False):
    """
    Takes a simulator object and runs the simulator
    """
    conf = Config()
    user_conf = UserConfig()
    app_id = conf.app_id

    mixpanel_props = {
        'app_id': app_id,
        'simulator': sim.formatted_name,
        'default_sim': default_sim
    }
    mixpanel_event(MIXPANEL_EVENT_DEVELOP, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Here we use the version in the Siphonfile since we are not pushing
    # the app.
    version = user_conf.get('base_version')
    # Ensure that node exists in our develop directory
    ensure_node(version)

    # Create a Build instance for the app
    build = Build(conf.app_id, version)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # Get/set the display name
    display_name = user_conf.get('display_name')
    if not display_name:
        print('A display name for this app has not been set. ' \
              'Please enter a display name. This can be changed by ' \
              'modifying the "display_name" value in the app ' \
              'Siphonfile.')
        new_display_name = get_input('Display name: ')
        user_conf.set('display_name', new_display_name)
        display_name = new_display_name

    # Get the facebook_app_id
    facebook_app_id = user_conf.get('facebook_app_id')

    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id, display_name,
                                                  facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # Make sure our develop directory is populated with the latest node modules
    # and is generally Initialized
    dev_dir = DevelopDir(version)
    dev_dir.clean_old()
    dev_dir.ensure_develop_dir()

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name,
                           bundle_id,
                           facebook_app_id=facebook_app_id,
                           app_transport_exception='localhost')
    base = BaseProject(build.project_dir(sim.platform))

    platform_version = sim.platform_version
    arch_dir = build.archive_dir_path(sim.formatted_name, platform_version)

    if not build.archived(sim.formatted_name, platform_version):
        with base.configure(base_conf):
            app = archive(sim, arch_dir, build.project_dir())
    else:
        app = sim.app_path(arch_dir)

    # Update the cached app_info & run the app
    cache.set_build_info(build.build_id, display_name, facebook_app_id)
    start_processes(dev_dir, sim, app, bundle_id, global_watchman)
Example #16
0
def publish(platform):
    # Ensure that a publish directory exists
    ensure_publish_dir()
    ensure_platform_keys()

    # Load the app config and wrapper
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)

    mixpanel_props = {'upgrade_required': False, 'platform': platform}

    # We first check that the user has a valid subscription, because we're
    # going to do an implicit push before submitting, and it would otherwise
    # be confusing.
    puts(colored.yellow('Checking your account...'))
    if not siphon.user_can_publish():
        mixpanel_props['upgrade_required'] = True
        mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
        prompt_for_upgrade()
        sys.exit(1)
    else:
        puts(colored.green('Your account is ready for publishing.'))

    mixpanel_event(MIXPANEL_EVENT_PUBLISH, properties=mixpanel_props)
    # It looks like the user can publish, so we do an implicit push
    # and validate the app.
    app_valid = validate_app([platform])
    if not app_valid:
        # The last push before publishing should be a successful one
        sys.exit(1)

    # Check if this submission requires a hard update, and if so then
    # prompt the user before going ahead with the submit so they understand.
    hard_update = siphon.app_requires_hard_update(conf.app_id, platform)
    if hard_update:
        if not prompt_for_hard_update(platform):
            return
    else:
        puts(colored.green('This update can be published without changing ' \
            'the app binary. It will be available straight away.'))

    # Prompt for platform info and do the actual submission
    user, password = None, None
    if hard_update:
        user, password = prompt_for_platform_info(platform)

    if hard_update:
        if not yn('Your app is about to be processed and submitted to the ' \
        'store. Are you sure you want to continue? [Y/n]: '):
            sys.exit(0)
    else:
        if not yn('Your app update is about to be submitted and the users ' \
        'of your app will receive it as an over-the-air update. Are you ' \
        'sure you want to continue? [Y/n]: '):
            sys.exit(0)

    puts(colored.yellow('Submitting...'))
    siphon.submit(conf.app_id, platform, username=user, password=password)

    puts(colored.green('\nThanks! Your app is in the queue. We will send ' \
        'status updates to your registered email address.'))
    print('Please note that after this submission has been released, you ' \
        'can push instant updates to your users by running the ' \
        '"siphon publish" command again.')
Example #17
0
def init():
    auth = Auth()
    # Create the app server-side
    siphon = Siphon(auth.auth_token)
    print('Please enter a name for your Siphon app.')

    try:
        name_valid = False
        while not name_valid:
            app_name = get_input('App name: ')
            name_valid = app_name_valid(app_name)
            if not name_valid:
                print('Siphon app names may only contain letters, numbers, ' \
                      'underscores and hyphens.')
    except KeyboardInterrupt:
        sys.exit(1)

    obj = siphon.create(app_name)
    app_id = obj['id']  # server gives us back our internal app ID

    # Write out a .siphon configuration to the new directory
    conf = Config()
    conf.app_id = app_id

    # Copy our .siphonignore file over
    siphon_ignore = os.path.join(CLI_RESOURCES, '.siphonignore')
    shutil.copyfile(siphon_ignore, '.siphonignore')

    puts(colored.green('Siphon app created.'))

    # Register Mixpanel event
    username = auth.username
    mixpanel_event(MIXPANEL_EVENT_CREATE, username, {'app_id': app_id,
                                                     'existing_app': True})

    # Write out the Siphonfile
    with open(SIPHON_USER_CONFIG, 'w') as fp:
        json.dump({'base_version': obj['base_version']}, fp, indent=2)

    # Implicitly do a push too
    from siphon.cli.commands.push import push
    push(track_event=False)
    puts(colored.green('Done.'))

    # Print warning about mismatched React Native versions
    project_rn_version = node_module_version('node_modules/react-native')
    siphon_rn_version = obj['react_native_version']
    if project_rn_version != siphon_rn_version:
        puts(colored.yellow('Note: Siphon app is using React Native %s but ' \
            'existing project is using React Native %s.\n'\
            'Please visit https://getsiphon.com/docs/base-version/ to learn ' \
            'more about base versions.\n' %
                            (siphon_rn_version, project_rn_version)))

    if os.path.isfile('index.js'):
        print('You must register your component with the name \'App\'' \
              'in your index.js file to use Siphon.\n')
        if os.path.isfile('index.js'):
            print_app_registry_message('index.js')
    else:
        print('You must register your component with the name \'App\'' \
              'in your index.ios.js and index.android.js files ' \
              ' to use Siphon.\n')
        if os.path.isfile('index.ios.js'):
            print_app_registry_message('index.ios.js')
        if os.path.isfile('index.android.js'):
            print_app_registry_message('index.android.js')
Example #18
0
def play(device, dev_mode=False, platform='ios'):
    """
    Takes a device object and runs an app
    """
    conf = Config()
    auth = Auth()
    siphon = Siphon(auth.auth_token)

    # Mixpanel tracking
    current_app_data = siphon.get_app(conf.app_id)
    mixpanel_props = {
        'app_id': conf.app_id,
        'app_name': current_app_data['name'],
    }
    mixpanel_event(MIXPANEL_EVENT_PLAY, properties=mixpanel_props)

    # The app needs to be built (archived) if:
    #
    #   1. An archive for this base version doesn't exist
    #   2. An archive exists and the display name has changed
    #   3. An archive exists and the facebook app id has changed
    #   4. An archive exists and the provisioning profile used to create it
    #      is not installed or is not compatible with the connected device.
    #      (device.profile_synchronised() is False.)
    #
    # After each build, we cache the build info and use it for future
    # reference. If build info is not stored for this app and base version,
    # we build the app anyway and cache the build info.

    # Push the app and fetch the updated settings if successful
    push(track_event=False)
    updated_app_data = siphon.get_app(conf.app_id)
    version = updated_app_data['base_version']

    # Create a Build instance for the app
    build = Build(conf.app_id, version, dev_mode)

    # Make sure the correct base version is installed and the build
    # directory exists
    ensure_base(version)
    build.ensure_build_dir()

    # The new display name defaults to the app name if it is not set
    display_name = updated_app_data.get('display_name')
    if not display_name:
        display_name = updated_app_data.get('name')

    facebook_app_id = updated_app_data.get('facebook_app_id')

    bundle_id = 'com.getsiphon.%s' % conf.app_id

    # Initialize a BaseConfig instance that will be used to configure
    # the base project we're archiving.
    base_conf = BaseConfig(display_name,
                           bundle_id,
                           facebook_app_id=facebook_app_id)

    # Get the cached build info for the previous build
    cache = Cache()
    build_info_updated = cache.build_info_updated(build.build_id, display_name,
                                                  facebook_app_id)

    # If the build info has been updated, we clear the old archives
    if build_info_updated:
        build.clean_archives()

    # If the provisioning profile needs updating, then we clear the archive
    # of the device build only. (The profile doesn't affect
    # other builds for the simulator).

    # Is the installed profile compatible with the connected device?
    profile_synchronised = device.profile_synchronised()
    if not profile_synchronised:
        build.clean_archive(device.formatted_name)
        # Sort out a new provisioning profile that includes the device
        device.update_requirements()

    # Was previous build performed with the installed profile? If we're
    # here then a compatible profile is installed.
    build_profile_ok = device.build_profile_ok(build.build_id)
    if not build_profile_ok:
        build.clean_archive(device.formatted_name)

    # If an archive doesn't exist at this point (perhaps it never existed,
    # or was deleted when invalidated above) we need to rebuild.
    if not build.archived(device.formatted_name):
        archive = True
    else:
        archive = False

    arch_dir = build.archive_dir_path(device.formatted_name)

    # Archiving takes a while, so prompt the user
    if archive:
        proceed = yn('A build is required for this app. ' \
                     'This step is needed if you haven\'t run this ' \
                     'app on the device before, ' \
                     'you have changed the name or base version, or ' \
                     'if an update has recently been performed. ' \
                     'This may take a few minutes. Proceed? ' \
                     '[Y/n]: ')
        if proceed:
            try:
                provisioning_profile = cache.get_provisioning_profile_id()
                base = BaseProject(build.project_dir(platform))

                with base.configure(base_conf):
                    app = device.archive(base.directory, arch_dir, conf.app_id,
                                         auth.auth_token, provisioning_profile,
                                         dev_mode)

                # Update the cached build info
                cache.set_build_info(build.build_id, display_name,
                                     facebook_app_id)
                # Record the provisioning profile used to archive this app
                cache.set_build_profile(build.build_id, provisioning_profile)
            except SiphonDeviceException:
                cleanup_dir(arch_dir)
                sys.exit(1)
            except KeyboardInterrupt:
                puts(colored.red('\nArchiving interrupted'))
                cleanup_dir(arch_dir)
                sys.exit(1)
        else:
            sys.exit(1)
    else:
        app = device.app_path(arch_dir)

    device.run(app)
Example #19
0
def post_archive(bundler_url, server_hashes):
    puts(colored.yellow('Hashing...'))
    # Generate a .zip archive for our payload, writing out a listing file, and
    # any new/changed files into the diffs/ directory.
    fp = BytesIO()
    ignore_patterns = get_ignored()

    with zipfile.ZipFile(fp, 'w') as zf:
        listing = {}
        for root, dirs, files in os.walk('.'):
            if root == '.':
                prefix = ''
            elif root.startswith('./'):
                prefix = root[2:] + '/'
            else:
                raise RuntimeError('Unexpected root "%s"' % root)
            dirs[:] = keep(dirs, ignore_patterns, prefix)

            for fil in keep(files, ignore_patterns, prefix):
                local_path = root + '/' + fil # relative to current directory
                remote_path = prefix + fil # relative to app root
                zip_path = 'diffs/' + remote_path # path within .zip archive

                # Generate SHA-256 for this file and record it in the listing
                sha = sha256(local_path)
                listing[remote_path] = sha
                # Only write this file into diffs/ if we need to
                if should_push(sha, remote_path, server_hashes):
                    zf.write(local_path, zip_path)
        # Write out the listing file JSON
        zf.writestr('listing.json', json.dumps(listing))
    # Wrap it in a ProgressReader. It logs a progress bar to console as
    # python-requests cycles through the bytes doing the POST below.
    fp.seek(0)
    reader = ProgressReader(fp.read())
    fp.close()

    # Do the push.
    puts(colored.yellow('Pushing to the bundler...'))
    response = requests.post(bundler_url, data=reader, stream=True, headers={
        'Accept-Encoding': 'gzip;q=0,deflate,sdch' # explicitly disables gzip
    }, timeout=(10, None)) # unlimited read timeout
    reader.close()

    if response.ok:
        msg = None
        for line in response.iter_lines():
            l = line.decode('utf-8')
            if 'ERROR' in l:
                msg = l
                if len(msg) > 200:
                    msg = msg[0:200]
            print(l)

        if msg:
            mixpanel_props = {'error': msg}
            mixpanel_event(MIXPANEL_EVENT_PUSH_ERROR,
                           properties=mixpanel_props)
            return False
    else:
        msg = response.content.decode('utf-8')
        if len(msg) > 200:
            msg = msg[0:200]
        mixpanel_props = {'error': msg}
        mixpanel_event(MIXPANEL_EVENT_PUSH_ERROR,
                       properties=mixpanel_props)
        raise SiphonBundlerException('Problem writing changes: %s' %
                                     response.content)
    return True