예제 #1
0
def validate_app(platforms):
    push_successful = push(track_event=False)
    if not push_successful:
        # The push failed, so no point in validating the app
        sys.exit(1)

    # 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)

    # Send a request to out validation endpoint to make sure we
    # have the required details
    for p in platforms:
        if p == 'ios':
            formatted_platform = 'iOS'
        else:
            formatted_platform = 'Android'

        puts(colored.yellow('Validating your app for %s publishing...' % \
            formatted_platform))
        status = siphon.validate_app(conf.app_id, p)
        if status == 'ok':
            puts(colored.green('OK.'))
        else:
            return False
    return True
예제 #2
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.'))
예제 #3
0
def validate_app(platforms):
    push_successful = push(track_event=False)
    if not push_successful:
        # The push failed, so no point in validating the app
        sys.exit(1)

    # 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)

    # Send a request to out validation endpoint to make sure we
    # have the required details
    for p in platforms:
        if p == 'ios':
            formatted_platform = 'iOS'
        else:
            formatted_platform = 'Android'

        puts(colored.yellow('Validating your app for %s publishing...' % \
            formatted_platform))
        status = siphon.validate_app(conf.app_id, p)
        if status == 'ok':
            puts(colored.green('OK.'))
        else:
            return False
    return True
예제 #4
0
def ensure_base(version):
    # Make sure that the correct siphon-base package is installed for this
    # base version; if not, install it. Returns the location of the
    # base project.
    auth = Auth()
    conf = Config()
    siphon = Siphon(auth.auth_token)
    build = Build(conf.app_id, version)
    pkg_url = siphon.base_package_url(version)
    pkg_dest = Cache.base_package_path(version)

    puts(colored.yellow('Checking for required packages...'))
    content_length = siphon.content_length_for_version(version)

    if Cache.base_package_installed(version):
        cached_content_length = Cache.get_length_for_base_version(version)
        if content_length != cached_content_length:
            update_msg = 'Siphon needs to update some files in order to run ' \
                         'your app (%s). Proceed? ' \
                         '[Y/n]: ' % format_size(content_length)
            update = yn(update_msg)

            if not update:
                sys.exit(1)

            msg = 'Updating Siphon files for base version %s...' % version
            try:
                download_file(pkg_url, pkg_dest, msg)
                Cache.set_length_for_base_version(version, content_length)
            except KeyboardInterrupt:
                sys.exit(1)
            # Get rid of the old build dirs (they are now invalid)
            build.clean_builds()

    else:
        download_msg = 'Siphon needs to download some files in order to run ' \
                       'your app (%s). This will not be ' \
                       'required again unless an update is needed, or a ' \
                       'different base version is specified in ' \
                       'the Siphonfile of one of your apps. Proceed? ' \
                       '[Y/n]: ' % format_size(content_length)
        download = yn(download_msg)

        if not download:
            sys.exit(1)

        msg = 'Downloading compatibility files for base version ' \
            '%s...' % version
        try:
            download_file(pkg_url, pkg_dest, msg)
        except KeyboardInterrupt:
            sys.exit(1)
        Cache.set_length_for_base_version(version, content_length)
        build.clean_builds()
    build.ensure_build_dir()
예제 #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)
예제 #6
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)
예제 #7
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)
예제 #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()
예제 #9
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()
예제 #10
0
def request_login():
    puts('Please enter your Siphon credentials to continue.\n')
    try:
        username = get_input('Username or email: ')
        password = getpass('Password: '******'')  # newline
        sys.exit(0)

    auth_token = Siphon.authenticate(username, password)
    puts(colored.green('Logged in successfully.'))
    logout()  # Make sure we're logged out
    return {'auth_token': auth_token, 'username': username}
예제 #11
0
def request_login():
    puts('Please enter your Siphon credentials to continue.\n')
    try:
        username = get_input('Username or email: ')
        password = getpass('Password: '******'')  # newline
        sys.exit(0)

    auth_token = Siphon.authenticate(username, password)
    puts(colored.green('Logged in successfully.'))
    logout()  # Make sure we're logged out
    return {'auth_token': auth_token, 'username': username}
예제 #12
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)
예제 #13
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)
예제 #14
0
파일: ls.py 프로젝트: siphoncode/siphon-cli
def print_apps():
    auth = Auth()
    mixpanel_event(MIXPANEL_EVENT_LIST)
    siphon = Siphon(auth.auth_token)
    for app in siphon.list_apps():
        print(app['name'])
예제 #15
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.')
예제 #16
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)
예제 #17
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.')
예제 #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)
예제 #19
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')